blob: dbe3941fd6aff29bceeba0981487461b67c42ff8 [file] [log] [blame]
// Copyright 2015 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/trace_event_etw_export_win.h"
#include <stddef.h>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_impl.h"
#include <windows.h>
// The GetProcAddress technique is borrowed from
// https://github.com/google/UIforETW/tree/master/ETWProviders
//
// EVNTAPI is used in evntprov.h which is included by chrome_events_win.h.
// We define EVNTAPI without the DECLSPEC_IMPORT specifier so that we can
// implement these functions locally instead of using the import library, and
// can therefore still run on Windows XP.
#define EVNTAPI __stdcall
// Include the event register/write/unregister macros compiled from the manifest
// file. Note that this includes evntprov.h which requires a Vista+ Windows SDK.
//
// In SHARED_INTERMEDIATE_DIR.
// Headers generated by mc.exe have a ';' at the end of extern "C" {} blocks.
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wextra-semi"
#endif
#include "base/trace_event/etw_manifest/chrome_events_win.h" // NOLINT
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
namespace {
// |kFilteredEventGroupNames| contains the event categories that can be
// exported individually. These categories can be enabled by passing the correct
// keyword when starting the trace. A keyword is a 64-bit flag and we attribute
// one bit per category. We can therefore enable a particular category by
// setting its corresponding bit in the keyword. For events that are not present
// in |kFilteredEventGroupNames|, we have two bits that control their
// behaviour. When bit 61 is enabled, any event that is not disabled by default
// (ie. doesn't start with disabled-by-default-) will be exported. Likewise,
// when bit 62 is enabled, any event that is disabled by default will be
// exported.
//
// Note that bit 63 (MSB) must always be set, otherwise tracing will be disabled
// by ETW. Therefore, the keyword will always be greater than
// 0x8000000000000000.
//
// Examples of passing keywords to the provider using xperf:
// # This exports "benchmark" and "cc" events
// xperf -start chrome -on Chrome:0x8000000000000009
//
// # This exports "gpu", "netlog" and all other events that are not disabled by
// # default
// xperf -start chrome -on Chrome:0xA0000000000000A0
//
// More info about starting a trace and keyword can be obtained by using the
// help section of xperf (xperf -help start). Note that xperf documentation
// refers to keywords as flags and there are two ways to enable them, using
// group names or the hex representation. We only support the latter. Also, we
// ignore the level.
const char* const kFilteredEventGroupNames[] = {
"benchmark", // 0x1
"blink", // 0x2
"browser", // 0x4
"cc", // 0x8
"evdev", // 0x10
"gpu", // 0x20
"input", // 0x40
"netlog", // 0x80
"sequence_manager", // 0x100
"toplevel", // 0x200
"v8", // 0x400
"disabled-by-default-cc.debug", // 0x800
"disabled-by-default-cc.debug.picture", // 0x1000
"disabled-by-default-toplevel.flow", // 0x2000
"startup", // 0x4000
"latency"}; // 0x8000
const char kOtherEventsGroupName[] = "__OTHER_EVENTS"; // 0x2000000000000000
const char kDisabledOtherEventsGroupName[] =
"__DISABLED_OTHER_EVENTS"; // 0x4000000000000000
const uint64_t kOtherEventsKeywordBit = 1ULL << 61;
const uint64_t kDisabledOtherEventsKeywordBit = 1ULL << 62;
const size_t kNumberOfCategories = ARRAYSIZE(kFilteredEventGroupNames) + 2U;
static void __stdcall EtwEnableCallback(LPCGUID SourceId,
ULONG ControlCode,
UCHAR Level,
ULONGLONG MatchAnyKeyword,
ULONGLONG MatchAllKeyword,
PEVENT_FILTER_DESCRIPTOR FilterData,
PVOID CallbackContext) {
// Invoke the default callback, which updates the information inside
// CHROME_Context.
McGenControlCallbackV2(SourceId, ControlCode, Level, MatchAnyKeyword,
MatchAllKeyword, FilterData, CallbackContext);
base::trace_event::TraceEventETWExport::OnETWEnableUpdate();
}
} // namespace
namespace base {
namespace trace_event {
bool TraceEventETWExport::is_registration_complete_ = false;
TraceEventETWExport::TraceEventETWExport() : etw_match_any_keyword_(0ULL) {
// Register the ETW provider. If registration fails then the event logging
// calls will fail. We're essentially doing the same operation as
// EventRegisterChrome (which was auto generated for our provider by the
// ETW manifest compiler), but instead we're passing our own callback.
// This allows us to detect changes to enable/disable/keyword changes.
// ChromeHandle and the other parameters to EventRegister are all generated
// globals from chrome_events_win.h
DCHECK(!ChromeHandle);
EventRegister(&CHROME, &EtwEnableCallback, &CHROME_Context, &ChromeHandle);
TraceEventETWExport::is_registration_complete_ = true;
// Make sure to initialize the map with all the group names. Subsequent
// modifications will be made by the background thread and only affect the
// values of the keys (no key addition/deletion). Therefore, the map does not
// require a lock for access.
for (size_t i = 0; i < ARRAYSIZE(kFilteredEventGroupNames); i++)
categories_status_[kFilteredEventGroupNames[i]] = false;
categories_status_[kOtherEventsGroupName] = false;
categories_status_[kDisabledOtherEventsGroupName] = false;
DCHECK_EQ(kNumberOfCategories, categories_status_.size());
}
TraceEventETWExport::~TraceEventETWExport() {
EventUnregisterChrome();
is_registration_complete_ = false;
}
// static
void TraceEventETWExport::EnableETWExport() {
auto* instance = GetInstance();
if (instance) {
// Sync the enabled categories with ETW by calling UpdateEnabledCategories()
// that checks the keyword. We'll stay in sync via the EtwEnableCallback
// we register in TraceEventETWExport's constructor.
instance->UpdateEnabledCategories();
}
}
// static
void TraceEventETWExport::AddEvent(char phase,
const unsigned char* category_group_enabled,
const char* name,
unsigned long long id,
const TraceArguments* args) {
// We bail early in case exporting is disabled or no consumer is listening.
auto* instance = GetInstance();
if (!instance || !EventEnabledChromeEvent())
return;
const char* phase_string = nullptr;
// Space to store the phase identifier and null-terminator, when needed.
char phase_buffer[2];
switch (phase) {
case TRACE_EVENT_PHASE_BEGIN:
phase_string = "Begin";
break;
case TRACE_EVENT_PHASE_END:
phase_string = "End";
break;
case TRACE_EVENT_PHASE_COMPLETE:
phase_string = "Complete";
break;
case TRACE_EVENT_PHASE_INSTANT:
phase_string = "Instant";
break;
case TRACE_EVENT_PHASE_ASYNC_BEGIN:
phase_string = "Async Begin";
break;
case TRACE_EVENT_PHASE_ASYNC_STEP_INTO:
phase_string = "Async Step Into";
break;
case TRACE_EVENT_PHASE_ASYNC_STEP_PAST:
phase_string = "Async Step Past";
break;
case TRACE_EVENT_PHASE_ASYNC_END:
phase_string = "Async End";
break;
case TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN:
phase_string = "Nestable Async Begin";
break;
case TRACE_EVENT_PHASE_NESTABLE_ASYNC_END:
phase_string = "Nestable Async End";
break;
case TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT:
phase_string = "Nestable Async Instant";
break;
case TRACE_EVENT_PHASE_FLOW_BEGIN:
phase_string = "Phase Flow Begin";
break;
case TRACE_EVENT_PHASE_FLOW_STEP:
phase_string = "Phase Flow Step";
break;
case TRACE_EVENT_PHASE_FLOW_END:
phase_string = "Phase Flow End";
break;
case TRACE_EVENT_PHASE_METADATA:
phase_string = "Phase Metadata";
break;
case TRACE_EVENT_PHASE_COUNTER:
phase_string = "Phase Counter";
break;
case TRACE_EVENT_PHASE_SAMPLE:
phase_string = "Phase Sample";
break;
case TRACE_EVENT_PHASE_CREATE_OBJECT:
phase_string = "Phase Create Object";
break;
case TRACE_EVENT_PHASE_SNAPSHOT_OBJECT:
phase_string = "Phase Snapshot Object";
break;
case TRACE_EVENT_PHASE_DELETE_OBJECT:
phase_string = "Phase Delete Object";
break;
default:
phase_buffer[0] = phase;
phase_buffer[1] = 0;
phase_string = phase_buffer;
break;
}
std::string arg_values_string[3];
size_t num_args = args ? args->size() : 0;
for (size_t i = 0; i < num_args; i++) {
if (args->types()[i] == TRACE_VALUE_TYPE_CONVERTABLE) {
// Temporarily do nothing here. This function consumes 1/3 to 1/2 of
// *total* process CPU time when ETW tracing, and many of the strings
// created exceed WPA's 4094 byte limit and are shown as:
// "Unable to parse data". See crbug.com/488257
} else {
args->values()[i].AppendAsString(args->types()[i], arg_values_string + i);
}
}
EventWriteChromeEvent(
name, phase_string, num_args > 0 ? args->names()[0] : "",
arg_values_string[0].c_str(), num_args > 1 ? args->names()[1] : "",
arg_values_string[1].c_str(), "", "");
}
// static
void TraceEventETWExport::AddCompleteEndEvent(const char* name) {
auto* instance = GetInstance();
if (!instance || !EventEnabledChromeEvent())
return;
EventWriteChromeEvent(name, "Complete End", "", "", "", "", "", "");
}
// static
bool TraceEventETWExport::IsCategoryGroupEnabled(
StringPiece category_group_name) {
DCHECK(!category_group_name.empty());
auto* instance = GetInstanceIfExists();
if (instance == nullptr)
return false;
if (!EventEnabledChromeEvent())
return false;
CStringTokenizer category_group_tokens(category_group_name.begin(),
category_group_name.end(), ",");
while (category_group_tokens.GetNext()) {
StringPiece category_group_token = category_group_tokens.token_piece();
if (instance->IsCategoryEnabled(category_group_token)) {
return true;
}
}
return false;
}
bool TraceEventETWExport::UpdateEnabledCategories() {
if (etw_match_any_keyword_ == CHROME_Context.MatchAnyKeyword)
return false;
// If the keyword has changed, update each category.
// Chrome_Context.MatchAnyKeyword is set by UIforETW (or other ETW trace
// recording tools) using the ETW infrastructure. This value will be set in
// all Chrome processes that have registered their ETW provider.
etw_match_any_keyword_ = CHROME_Context.MatchAnyKeyword;
for (size_t i = 0; i < ARRAYSIZE(kFilteredEventGroupNames); i++) {
if (etw_match_any_keyword_ & (1ULL << i)) {
categories_status_[kFilteredEventGroupNames[i]] = true;
} else {
categories_status_[kFilteredEventGroupNames[i]] = false;
}
}
// Also update the two default categories.
if (etw_match_any_keyword_ & kOtherEventsKeywordBit) {
categories_status_[kOtherEventsGroupName] = true;
} else {
categories_status_[kOtherEventsGroupName] = false;
}
if (etw_match_any_keyword_ & kDisabledOtherEventsKeywordBit) {
categories_status_[kDisabledOtherEventsGroupName] = true;
} else {
categories_status_[kDisabledOtherEventsGroupName] = false;
}
DCHECK_EQ(kNumberOfCategories, categories_status_.size());
// Update the categories in TraceLog.
TraceLog::GetInstance()->UpdateETWCategoryGroupEnabledFlags();
return true;
}
bool TraceEventETWExport::IsCategoryEnabled(StringPiece category_name) const {
DCHECK_EQ(kNumberOfCategories, categories_status_.size());
// Try to find the category and return its status if found
auto it = categories_status_.find(category_name);
if (it != categories_status_.end())
return it->second;
// Otherwise return the corresponding default status by first checking if the
// category is disabled by default.
if (category_name.starts_with("disabled-by-default")) {
DCHECK(categories_status_.find(kDisabledOtherEventsGroupName) !=
categories_status_.end());
return categories_status_.find(kDisabledOtherEventsGroupName)->second;
} else {
DCHECK(categories_status_.find(kOtherEventsGroupName) !=
categories_status_.end());
return categories_status_.find(kOtherEventsGroupName)->second;
}
}
// static
void TraceEventETWExport::OnETWEnableUpdate() {
// During construction, if tracing is already enabled, we'll get
// a callback synchronously on the same thread. Calling GetInstance
// in that case will hang since we're in the process of creating the
// singleton.
if (is_registration_complete_) {
auto* instance = GetInstance();
if (instance)
instance->UpdateEnabledCategories();
}
}
// static
TraceEventETWExport* TraceEventETWExport::GetInstance() {
return Singleton<TraceEventETWExport,
StaticMemorySingletonTraits<TraceEventETWExport>>::get();
}
// static
TraceEventETWExport* TraceEventETWExport::GetInstanceIfExists() {
return Singleton<
TraceEventETWExport,
StaticMemorySingletonTraits<TraceEventETWExport>>::GetIfExists();
}
} // namespace trace_event
} // namespace base