|  | // 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" | 
|  |  | 
|  | 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 |