| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/trace_event/category_registry.h" |
| |
| #include <string.h> |
| |
| #include <type_traits> |
| |
| #include "base/atomicops.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/logging.h" |
| #include "base/third_party/dynamic_annotations/dynamic_annotations.h" |
| #include "base/trace_event/trace_category.h" |
| #include "starboard/common/string.h" |
| #include "starboard/types.h" |
| |
| namespace base { |
| namespace trace_event { |
| |
| namespace { |
| |
| constexpr size_t kMaxCategories = 200; |
| const int kNumBuiltinCategories = 4; |
| |
| // |g_categories| might end up causing creating dynamic initializers if not POD. |
| static_assert(std::is_pod<TraceCategory>::value, "TraceCategory must be POD"); |
| |
| // These entries must be kept consistent with the kCategory* consts below. |
| TraceCategory g_categories[kMaxCategories] = { |
| {0, 0, "tracing categories exhausted; must increase kMaxCategories"}, |
| {0, 0, "tracing already shutdown"}, // See kCategoryAlreadyShutdown below. |
| {0, 0, "__metadata"}, // See kCategoryMetadata below. |
| {0, 0, "toplevel"}, // Warmup the toplevel category. |
| }; |
| |
| base::subtle::AtomicWord g_category_index = kNumBuiltinCategories; |
| |
| bool IsValidCategoryPtr(const TraceCategory* category) { |
| // If any of these are hit, something has cached a corrupt category pointer. |
| uintptr_t ptr = reinterpret_cast<uintptr_t>(category); |
| return ptr % sizeof(void*) == 0 && |
| ptr >= reinterpret_cast<uintptr_t>(&g_categories[0]) && |
| ptr <= reinterpret_cast<uintptr_t>(&g_categories[kMaxCategories - 1]); |
| } |
| |
| } // namespace |
| |
| // static |
| TraceCategory* const CategoryRegistry::kCategoryExhausted = &g_categories[0]; |
| TraceCategory* const CategoryRegistry::kCategoryAlreadyShutdown = |
| &g_categories[1]; |
| TraceCategory* const CategoryRegistry::kCategoryMetadata = &g_categories[2]; |
| |
| // static |
| void CategoryRegistry::Initialize() { |
| // Trace is enabled or disabled on one thread while other threads are |
| // accessing the enabled flag. We don't care whether edge-case events are |
| // traced or not, so we allow races on the enabled flag to keep the trace |
| // macros fast. |
| for (size_t i = 0; i < kMaxCategories; ++i) { |
| ANNOTATE_BENIGN_RACE(g_categories[i].state_ptr(), |
| "trace_event category enabled"); |
| // If this DCHECK is hit in a test it means that ResetForTesting() is not |
| // called and the categories state leaks between test fixtures. |
| DCHECK(!g_categories[i].is_enabled()); |
| } |
| } |
| |
| // static |
| void CategoryRegistry::ResetForTesting() { |
| // reset_for_testing clears up only the enabled state and filters. The |
| // categories themselves cannot be cleared up because the static pointers |
| // injected by the macros still point to them and cannot be reset. |
| for (size_t i = 0; i < kMaxCategories; ++i) |
| g_categories[i].reset_for_testing(); |
| } |
| |
| // static |
| TraceCategory* CategoryRegistry::GetCategoryByName(const char* category_name) { |
| DCHECK(!strchr(category_name, '"')) |
| << "Category names may not contain double quote"; |
| |
| // The g_categories is append only, avoid using a lock for the fast path. |
| size_t category_index = base::subtle::Acquire_Load(&g_category_index); |
| |
| // Search for pre-existing category group. |
| for (size_t i = 0; i < category_index; ++i) { |
| if (strcmp(g_categories[i].name(), category_name) == 0) { |
| return &g_categories[i]; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool CategoryRegistry::GetOrCreateCategoryLocked( |
| const char* category_name, |
| CategoryInitializerFn category_initializer_fn, |
| TraceCategory** category) { |
| // This is the slow path: the lock is not held in the fastpath |
| // (GetCategoryByName), so more than one thread could have reached here trying |
| // to add the same category. |
| *category = GetCategoryByName(category_name); |
| if (*category) |
| return false; |
| |
| // Create a new category. |
| size_t category_index = base::subtle::Acquire_Load(&g_category_index); |
| if (category_index >= kMaxCategories) { |
| NOTREACHED() << "must increase kMaxCategories"; |
| *category = kCategoryExhausted; |
| return false; |
| } |
| |
| // TODO(primiano): this strdup should be removed. The only documented reason |
| // for it was TraceWatchEvent, which is gone. However, something might have |
| // ended up relying on this. Needs some auditing before removal. |
| const char* category_name_copy = strdup(category_name); |
| ANNOTATE_LEAKING_OBJECT_PTR(category_name_copy); |
| |
| *category = &g_categories[category_index]; |
| DCHECK(!(*category)->is_valid()); |
| DCHECK(!(*category)->is_enabled()); |
| (*category)->set_name(category_name_copy); |
| category_initializer_fn(*category); |
| |
| // Update the max index now. |
| base::subtle::Release_Store(&g_category_index, category_index + 1); |
| return true; |
| } |
| |
| // static |
| const TraceCategory* CategoryRegistry::GetCategoryByStatePtr( |
| const uint8_t* category_state) { |
| const TraceCategory* category = TraceCategory::FromStatePtr(category_state); |
| DCHECK(IsValidCategoryPtr(category)); |
| return category; |
| } |
| |
| // static |
| bool CategoryRegistry::IsBuiltinCategory(const TraceCategory* category) { |
| DCHECK(IsValidCategoryPtr(category)); |
| return category < &g_categories[kNumBuiltinCategories]; |
| } |
| |
| // static |
| CategoryRegistry::Range CategoryRegistry::GetAllCategories() { |
| // The |g_categories| array is append only. We have to only guarantee to |
| // not return an index to a category which is being initialized by |
| // GetOrCreateCategoryByName(). |
| size_t category_index = base::subtle::Acquire_Load(&g_category_index); |
| return CategoryRegistry::Range(&g_categories[0], |
| &g_categories[category_index]); |
| } |
| |
| } // namespace trace_event |
| } // namespace base |