Jay Civelli | 3a83cdd | 2017-03-22 17:31:44 -0700 | [diff] [blame^] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "base/trace_event/category_registry.h" |
| 6 | |
| 7 | #include <string.h> |
| 8 | |
| 9 | #include <type_traits> |
| 10 | |
| 11 | #include "base/atomicops.h" |
| 12 | #include "base/debug/leak_annotations.h" |
| 13 | #include "base/logging.h" |
| 14 | #include "base/third_party/dynamic_annotations/dynamic_annotations.h" |
| 15 | #include "base/trace_event/trace_category.h" |
| 16 | |
| 17 | namespace base { |
| 18 | namespace trace_event { |
| 19 | |
| 20 | namespace { |
| 21 | |
| 22 | constexpr size_t kMaxCategories = 200; |
| 23 | const int kNumBuiltinCategories = 4; |
| 24 | |
| 25 | // |g_categories| might end up causing creating dynamic initializers if not POD. |
| 26 | static_assert(std::is_pod<TraceCategory>::value, "TraceCategory must be POD"); |
| 27 | |
| 28 | // These entries must be kept consistent with the kCategory* consts below. |
| 29 | TraceCategory g_categories[kMaxCategories] = { |
| 30 | {0, 0, "tracing categories exhausted; must increase kMaxCategories"}, |
| 31 | {0, 0, "tracing already shutdown"}, // See kCategoryAlreadyShutdown below. |
| 32 | {0, 0, "__metadata"}, // See kCategoryMetadata below. |
| 33 | {0, 0, "toplevel"}, // Warmup the toplevel category. |
| 34 | }; |
| 35 | |
| 36 | base::subtle::AtomicWord g_category_index = kNumBuiltinCategories; |
| 37 | |
| 38 | bool IsValidCategoryPtr(const TraceCategory* category) { |
| 39 | // If any of these are hit, something has cached a corrupt category pointer. |
| 40 | uintptr_t ptr = reinterpret_cast<uintptr_t>(category); |
| 41 | return ptr % sizeof(void*) == 0 && |
| 42 | ptr >= reinterpret_cast<uintptr_t>(&g_categories[0]) && |
| 43 | ptr <= reinterpret_cast<uintptr_t>(&g_categories[kMaxCategories - 1]); |
| 44 | } |
| 45 | |
| 46 | } // namespace |
| 47 | |
| 48 | // static |
| 49 | TraceCategory* const CategoryRegistry::kCategoryExhausted = &g_categories[0]; |
| 50 | TraceCategory* const CategoryRegistry::kCategoryAlreadyShutdown = |
| 51 | &g_categories[1]; |
| 52 | TraceCategory* const CategoryRegistry::kCategoryMetadata = &g_categories[2]; |
| 53 | |
| 54 | // static |
| 55 | void CategoryRegistry::Initialize() { |
| 56 | // Trace is enabled or disabled on one thread while other threads are |
| 57 | // accessing the enabled flag. We don't care whether edge-case events are |
| 58 | // traced or not, so we allow races on the enabled flag to keep the trace |
| 59 | // macros fast. |
| 60 | for (size_t i = 0; i < kMaxCategories; ++i) { |
| 61 | ANNOTATE_BENIGN_RACE(g_categories[i].state_ptr(), |
| 62 | "trace_event category enabled"); |
| 63 | // If this DCHECK is hit in a test it means that ResetForTesting() is not |
| 64 | // called and the categories state leaks between test fixtures. |
| 65 | DCHECK(!g_categories[i].is_enabled()); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | // static |
| 70 | void CategoryRegistry::ResetForTesting() { |
| 71 | // reset_for_testing clears up only the enabled state and filters. The |
| 72 | // categories themselves cannot be cleared up because the static pointers |
| 73 | // injected by the macros still point to them and cannot be reset. |
| 74 | for (size_t i = 0; i < kMaxCategories; ++i) |
| 75 | g_categories[i].reset_for_testing(); |
| 76 | } |
| 77 | |
| 78 | // static |
| 79 | TraceCategory* CategoryRegistry::GetCategoryByName(const char* category_name) { |
| 80 | DCHECK(!strchr(category_name, '"')) |
| 81 | << "Category names may not contain double quote"; |
| 82 | |
| 83 | // The g_categories is append only, avoid using a lock for the fast path. |
| 84 | size_t category_index = base::subtle::Acquire_Load(&g_category_index); |
| 85 | |
| 86 | // Search for pre-existing category group. |
| 87 | for (size_t i = 0; i < category_index; ++i) { |
| 88 | if (strcmp(g_categories[i].name(), category_name) == 0) { |
| 89 | return &g_categories[i]; |
| 90 | } |
| 91 | } |
| 92 | return nullptr; |
| 93 | } |
| 94 | |
| 95 | bool CategoryRegistry::GetOrCreateCategoryLocked( |
| 96 | const char* category_name, |
| 97 | CategoryInitializerFn category_initializer_fn, |
| 98 | TraceCategory** category) { |
| 99 | // This is the slow path: the lock is not held in the fastpath |
| 100 | // (GetCategoryByName), so more than one thread could have reached here trying |
| 101 | // to add the same category. |
| 102 | *category = GetCategoryByName(category_name); |
| 103 | if (*category) |
| 104 | return false; |
| 105 | |
| 106 | // Create a new category. |
| 107 | size_t category_index = base::subtle::Acquire_Load(&g_category_index); |
| 108 | if (category_index >= kMaxCategories) { |
| 109 | NOTREACHED() << "must increase kMaxCategories"; |
| 110 | *category = kCategoryExhausted; |
| 111 | return false; |
| 112 | } |
| 113 | |
| 114 | // TODO(primiano): this strdup should be removed. The only documented reason |
| 115 | // for it was TraceWatchEvent, which is gone. However, something might have |
| 116 | // ended up relying on this. Needs some auditing before removal. |
| 117 | const char* category_name_copy = strdup(category_name); |
| 118 | ANNOTATE_LEAKING_OBJECT_PTR(category_name_copy); |
| 119 | |
| 120 | *category = &g_categories[category_index]; |
| 121 | DCHECK(!(*category)->is_valid()); |
| 122 | DCHECK(!(*category)->is_enabled()); |
| 123 | (*category)->set_name(category_name_copy); |
| 124 | category_initializer_fn(*category); |
| 125 | |
| 126 | // Update the max index now. |
| 127 | base::subtle::Release_Store(&g_category_index, category_index + 1); |
| 128 | return true; |
| 129 | } |
| 130 | |
| 131 | // static |
| 132 | const TraceCategory* CategoryRegistry::GetCategoryByStatePtr( |
| 133 | const uint8_t* category_state) { |
| 134 | const TraceCategory* category = TraceCategory::FromStatePtr(category_state); |
| 135 | DCHECK(IsValidCategoryPtr(category)); |
| 136 | return category; |
| 137 | } |
| 138 | |
| 139 | // static |
| 140 | bool CategoryRegistry::IsBuiltinCategory(const TraceCategory* category) { |
| 141 | DCHECK(IsValidCategoryPtr(category)); |
| 142 | return category < &g_categories[kNumBuiltinCategories]; |
| 143 | } |
| 144 | |
| 145 | // static |
| 146 | CategoryRegistry::Range CategoryRegistry::GetAllCategories() { |
| 147 | // The |g_categories| array is append only. We have to only guarantee to |
| 148 | // not return an index to a category which is being initialized by |
| 149 | // GetOrCreateCategoryByName(). |
| 150 | size_t category_index = base::subtle::Acquire_Load(&g_category_index); |
| 151 | return CategoryRegistry::Range(&g_categories[0], |
| 152 | &g_categories[category_index]); |
| 153 | } |
| 154 | |
| 155 | } // namespace trace_event |
| 156 | } // namespace base |