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