blob: 6bd11248e61de6d30729d8585db7ae1a0a673851 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
#define INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_
#include "perfetto/tracing/data_source.h"
#include <stddef.h>
#include <atomic>
#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 PERFETTO_EXPORT_COMPONENT 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 ptrdiff_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
// using dynamic categories. Events with dynamic categories will always be
// slightly more expensive than regular events, so use them sparingly.
class PERFETTO_EXPORT_COMPONENT DynamicCategory final {
public:
explicit DynamicCategory(const std::string& name_) : name(name_) {}
explicit DynamicCategory(const char* name_) : name(name_) {}
DynamicCategory() {}
~DynamicCategory() = default;
DynamicCategory(const DynamicCategory&) = default;
DynamicCategory& operator=(const DynamicCategory&) = delete;
DynamicCategory(DynamicCategory&&) = default;
DynamicCategory& operator=(DynamicCategory&&) = delete;
const std::string name;
};
namespace internal {
constexpr const char* NullCategory(const char*) {
return nullptr;
}
perfetto::DynamicCategory NullCategory(const perfetto::DynamicCategory&);
constexpr bool StringMatchesPrefix(const char* str, const char* prefix) {
return !*str ? !*prefix
: !*prefix ? true
: *str != *prefix
? false
: StringMatchesPrefix(str + 1, prefix + 1);
}
constexpr bool IsStringInPrefixList(const char*) {
return false;
}
template <typename... Args>
constexpr bool IsStringInPrefixList(const char* str,
const char* prefix,
Args... args) {
return StringMatchesPrefix(str, prefix) ||
IsStringInPrefixList(str, std::forward<Args>(args)...);
}
// Holds all the registered categories for one category namespace. See
// PERFETTO_DEFINE_CATEGORIES for building the registry.
class PERFETTO_EXPORT_COMPONENT TrackEventCategoryRegistry {
public:
constexpr TrackEventCategoryRegistry(size_t category_count,
const Category* categories,
std::atomic<uint8_t>* state_storage)
: categories_(categories),
category_count_(category_count),
state_storage_(state_storage) {
static_assert(
sizeof(state_storage[0].load()) * 8 >= kMaxDataSourceInstances,
"The category state must have enough bits for all possible data source "
"instances");
}
size_t category_count() const { return category_count_; }
// Returns a category based on its index.
const Category* GetCategory(size_t index) const {
PERFETTO_DCHECK(index < category_count_);
return &categories_[index];
}
// Turn tracing on or off for the given category in a track event data source
// instance.
void EnableCategoryForInstance(size_t category_index,
uint32_t instance_index) const;
void DisableCategoryForInstance(size_t category_index,
uint32_t instance_index) const;
constexpr std::atomic<uint8_t>* GetCategoryState(
size_t category_index) const {
return &state_storage_[category_index];
}
// --------------------------------------------------------------------------
// Trace point support
// --------------------------------------------------------------------------
//
// (The following methods are used by the track event trace point
// implementation and typically don't need to be called by other code.)
// At compile time, turn a category name into an index into the registry.
// Returns kInvalidCategoryIndex if the category was not found, or
// kDynamicCategoryIndex if |is_dynamic| is true or a DynamicCategory was
// passed in.
static constexpr size_t kInvalidCategoryIndex = static_cast<size_t>(-1);
static constexpr size_t kDynamicCategoryIndex = static_cast<size_t>(-2);
constexpr size_t Find(const char* name, bool is_dynamic) const {
return CheckIsValidCategoryIndex(FindImpl(name, is_dynamic));
}
constexpr size_t Find(const DynamicCategory&, bool) const {
return kDynamicCategoryIndex;
}
constexpr bool ValidateCategories(size_t index = 0) const {
return (index == category_count_)
? true
: IsValidCategoryName(categories_[index].name)
? ValidateCategories(index + 1)
: false;
}
private:
// TODO(skyostil): Make the compile-time routines nicer with C++14.
constexpr size_t FindImpl(const char* name,
bool is_dynamic,
size_t index = 0) const {
return is_dynamic ? kDynamicCategoryIndex
: (index == category_count_)
? kInvalidCategoryIndex
: StringEq(categories_[index].name, name)
? index
: FindImpl(name, false, index + 1);
}
// A compile time helper for checking that a category index is valid.
static constexpr size_t CheckIsValidCategoryIndex(size_t index) {
// Relies on PERFETTO_CHECK() (and the surrounding lambda) being a
// non-constexpr function, which will fail the build if the given |index| is
// invalid. The funny formatting here is so that clang shows the comment
// below as part of the error message.
// clang-format off
return index != kInvalidCategoryIndex ? index : \
/* Invalid category -- add it to PERFETTO_DEFINE_CATEGORIES(). */ [] {
PERFETTO_CHECK(
false &&
"A track event used an unknown category. Please add it to "
"PERFETTO_DEFINE_CATEGORIES().");
return kInvalidCategoryIndex;
}();
// clang-format on
}
static constexpr bool IsValidCategoryName(const char* name) {
return (!name || *name == '\"' || *name == '*' || *name == ' ')
? false
: *name ? IsValidCategoryName(name + 1) : true;
}
static constexpr bool StringEq(const char* a, const char* b) {
return *a != *b ? false
: (!*a || !*b) ? (*a == *b) : StringEq(a + 1, b + 1);
}
const Category* const categories_;
const size_t category_count_;
std::atomic<uint8_t>* const state_storage_;
};
} // namespace internal
} // namespace perfetto
#endif // INCLUDE_PERFETTO_TRACING_TRACK_EVENT_CATEGORY_REGISTRY_H_