TrackEvent: Add category tags and groups
This patch makes it possible to classify a single trace point to
multiple categories:
TRACE_EVENT("benchmark,input", "Name);
Each category group must be registered at build time:
PERFETTO_DEFINE_CATEGORIES(
perfetto::Category::Group("benchmark,input")
);
Additionally, we introduce a builder pattern for adding properties
such as tags and descriptions to categories:
PERFETTO_DEFINE_CATEGORIES(
perfetto::Category("moon_lander")
.SetDescription("Events from the moon lander module"),
perfetto::Category("moon_lander.verbose")
.SetDescription("Verbose events from the moon lander module")
.SetTags("slow")
);
Design doc: https://docs.google.com/document/d/1YSmRh1g8QuyxHQxN26m_uUNN-F-RzwHidARbWvPeApY/
Bug: 148779455
Change-Id: I453a7b1539894361d4a72200154cc8bf8f43823a
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index 05ebd9d..bef0382 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -402,23 +402,6 @@
}
};
- // Given either a static or dynamic category, extract a raw pointer to its
- // underlying name. Note that the pointer is only valid as long as the
- // parameter that was passed in.
- static const char* GetCategoryName(size_t category_index,
- const char* dynamic_category)
- PERFETTO_ALWAYS_INLINE {
- if (category_index == TrackEventCategoryRegistry::kDynamicCategoryIndex)
- return dynamic_category;
- return Registry->GetCategory(category_index)->name;
- }
-
- static const char* GetCategoryName(size_t,
- const DynamicCategory& dynamic_category)
- PERFETTO_ALWAYS_INLINE {
- return dynamic_category.name.c_str();
- }
-
// TODO(skyostil): Make |CategoryIndex| a regular parameter to reuse trace
// point code across different categories.
template <size_t CategoryIndex,
@@ -441,22 +424,35 @@
TraceWithInstances<CategoryIndex>(
instances, [&](typename Base::TraceContext ctx) {
// If this category is dynamic, first check whether it's enabled.
- if (CategoryIndex ==
- TrackEventCategoryRegistry::kDynamicCategoryIndex &&
- !IsDynamicCategoryEnabled(&ctx,
- DynamicCategory{dynamic_category})) {
+ constexpr bool kIsDynamic =
+ CategoryIndex ==
+ TrackEventCategoryRegistry::kDynamicCategoryIndex;
+ if (kIsDynamic && !IsDynamicCategoryEnabled(
+ &ctx, DynamicCategory{dynamic_category})) {
return;
}
+
{
// TODO(skyostil): Intern categories at compile time.
+ const Category* static_category =
+ kIsDynamic ? nullptr : Registry->GetCategory(CategoryIndex);
auto event_ctx = TrackEventInternal::WriteEvent(
ctx.tls_inst_->trace_writer.get(), ctx.GetIncrementalState(),
- GetCategoryName(CategoryIndex, dynamic_category), event_name,
- type, timestamp);
+ static_category, event_name, type, timestamp);
+ if (kIsDynamic) {
+ Category category{
+ Category::FromDynamicCategory(dynamic_category)};
+ category.ForEachGroupMember(
+ [&](const char* member_name, size_t name_size) {
+ event_ctx.event()->add_categories(member_name, name_size);
+ return true;
+ });
+ }
if (track)
event_ctx.event()->set_track_uuid(track.uuid);
arg_function(std::move(event_ctx));
- }
+ } // event_ctx
+
if (track) {
TrackEventInternal::WriteTrackDescriptorIfNeeded(
track, ctx.tls_inst_->trace_writer.get(),
@@ -503,8 +499,9 @@
// We haven't seen this category before. Let's figure out if it's enabled.
// This requires grabbing a lock to read the session's trace config.
auto ds = ctx->GetDataSourceLocked();
+ Category category{Category::FromDynamicCategory(dynamic_category)};
bool enabled = TrackEventInternal::IsCategoryEnabled(
- ds->config_, TrackEventCategory{dynamic_category.name.c_str()});
+ *Registry, ds->config_, category);
// TODO(skyostil): Cap the size of |dynamic_categories|.
incr_state->dynamic_categories[dynamic_category.name] = enabled;
return enabled;
diff --git a/include/perfetto/tracing/internal/track_event_internal.h b/include/perfetto/tracing/internal/track_event_internal.h
index 09511d8..ed3d7cb 100644
--- a/include/perfetto/tracing/internal/track_event_internal.h
+++ b/include/perfetto/tracing/internal/track_event_internal.h
@@ -30,6 +30,7 @@
namespace perfetto {
class EventContext;
+struct Category;
namespace protos {
namespace gen {
class TrackEventConfig;
@@ -40,7 +41,6 @@
} // namespace protos
namespace internal {
-struct TrackEventCategory;
class TrackEventCategoryRegistry;
class BaseTrackEventInternedDataIndex {
@@ -102,13 +102,14 @@
uint32_t instance_index);
static void DisableTracing(const TrackEventCategoryRegistry& registry,
uint32_t instance_index);
- static bool IsCategoryEnabled(const protos::gen::TrackEventConfig& config,
- const TrackEventCategory& category);
+ static bool IsCategoryEnabled(const TrackEventCategoryRegistry& registry,
+ const protos::gen::TrackEventConfig& config,
+ const Category& category);
static perfetto::EventContext WriteEvent(
TraceWriterBase*,
TrackEventIncrementalState*,
- const char* category,
+ const Category* category,
const char* name,
perfetto::protos::pbzero::TrackEvent::Type,
uint64_t timestamp = GetTimeNs());
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
index 4d35f73..1de69aa 100644
--- a/include/perfetto/tracing/internal/track_event_macros.h
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -40,8 +40,7 @@
//
#define PERFETTO_INTERNAL_DECLARE_CATEGORIES(...) \
namespace internal { \
- constexpr ::perfetto::internal::TrackEventCategory kCategories[] = { \
- __VA_ARGS__}; \
+ constexpr ::perfetto::Category kCategories[] = {__VA_ARGS__}; \
constexpr size_t kCategoryCount = \
sizeof(kCategories) / sizeof(kCategories[0]); \
/* The per-instance enable/disable state per category */ \
diff --git a/include/perfetto/tracing/track_event.h b/include/perfetto/tracing/track_event.h
index 7d5979e..be20e0b 100644
--- a/include/perfetto/tracing/track_event.h
+++ b/include/perfetto/tracing/track_event.h
@@ -39,9 +39,9 @@
// e.g., my_tracing.h:
//
// PERFETTO_DEFINE_CATEGORIES(
-// PERFETTO_CATEGORY(base),
-// PERFETTO_CATEGORY(v8),
-// PERFETTO_CATEGORY(cc));
+// perfetto::Category("base"),
+// perfetto::Category("v8"),
+// perfetto::Category("cc"));
//
// Then in a single .cc file, e.g., my_tracing.cc:
//
@@ -125,10 +125,9 @@
#define PERFETTO_TRACK_EVENT_NAMESPACE perfetto
#endif
-// A name for a single category. Wrapped in a macro in case we need to introduce
-// more fields in the future.
+// Deprecated; see perfetto::Category().
#define PERFETTO_CATEGORY(name) \
- { #name }
+ ::perfetto::Category { #name }
// Internal helpers for determining if a given category is defined at build or
// runtime.
diff --git a/include/perfetto/tracing/track_event_category_registry.h b/include/perfetto/tracing/track_event_category_registry.h
index c769aef..8346dc4 100644
--- a/include/perfetto/tracing/track_event_category_registry.h
+++ b/include/perfetto/tracing/track_event_category_registry.h
@@ -23,6 +23,126 @@
#include <utility>
namespace perfetto {
+class DynamicCategory;
+
+// A compile-time representation of a track event category. See
+// PERFETTO_DEFINE_CATEGORIES for registering your own categories.
+struct Category {
+ using Tags = std::array<const char*, 4>;
+
+ const char* const name = nullptr;
+ const char* const description = nullptr;
+ const Tags tags = {};
+
+ constexpr Category(const Category&) = default;
+ constexpr explicit Category(const char* name_)
+ : name(CheckIsValidCategory(name_)),
+ name_sizes_(ComputeNameSizes(name_)) {}
+
+ constexpr Category SetDescription(const char* description_) const {
+ return Category(name, description_, tags, name_sizes_);
+ }
+
+ template <typename... Args>
+ constexpr Category SetTags(Args&&... args) const {
+ return Category(name, description, {std::forward<Args>(args)...},
+ name_sizes_);
+ }
+
+ // A comma separated list of multiple categories to be used in a single trace
+ // point.
+ static constexpr Category Group(const char* names) {
+ return Category(names, AllowGroup{});
+ }
+
+ // Used for parsing dynamic category groups. Note that |name| and
+ // |DynamicCategory| must outlive the returned object because the category
+ // name isn't copied.
+ static Category FromDynamicCategory(const char* name);
+ static Category FromDynamicCategory(const DynamicCategory&);
+
+ constexpr bool IsGroup() const { return GetNameSize(1) > 0; }
+
+ // Returns the number of character in the category name. Not valid for
+ // category groups.
+ size_t name_size() const {
+ PERFETTO_DCHECK(!IsGroup());
+ return GetNameSize(0);
+ }
+
+ // Iterates over all the members of this category group, or just the name of
+ // the category itself if this isn't a category group. Return false from
+ // |callback| to stop iteration.
+ template <typename T>
+ void ForEachGroupMember(T callback) const {
+ const char* name_ptr = name;
+ size_t i = 0;
+ while (size_t name_size = GetNameSize(i++)) {
+ if (!callback(name_ptr, name_size))
+ break;
+ name_ptr += name_size + 1;
+ }
+ }
+
+ private:
+ static constexpr size_t kMaxGroupSize = 4;
+ using NameSizes = std::array<uint8_t, kMaxGroupSize>;
+
+ constexpr Category(const char* name_,
+ const char* description_,
+ Tags tags_,
+ NameSizes name_sizes)
+ : name(name_),
+ description(description_),
+ tags(tags_),
+ name_sizes_(name_sizes) {}
+
+ enum AllowGroup {};
+ constexpr Category(const char* name_, AllowGroup)
+ : name(CheckIsValidCategoryGroup(name_)),
+ name_sizes_(ComputeNameSizes(name_)) {}
+
+ constexpr size_t GetNameSize(size_t i) const {
+ return i < name_sizes_.size() ? name_sizes_[i] : 0;
+ }
+
+ static constexpr NameSizes ComputeNameSizes(const char* s) {
+ static_assert(kMaxGroupSize == 4, "Unexpected maximum category group size");
+ return NameSizes{static_cast<uint8_t>(GetNthNameSize(0, s, s)),
+ static_cast<uint8_t>(GetNthNameSize(1, s, s)),
+ static_cast<uint8_t>(GetNthNameSize(2, s, s)),
+ static_cast<uint8_t>(GetNthNameSize(3, s, s))};
+ }
+
+ static constexpr ssize_t GetNthNameSize(int n,
+ const char* start,
+ const char* end,
+ int counter = 0) {
+ return (!*end || *end == ',')
+ ? ((!*end || counter == n)
+ ? (counter == n ? end - start : 0)
+ : GetNthNameSize(n, end + 1, end + 1, counter + 1))
+ : GetNthNameSize(n, start, end + 1, counter);
+ }
+
+ static constexpr const char* CheckIsValidCategory(const char* n) {
+ // We just replace invalid input with a nullptr here; it will trigger a
+ // static assert in TrackEventCategoryRegistry::ValidateCategories().
+ return GetNthNameSize(1, n, n) ? nullptr : n;
+ }
+
+ static constexpr const char* CheckIsValidCategoryGroup(const char* n) {
+ // Same as above: replace invalid input with nullptr.
+ return !GetNthNameSize(1, n, n) || GetNthNameSize(kMaxGroupSize, n, n)
+ ? nullptr
+ : n;
+ }
+
+ // An array of lengths of the different names associated with this category.
+ // If this category doesn't represent a group of multiple categories, only the
+ // first element is non-zero.
+ const NameSizes name_sizes_ = {};
+};
// Dynamically constructed category names should marked as such through this
// container type to make it less likely for trace points to accidentally start
@@ -66,18 +186,12 @@
IsStringInPrefixList(str, std::forward<Args>(args)...);
}
-// A compile-time representation of a track event category. See
-// PERFETTO_DEFINE_CATEGORIES for registering your own categories.
-struct TrackEventCategory {
- const char* const name;
-};
-
// Holds all the registered categories for one category namespace. See
// PERFETTO_DEFINE_CATEGORIES for building the registry.
class TrackEventCategoryRegistry {
public:
constexpr TrackEventCategoryRegistry(size_t category_count,
- const TrackEventCategory* categories,
+ const Category* categories,
std::atomic<uint8_t>* state_storage)
: categories_(categories),
category_count_(category_count),
@@ -91,7 +205,7 @@
size_t category_count() const { return category_count_; }
// Returns a category based on its index.
- const TrackEventCategory* GetCategory(size_t index) const;
+ const Category* GetCategory(size_t index) const;
// Turn tracing on or off for the given category in a track event data source
// instance.
@@ -154,7 +268,7 @@
private:
// TODO(skyostil): Make the compile-time routines nicer with C++14.
static constexpr bool IsValidCategoryName(const char* name) {
- return (*name == '\"' || *name == '*')
+ return (!name || *name == '\"' || *name == '*' || *name == ' ')
? false
: *name ? IsValidCategoryName(name + 1) : true;
}
@@ -164,7 +278,7 @@
: (!*a || !*b) ? (*a == *b) : StringEq(a + 1, b + 1);
}
- const TrackEventCategory* const categories_;
+ const Category* const categories_;
const size_t category_count_;
std::atomic<uint8_t>* const state_storage_;
};
diff --git a/include/perfetto/tracing/track_event_interned_data_index.h b/include/perfetto/tracing/track_event_interned_data_index.h
index be4c128..cce2665 100644
--- a/include/perfetto/tracing/track_event_interned_data_index.h
+++ b/include/perfetto/tracing/track_event_interned_data_index.h
@@ -170,8 +170,12 @@
: public internal::BaseTrackEventInternedDataIndex {
public:
// Return an interning id for |value|. The returned id can be immediately
- // written to the trace.
- static size_t Get(EventContext* ctx, const ValueType& value) {
+ // written to the trace. The optional |add_args| are passed to the Add()
+ // function.
+ template <typename... Args>
+ static size_t Get(EventContext* ctx,
+ const ValueType& value,
+ Args&&... add_args) {
// First check if the value exists in the dictionary.
auto index_for_field = GetOrCreateIndexForField(ctx->incremental_state_);
size_t iid;
@@ -186,7 +190,7 @@
PERFETTO_DCHECK(iid);
InternedDataType::Add(
ctx->incremental_state_->serialized_interned_data.get(), iid,
- std::move(value));
+ std::move(value), std::forward<Args>(add_args)...);
return iid;
}
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 2acc000..102bac2 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -1154,13 +1154,35 @@
// The following fields define the set of enabled trace categories. Each list
// item is a glob.
//
- // To determine if category X is enabled:
+ // To determine if category is enabled, it is checked against the filters in
+ // the following order:
//
- // if (X in disabled_categories):
- // return (X in enabled_categories)
- // else if (X in disabled_tags):
- // return (X in enabled_categories or X in enabled_tags)
- // else return true
+ // 1. Exact matches in enabled categories.
+ // 2. Exact matches in enabled tags.
+ // 3. Exact matches in disabled categories.
+ // 4. Exact matches in disabled tags.
+ // 5. Pattern matches in enabled categories.
+ // 6. Pattern matches in enabled tags.
+ // 7. Pattern matches in disabled categories.
+ // 8. Pattern matches in disabled tags.
+ //
+ // If none of the steps produced a match, the category is enabled by default.
+ //
+ // Examples:
+ //
+ // - To enable all non-slow/debug categories:
+ //
+ // No configuration needed, happens by default.
+ //
+ // - To enable a specific category:
+ //
+ // disabled_categories = ["*"]
+ // enabled_categories = ["my_category"]
+ //
+ // - To enable only categories with a specific tag:
+ //
+ // disabled_tags = ["*"]
+ // enabled_tags = ["my_tag"]
//
repeated string disabled_categories = 1; // Default: []
repeated string enabled_categories = 2; // Default: []
diff --git a/protos/perfetto/config/track_event/track_event_config.proto b/protos/perfetto/config/track_event/track_event_config.proto
index 8fab47c..6ae1f8f 100644
--- a/protos/perfetto/config/track_event/track_event_config.proto
+++ b/protos/perfetto/config/track_event/track_event_config.proto
@@ -22,13 +22,35 @@
// The following fields define the set of enabled trace categories. Each list
// item is a glob.
//
- // To determine if category X is enabled:
+ // To determine if category is enabled, it is checked against the filters in
+ // the following order:
//
- // if (X in disabled_categories):
- // return (X in enabled_categories)
- // else if (X in disabled_tags):
- // return (X in enabled_categories or X in enabled_tags)
- // else return true
+ // 1. Exact matches in enabled categories.
+ // 2. Exact matches in enabled tags.
+ // 3. Exact matches in disabled categories.
+ // 4. Exact matches in disabled tags.
+ // 5. Pattern matches in enabled categories.
+ // 6. Pattern matches in enabled tags.
+ // 7. Pattern matches in disabled categories.
+ // 8. Pattern matches in disabled tags.
+ //
+ // If none of the steps produced a match, the category is enabled by default.
+ //
+ // Examples:
+ //
+ // - To enable all non-slow/debug categories:
+ //
+ // No configuration needed, happens by default.
+ //
+ // - To enable a specific category:
+ //
+ // disabled_categories = ["*"]
+ // enabled_categories = ["my_category"]
+ //
+ // - To enable only categories with a specific tag:
+ //
+ // disabled_tags = ["*"]
+ // enabled_tags = ["my_tag"]
//
repeated string disabled_categories = 1; // Default: []
repeated string enabled_categories = 2; // Default: []
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 16ab8d0..a2ea1d1 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -5591,13 +5591,35 @@
// The following fields define the set of enabled trace categories. Each list
// item is a glob.
//
- // To determine if category X is enabled:
+ // To determine if category is enabled, it is checked against the filters in
+ // the following order:
//
- // if (X in disabled_categories):
- // return (X in enabled_categories)
- // else if (X in disabled_tags):
- // return (X in enabled_categories or X in enabled_tags)
- // else return true
+ // 1. Exact matches in enabled categories.
+ // 2. Exact matches in enabled tags.
+ // 3. Exact matches in disabled categories.
+ // 4. Exact matches in disabled tags.
+ // 5. Pattern matches in enabled categories.
+ // 6. Pattern matches in enabled tags.
+ // 7. Pattern matches in disabled categories.
+ // 8. Pattern matches in disabled tags.
+ //
+ // If none of the steps produced a match, the category is enabled by default.
+ //
+ // Examples:
+ //
+ // - To enable all non-slow/debug categories:
+ //
+ // No configuration needed, happens by default.
+ //
+ // - To enable a specific category:
+ //
+ // disabled_categories = ["*"]
+ // enabled_categories = ["my_category"]
+ //
+ // - To enable only categories with a specific tag:
+ //
+ // disabled_tags = ["*"]
+ // enabled_tags = ["my_tag"]
//
repeated string disabled_categories = 1; // Default: []
repeated string enabled_categories = 2; // Default: []
diff --git a/src/perfetto_cmd/perfetto_config.descriptor.h b/src/perfetto_cmd/perfetto_config.descriptor.h
index 8a77cfd..fb22c36 100644
--- a/src/perfetto_cmd/perfetto_config.descriptor.h
+++ b/src/perfetto_cmd/perfetto_config.descriptor.h
@@ -27,7 +27,7 @@
// SHA1(tools/gen_binary_descriptors)
// d6628b15181dba5287e35b56b966b39ea93d42b1
// SHA1(protos/perfetto/config/perfetto_config.proto)
-// 0cf480593a4dbbfdf5aa88924418e6973523489d
+// a5fa2ae0a3cc1fc0f9e5ab400145cf0a3d086fa3
// This is the proto PerfettoConfig encoded as a ProtoFileDescriptor to allow
// for reflection without libprotobuf full/non-lite protos.
diff --git a/src/tracing/internal/track_event_internal.cc b/src/tracing/internal/track_event_internal.cc
index d27b9ef..1b740c2 100644
--- a/src/tracing/internal/track_event_internal.cc
+++ b/src/tracing/internal/track_event_internal.cc
@@ -39,6 +39,9 @@
namespace {
std::atomic<perfetto::base::PlatformThreadId> g_main_thread;
+static constexpr const char kLegacySlowPrefix[] = "disabled-by-default-";
+static constexpr const char kSlowTag[] = "slow";
+static constexpr const char kDebugTag[] = "debug";
struct InternedEventCategory
: public TrackEventInternedDataIndex<
@@ -48,10 +51,11 @@
SmallInternedDataTraits> {
static void Add(protos::pbzero::InternedData* interned_data,
size_t iid,
- const char* value) {
+ const char* value,
+ size_t length) {
auto category = interned_data->add_event_categories();
category->set_iid(iid);
- category->set_name(value);
+ category->set_name(value, length);
}
};
@@ -95,22 +99,28 @@
#endif
}
-bool NameMatchesPattern(const std::string& pattern, const std::string& name) {
+enum class MatchType { kExact, kPattern };
+
+bool NameMatchesPattern(const std::string& pattern,
+ const std::string& name,
+ MatchType match_type) {
// To avoid pulling in all of std::regex, for now we only support a single "*"
// wildcard at the end of the pattern.
- // TODO(skyostil): Support comma-separated categories.
size_t i = pattern.find('*');
if (i != std::string::npos) {
PERFETTO_DCHECK(i == pattern.size() - 1);
+ if (match_type != MatchType::kPattern)
+ return false;
return name.substr(0, i) == pattern.substr(0, i);
}
return name == pattern;
}
bool NameMatchesPatternList(const std::vector<std::string>& patterns,
- const std::string& name) {
+ const std::string& name,
+ MatchType match_type) {
for (const auto& pattern : patterns) {
- if (NameMatchesPattern(pattern, name))
+ if (NameMatchesPattern(pattern, name, match_type))
return true;
}
return false;
@@ -131,9 +141,20 @@
protozero::HeapBuffered<protos::pbzero::TrackEventDescriptor> ted;
for (size_t i = 0; i < registry.category_count(); i++) {
auto category = registry.GetCategory(i);
+ // Don't register group categories.
+ if (category->IsGroup())
+ continue;
auto cat = ted->add_available_categories();
cat->set_name(category->name);
- // TODO(skyostil): Advertise category tags and descriptions.
+ if (category->description)
+ cat->set_description(category->description);
+ for (const auto& tag : category->tags) {
+ if (tag)
+ cat->add_tags(tag);
+ }
+ // Disabled-by-default categories get a "slow" tag.
+ if (!strncmp(category->name, kLegacySlowPrefix, strlen(kLegacySlowPrefix)))
+ cat->add_tags(kSlowTag);
}
dsd.set_track_event_descriptor_raw(ted.SerializeAsString());
@@ -146,7 +167,7 @@
const protos::gen::TrackEventConfig& config,
uint32_t instance_index) {
for (size_t i = 0; i < registry.category_count(); i++) {
- if (IsCategoryEnabled(config, *registry.GetCategory(i)))
+ if (IsCategoryEnabled(registry, config, *registry.GetCategory(i)))
registry.EnableCategoryForInstance(i, instance_index);
}
}
@@ -161,11 +182,91 @@
// static
bool TrackEventInternal::IsCategoryEnabled(
+ const TrackEventCategoryRegistry& registry,
const protos::gen::TrackEventConfig& config,
- const TrackEventCategory& category) {
- if (NameMatchesPatternList(config.disabled_categories(), category.name))
- return NameMatchesPatternList(config.enabled_categories(), category.name);
- // TODO(skyostil): Support tag-based category configs.
+ const Category& category) {
+ // If this is a group category, check if any of its constituent categories are
+ // enabled. If so, then this one is enabled too.
+ if (category.IsGroup()) {
+ bool result = false;
+ category.ForEachGroupMember([&](const char* member_name, size_t name_size) {
+ for (size_t i = 0; i < registry.category_count(); i++) {
+ const auto ref_category = registry.GetCategory(i);
+ // Groups can't refer to other groups.
+ if (ref_category->IsGroup())
+ continue;
+ // Require an exact match.
+ if (ref_category->name_size() != name_size ||
+ strncmp(ref_category->name, member_name, name_size)) {
+ continue;
+ }
+ if (IsCategoryEnabled(registry, config, *ref_category)) {
+ result = true;
+ // Break ForEachGroupMember() loop.
+ return false;
+ }
+ break;
+ }
+ // No match found => keep iterating.
+ return true;
+ });
+ return result;
+ }
+
+ auto has_matching_tag = [&](std::function<bool(const char*)> matcher) {
+ for (const auto& tag : category.tags) {
+ if (!tag)
+ break;
+ if (matcher(tag))
+ return true;
+ }
+ // Legacy "disabled-by-default" categories automatically get the "slow" tag.
+ if (!strncmp(category.name, kLegacySlowPrefix, strlen(kLegacySlowPrefix)) &&
+ matcher(kSlowTag)) {
+ return true;
+ }
+ return false;
+ };
+
+ // First try exact matches, then pattern matches.
+ const std::array<MatchType, 2> match_types = {MatchType::kExact,
+ MatchType::kPattern};
+ for (auto match_type : match_types) {
+ // 1. Enabled categories.
+ if (NameMatchesPatternList(config.enabled_categories(), category.name,
+ match_type)) {
+ return true;
+ }
+
+ // 2. Enabled tags.
+ if (has_matching_tag([&](const char* tag) {
+ return NameMatchesPatternList(config.enabled_tags(), tag, match_type);
+ })) {
+ return true;
+ }
+
+ // 3. Disabled categories.
+ if (NameMatchesPatternList(config.disabled_categories(), category.name,
+ match_type)) {
+ return false;
+ }
+
+ // 4. Disabled tags.
+ if (has_matching_tag([&](const char* tag) {
+ if (config.disabled_tags_size()) {
+ return NameMatchesPatternList(config.disabled_tags(), tag,
+ match_type);
+ } else {
+ // The "slow" and "debug" tags are disabled by default.
+ return NameMatchesPattern(kSlowTag, tag, match_type) ||
+ NameMatchesPattern(kDebugTag, tag, match_type);
+ }
+ })) {
+ return false;
+ }
+ }
+
+ // If nothing matched, enable the category by default.
return true;
}
@@ -224,11 +325,10 @@
EventContext TrackEventInternal::WriteEvent(
TraceWriterBase* trace_writer,
TrackEventIncrementalState* incr_state,
- const char* category,
+ const Category* category,
const char* name,
perfetto::protos::pbzero::TrackEvent::Type type,
uint64_t timestamp) {
- PERFETTO_DCHECK(category);
PERFETTO_DCHECK(g_main_thread);
if (incr_state->was_cleared) {
@@ -244,10 +344,14 @@
// We assume that |category| and |name| point to strings with static lifetime.
// This means we can use their addresses as interning keys.
- if (type != protos::pbzero::TrackEvent::TYPE_SLICE_END) {
- // TODO(skyostil): Handle multiple categories.
- size_t category_iid = InternedEventCategory::Get(&ctx, category);
- track_event->add_category_iids(category_iid);
+ if (category && type != protos::pbzero::TrackEvent::TYPE_SLICE_END) {
+ category->ForEachGroupMember(
+ [&](const char* member_name, size_t name_size) {
+ size_t category_iid =
+ InternedEventCategory::Get(&ctx, member_name, name_size);
+ track_event->add_category_iids(category_iid);
+ return true;
+ });
}
if (name) {
size_t name_iid = InternedEventName::Get(&ctx, name);
diff --git a/src/tracing/test/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 3605e4e..76ce6ab 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -76,13 +76,19 @@
PERFETTO_DEFINE_TEST_CATEGORY_PREFIXES("dynamic");
// Trace categories used in the tests.
-PERFETTO_DEFINE_CATEGORIES(PERFETTO_CATEGORY(test),
- PERFETTO_CATEGORY(foo),
- PERFETTO_CATEGORY(bar),
- PERFETTO_CATEGORY(cat),
- // TODO(skyostil): Figure out how to represent
- // disabled-by-default categories
- {TRACE_DISABLED_BY_DEFAULT("cat")});
+PERFETTO_DEFINE_CATEGORIES(
+ perfetto::Category("test")
+ .SetDescription("This is a test category")
+ .SetTags("tag"),
+ perfetto::Category("foo"),
+ perfetto::Category("bar"),
+ perfetto::Category("cat").SetTags("slow"),
+ perfetto::Category("cat.verbose").SetTags("debug"),
+ perfetto::Category::Group("foo,bar"),
+ perfetto::Category::Group("baz,bar,quux"),
+ perfetto::Category::Group("red,green,blue,foo"),
+ perfetto::Category::Group("red,green,blue,yellow"),
+ perfetto::Category(TRACE_DISABLED_BY_DEFAULT("cat")));
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
// For testing interning of complex objects.
@@ -520,8 +526,11 @@
id << "(tid_override=" << legacy_event.tid_override() << ")";
slice += id.str();
}
- if (!track_event.category_iids().empty())
- slice += ":" + categories[track_event.category_iids()[0]];
+ size_t category_count = 0;
+ for (const auto& it : track_event.category_iids())
+ slice += (category_count++ ? "," : ":") + categories[it];
+ for (const auto& it : track_event.categories())
+ slice += (category_count++ ? ",$" : ":$") + it;
if (track_event.has_name_iid())
slice += "." + event_names[track_event.name_iid()];
@@ -808,12 +817,19 @@
// Check that the advertised categories match PERFETTO_DEFINE_CATEGORIES (see
// above).
- EXPECT_EQ(5, desc.available_categories_size());
+ EXPECT_EQ(6, desc.available_categories_size());
EXPECT_EQ("test", desc.available_categories()[0].name());
+ EXPECT_EQ("This is a test category",
+ desc.available_categories()[0].description());
+ EXPECT_EQ("tag", desc.available_categories()[0].tags()[0]);
EXPECT_EQ("foo", desc.available_categories()[1].name());
EXPECT_EQ("bar", desc.available_categories()[2].name());
EXPECT_EQ("cat", desc.available_categories()[3].name());
- EXPECT_EQ("disabled-by-default-cat", desc.available_categories()[4].name());
+ EXPECT_EQ("slow", desc.available_categories()[3].tags()[0]);
+ EXPECT_EQ("cat.verbose", desc.available_categories()[4].name());
+ EXPECT_EQ("debug", desc.available_categories()[4].tags()[0]);
+ EXPECT_EQ("disabled-by-default-cat", desc.available_categories()[5].name());
+ EXPECT_EQ("slow", desc.available_categories()[5].tags()[0]);
}
TEST_F(PerfettoApiTest, TrackEventSharedIncrementalState) {
@@ -1653,17 +1669,35 @@
TRACE_EVENT_BEGIN("foo", "FooEvent");
TRACE_EVENT_BEGIN("bar", "BarEvent");
+ TRACE_EVENT_BEGIN("foo,bar", "MultiFooBar");
+ TRACE_EVENT_BEGIN("baz,bar,quux", "MultiBar");
+ TRACE_EVENT_BEGIN("red,green,blue,foo", "MultiFoo");
+ TRACE_EVENT_BEGIN("red,green,blue,yellow", "MultiNone");
+ TRACE_EVENT_BEGIN("cat", "SlowEvent");
+ TRACE_EVENT_BEGIN("cat.verbose", "DebugEvent");
+ TRACE_EVENT_BEGIN("test", "TagEvent");
+ TRACE_EVENT_BEGIN(TRACE_DISABLED_BY_DEFAULT("cat"), "SlowDisabledEvent");
+ TRACE_EVENT_BEGIN("dynamic,foo", "DynamicGroupFooEvent");
+ perfetto::DynamicCategory dyn{"dynamic,bar"};
+ TRACE_EVENT_BEGIN(dyn, "DynamicGroupBarEvent");
perfetto::TrackEvent::Flush();
tracing_session->get()->StopBlocking();
- return ReadSlicesFromTrace(tracing_session->get());
+ auto slices = ReadSlicesFromTrace(tracing_session->get());
+ tracing_session->session.reset();
+ return slices;
};
- // Empty config should enable all categories.
+ // Empty config should enable all categories except slow ones.
{
perfetto::protos::gen::TrackEventConfig te_cfg;
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:bar.BarEvent"));
+ EXPECT_THAT(
+ slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
+ "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
+ "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
// Enable exactly one category.
@@ -1672,7 +1706,9 @@
te_cfg.add_disabled_categories("*");
te_cfg.add_enabled_categories("foo");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent"));
+ EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:foo,bar.MultiFooBar",
+ "B:red,green,blue,foo.MultiFoo",
+ "B:$dynamic,$foo.DynamicGroupFooEvent"));
}
// Enable two categories.
@@ -1683,17 +1719,25 @@
te_cfg.add_enabled_categories("baz");
te_cfg.add_enabled_categories("bar");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:bar.BarEvent"));
+ EXPECT_THAT(
+ slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
+ "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
+ "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
- // Enable overrides disable.
+ // Enabling all categories with a pattern doesn't enable slow ones.
{
perfetto::protos::gen::TrackEventConfig te_cfg;
- te_cfg.add_disabled_categories("foo");
- te_cfg.add_disabled_categories("bar");
te_cfg.add_enabled_categories("*");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:bar.BarEvent"));
+ EXPECT_THAT(
+ slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent", "B:foo,bar.MultiFooBar",
+ "B:baz,bar,quux.MultiBar", "B:red,green,blue,foo.MultiFoo",
+ "B:test.TagEvent", "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
// Enable with a pattern.
@@ -1702,7 +1746,46 @@
te_cfg.add_disabled_categories("*");
te_cfg.add_enabled_categories("fo*");
auto slices = check_config(te_cfg);
- EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent"));
+ EXPECT_THAT(slices, ElementsAre("B:foo.FooEvent", "B:foo,bar.MultiFooBar",
+ "B:red,green,blue,foo.MultiFoo",
+ "B:$dynamic,$foo.DynamicGroupFooEvent"));
+ }
+
+ // Enable with a tag.
+ {
+ perfetto::protos::gen::TrackEventConfig te_cfg;
+ te_cfg.add_disabled_categories("*");
+ te_cfg.add_enabled_tags("tag");
+ auto slices = check_config(te_cfg);
+ EXPECT_THAT(slices, ElementsAre("B:test.TagEvent"));
+ }
+
+ // Enable just slow categories.
+ {
+ perfetto::protos::gen::TrackEventConfig te_cfg;
+ te_cfg.add_disabled_categories("*");
+ te_cfg.add_enabled_tags("slow");
+ auto slices = check_config(te_cfg);
+ EXPECT_THAT(slices,
+ ElementsAre("B:cat.SlowEvent",
+ "B:disabled-by-default-cat.SlowDisabledEvent"));
+ }
+
+ // Enable everything including slow/debug categories.
+ {
+ perfetto::protos::gen::TrackEventConfig te_cfg;
+ te_cfg.add_enabled_categories("*");
+ te_cfg.add_enabled_tags("slow");
+ te_cfg.add_enabled_tags("debug");
+ auto slices = check_config(te_cfg);
+ EXPECT_THAT(slices,
+ ElementsAre("B:foo.FooEvent", "B:bar.BarEvent",
+ "B:foo,bar.MultiFooBar", "B:baz,bar,quux.MultiBar",
+ "B:red,green,blue,foo.MultiFoo", "B:cat.SlowEvent",
+ "B:cat.verbose.DebugEvent", "B:test.TagEvent",
+ "B:disabled-by-default-cat.SlowDisabledEvent",
+ "B:$dynamic,$foo.DynamicGroupFooEvent",
+ "B:$dynamic,$bar.DynamicGroupBarEvent"));
}
}
@@ -2071,15 +2154,9 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEvents) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
// Create a new trace session.
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session =
+ NewTraceWithCategories({"cat", TRACE_DISABLED_BY_DEFAULT("cat")});
tracing_session->get()->StartBlocking();
// Basic events.
@@ -2131,15 +2208,8 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEventsWithCustomAnnotation) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
// Create a new trace session.
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
MyDebugAnnotation annotation;
@@ -2160,17 +2230,10 @@
// Make sure that a uniquely owned debug annotation can be written into
// multiple concurrent tracing sessions.
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
- auto* tracing_session2 = NewTrace(cfg);
+ auto* tracing_session2 = NewTraceWithCategories({"cat"});
tracing_session2->get()->StartBlocking();
std::unique_ptr<MyDebugAnnotation> owned_annotation(new MyDebugAnnotation());
@@ -2189,14 +2252,7 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEventsWithId) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
TRACE_EVENT_ASYNC_BEGIN0("cat", "UnscopedId", 0x1000);
@@ -2217,14 +2273,7 @@
}
TEST_F(PerfettoApiTest, LegacyTraceEventsWithFlow) {
- // Setup the trace config.
- perfetto::TraceConfig cfg;
- cfg.set_duration_ms(500);
- cfg.add_buffers()->set_size_kb(1024);
- auto* ds_cfg = cfg.add_data_sources()->mutable_config();
- ds_cfg->set_name("track_event");
-
- auto* tracing_session = NewTrace(cfg);
+ auto* tracing_session = NewTraceWithCategories({"cat"});
tracing_session->get()->StartBlocking();
const uint64_t flow_id = 1234;
diff --git a/src/tracing/test/tracing_module_categories.h b/src/tracing/test/tracing_module_categories.h
index b492db8..03c0425 100644
--- a/src/tracing/test/tracing_module_categories.h
+++ b/src/tracing/test/tracing_module_categories.h
@@ -27,6 +27,7 @@
#include "perfetto/tracing.h"
+// Note: Using the old syntax here to ensure backwards compatibility.
PERFETTO_DEFINE_CATEGORIES(PERFETTO_CATEGORY(cat1),
PERFETTO_CATEGORY(cat2),
PERFETTO_CATEGORY(cat3),
diff --git a/src/tracing/track_event_category_registry.cc b/src/tracing/track_event_category_registry.cc
index 5bab490..6d9a649 100644
--- a/src/tracing/track_event_category_registry.cc
+++ b/src/tracing/track_event_category_registry.cc
@@ -17,14 +17,31 @@
#include "perfetto/tracing/track_event_category_registry.h"
namespace perfetto {
+
+// static
+Category Category::FromDynamicCategory(const char* name) {
+ if (GetNthNameSize(1, name, name)) {
+ Category group(Group(name));
+ PERFETTO_DCHECK(group.name);
+ return group;
+ }
+ Category category(name);
+ PERFETTO_DCHECK(category.name);
+ return category;
+}
+
+Category Category::FromDynamicCategory(
+ const DynamicCategory& dynamic_category) {
+ return FromDynamicCategory(dynamic_category.name.c_str());
+}
+
namespace internal {
perfetto::DynamicCategory NullCategory(const perfetto::DynamicCategory&) {
return perfetto::DynamicCategory{};
}
-const TrackEventCategory* TrackEventCategoryRegistry::GetCategory(
- size_t index) const {
+const Category* TrackEventCategoryRegistry::GetCategory(size_t index) const {
PERFETTO_DCHECK(index < category_count_);
return &categories_[index];
}