1 // Copyright 2023 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/test/test_trace_processor.h"
6 #include "base/command_line.h"
7 #include "base/files/file_util.h"
8 #include "base/test/chrome_track_event.descriptor.h"
9 #include "base/test/perfetto_sql_stdlib.h"
10 #include "base/trace_event/trace_log.h"
11 #include "third_party/perfetto/protos/perfetto/trace/extension_descriptor.pbzero.h"
12
13 namespace base::test {
14
15 #if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
16
17 namespace {
18 // Emitting the chrome_track_event.descriptor into the trace allows the trace
19 // processor to parse the arguments during ingestion of the trace events.
20 // This function emits the descriptor generated from
21 // base/tracing/protos/chrome_track_event.proto so we can use TestTraceProcessor
22 // to write tests based on new arguments/types added in the same patch.
EmitChromeTrackEventDescriptor()23 void EmitChromeTrackEventDescriptor() {
24 base::TrackEvent::Trace([&](base::TrackEvent::TraceContext ctx) {
25 protozero::MessageHandle<perfetto::protos::pbzero::TracePacket> handle =
26 ctx.NewTracePacket();
27 auto* extension_descriptor = handle->BeginNestedMessage<protozero::Message>(
28 perfetto::protos::pbzero::TracePacket::kExtensionDescriptorFieldNumber);
29 extension_descriptor->AppendBytes(
30 perfetto::protos::pbzero::ExtensionDescriptor::kExtensionSetFieldNumber,
31 perfetto::kChromeTrackEventDescriptor.data(),
32 perfetto::kChromeTrackEventDescriptor.size());
33 handle->Finalize();
34 });
35 }
36
37 std::string kChromeSqlModuleName = "chrome";
38 // A command-line switch to save the trace test trace processor generated to
39 // make debugging complex traces.
40 constexpr char kSaveTraceSwitch[] = "ttp-save-trace";
41
42 // Returns a vector of pairs of strings consisting of
43 // {include_key, sql_file_contents}. For example, the include key for
44 // `chrome/scroll_jank/utils.sql` is `chrome.scroll_jank.utils`.
45 // The output is used to override the Chrome SQL module in the trace processor.
GetChromeStdlib()46 TestTraceProcessorImpl::PerfettoSQLModule GetChromeStdlib() {
47 std::vector<std::pair<std::string, std::string>> stdlib;
48 for (const auto& file_to_sql :
49 perfetto::trace_processor::chrome_stdlib::kFileToSql) {
50 std::string include_key;
51 base::ReplaceChars(file_to_sql.path, "/", ".", &include_key);
52 if (include_key.ends_with(".sql")) {
53 include_key.resize(include_key.size() - 4);
54 }
55 stdlib.emplace_back(kChromeSqlModuleName + "." + include_key,
56 file_to_sql.sql);
57 }
58 return stdlib;
59 }
60 } // namespace
61
DefaultTraceConfig(const StringPiece & category_filter_string,bool privacy_filtering)62 TraceConfig DefaultTraceConfig(const StringPiece& category_filter_string,
63 bool privacy_filtering) {
64 TraceConfig trace_config;
65 auto* buffer_config = trace_config.add_buffers();
66 buffer_config->set_size_kb(4 * 1024);
67
68 auto* data_source = trace_config.add_data_sources();
69 auto* source_config = data_source->mutable_config();
70 source_config->set_name("track_event");
71 source_config->set_target_buffer(0);
72
73 perfetto::protos::gen::TrackEventConfig track_event_config;
74 base::trace_event::TraceConfigCategoryFilter category_filter;
75 category_filter.InitializeFromString(category_filter_string);
76
77 // If no categories are explicitly enabled, enable the default ones.
78 // Otherwise only matching categories are enabled.
79 if (!category_filter.included_categories().empty()) {
80 track_event_config.add_disabled_categories("*");
81 }
82 for (const auto& included_category : category_filter.included_categories()) {
83 track_event_config.add_enabled_categories(included_category);
84 }
85 for (const auto& disabled_category : category_filter.disabled_categories()) {
86 track_event_config.add_enabled_categories(disabled_category);
87 }
88 for (const auto& excluded_category : category_filter.excluded_categories()) {
89 track_event_config.add_disabled_categories(excluded_category);
90 }
91
92 // This category is added by default to tracing sessions initiated via
93 // command-line flags (see TraceConfig::ToPerfettoTrackEventConfigRaw),
94 // so to adopt startup sessions correctly, we need to specify it too.
95 track_event_config.add_enabled_categories("__metadata");
96
97 if (privacy_filtering) {
98 track_event_config.set_filter_debug_annotations(true);
99 track_event_config.set_filter_dynamic_event_names(true);
100 }
101
102 source_config->set_track_event_config_raw(
103 track_event_config.SerializeAsString());
104
105 return trace_config;
106 }
107
TestTraceProcessor()108 TestTraceProcessor::TestTraceProcessor() {
109 auto status = test_trace_processor_.OverrideSqlModule(kChromeSqlModuleName,
110 GetChromeStdlib());
111 CHECK(status.ok());
112 }
113
114 TestTraceProcessor::~TestTraceProcessor() = default;
115
StartTrace(const StringPiece & category_filter_string,bool privacy_filtering)116 void TestTraceProcessor::StartTrace(const StringPiece& category_filter_string,
117 bool privacy_filtering) {
118 StartTrace(DefaultTraceConfig(category_filter_string, privacy_filtering));
119 }
120
StartTrace(const TraceConfig & config,perfetto::BackendType backend)121 void TestTraceProcessor::StartTrace(const TraceConfig& config,
122 perfetto::BackendType backend) {
123 // Try to guess the correct backend if it's unspecified. In unit tests
124 // Perfetto is initialized by TraceLog, and only the in-process backend is
125 // available. In browser tests multiple backend can be available, so we
126 // explicitly specialize the custom backend to prevent tests from connecting
127 // to a system backend.
128 if (backend == perfetto::kUnspecifiedBackend) {
129 if (base::trace_event::TraceLog::GetInstance()
130 ->IsPerfettoInitializedByTraceLog()) {
131 backend = perfetto::kInProcessBackend;
132 } else {
133 backend = perfetto::kCustomBackend;
134 }
135 }
136 session_ = perfetto::Tracing::NewTrace(backend);
137 session_->Setup(config);
138 // Some tests run the tracing service on the main thread and StartBlocking()
139 // can deadlock so use a RunLoop instead.
140 base::RunLoop run_loop;
141 session_->SetOnStartCallback([&run_loop]() { run_loop.QuitWhenIdle(); });
142 session_->Start();
143 run_loop.Run();
144 }
145
StopAndParseTrace()146 absl::Status TestTraceProcessor::StopAndParseTrace() {
147 EmitChromeTrackEventDescriptor();
148 base::TrackEvent::Flush();
149 session_->StopBlocking();
150 std::vector<char> trace = session_->ReadTraceBlocking();
151
152 if (CommandLine::ForCurrentProcess()->HasSwitch(kSaveTraceSwitch)) {
153 ScopedAllowBlockingForTesting allow;
154 WriteFile(base::FilePath::FromASCII("test.pftrace"), trace.data(),
155 trace.size());
156 }
157
158 return test_trace_processor_.ParseTrace(trace);
159 }
160
161 base::expected<TestTraceProcessor::QueryResult, std::string>
RunQuery(const std::string & query)162 TestTraceProcessor::RunQuery(const std::string& query) {
163 auto result_or_error = test_trace_processor_.ExecuteQuery(query);
164 if (!result_or_error.ok()) {
165 return base::unexpected(result_or_error.error());
166 }
167 return base::ok(result_or_error.result());
168 }
169
170 #endif // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
171
172 } // namespace base::test
173