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