1 // Copyright 2015 The Chromium Authors
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/trace_event_etw_export_win.h"
6
7 #include <evntrace.h>
8 #include <guiddef.h>
9 #include <stddef.h>
10 #include <stdlib.h>
11 #include <windows.h>
12 #include <utility>
13
14 #include "base/at_exit.h"
15 #include "base/check_op.h"
16 #include "base/command_line.h"
17 #include "base/logging.h"
18 #include "base/memory/singleton.h"
19 #include "base/no_destructor.h"
20 #include "base/strings/string_tokenizer.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/threading/platform_thread.h"
24 #include "base/trace_event/trace_event.h"
25 #include "base/trace_event/trace_event_impl.h"
26 #include "base/trace_event/trace_logging_minimal_win.h"
27
28 namespace {
29
30 // |kFilteredEventGroupNames| contains the event categories that can be
31 // exported individually. These categories can be enabled by passing the correct
32 // keyword when starting the trace. A keyword is a 64-bit flag and we attribute
33 // one bit per category. We can therefore enable a particular category by
34 // setting its corresponding bit in the keyword. For events that are not present
35 // in |kFilteredEventGroupNames|, we have two bits that control their
36 // behaviour. When bit 46 is enabled, any event that is not disabled by default
37 // (ie. doesn't start with disabled-by-default-) will be exported. Likewise,
38 // when bit 47 is enabled, any event that is disabled by default will be
39 // exported.
40 //
41 // Examples of passing keywords to the provider using xperf:
42 // # This exports "benchmark" and "cc" events
43 // xperf -start chrome -on Chrome:0x9
44 //
45 // # This exports "gpu", "netlog" and all other events that are not disabled by
46 // # default
47 // xperf -start chrome -on Chrome:0x4000000000A0
48 //
49 // More info about starting a trace and keyword can be obtained by using the
50 // help section of xperf (xperf -help start). Note that xperf documentation
51 // refers to keywords as flags and there are two ways to enable them, using
52 // group names or the hex representation. We only support the latter. Also, we
53 // ignore the level.
54 //
55 // To avoid continually having to bump MSEdge values to next higher bits, we
56 // are putting MSEdge values at the high end of the bit range and will grow
57 // 'down' to lower bits for future MSEdge entries.
58 //
59 // As the writing of this comment, we have 4 values:
60 // "navigation", // 0x40000000000
61 // "ServiceWorker", // 0x80000000000
62 // "edge_webview", // 0x100000000000
63 // "diagnostic_event", // 0x200000000000
64 //
65 // This means the next value added should be:
66 // "the_next_value", // 0x20000000000
67 // "navigation", // 0x40000000000
68 // "ServiceWorker", // 0x80000000000
69 // "edge_webview", // 0x100000000000
70 // "diagnostic_event", // 0x200000000000
71 //
72 // The addition of the "unused_bit_nn" entries keeps the existing code execution
73 // routines working (ex. TraceEventETWExport::UpdateEnabledCategories()) and
74 // enables others to see which bits are available.
75 //
76 // Example: For some new category group...
77 // "latency", // 0x8000
78 // "blink.user_timing", // 0x10000
79 // "unused_bit_18", // 0x20000
80 // "unused_bit_19", // 0x40000
81 // "unused_bit_20", // 0x80000
82 // ...
83 // becomes:
84 // "latency", // 0x8000
85 // "blink.user_timing", // 0x10000
86 // "new_upstream_value", // 0x20000
87 // "unused_bit_19", // 0x40000
88 // "unused_bit_20", // 0x80000
89 //
90 // The high 16 bits of the keyword have special semantics and should not be
91 // set for enabling individual categories as they are reserved by winmeta.xml.
92 // TODO(crbug.com/1497783): Move this to
93 // components/tracing/common/etw_export_win.cc once no longer used by
94 // TraceEventETWExport.
95 const char* const kFilteredEventGroupNames[] = {
96 "benchmark", // 0x1
97 "blink", // 0x2
98 "browser", // 0x4
99 "cc", // 0x8
100 "evdev", // 0x10
101 "gpu", // 0x20
102 "input", // 0x40
103 "netlog", // 0x80
104 "sequence_manager", // 0x100
105 "toplevel", // 0x200
106 "v8", // 0x400
107 "disabled-by-default-cc.debug", // 0x800
108 "disabled-by-default-cc.debug.picture", // 0x1000
109 "disabled-by-default-toplevel.flow", // 0x2000
110 "startup", // 0x4000
111 "latency", // 0x8000
112 "blink.user_timing", // 0x10000
113 "media", // 0x20000
114 "loading", // 0x40000
115 "base", // 0x80000
116 "unused_bit_20", // 0x100000
117 "unused_bit_21", // 0x200000
118 "unused_bit_22", // 0x400000
119 "unused_bit_23", // 0x800000
120 "unused_bit_24", // 0x1000000
121 "unused_bit_25", // 0x2000000
122 "unused_bit_26", // 0x4000000
123 "unused_bit_27", // 0x8000000
124 "unused_bit_28", // 0x10000000
125 "unused_bit_29", // 0x20000000
126 "unused_bit_30", // 0x40000000
127 "unused_bit_31", // 0x80000000
128 "unused_bit_32", // 0x100000000
129 "unused_bit_33", // 0x200000000
130 "unused_bit_34", // 0x400000000
131 "unused_bit_35", // 0x800000000
132 "unused_bit_36", // 0x1000000000
133 "unused_bit_37", // 0x2000000000
134 "unused_bit_38", // 0x4000000000
135 "unused_bit_39", // 0x8000000000
136 "unused_bit_40", // 0x10000000000
137 "unused_bit_41", // 0x20000000000
138 "navigation", // 0x40000000000
139 "ServiceWorker", // 0x80000000000
140 "edge_webview", // 0x100000000000
141 "diagnostic_event", // 0x200000000000
142 "__OTHER_EVENTS", // 0x400000000000 See below
143 "__DISABLED_OTHER_EVENTS", // 0x800000000000 See below
144 };
145
146 // These must be kept as the last two entries in the above array.
147 constexpr uint8_t kOtherEventsGroupNameIndex = 46;
148 constexpr uint8_t kDisabledOtherEventsGroupNameIndex = 47;
149 constexpr uint64_t kCategoryKeywordMask = ~0xFFFF000000000000;
150
151 // Max number of available keyword bits.
152 constexpr size_t kMaxNumberOfGroupNames = 48;
153
154 } // namespace
155
156 namespace base {
157 namespace trace_event {
158
TraceEventETWExport()159 TraceEventETWExport::TraceEventETWExport() {
160 // Construct the ETW provider. If construction fails then the event logging
161 // calls will fail. We're passing a callback function as part of registration.
162 // This allows us to detect changes to enable/disable/keyword changes.
163 etw_provider_ = std::make_unique<TlmProvider>(
164 "Google.Chrome", Chrome_GUID,
165 base::BindRepeating(&TraceEventETWExport::OnETWEnableUpdate,
166 base::Unretained(this)));
167 is_registration_complete_ = true;
168
169 // Make sure to initialize the map with all the group names. Subsequent
170 // modifications will be made by the background thread and only affect the
171 // values of the keys (no key addition/deletion). Therefore, the map does not
172 // require a lock for access.
173 // Also set up the map from category name to keyword.
174 for (size_t i = 0; i < std::size(kFilteredEventGroupNames); i++) {
175 categories_status_[kFilteredEventGroupNames[i]] = false;
176 }
177 // Make sure we stay at 48 entries, the maximum number of bits available
178 // for keyword use.
179 static_assert(std::size(kFilteredEventGroupNames) <= kMaxNumberOfGroupNames,
180 "Exceeded max ETW keyword bits");
181 }
182
~TraceEventETWExport()183 TraceEventETWExport::~TraceEventETWExport() {
184 is_registration_complete_ = false;
185 }
186
187 // static
EnableETWExport()188 void TraceEventETWExport::EnableETWExport() {
189 auto* instance = GetInstance();
190 if (instance) {
191 // Sync the enabled categories with ETW by calling UpdateEnabledCategories()
192 // that checks the keyword. We'll stay in sync via the EtwEnableCallback
193 // we register in TraceEventETWExport's constructor.
194 instance->UpdateEnabledCategories();
195 }
196 }
197
198 // static
AddEvent(char phase,const unsigned char * category_group_enabled,const char * name,unsigned long long id,TimeTicks timestamp,const TraceArguments * args)199 void TraceEventETWExport::AddEvent(char phase,
200 const unsigned char* category_group_enabled,
201 const char* name,
202 unsigned long long id,
203 TimeTicks timestamp,
204 const TraceArguments* args) {
205 // We bail early in case exporting is disabled or no consumer is listening.
206 auto* instance = GetInstance();
207 if (!instance) {
208 return;
209 }
210 uint64_t keyword =
211 instance->CategoryStateToETWKeyword(category_group_enabled);
212 if (!instance->etw_provider_->IsEnabled(TRACE_LEVEL_NONE, keyword)) {
213 return;
214 }
215
216 const char* phase_string = nullptr;
217
218 // Space to store the phase identifier and null-terminator, when needed.
219 char phase_buffer[2];
220 switch (phase) {
221 case TRACE_EVENT_PHASE_BEGIN:
222 phase_string = "Begin";
223 break;
224 case TRACE_EVENT_PHASE_END:
225 phase_string = "End";
226 break;
227 case TRACE_EVENT_PHASE_COMPLETE:
228 phase_string = "Complete";
229 break;
230 case TRACE_EVENT_PHASE_INSTANT:
231 phase_string = "Instant";
232 break;
233 case TRACE_EVENT_PHASE_ASYNC_BEGIN:
234 phase_string = "Async Begin";
235 break;
236 case TRACE_EVENT_PHASE_ASYNC_STEP_INTO:
237 phase_string = "Async Step Into";
238 break;
239 case TRACE_EVENT_PHASE_ASYNC_STEP_PAST:
240 phase_string = "Async Step Past";
241 break;
242 case TRACE_EVENT_PHASE_ASYNC_END:
243 phase_string = "Async End";
244 break;
245 case TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN:
246 phase_string = "Nestable Async Begin";
247 break;
248 case TRACE_EVENT_PHASE_NESTABLE_ASYNC_END:
249 phase_string = "Nestable Async End";
250 break;
251 case TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT:
252 phase_string = "Nestable Async Instant";
253 break;
254 case TRACE_EVENT_PHASE_FLOW_BEGIN:
255 phase_string = "Phase Flow Begin";
256 break;
257 case TRACE_EVENT_PHASE_FLOW_STEP:
258 phase_string = "Phase Flow Step";
259 break;
260 case TRACE_EVENT_PHASE_FLOW_END:
261 phase_string = "Phase Flow End";
262 break;
263 case TRACE_EVENT_PHASE_METADATA:
264 phase_string = "Phase Metadata";
265 break;
266 case TRACE_EVENT_PHASE_COUNTER:
267 phase_string = "Phase Counter";
268 break;
269 case TRACE_EVENT_PHASE_SAMPLE:
270 phase_string = "Phase Sample";
271 break;
272 case TRACE_EVENT_PHASE_CREATE_OBJECT:
273 phase_string = "Phase Create Object";
274 break;
275 case TRACE_EVENT_PHASE_SNAPSHOT_OBJECT:
276 phase_string = "Phase Snapshot Object";
277 break;
278 case TRACE_EVENT_PHASE_DELETE_OBJECT:
279 phase_string = "Phase Delete Object";
280 break;
281 default:
282 phase_buffer[0] = phase;
283 phase_buffer[1] = 0;
284 phase_string = phase_buffer;
285 break;
286 }
287
288 std::string arg_values_string[3];
289 size_t num_args = args ? args->size() : 0;
290 for (size_t i = 0; i < num_args; i++) {
291 const auto type = args->types()[i];
292 if (type == TRACE_VALUE_TYPE_CONVERTABLE ||
293 type == TRACE_VALUE_TYPE_PROTO) {
294 // For convertable types, temporarily do nothing here. This function
295 // consumes 1/3 to 1/2 of *total* process CPU time when ETW tracing, and
296 // many of the strings created exceed WPA's 4094 byte limit and are shown
297 // as "Unable to parse data". See crbug.com/488257.
298 //
299 // For protobuf-based values, there is no string serialization so skip
300 // those as well.
301 } else {
302 args->values()[i].AppendAsString(type, arg_values_string + i);
303 }
304 }
305
306 int64_t timestamp_ms = (timestamp - TimeTicks()).InMilliseconds();
307 // Log the event and include the info needed to decode it via TraceLogging
308 if (num_args == 0) {
309 instance->etw_provider_->WriteEvent(
310 name, TlmEventDescriptor(0, keyword),
311 TlmMbcsStringField("Phase", phase_string),
312 TlmInt64Field("Timestamp", timestamp_ms));
313 } else if (num_args == 1) {
314 instance->etw_provider_->WriteEvent(
315 name, TlmEventDescriptor(0, keyword),
316 TlmMbcsStringField("Phase", phase_string),
317 TlmInt64Field("Timestamp", timestamp_ms),
318 TlmMbcsStringField((args->names()[0]), (arg_values_string[0].c_str())));
319 } else if (num_args == 2) {
320 instance->etw_provider_->WriteEvent(
321 name, TlmEventDescriptor(0, keyword),
322 TlmMbcsStringField("Phase", phase_string),
323 TlmInt64Field("Timestamp", timestamp_ms),
324 TlmMbcsStringField((args->names()[0]), (arg_values_string[0].c_str())),
325 TlmMbcsStringField((args->names()[1]), (arg_values_string[1].c_str())));
326 } else {
327 NOTREACHED();
328 }
329 }
330
331 // static
AddCompleteEndEvent(const unsigned char * category_group_enabled,const char * name)332 void TraceEventETWExport::AddCompleteEndEvent(
333 const unsigned char* category_group_enabled,
334 const char* name) {
335 auto* instance = GetInstance();
336 if (!instance) {
337 return;
338 }
339 uint64_t keyword =
340 instance->CategoryStateToETWKeyword(category_group_enabled);
341 if (!instance->etw_provider_->IsEnabled(TRACE_LEVEL_NONE, keyword)) {
342 return;
343 }
344
345 // Log the event and include the info needed to decode it via TraceLogging
346 instance->etw_provider_->WriteEvent(
347 name, TlmEventDescriptor(0, keyword),
348 TlmMbcsStringField("Phase", "Complete End"));
349 }
350
351 // static
IsCategoryGroupEnabled(StringPiece category_group_name)352 bool TraceEventETWExport::IsCategoryGroupEnabled(
353 StringPiece category_group_name) {
354 DCHECK(!category_group_name.empty());
355
356 auto* instance = GetInstanceIfExists();
357 if (instance == nullptr)
358 return false;
359
360 if (!instance->etw_provider_->IsEnabled())
361 return false;
362
363 CStringTokenizer category_group_tokens(category_group_name.begin(),
364 category_group_name.end(), ",");
365 while (category_group_tokens.GetNext()) {
366 StringPiece category_group_token = category_group_tokens.token_piece();
367 if (instance->IsCategoryEnabled(category_group_token)) {
368 return true;
369 }
370 }
371 return false;
372 }
373
UpdateEnabledCategories()374 bool TraceEventETWExport::UpdateEnabledCategories() {
375 if (etw_match_any_keyword_ ==
376 (etw_provider_->keyword_any() & kCategoryKeywordMask)) {
377 return false;
378 }
379
380 // If keyword_any() has changed, update each category. The global
381 // context is set by UIforETW (or other ETW trace recording tools)
382 // using the ETW infrastructure. When the global context changes the
383 // callback will be called to set the updated keyword bits in each
384 // process that has registered their ETW provider.
385 etw_match_any_keyword_ = etw_provider_->keyword_any() & kCategoryKeywordMask;
386 for (size_t i = 0; i < std::size(kFilteredEventGroupNames); i++) {
387 if (etw_match_any_keyword_ & (1ULL << i)) {
388 categories_status_[kFilteredEventGroupNames[i]] = true;
389 } else {
390 categories_status_[kFilteredEventGroupNames[i]] = false;
391 }
392 }
393
394 // Update the categories in TraceLog.
395 TraceLog::GetInstance()->UpdateETWCategoryGroupEnabledFlags();
396
397 return true;
398 }
399
IsCategoryEnabled(StringPiece category_name) const400 bool TraceEventETWExport::IsCategoryEnabled(StringPiece category_name) const {
401 // Try to find the category and return its status if found
402 auto it = categories_status_.find(category_name);
403 if (it != categories_status_.end())
404 return it->second;
405
406 // Otherwise return the corresponding default status by first checking if the
407 // category is disabled by default.
408 if (StartsWith(category_name, "disabled-by-default")) {
409 DCHECK(categories_status_.find(
410 kFilteredEventGroupNames[kDisabledOtherEventsGroupNameIndex]) !=
411 categories_status_.end());
412 return categories_status_
413 .find(kFilteredEventGroupNames[kDisabledOtherEventsGroupNameIndex])
414 ->second;
415 } else {
416 DCHECK(categories_status_.find(
417 kFilteredEventGroupNames[kOtherEventsGroupNameIndex]) !=
418 categories_status_.end());
419 return categories_status_
420 .find(kFilteredEventGroupNames[kOtherEventsGroupNameIndex])
421 ->second;
422 }
423 }
424
CategoryStateToETWKeyword(const uint8_t * category_state)425 uint64_t TraceEventETWExport::CategoryStateToETWKeyword(
426 const uint8_t* category_state) {
427 const TraceCategory* category = TraceCategory::FromStatePtr(category_state);
428 uint64_t keyword = CategoryGroupToETWKeyword(category->name());
429 return keyword;
430 }
431
OnETWEnableUpdate(TlmProvider::EventControlCode enabled)432 void TraceEventETWExport::OnETWEnableUpdate(
433 TlmProvider::EventControlCode enabled) {
434 // During construction, if tracing is already enabled, we'll get
435 // a callback synchronously on the same thread. Calling GetInstance
436 // in that case will hang since we're in the process of creating the
437 // singleton.
438 if (is_registration_complete_) {
439 UpdateEnabledCategories();
440 }
441 }
442
443 // static
GetInstance()444 TraceEventETWExport* TraceEventETWExport::GetInstance() {
445 return Singleton<TraceEventETWExport,
446 StaticMemorySingletonTraits<TraceEventETWExport>>::get();
447 }
448
449 // static
GetInstanceIfExists()450 TraceEventETWExport* TraceEventETWExport::GetInstanceIfExists() {
451 return Singleton<
452 TraceEventETWExport,
453 StaticMemorySingletonTraits<TraceEventETWExport>>::GetIfExists();
454 }
455
CategoryGroupToETWKeyword(std::string_view category_group_name)456 uint64_t CategoryGroupToETWKeyword(std::string_view category_group_name) {
457 static NoDestructor<base::flat_map<std::string_view, uint64_t>>
458 categories_to_keyword([]() {
459 std::vector<std::pair<std::string_view, uint64_t>> items;
460 for (size_t i = 0; i < kOtherEventsGroupNameIndex; i++) {
461 uint64_t keyword = 1ULL << i;
462 items.emplace_back(kFilteredEventGroupNames[i], keyword);
463 }
464 std::sort(items.begin(), items.end());
465 return base::flat_map<std::string_view, uint64_t>(base::sorted_unique,
466 std::move(items));
467 }());
468
469 uint64_t keyword = 0;
470
471 // To enable multiple sessions with this provider enabled we need to log the
472 // level and keyword with the event so that if the sessions differ in the
473 // level or keywords enabled we log the right events and allow ETW to
474 // route the data to the appropriate session.
475 // TODO(joel@microsoft.com) Explore better methods in future integration
476 // with perfetto.
477
478 CStringTokenizer category_group_tokens(category_group_name.begin(),
479 category_group_name.end(), ",");
480 while (category_group_tokens.GetNext()) {
481 StringPiece category_group_token = category_group_tokens.token_piece();
482
483 // Lookup the keyword for this part of the category_group_name
484 // and or in the keyword.
485 auto it = categories_to_keyword->find(category_group_token);
486 if (it != categories_to_keyword->end()) {
487 keyword |= it->second;
488 } else {
489 if (StartsWith(category_group_token, "disabled-by-default")) {
490 keyword |= (1ULL << kDisabledOtherEventsGroupNameIndex);
491 } else {
492 keyword |= (1ULL << kOtherEventsGroupNameIndex);
493 }
494 }
495 }
496 return keyword;
497 }
498
499 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
500
ETWKeywordToTrackEventConfig(uint64_t keyword)501 perfetto::protos::gen::TrackEventConfig ETWKeywordToTrackEventConfig(
502 uint64_t keyword) {
503 perfetto::protos::gen::TrackEventConfig track_event_config;
504 for (size_t i = 0; i < kOtherEventsGroupNameIndex; ++i) {
505 if (keyword & (1ULL << i)) {
506 track_event_config.add_enabled_categories(kFilteredEventGroupNames[i]);
507 }
508 }
509 bool other_events_enabled = (keyword & (1ULL << kOtherEventsGroupNameIndex));
510 bool disabled_other_events_enables =
511 (keyword & (1ULL << kDisabledOtherEventsGroupNameIndex));
512 if (!other_events_enabled) {
513 track_event_config.add_disabled_categories("*");
514 }
515 if (!disabled_other_events_enables) {
516 track_event_config.add_disabled_categories("disabled-by-default-*");
517 } else if (other_events_enabled) {
518 track_event_config.add_enabled_categories("disabled-by-default-*");
519 }
520 return track_event_config;
521 }
522
523 #endif // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
524
525 } // namespace trace_event
526 } // namespace base
527