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_COMPONENT 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_COMPONENT 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 DynamicCategory(const DynamicCategory&) = default;
161 DynamicCategory& operator=(const DynamicCategory&) = delete;
162
163 DynamicCategory(DynamicCategory&&) = default;
164 DynamicCategory& operator=(DynamicCategory&&) = delete;
165
166 const std::string name;
167 };
168
169 namespace internal {
170
NullCategory(const char *)171 constexpr const char* NullCategory(const char*) {
172 return nullptr;
173 }
174
175 perfetto::DynamicCategory NullCategory(const perfetto::DynamicCategory&);
176
StringMatchesPrefix(const char * str,const char * prefix)177 constexpr bool StringMatchesPrefix(const char* str, const char* prefix) {
178 return !*str ? !*prefix
179 : !*prefix ? true
180 : *str != *prefix
181 ? false
182 : StringMatchesPrefix(str + 1, prefix + 1);
183 }
184
IsStringInPrefixList(const char *)185 constexpr bool IsStringInPrefixList(const char*) {
186 return false;
187 }
188
189 template <typename... Args>
IsStringInPrefixList(const char * str,const char * prefix,Args...args)190 constexpr bool IsStringInPrefixList(const char* str,
191 const char* prefix,
192 Args... args) {
193 return StringMatchesPrefix(str, prefix) ||
194 IsStringInPrefixList(str, std::forward<Args>(args)...);
195 }
196
197 // Holds all the registered categories for one category namespace. See
198 // PERFETTO_DEFINE_CATEGORIES for building the registry.
199 class PERFETTO_EXPORT_COMPONENT TrackEventCategoryRegistry {
200 public:
TrackEventCategoryRegistry(size_t category_count,const Category * categories,std::atomic<uint8_t> * state_storage)201 constexpr TrackEventCategoryRegistry(size_t category_count,
202 const Category* categories,
203 std::atomic<uint8_t>* state_storage)
204 : categories_(categories),
205 category_count_(category_count),
206 state_storage_(state_storage) {
207 static_assert(
208 sizeof(state_storage[0].load()) * 8 >= kMaxDataSourceInstances,
209 "The category state must have enough bits for all possible data source "
210 "instances");
211 }
212
category_count()213 size_t category_count() const { return category_count_; }
214
215 // Returns a category based on its index.
GetCategory(size_t index)216 const Category* GetCategory(size_t index) const {
217 PERFETTO_DCHECK(index < category_count_);
218 return &categories_[index];
219 }
220
221 // Turn tracing on or off for the given category in a track event data source
222 // instance.
223 void EnableCategoryForInstance(size_t category_index,
224 uint32_t instance_index) const;
225 void DisableCategoryForInstance(size_t category_index,
226 uint32_t instance_index) const;
227
GetCategoryState(size_t category_index)228 constexpr std::atomic<uint8_t>* GetCategoryState(
229 size_t category_index) const {
230 return &state_storage_[category_index];
231 }
232
233 // --------------------------------------------------------------------------
234 // Trace point support
235 // --------------------------------------------------------------------------
236 //
237 // (The following methods are used by the track event trace point
238 // implementation and typically don't need to be called by other code.)
239
240 // At compile time, turn a category name into an index into the registry.
241 // Returns kInvalidCategoryIndex if the category was not found, or
242 // kDynamicCategoryIndex if |is_dynamic| is true or a DynamicCategory was
243 // passed in.
244 static constexpr size_t kInvalidCategoryIndex = static_cast<size_t>(-1);
245 static constexpr size_t kDynamicCategoryIndex = static_cast<size_t>(-2);
Find(const char * name,bool is_dynamic)246 constexpr size_t Find(const char* name, bool is_dynamic) const {
247 return CheckIsValidCategoryIndex(FindImpl(name, is_dynamic));
248 }
249
Find(const DynamicCategory &,bool)250 constexpr size_t Find(const DynamicCategory&, bool) const {
251 return kDynamicCategoryIndex;
252 }
253
254 constexpr bool ValidateCategories(size_t index = 0) const {
255 return (index == category_count_)
256 ? true
257 : IsValidCategoryName(categories_[index].name)
258 ? ValidateCategories(index + 1)
259 : false;
260 }
261
262 private:
263 // TODO(skyostil): Make the compile-time routines nicer with C++14.
264 constexpr size_t FindImpl(const char* name,
265 bool is_dynamic,
266 size_t index = 0) const {
267 return is_dynamic ? kDynamicCategoryIndex
268 : (index == category_count_)
269 ? kInvalidCategoryIndex
270 : StringEq(categories_[index].name, name)
271 ? index
272 : FindImpl(name, false, index + 1);
273 }
274
275 // A compile time helper for checking that a category index is valid.
CheckIsValidCategoryIndex(size_t index)276 static constexpr size_t CheckIsValidCategoryIndex(size_t index) {
277 // Relies on PERFETTO_CHECK() (and the surrounding lambda) being a
278 // non-constexpr function, which will fail the build if the given |index| is
279 // invalid. The funny formatting here is so that clang shows the comment
280 // below as part of the error message.
281 // clang-format off
282 return index != kInvalidCategoryIndex ? index : \
283 /* Invalid category -- add it to PERFETTO_DEFINE_CATEGORIES(). */ [] {
284 PERFETTO_CHECK(
285 false &&
286 "A track event used an unknown category. Please add it to "
287 "PERFETTO_DEFINE_CATEGORIES().");
288 return kInvalidCategoryIndex;
289 }();
290 // clang-format on
291 }
292
IsValidCategoryName(const char * name)293 static constexpr bool IsValidCategoryName(const char* name) {
294 return (!name || *name == '\"' || *name == '*' || *name == ' ')
295 ? false
296 : *name ? IsValidCategoryName(name + 1) : true;
297 }
298
StringEq(const char * a,const char * b)299 static constexpr bool StringEq(const char* a, const char* b) {
300 return *a != *b ? false
301 : (!*a || !*b) ? (*a == *b) : StringEq(a + 1, b + 1);
302 }
303
304 const Category* const categories_;
305 const size_t category_count_;
306 std::atomic<uint8_t>* const state_storage_;
307 };
308
309 } // namespace internal
310 } // namespace perfetto
311
312 #endif // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
313