1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
18 #define INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
19 
20 #include "perfetto/tracing/data_source.h"
21 
22 #include <stddef.h>
23 
24 #include <atomic>
25 #include <utility>
26 
27 namespace perfetto {
28 class DynamicCategory;
29 
30 // A compile-time representation of a track event category. See
31 // PERFETTO_DEFINE_CATEGORIES for registering your own categories.
32 struct PERFETTO_EXPORT Category {
33   using Tags = std::array<const char*, 4>;
34 
35   const char* const name = nullptr;
36   const char* const description = nullptr;
37   const Tags tags = {};
38 
39   constexpr Category(const Category&) = default;
CategoryCategory40   constexpr explicit Category(const char* name_)
41       : name(CheckIsValidCategory(name_)),
42         name_sizes_(ComputeNameSizes(name_)) {}
43 
SetDescriptionCategory44   constexpr Category SetDescription(const char* description_) const {
45     return Category(name, description_, tags, name_sizes_);
46   }
47 
48   template <typename... Args>
SetTagsCategory49   constexpr Category SetTags(Args&&... args) const {
50     return Category(name, description, {std::forward<Args>(args)...},
51                     name_sizes_);
52   }
53 
54   // A comma separated list of multiple categories to be used in a single trace
55   // point.
GroupCategory56   static constexpr Category Group(const char* names) {
57     return Category(names, AllowGroup{});
58   }
59 
60   // Used for parsing dynamic category groups. Note that |name| and
61   // |DynamicCategory| must outlive the returned object because the category
62   // name isn't copied.
63   static Category FromDynamicCategory(const char* name);
64   static Category FromDynamicCategory(const DynamicCategory&);
65 
IsGroupCategory66   constexpr bool IsGroup() const { return GetNameSize(1) > 0; }
67 
68   // Returns the number of character in the category name. Not valid for
69   // category groups.
name_sizeCategory70   size_t name_size() const {
71     PERFETTO_DCHECK(!IsGroup());
72     return GetNameSize(0);
73   }
74 
75   // Iterates over all the members of this category group, or just the name of
76   // the category itself if this isn't a category group. Return false from
77   // |callback| to stop iteration.
78   template <typename T>
ForEachGroupMemberCategory79   void ForEachGroupMember(T callback) const {
80     const char* name_ptr = name;
81     size_t i = 0;
82     while (size_t name_size = GetNameSize(i++)) {
83       if (!callback(name_ptr, name_size))
84         break;
85       name_ptr += name_size + 1;
86     }
87   }
88 
89  private:
90   static constexpr size_t kMaxGroupSize = 4;
91   using NameSizes = std::array<uint8_t, kMaxGroupSize>;
92 
CategoryCategory93   constexpr Category(const char* name_,
94                      const char* description_,
95                      Tags tags_,
96                      NameSizes name_sizes)
97       : name(name_),
98         description(description_),
99         tags(tags_),
100         name_sizes_(name_sizes) {}
101 
102   enum AllowGroup {};
CategoryCategory103   constexpr Category(const char* name_, AllowGroup)
104       : name(CheckIsValidCategoryGroup(name_)),
105         name_sizes_(ComputeNameSizes(name_)) {}
106 
GetNameSizeCategory107   constexpr size_t GetNameSize(size_t i) const {
108     return i < name_sizes_.size() ? name_sizes_[i] : 0;
109   }
110 
ComputeNameSizesCategory111   static constexpr NameSizes ComputeNameSizes(const char* s) {
112     static_assert(kMaxGroupSize == 4, "Unexpected maximum category group size");
113     return NameSizes{{static_cast<uint8_t>(GetNthNameSize(0, s, s)),
114                       static_cast<uint8_t>(GetNthNameSize(1, s, s)),
115                       static_cast<uint8_t>(GetNthNameSize(2, s, s)),
116                       static_cast<uint8_t>(GetNthNameSize(3, s, s))}};
117   }
118 
119   static constexpr ptrdiff_t GetNthNameSize(int n,
120                                             const char* start,
121                                             const char* end,
122                                             int counter = 0) {
123     return (!*end || *end == ',')
124                ? ((!*end || counter == n)
125                       ? (counter == n ? end - start : 0)
126                       : GetNthNameSize(n, end + 1, end + 1, counter + 1))
127                : GetNthNameSize(n, start, end + 1, counter);
128   }
129 
CheckIsValidCategoryCategory130   static constexpr const char* CheckIsValidCategory(const char* n) {
131     // We just replace invalid input with a nullptr here; it will trigger a
132     // static assert in TrackEventCategoryRegistry::ValidateCategories().
133     return GetNthNameSize(1, n, n) ? nullptr : n;
134   }
135 
CheckIsValidCategoryGroupCategory136   static constexpr const char* CheckIsValidCategoryGroup(const char* n) {
137     // Same as above: replace invalid input with nullptr.
138     return !GetNthNameSize(1, n, n) || GetNthNameSize(kMaxGroupSize, n, n)
139                ? nullptr
140                : n;
141   }
142 
143   // An array of lengths of the different names associated with this category.
144   // If this category doesn't represent a group of multiple categories, only the
145   // first element is non-zero.
146   const NameSizes name_sizes_ = {};
147 };
148 
149 // Dynamically constructed category names should marked as such through this
150 // container type to make it less likely for trace points to accidentally start
151 // using dynamic categories. Events with dynamic categories will always be
152 // slightly more expensive than regular events, so use them sparingly.
153 class PERFETTO_EXPORT DynamicCategory final {
154  public:
DynamicCategory(const std::string & name_)155   explicit DynamicCategory(const std::string& name_) : name(name_) {}
DynamicCategory(const char * name_)156   explicit DynamicCategory(const char* name_) : name(name_) {}
DynamicCategory()157   DynamicCategory() {}
158   ~DynamicCategory() = default;
159 
160   const std::string name;
161 };
162 
163 namespace internal {
164 
NullCategory(const char *)165 constexpr const char* NullCategory(const char*) {
166   return nullptr;
167 }
168 
169 perfetto::DynamicCategory NullCategory(const perfetto::DynamicCategory&);
170 
StringMatchesPrefix(const char * str,const char * prefix)171 constexpr bool StringMatchesPrefix(const char* str, const char* prefix) {
172   return !*str ? !*prefix
173                : !*prefix ? true
174                           : *str != *prefix
175                                 ? false
176                                 : StringMatchesPrefix(str + 1, prefix + 1);
177 }
178 
IsStringInPrefixList(const char *)179 constexpr bool IsStringInPrefixList(const char*) {
180   return false;
181 }
182 
183 template <typename... Args>
IsStringInPrefixList(const char * str,const char * prefix,Args...args)184 constexpr bool IsStringInPrefixList(const char* str,
185                                     const char* prefix,
186                                     Args... args) {
187   return StringMatchesPrefix(str, prefix) ||
188          IsStringInPrefixList(str, std::forward<Args>(args)...);
189 }
190 
191 // Holds all the registered categories for one category namespace. See
192 // PERFETTO_DEFINE_CATEGORIES for building the registry.
193 class PERFETTO_EXPORT TrackEventCategoryRegistry {
194  public:
TrackEventCategoryRegistry(size_t category_count,const Category * categories,std::atomic<uint8_t> * state_storage)195   constexpr TrackEventCategoryRegistry(size_t category_count,
196                                        const Category* categories,
197                                        std::atomic<uint8_t>* state_storage)
198       : categories_(categories),
199         category_count_(category_count),
200         state_storage_(state_storage) {
201     static_assert(
202         sizeof(state_storage[0].load()) * 8 >= kMaxDataSourceInstances,
203         "The category state must have enough bits for all possible data source "
204         "instances");
205   }
206 
category_count()207   size_t category_count() const { return category_count_; }
208 
209   // Returns a category based on its index.
GetCategory(size_t index)210   const Category* GetCategory(size_t index) const {
211     PERFETTO_DCHECK(index < category_count_);
212     return &categories_[index];
213   }
214 
215   // Turn tracing on or off for the given category in a track event data source
216   // instance.
217   void EnableCategoryForInstance(size_t category_index,
218                                  uint32_t instance_index) const;
219   void DisableCategoryForInstance(size_t category_index,
220                                   uint32_t instance_index) const;
221 
GetCategoryState(size_t category_index)222   constexpr std::atomic<uint8_t>* GetCategoryState(
223       size_t category_index) const {
224     return &state_storage_[category_index];
225   }
226 
227   // --------------------------------------------------------------------------
228   // Trace point support
229   // --------------------------------------------------------------------------
230   //
231   // (The following methods are used by the track event trace point
232   // implementation and typically don't need to be called by other code.)
233 
234   // At compile time, turn a category name into an index into the registry.
235   // Returns kInvalidCategoryIndex if the category was not found, or
236   // kDynamicCategoryIndex if |is_dynamic| is true or a DynamicCategory was
237   // passed in.
238   static constexpr size_t kInvalidCategoryIndex = static_cast<size_t>(-1);
239   static constexpr size_t kDynamicCategoryIndex = static_cast<size_t>(-2);
Find(const char * name,bool is_dynamic)240   constexpr size_t Find(const char* name, bool is_dynamic) const {
241     return CheckIsValidCategoryIndex(FindImpl(name, is_dynamic));
242   }
243 
Find(const DynamicCategory &,bool)244   constexpr size_t Find(const DynamicCategory&, bool) const {
245     return kDynamicCategoryIndex;
246   }
247 
248   constexpr bool ValidateCategories(size_t index = 0) const {
249     return (index == category_count_)
250                ? true
251                : IsValidCategoryName(categories_[index].name)
252                      ? ValidateCategories(index + 1)
253                      : false;
254   }
255 
256  private:
257   // TODO(skyostil): Make the compile-time routines nicer with C++14.
258   constexpr size_t FindImpl(const char* name,
259                             bool is_dynamic,
260                             size_t index = 0) const {
261     return is_dynamic ? kDynamicCategoryIndex
262                       : (index == category_count_)
263                             ? kInvalidCategoryIndex
264                             : StringEq(categories_[index].name, name)
265                                   ? index
266                                   : FindImpl(name, false, index + 1);
267   }
268 
269   // A compile time helper for checking that a category index is valid.
CheckIsValidCategoryIndex(size_t index)270   static constexpr size_t CheckIsValidCategoryIndex(size_t index) {
271     // Relies on PERFETTO_CHECK() (and the surrounding lambda) being a
272     // non-constexpr function, which will fail the build if the given |index| is
273     // invalid. The funny formatting here is so that clang shows the comment
274     // below as part of the error message.
275     // clang-format off
276     return index != kInvalidCategoryIndex ? index : \
277         /* Invalid category -- add it to PERFETTO_DEFINE_CATEGORIES(). */ [] {
278         PERFETTO_CHECK(
279             false &&
280             "A track event used an unknown category. Please add it to "
281             "PERFETTO_DEFINE_CATEGORIES().");
282         return kInvalidCategoryIndex;
283       }();
284     // clang-format on
285   }
286 
IsValidCategoryName(const char * name)287   static constexpr bool IsValidCategoryName(const char* name) {
288     return (!name || *name == '\"' || *name == '*' || *name == ' ')
289                ? false
290                : *name ? IsValidCategoryName(name + 1) : true;
291   }
292 
StringEq(const char * a,const char * b)293   static constexpr bool StringEq(const char* a, const char* b) {
294     return *a != *b ? false
295                     : (!*a || !*b) ? (*a == *b) : StringEq(a + 1, b + 1);
296   }
297 
298   const Category* const categories_;
299   const size_t category_count_;
300   std::atomic<uint8_t>* const state_storage_;
301 };
302 
303 }  // namespace internal
304 }  // namespace perfetto
305 
306 #endif  // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
307