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