blob: e7c14606d66bd997cb3b5fb0ce4df19a2996d6f3 [file] [log] [blame]
Jay Civelli3a83cdd2017-03-22 17:31:44 -07001// 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
17namespace base {
18namespace trace_event {
19
20namespace {
21
22constexpr size_t kMaxCategories = 200;
23const int kNumBuiltinCategories = 4;
24
25// |g_categories| might end up causing creating dynamic initializers if not POD.
26static_assert(std::is_pod<TraceCategory>::value, "TraceCategory must be POD");
27
28// These entries must be kept consistent with the kCategory* consts below.
29TraceCategory 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
36base::subtle::AtomicWord g_category_index = kNumBuiltinCategories;
37
38bool 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
49TraceCategory* const CategoryRegistry::kCategoryExhausted = &g_categories[0];
50TraceCategory* const CategoryRegistry::kCategoryAlreadyShutdown =
51 &g_categories[1];
52TraceCategory* const CategoryRegistry::kCategoryMetadata = &g_categories[2];
53
54// static
55void 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
70void 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
79TraceCategory* 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
95bool 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
132const 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
140bool CategoryRegistry::IsBuiltinCategory(const TraceCategory* category) {
141 DCHECK(IsValidCategoryPtr(category));
142 return category < &g_categories[kNumBuiltinCategories];
143}
144
145// static
146CategoryRegistry::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