1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <fcntl.h>
18 #include <stdio.h>
19 #include <sys/stat.h>
20 #include <algorithm>
21 #include <cctype>
22 #include <cerrno>
23 #include <chrono>
24 #include <cinttypes>
25 #include <cstdint>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <cstring>
29 #include <memory>
30 #include <optional>
31 #include <string>
32 #include <unordered_set>
33 #include <utility>
34 #include <vector>
35
36 #include <google/protobuf/compiler/parser.h>
37 #include <google/protobuf/descriptor.pb.h>
38 #include <google/protobuf/dynamic_message.h>
39 #include <google/protobuf/io/tokenizer.h>
40 #include <google/protobuf/io/zero_copy_stream_impl.h>
41 #include <google/protobuf/text_format.h>
42
43 #include "perfetto/base/build_config.h"
44 #include "perfetto/base/logging.h"
45 #include "perfetto/base/status.h"
46 #include "perfetto/base/time.h"
47 #include "perfetto/ext/base/file_utils.h"
48 #include "perfetto/ext/base/getopt.h" // IWYU pragma: keep
49 #include "perfetto/ext/base/scoped_file.h"
50 #include "perfetto/ext/base/status_or.h"
51 #include "perfetto/ext/base/string_splitter.h"
52 #include "perfetto/ext/base/string_utils.h"
53 #include "perfetto/ext/base/version.h"
54 #include "perfetto/trace_processor/basic_types.h"
55 #include "perfetto/trace_processor/iterator.h"
56 #include "perfetto/trace_processor/metatrace_config.h"
57 #include "perfetto/trace_processor/read_trace.h"
58 #include "perfetto/trace_processor/trace_processor.h"
59 #include "src/profiling/deobfuscator.h"
60 #include "src/profiling/symbolizer/local_symbolizer.h"
61 #include "src/profiling/symbolizer/symbolize_database.h"
62 #include "src/trace_processor/metrics/all_chrome_metrics.descriptor.h"
63 #include "src/trace_processor/metrics/all_webview_metrics.descriptor.h"
64 #include "src/trace_processor/metrics/metrics.descriptor.h"
65 #include "src/trace_processor/read_trace_internal.h"
66 #include "src/trace_processor/rpc/stdiod.h"
67 #include "src/trace_processor/util/sql_modules.h"
68 #include "src/trace_processor/util/status_macros.h"
69
70 #include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
71
72 #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
73 #include "src/trace_processor/rpc/httpd.h"
74 #endif
75
76 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
77 PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
78 PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
79 #define PERFETTO_HAS_SIGNAL_H() 1
80 #else
81 #define PERFETTO_HAS_SIGNAL_H() 0
82 #endif
83
84 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE)
85 #include <linenoise.h>
86 #include <pwd.h>
87 #include <sys/types.h>
88 #endif
89
90 #if PERFETTO_HAS_SIGNAL_H()
91 #include <signal.h>
92 #endif
93
94 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
95 #include <io.h>
96 #define ftruncate _chsize
97 #else
98 #include <dirent.h>
99 #endif
100
101 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE) && \
102 !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
103 #include <unistd.h> // For getuid() in GetConfigPath().
104 #endif
105
106 namespace perfetto::trace_processor {
107
108 namespace {
109 TraceProcessor* g_tp;
110
111 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE)
112
EnsureDir(const std::string & path)113 bool EnsureDir(const std::string& path) {
114 return base::Mkdir(path) || errno == EEXIST;
115 }
116
EnsureFile(const std::string & path)117 bool EnsureFile(const std::string& path) {
118 return base::OpenFile(path, O_RDONLY | O_CREAT, 0644).get() != -1;
119 }
120
GetConfigPath()121 std::string GetConfigPath() {
122 const char* homedir = getenv("HOME");
123 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
124 PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
125 PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
126 if (homedir == nullptr)
127 homedir = getpwuid(getuid())->pw_dir;
128 #elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
129 if (homedir == nullptr)
130 homedir = getenv("USERPROFILE");
131 #endif
132 if (homedir == nullptr)
133 return "";
134 return std::string(homedir) + "/.config";
135 }
136
GetPerfettoPath()137 std::string GetPerfettoPath() {
138 std::string config = GetConfigPath();
139 if (config.empty())
140 return "";
141 return config + "/perfetto";
142 }
143
GetHistoryPath()144 std::string GetHistoryPath() {
145 std::string perfetto = GetPerfettoPath();
146 if (perfetto.empty())
147 return "";
148 return perfetto + "/.trace_processor_shell_history";
149 }
150
SetupLineEditor()151 void SetupLineEditor() {
152 linenoiseSetMultiLine(true);
153 linenoiseHistorySetMaxLen(1000);
154
155 bool success = !GetHistoryPath().empty();
156 success = success && EnsureDir(GetConfigPath());
157 success = success && EnsureDir(GetPerfettoPath());
158 success = success && EnsureFile(GetHistoryPath());
159 success = success && linenoiseHistoryLoad(GetHistoryPath().c_str()) != -1;
160 if (!success) {
161 PERFETTO_PLOG("Could not load history from %s", GetHistoryPath().c_str());
162 }
163 }
164
165 struct LineDeleter {
operator ()perfetto::trace_processor::__anonec9ff9cc0111::LineDeleter166 void operator()(char* p) const {
167 linenoiseHistoryAdd(p);
168 linenoiseHistorySave(GetHistoryPath().c_str());
169 linenoiseFree(p);
170 }
171 };
172
173 using ScopedLine = std::unique_ptr<char, LineDeleter>;
174
GetLine(const char * prompt)175 ScopedLine GetLine(const char* prompt) {
176 errno = 0;
177 auto line = ScopedLine(linenoise(prompt));
178 // linenoise returns a nullptr both for CTRL-C and CTRL-D, however in the
179 // former case it sets errno to EAGAIN.
180 // If the user press CTRL-C return "" instead of nullptr. We don't want the
181 // main loop to quit in that case as that is inconsistent with the behavior
182 // "CTRL-C interrupts the current query" and frustrating when hitting that
183 // a split second after the query is done.
184 if (!line && errno == EAGAIN)
185 return ScopedLine(strdup(""));
186 return line;
187 }
188
189 #else
190
SetupLineEditor()191 void SetupLineEditor() {}
192
193 using ScopedLine = std::unique_ptr<char>;
194
GetLine(const char * prompt)195 ScopedLine GetLine(const char* prompt) {
196 printf("\r%80s\r%s", "", prompt);
197 fflush(stdout);
198 ScopedLine line(new char[1024]);
199 if (!fgets(line.get(), 1024 - 1, stdin))
200 return nullptr;
201 if (strlen(line.get()) > 0)
202 line.get()[strlen(line.get()) - 1] = 0;
203 return line;
204 }
205
206 #endif // PERFETTO_TP_LINENOISE
207
PrintStats()208 base::Status PrintStats() {
209 auto it = g_tp->ExecuteQuery(
210 "SELECT name, idx, source, value from stats "
211 "where severity IN ('error', 'data_loss') and value > 0");
212
213 bool first = true;
214 while (it.Next()) {
215 if (first) {
216 fprintf(stderr, "Error stats for this trace:\n");
217
218 for (uint32_t i = 0; i < it.ColumnCount(); i++)
219 fprintf(stderr, "%40s ", it.GetColumnName(i).c_str());
220 fprintf(stderr, "\n");
221
222 for (uint32_t i = 0; i < it.ColumnCount(); i++)
223 fprintf(stderr, "%40s ", "----------------------------------------");
224 fprintf(stderr, "\n");
225
226 first = false;
227 }
228
229 for (uint32_t c = 0; c < it.ColumnCount(); c++) {
230 auto value = it.Get(c);
231 switch (value.type) {
232 case SqlValue::Type::kNull:
233 fprintf(stderr, "%-40.40s", "[NULL]");
234 break;
235 case SqlValue::Type::kDouble:
236 fprintf(stderr, "%40f", value.double_value);
237 break;
238 case SqlValue::Type::kLong:
239 fprintf(stderr, "%40" PRIi64, value.long_value);
240 break;
241 case SqlValue::Type::kString:
242 fprintf(stderr, "%-40.40s", value.string_value);
243 break;
244 case SqlValue::Type::kBytes:
245 printf("%-40.40s", "<raw bytes>");
246 break;
247 }
248 fprintf(stderr, " ");
249 }
250 fprintf(stderr, "\n");
251 }
252
253 base::Status status = it.Status();
254 if (!status.ok()) {
255 return base::ErrStatus("Error while iterating stats (%s)",
256 status.c_message());
257 }
258 return base::OkStatus();
259 }
260
ExportTraceToDatabase(const std::string & output_name)261 base::Status ExportTraceToDatabase(const std::string& output_name) {
262 PERFETTO_CHECK(output_name.find('\'') == std::string::npos);
263 {
264 base::ScopedFile fd(base::OpenFile(output_name, O_CREAT | O_RDWR, 0600));
265 if (!fd)
266 return base::ErrStatus("Failed to create file: %s", output_name.c_str());
267 int res = ftruncate(fd.get(), 0);
268 PERFETTO_CHECK(res == 0);
269 }
270
271 std::string attach_sql =
272 "ATTACH DATABASE '" + output_name + "' AS perfetto_export";
273 auto attach_it = g_tp->ExecuteQuery(attach_sql);
274 bool attach_has_more = attach_it.Next();
275 PERFETTO_DCHECK(!attach_has_more);
276
277 base::Status status = attach_it.Status();
278 if (!status.ok())
279 return base::ErrStatus("%s", status.c_message());
280
281 // Export real and virtual tables.
282 auto tables_it = g_tp->ExecuteQuery("SELECT name FROM perfetto_tables");
283 while (tables_it.Next()) {
284 std::string table_name = tables_it.Get(0).string_value;
285 PERFETTO_CHECK(!base::Contains(table_name, '\''));
286 std::string export_sql = "CREATE TABLE perfetto_export." + table_name +
287 " AS SELECT * FROM " + table_name;
288
289 auto export_it = g_tp->ExecuteQuery(export_sql);
290 bool export_has_more = export_it.Next();
291 PERFETTO_DCHECK(!export_has_more);
292
293 status = export_it.Status();
294 if (!status.ok())
295 return base::ErrStatus("%s", status.c_message());
296 }
297 status = tables_it.Status();
298 if (!status.ok())
299 return base::ErrStatus("%s", status.c_message());
300
301 // Export views.
302 auto views_it =
303 g_tp->ExecuteQuery("SELECT sql FROM sqlite_master WHERE type='view'");
304 while (views_it.Next()) {
305 std::string sql = views_it.Get(0).string_value;
306 // View statements are of the form "CREATE VIEW name AS stmt". We need to
307 // rewrite name to point to the exported db.
308 const std::string kPrefix = "CREATE VIEW ";
309 PERFETTO_CHECK(sql.find(kPrefix) == 0);
310 sql = sql.substr(0, kPrefix.size()) + "perfetto_export." +
311 sql.substr(kPrefix.size());
312
313 auto export_it = g_tp->ExecuteQuery(sql);
314 bool export_has_more = export_it.Next();
315 PERFETTO_DCHECK(!export_has_more);
316
317 status = export_it.Status();
318 if (!status.ok())
319 return base::ErrStatus("%s", status.c_message());
320 }
321 status = views_it.Status();
322 if (!status.ok())
323 return base::ErrStatus("%s", status.c_message());
324
325 auto detach_it = g_tp->ExecuteQuery("DETACH DATABASE perfetto_export");
326 bool detach_has_more = attach_it.Next();
327 PERFETTO_DCHECK(!detach_has_more);
328 status = detach_it.Status();
329 return status.ok() ? base::OkStatus()
330 : base::ErrStatus("%s", status.c_message());
331 }
332
333 class ErrorPrinter : public google::protobuf::io::ErrorCollector {
AddError(int line,int col,const std::string & msg)334 void AddError(int line, int col, const std::string& msg) override {
335 PERFETTO_ELOG("%d:%d: %s", line, col, msg.c_str());
336 }
337
AddWarning(int line,int col,const std::string & msg)338 void AddWarning(int line, int col, const std::string& msg) override {
339 PERFETTO_ILOG("%d:%d: %s", line, col, msg.c_str());
340 }
341 };
342
343 // This function returns an indentifier for a metric suitable for use
344 // as an SQL table name (i.e. containing no forward or backward slashes).
BaseName(std::string metric_path)345 std::string BaseName(std::string metric_path) {
346 std::replace(metric_path.begin(), metric_path.end(), '\\', '/');
347 auto slash_idx = metric_path.rfind('/');
348 return slash_idx == std::string::npos ? metric_path
349 : metric_path.substr(slash_idx + 1);
350 }
351
RegisterMetric(const std::string & register_metric)352 base::Status RegisterMetric(const std::string& register_metric) {
353 std::string sql;
354 base::ReadFile(register_metric, &sql);
355
356 std::string path = "shell/" + BaseName(register_metric);
357 return g_tp->RegisterMetric(path, sql);
358 }
359
ParseToFileDescriptorProto(const std::string & filename,google::protobuf::FileDescriptorProto * file_desc)360 base::Status ParseToFileDescriptorProto(
361 const std::string& filename,
362 google::protobuf::FileDescriptorProto* file_desc) {
363 base::ScopedFile file(base::OpenFile(filename, O_RDONLY));
364 if (file.get() == -1) {
365 return base::ErrStatus("Failed to open proto file %s", filename.c_str());
366 }
367
368 google::protobuf::io::FileInputStream stream(file.get());
369 ErrorPrinter printer;
370 google::protobuf::io::Tokenizer tokenizer(&stream, &printer);
371
372 google::protobuf::compiler::Parser parser;
373 parser.Parse(&tokenizer, file_desc);
374 return base::OkStatus();
375 }
376
ExtendMetricsProto(const std::string & extend_metrics_proto,google::protobuf::DescriptorPool * pool)377 base::Status ExtendMetricsProto(const std::string& extend_metrics_proto,
378 google::protobuf::DescriptorPool* pool) {
379 google::protobuf::FileDescriptorSet desc_set;
380 auto* file_desc = desc_set.add_file();
381 RETURN_IF_ERROR(ParseToFileDescriptorProto(extend_metrics_proto, file_desc));
382
383 file_desc->set_name(BaseName(extend_metrics_proto));
384 pool->BuildFile(*file_desc);
385
386 std::vector<uint8_t> metric_proto;
387 metric_proto.resize(desc_set.ByteSizeLong());
388 desc_set.SerializeToArray(metric_proto.data(),
389 static_cast<int>(metric_proto.size()));
390
391 return g_tp->ExtendMetricsProto(metric_proto.data(), metric_proto.size());
392 }
393
394 enum OutputFormat {
395 kBinaryProto,
396 kTextProto,
397 kJson,
398 kNone,
399 };
400
401 struct MetricNameAndPath {
402 std::string name;
403 std::optional<std::string> no_ext_path;
404 };
405
RunMetrics(const std::vector<MetricNameAndPath> & metrics,OutputFormat format)406 base::Status RunMetrics(const std::vector<MetricNameAndPath>& metrics,
407 OutputFormat format) {
408 std::vector<std::string> metric_names(metrics.size());
409 for (size_t i = 0; i < metrics.size(); ++i) {
410 metric_names[i] = metrics[i].name;
411 }
412
413 switch (format) {
414 case OutputFormat::kBinaryProto: {
415 std::vector<uint8_t> metric_result;
416 RETURN_IF_ERROR(g_tp->ComputeMetric(metric_names, &metric_result));
417 fwrite(metric_result.data(), sizeof(uint8_t), metric_result.size(),
418 stdout);
419 break;
420 }
421 case OutputFormat::kJson: {
422 std::string out;
423 RETURN_IF_ERROR(g_tp->ComputeMetricText(
424 metric_names, TraceProcessor::MetricResultFormat::kJson, &out));
425 out += '\n';
426 fwrite(out.c_str(), sizeof(char), out.size(), stdout);
427 break;
428 }
429 case OutputFormat::kTextProto: {
430 std::string out;
431 RETURN_IF_ERROR(g_tp->ComputeMetricText(
432 metric_names, TraceProcessor::MetricResultFormat::kProtoText, &out));
433 out += '\n';
434 fwrite(out.c_str(), sizeof(char), out.size(), stdout);
435 break;
436 }
437 case OutputFormat::kNone:
438 break;
439 }
440
441 return base::OkStatus();
442 }
443
PrintQueryResultInteractively(Iterator * it,base::TimeNanos t_start,uint32_t column_width)444 void PrintQueryResultInteractively(Iterator* it,
445 base::TimeNanos t_start,
446 uint32_t column_width) {
447 base::TimeNanos t_end = base::GetWallTimeNs();
448 for (uint32_t rows = 0; it->Next(); rows++) {
449 if (rows % 32 == 0) {
450 if (rows == 0) {
451 t_end = base::GetWallTimeNs();
452 } else {
453 fprintf(stderr, "...\nType 'q' to stop, Enter for more records: ");
454 fflush(stderr);
455 char input[32];
456 if (!fgets(input, sizeof(input) - 1, stdin))
457 exit(0);
458 if (input[0] == 'q')
459 break;
460 }
461 for (uint32_t i = 0; i < it->ColumnCount(); i++)
462 printf("%-*.*s ", column_width, column_width,
463 it->GetColumnName(i).c_str());
464 printf("\n");
465
466 std::string divider(column_width, '-');
467 for (uint32_t i = 0; i < it->ColumnCount(); i++) {
468 printf("%-*s ", column_width, divider.c_str());
469 }
470 printf("\n");
471 }
472
473 for (uint32_t c = 0; c < it->ColumnCount(); c++) {
474 auto value = it->Get(c);
475 switch (value.type) {
476 case SqlValue::Type::kNull:
477 printf("%-*s", column_width, "[NULL]");
478 break;
479 case SqlValue::Type::kDouble:
480 printf("%*f", column_width, value.double_value);
481 break;
482 case SqlValue::Type::kLong:
483 printf("%*" PRIi64, column_width, value.long_value);
484 break;
485 case SqlValue::Type::kString:
486 printf("%-*.*s", column_width, column_width, value.string_value);
487 break;
488 case SqlValue::Type::kBytes:
489 printf("%-*s", column_width, "<raw bytes>");
490 break;
491 }
492 printf(" ");
493 }
494 printf("\n");
495 }
496
497 base::Status status = it->Status();
498 if (!status.ok()) {
499 fprintf(stderr, "%s\n", status.c_message());
500 }
501 printf("\nQuery executed in %.3f ms\n\n",
502 static_cast<double>((t_end - t_start).count()) / 1E6);
503 }
504
505 struct QueryResult {
506 std::vector<std::string> column_names;
507 std::vector<std::vector<std::string>> rows;
508 };
509
ExtractQueryResult(Iterator * it,bool has_more)510 base::StatusOr<QueryResult> ExtractQueryResult(Iterator* it, bool has_more) {
511 QueryResult result;
512
513 for (uint32_t c = 0; c < it->ColumnCount(); c++) {
514 fprintf(stderr, "column %d = %s\n", c, it->GetColumnName(c).c_str());
515 result.column_names.push_back(it->GetColumnName(c));
516 }
517
518 for (; has_more; has_more = it->Next()) {
519 std::vector<std::string> row;
520 for (uint32_t c = 0; c < it->ColumnCount(); c++) {
521 SqlValue value = it->Get(c);
522 std::string str_value;
523 switch (value.type) {
524 case SqlValue::Type::kNull:
525 str_value = "\"[NULL]\"";
526 break;
527 case SqlValue::Type::kDouble:
528 str_value =
529 base::StackString<256>("%f", value.double_value).ToStdString();
530 break;
531 case SqlValue::Type::kLong:
532 str_value = base::StackString<256>("%" PRIi64, value.long_value)
533 .ToStdString();
534 break;
535 case SqlValue::Type::kString:
536 str_value = '"' + std::string(value.string_value) + '"';
537 break;
538 case SqlValue::Type::kBytes:
539 str_value = "\"<raw bytes>\"";
540 break;
541 }
542
543 row.push_back(std::move(str_value));
544 }
545 result.rows.push_back(std::move(row));
546 }
547 RETURN_IF_ERROR(it->Status());
548 return result;
549 }
550
PrintQueryResultAsCsv(const QueryResult & result,FILE * output)551 void PrintQueryResultAsCsv(const QueryResult& result, FILE* output) {
552 for (uint32_t c = 0; c < result.column_names.size(); c++) {
553 if (c > 0)
554 fprintf(output, ",");
555 fprintf(output, "\"%s\"", result.column_names[c].c_str());
556 }
557 fprintf(output, "\n");
558
559 for (const auto& row : result.rows) {
560 for (uint32_t c = 0; c < result.column_names.size(); c++) {
561 if (c > 0)
562 fprintf(output, ",");
563 fprintf(output, "%s", row[c].c_str());
564 }
565 fprintf(output, "\n");
566 }
567 }
568
RunQueriesWithoutOutput(const std::string & sql_query)569 base::Status RunQueriesWithoutOutput(const std::string& sql_query) {
570 auto it = g_tp->ExecuteQuery(sql_query);
571 if (it.StatementWithOutputCount() > 0)
572 return base::ErrStatus("Unexpected result from a query.");
573
574 RETURN_IF_ERROR(it.Status());
575 return it.Next() ? base::ErrStatus("Unexpected result from a query.")
576 : it.Status();
577 }
578
RunQueriesAndPrintResult(const std::string & sql_query,FILE * output)579 base::Status RunQueriesAndPrintResult(const std::string& sql_query,
580 FILE* output) {
581 PERFETTO_DLOG("Executing query: %s", sql_query.c_str());
582 auto query_start = std::chrono::steady_clock::now();
583
584 auto it = g_tp->ExecuteQuery(sql_query);
585 RETURN_IF_ERROR(it.Status());
586
587 bool has_more = it.Next();
588 RETURN_IF_ERROR(it.Status());
589
590 uint32_t prev_count = it.StatementCount() - 1;
591 uint32_t prev_with_output = has_more ? it.StatementWithOutputCount() - 1
592 : it.StatementWithOutputCount();
593 uint32_t prev_without_output_count = prev_count - prev_with_output;
594 if (prev_with_output > 0) {
595 return base::ErrStatus(
596 "Result rows were returned for multiples queries. Ensure that only the "
597 "final statement is a SELECT statment or use `suppress_query_output` "
598 "to prevent function invocations causing this "
599 "error (see "
600 "https://perfetto.dev/docs/contributing/"
601 "testing#trace-processor-diff-tests).");
602 }
603 for (uint32_t i = 0; i < prev_without_output_count; ++i) {
604 fprintf(output, "\n");
605 }
606 if (it.ColumnCount() == 0) {
607 PERFETTO_DCHECK(!has_more);
608 return base::OkStatus();
609 }
610
611 auto query_result = ExtractQueryResult(&it, has_more);
612 RETURN_IF_ERROR(query_result.status());
613
614 // We want to include the query iteration time (as it's a part of executing
615 // SQL and can be non-trivial), and we want to exclude the time spent printing
616 // the result (which can be significant for large results), so we materialise
617 // the results first, then take the measurement, then print them.
618 auto query_end = std::chrono::steady_clock::now();
619
620 PrintQueryResultAsCsv(query_result.value(), output);
621
622 auto dur = query_end - query_start;
623 PERFETTO_ILOG(
624 "Query execution time: %" PRIi64 " ms",
625 static_cast<int64_t>(
626 std::chrono::duration_cast<std::chrono::milliseconds>(dur).count()));
627 return base::OkStatus();
628 }
629
PrintPerfFile(const std::string & perf_file_path,base::TimeNanos t_load,base::TimeNanos t_run)630 base::Status PrintPerfFile(const std::string& perf_file_path,
631 base::TimeNanos t_load,
632 base::TimeNanos t_run) {
633 char buf[128];
634 size_t count = base::SprintfTrunc(buf, sizeof(buf), "%" PRId64 ",%" PRId64,
635 static_cast<int64_t>(t_load.count()),
636 static_cast<int64_t>(t_run.count()));
637 if (count == 0) {
638 return base::ErrStatus("Failed to write perf data");
639 }
640
641 auto fd(base::OpenFile(perf_file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666));
642 if (!fd) {
643 return base::ErrStatus("Failed to open perf file");
644 }
645 base::WriteAll(fd.get(), buf, count);
646 return base::OkStatus();
647 }
648
649 class MetricExtension {
650 public:
SetDiskPath(std::string path)651 void SetDiskPath(std::string path) {
652 AddTrailingSlashIfNeeded(path);
653 disk_path_ = std::move(path);
654 }
SetVirtualPath(std::string path)655 void SetVirtualPath(std::string path) {
656 AddTrailingSlashIfNeeded(path);
657 virtual_path_ = std::move(path);
658 }
659
660 // Disk location. Ends with a trailing slash.
disk_path() const661 const std::string& disk_path() const { return disk_path_; }
662 // Virtual location. Ends with a trailing slash.
virtual_path() const663 const std::string& virtual_path() const { return virtual_path_; }
664
665 private:
666 std::string disk_path_;
667 std::string virtual_path_;
668
AddTrailingSlashIfNeeded(std::string & path)669 static void AddTrailingSlashIfNeeded(std::string& path) {
670 if (path.length() > 0 && path[path.length() - 1] != '/') {
671 path.push_back('/');
672 }
673 }
674 };
675
ParseMetatraceCategories(std::string s)676 metatrace::MetatraceCategories ParseMetatraceCategories(std::string s) {
677 using Cat = metatrace::MetatraceCategories;
678 std::transform(s.begin(), s.end(), s.begin(),
679 [](unsigned char c) { return std::tolower(c); });
680 base::StringSplitter splitter(s, ',');
681
682 Cat result = Cat::NONE;
683 for (; splitter.Next();) {
684 std::string cur = splitter.cur_token();
685 if (cur == "all" || cur == "*") {
686 result = Cat::ALL;
687 } else if (cur == "query_toplevel") {
688 result = static_cast<Cat>(result | Cat::QUERY_TIMELINE);
689 } else if (cur == "query_detailed") {
690 result = static_cast<Cat>(result | Cat::QUERY_DETAILED);
691 } else if (cur == "function_call") {
692 result = static_cast<Cat>(result | Cat::FUNCTION_CALL);
693 } else if (cur == "db") {
694 result = static_cast<Cat>(result | Cat::DB);
695 } else if (cur == "api") {
696 result = static_cast<Cat>(result | Cat::API_TIMELINE);
697 } else {
698 PERFETTO_ELOG("Unknown metatrace category %s", cur.data());
699 exit(1);
700 }
701 }
702 return result;
703 }
704
705 struct CommandLineOptions {
706 std::string perf_file_path;
707 std::string query_file_path;
708 std::string query_string;
709 std::string pre_metrics_path;
710 std::string sqlite_file_path;
711 std::string sql_module_path;
712 std::string metric_names;
713 std::string metric_output;
714 std::string trace_file_path;
715 std::string port_number;
716 std::string override_stdlib_path;
717 std::vector<std::string> override_sql_module_paths;
718 std::vector<std::string> raw_metric_extensions;
719 bool launch_shell = false;
720 bool enable_httpd = false;
721 bool enable_stdiod = false;
722 bool wide = false;
723 bool force_full_sort = false;
724 std::string metatrace_path;
725 size_t metatrace_buffer_capacity = 0;
726 metatrace::MetatraceCategories metatrace_categories =
727 static_cast<metatrace::MetatraceCategories>(
728 metatrace::MetatraceCategories::QUERY_TIMELINE |
729 metatrace::MetatraceCategories::API_TIMELINE);
730 bool dev = false;
731 bool extra_checks = false;
732 bool no_ftrace_raw = false;
733 bool analyze_trace_proto_content = false;
734 bool crop_track_events = false;
735 std::vector<std::string> dev_flags;
736 };
737
PrintUsage(char ** argv)738 void PrintUsage(char** argv) {
739 PERFETTO_ELOG(R"(
740 Interactive trace processor shell.
741 Usage: %s [FLAGS] trace_file.pb
742
743 Options:
744 -h, --help Prints this guide.
745 -v, --version Prints the version of trace processor.
746 -d, --debug Enable virtual table debugging.
747 -W, --wide Prints interactive output with double
748 column width.
749 -p, --perf-file FILE Writes the time taken to ingest the trace
750 and execute the queries to the given file.
751 Only valid with -q or --run-metrics and
752 the file will only be written if the
753 execution is successful.
754 -q, --query-file FILE Read and execute an SQL query from a file.
755 If used with --run-metrics, the query is
756 executed after the selected metrics and
757 the metrics output is suppressed.
758 -Q, --query-string QUERY Execute the SQL query QUERY.
759 If used with --run-metrics, the query is
760 executed after the selected metrics and
761 the metrics output is suppressed.
762 -D, --httpd Enables the HTTP RPC server.
763 --http-port PORT Specify what port to run HTTP RPC server.
764 --stdiod Enables the stdio RPC server.
765 -i, --interactive Starts interactive mode even after a query
766 file is specified with -q or
767 --run-metrics.
768 -e, --export FILE Export the contents of trace processor
769 into an SQLite database after running any
770 metrics or queries specified.
771
772 Feature flags:
773 --full-sort Forces the trace processor into performing
774 a full sort ignoring any windowing
775 logic.
776 --no-ftrace-raw Prevents ingestion of typed ftrace events
777 into the raw table. This significantly
778 reduces the memory usage of trace
779 processor when loading traces containing
780 ftrace events.
781 --analyze-trace-proto-content Enables trace proto content analysis in
782 trace processor.
783 --crop-track-events Ignores track event outside of the
784 range of interest in trace processor.
785 --dev Enables features which are reserved for
786 local development use only and
787 *should not* be enabled on production
788 builds. The features behind this flag can
789 break at any time without any warning.
790 --dev-flag KEY=VALUE Set a development flag to the given value.
791 Does not have any affect unless --dev is
792 specified.
793 --extra-checks Enables additional checks which can catch
794 more SQL errors, but which incur
795 additional runtime overhead.
796
797 Standard library:
798 --add-sql-module MODULE_PATH Files from the directory will be treated
799 as a new SQL module and can be used for
800 IMPORT. The name of the directory is the
801 module name.
802 --override-sql-module MODULE_PATH Will override trace processor module with
803 passed contents. The outer directory will
804 specify the module name.
805 --override-stdlib=[path_to_stdlib] Will override trace_processor/stdlib with
806 passed contents. The outer directory will
807 be ignored. Only allowed when --dev is
808 specified.
809
810 Metrics:
811 --run-metrics x,y,z Runs a comma separated list of metrics and
812 prints the result as a TraceMetrics proto
813 to stdout. The specified can either be
814 in-built metrics or SQL/proto files of
815 extension metrics.
816 --pre-metrics FILE Read and execute an SQL query from a file.
817 This query is executed before the selected
818 metrics and can't output any results.
819 --metrics-output=[binary|text|json] Allows the output of --run-metrics to be
820 specified in either proto binary, proto
821 text format or JSON format (default: proto
822 text).
823 --metric-extension DISK_PATH@VIRTUAL_PATH
824 Loads metric proto and sql files from
825 DISK_PATH/protos and DISK_PATH/sql
826 respectively, and mounts them onto
827 VIRTUAL_PATH.
828
829 Metatracing:
830 -m, --metatrace FILE Enables metatracing of trace processor
831 writing the resulting trace into FILE.
832 --metatrace-buffer-capacity N Sets metatrace event buffer to capture
833 last N events.
834 --metatrace-categories CATEGORIES A comma-separated list of metatrace
835 categories to enable.)",
836 argv[0]);
837 }
838
ParseCommandLineOptions(int argc,char ** argv)839 CommandLineOptions ParseCommandLineOptions(int argc, char** argv) {
840 CommandLineOptions command_line_options;
841 enum LongOption {
842 OPT_RUN_METRICS = 1000,
843 OPT_PRE_METRICS,
844 OPT_METRICS_OUTPUT,
845 OPT_FORCE_FULL_SORT,
846 OPT_HTTP_PORT,
847 OPT_ADD_SQL_MODULE,
848 OPT_METRIC_EXTENSION,
849 OPT_DEV,
850 OPT_EXTRA_CHECKS,
851 OPT_OVERRIDE_STDLIB,
852 OPT_OVERRIDE_SQL_MODULE,
853 OPT_NO_FTRACE_RAW,
854 OPT_METATRACE_BUFFER_CAPACITY,
855 OPT_METATRACE_CATEGORIES,
856 OPT_ANALYZE_TRACE_PROTO_CONTENT,
857 OPT_CROP_TRACK_EVENTS,
858 OPT_DEV_FLAG,
859 OPT_STDIOD,
860 };
861
862 static const option long_options[] = {
863 {"help", no_argument, nullptr, 'h'},
864 {"version", no_argument, nullptr, 'v'},
865 {"wide", no_argument, nullptr, 'W'},
866 {"perf-file", required_argument, nullptr, 'p'},
867 {"query-file", required_argument, nullptr, 'q'},
868 {"query-string", required_argument, nullptr, 'Q'},
869 {"httpd", no_argument, nullptr, 'D'},
870 {"http-port", required_argument, nullptr, OPT_HTTP_PORT},
871 {"stdiod", no_argument, nullptr, OPT_STDIOD},
872 {"interactive", no_argument, nullptr, 'i'},
873 {"export", required_argument, nullptr, 'e'},
874 {"metatrace", required_argument, nullptr, 'm'},
875 {"metatrace-buffer-capacity", required_argument, nullptr,
876 OPT_METATRACE_BUFFER_CAPACITY},
877 {"metatrace-categories", required_argument, nullptr,
878 OPT_METATRACE_CATEGORIES},
879 {"full-sort", no_argument, nullptr, OPT_FORCE_FULL_SORT},
880 {"no-ftrace-raw", no_argument, nullptr, OPT_NO_FTRACE_RAW},
881 {"analyze-trace-proto-content", no_argument, nullptr,
882 OPT_ANALYZE_TRACE_PROTO_CONTENT},
883 {"crop-track-events", no_argument, nullptr, OPT_CROP_TRACK_EVENTS},
884 {"dev", no_argument, nullptr, OPT_DEV},
885 {"extra-checks", no_argument, nullptr, OPT_EXTRA_CHECKS},
886 {"add-sql-module", required_argument, nullptr, OPT_ADD_SQL_MODULE},
887 {"override-sql-module", required_argument, nullptr,
888 OPT_OVERRIDE_SQL_MODULE},
889 {"override-stdlib", required_argument, nullptr, OPT_OVERRIDE_STDLIB},
890 {"run-metrics", required_argument, nullptr, OPT_RUN_METRICS},
891 {"pre-metrics", required_argument, nullptr, OPT_PRE_METRICS},
892 {"metrics-output", required_argument, nullptr, OPT_METRICS_OUTPUT},
893 {"metric-extension", required_argument, nullptr, OPT_METRIC_EXTENSION},
894 {"dev-flag", required_argument, nullptr, OPT_DEV_FLAG},
895 {nullptr, 0, nullptr, 0}};
896
897 bool explicit_interactive = false;
898 for (;;) {
899 int option =
900 getopt_long(argc, argv, "hvWiDdm:p:q:Q:e:", long_options, nullptr);
901
902 if (option == -1)
903 break; // EOF.
904
905 if (option == 'v') {
906 printf("%s\n", base::GetVersionString());
907 printf("Trace Processor RPC API version: %d\n",
908 protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION);
909 exit(0);
910 }
911
912 if (option == 'W') {
913 command_line_options.wide = true;
914 continue;
915 }
916
917 if (option == 'p') {
918 command_line_options.perf_file_path = optarg;
919 continue;
920 }
921
922 if (option == 'q') {
923 command_line_options.query_file_path = optarg;
924 continue;
925 }
926
927 if (option == 'Q') {
928 command_line_options.query_string = optarg;
929 continue;
930 }
931
932 if (option == 'D') {
933 #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
934 command_line_options.enable_httpd = true;
935 #else
936 PERFETTO_FATAL("HTTP RPC module not supported in this build");
937 #endif
938 continue;
939 }
940
941 if (option == OPT_HTTP_PORT) {
942 command_line_options.port_number = optarg;
943 continue;
944 }
945
946 if (option == OPT_STDIOD) {
947 command_line_options.enable_stdiod = true;
948 continue;
949 }
950
951 if (option == 'i') {
952 explicit_interactive = true;
953 continue;
954 }
955
956 if (option == 'e') {
957 command_line_options.sqlite_file_path = optarg;
958 continue;
959 }
960
961 if (option == 'm') {
962 command_line_options.metatrace_path = optarg;
963 continue;
964 }
965
966 if (option == OPT_METATRACE_BUFFER_CAPACITY) {
967 command_line_options.metatrace_buffer_capacity =
968 static_cast<size_t>(atoi(optarg));
969 continue;
970 }
971
972 if (option == OPT_METATRACE_CATEGORIES) {
973 command_line_options.metatrace_categories =
974 ParseMetatraceCategories(optarg);
975 continue;
976 }
977
978 if (option == OPT_FORCE_FULL_SORT) {
979 command_line_options.force_full_sort = true;
980 continue;
981 }
982
983 if (option == OPT_NO_FTRACE_RAW) {
984 command_line_options.no_ftrace_raw = true;
985 continue;
986 }
987
988 if (option == OPT_ANALYZE_TRACE_PROTO_CONTENT) {
989 command_line_options.analyze_trace_proto_content = true;
990 continue;
991 }
992
993 if (option == OPT_CROP_TRACK_EVENTS) {
994 command_line_options.crop_track_events = true;
995 continue;
996 }
997
998 if (option == OPT_DEV) {
999 command_line_options.dev = true;
1000 continue;
1001 }
1002
1003 if (option == OPT_EXTRA_CHECKS) {
1004 command_line_options.extra_checks = true;
1005 continue;
1006 }
1007
1008 if (option == OPT_ADD_SQL_MODULE) {
1009 command_line_options.sql_module_path = optarg;
1010 continue;
1011 }
1012
1013 if (option == OPT_OVERRIDE_SQL_MODULE) {
1014 command_line_options.override_sql_module_paths.push_back(optarg);
1015 continue;
1016 }
1017
1018 if (option == OPT_OVERRIDE_STDLIB) {
1019 command_line_options.override_stdlib_path = optarg;
1020 continue;
1021 }
1022
1023 if (option == OPT_RUN_METRICS) {
1024 command_line_options.metric_names = optarg;
1025 continue;
1026 }
1027
1028 if (option == OPT_PRE_METRICS) {
1029 command_line_options.pre_metrics_path = optarg;
1030 continue;
1031 }
1032
1033 if (option == OPT_METRICS_OUTPUT) {
1034 command_line_options.metric_output = optarg;
1035 continue;
1036 }
1037
1038 if (option == OPT_METRIC_EXTENSION) {
1039 command_line_options.raw_metric_extensions.push_back(optarg);
1040 continue;
1041 }
1042
1043 if (option == OPT_DEV_FLAG) {
1044 command_line_options.dev_flags.push_back(optarg);
1045 continue;
1046 }
1047
1048 PrintUsage(argv);
1049 exit(option == 'h' ? 0 : 1);
1050 }
1051
1052 command_line_options.launch_shell =
1053 explicit_interactive || (command_line_options.pre_metrics_path.empty() &&
1054 command_line_options.metric_names.empty() &&
1055 command_line_options.query_file_path.empty() &&
1056 command_line_options.query_string.empty() &&
1057 command_line_options.sqlite_file_path.empty());
1058
1059 // Only allow non-interactive queries to emit perf data.
1060 if (!command_line_options.perf_file_path.empty() &&
1061 command_line_options.launch_shell) {
1062 PrintUsage(argv);
1063 exit(1);
1064 }
1065
1066 // The only case where we allow omitting the trace file path is when running
1067 // in --httpd or --stdiod mode. In all other cases, the last argument must be
1068 // the trace file.
1069 if (optind == argc - 1 && argv[optind]) {
1070 command_line_options.trace_file_path = argv[optind];
1071 } else if (!command_line_options.enable_httpd &&
1072 !command_line_options.enable_stdiod) {
1073 PrintUsage(argv);
1074 exit(1);
1075 }
1076
1077 return command_line_options;
1078 }
1079
ExtendPoolWithBinaryDescriptor(google::protobuf::DescriptorPool & pool,const void * data,int size,const std::vector<std::string> & skip_prefixes)1080 void ExtendPoolWithBinaryDescriptor(
1081 google::protobuf::DescriptorPool& pool,
1082 const void* data,
1083 int size,
1084 const std::vector<std::string>& skip_prefixes) {
1085 google::protobuf::FileDescriptorSet desc_set;
1086 PERFETTO_CHECK(desc_set.ParseFromArray(data, size));
1087 for (const auto& file_desc : desc_set.file()) {
1088 if (base::StartsWithAny(file_desc.name(), skip_prefixes))
1089 continue;
1090 pool.BuildFile(file_desc);
1091 }
1092 }
1093
LoadTrace(const std::string & trace_file_path,double * size_mb)1094 base::Status LoadTrace(const std::string& trace_file_path, double* size_mb) {
1095 base::Status read_status = ReadTraceUnfinalized(
1096 g_tp, trace_file_path.c_str(), [&size_mb](size_t parsed_size) {
1097 *size_mb = static_cast<double>(parsed_size) / 1E6;
1098 fprintf(stderr, "\rLoading trace: %.2f MB\r", *size_mb);
1099 });
1100 g_tp->Flush();
1101 if (!read_status.ok()) {
1102 return base::ErrStatus("Could not read trace file (path: %s): %s",
1103 trace_file_path.c_str(), read_status.c_message());
1104 }
1105
1106 std::unique_ptr<profiling::Symbolizer> symbolizer =
1107 profiling::LocalSymbolizerOrDie(profiling::GetPerfettoBinaryPath(),
1108 getenv("PERFETTO_SYMBOLIZER_MODE"));
1109
1110 if (symbolizer) {
1111 profiling::SymbolizeDatabase(
1112 g_tp, symbolizer.get(), [](const std::string& trace_proto) {
1113 std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
1114 memcpy(buf.get(), trace_proto.data(), trace_proto.size());
1115 auto status = g_tp->Parse(std::move(buf), trace_proto.size());
1116 if (!status.ok()) {
1117 PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
1118 status.message().c_str());
1119 return;
1120 }
1121 });
1122 g_tp->Flush();
1123 }
1124
1125 auto maybe_map = profiling::GetPerfettoProguardMapPath();
1126 if (!maybe_map.empty()) {
1127 profiling::ReadProguardMapsToDeobfuscationPackets(
1128 maybe_map, [](const std::string& trace_proto) {
1129 std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
1130 memcpy(buf.get(), trace_proto.data(), trace_proto.size());
1131 auto status = g_tp->Parse(std::move(buf), trace_proto.size());
1132 if (!status.ok()) {
1133 PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
1134 status.message().c_str());
1135 return;
1136 }
1137 });
1138 }
1139 return g_tp->NotifyEndOfFile();
1140 }
1141
RunQueries(const std::string & queries,bool expect_output)1142 base::Status RunQueries(const std::string& queries, bool expect_output) {
1143 base::Status status;
1144 if (expect_output) {
1145 status = RunQueriesAndPrintResult(queries, stdout);
1146 } else {
1147 status = RunQueriesWithoutOutput(queries);
1148 }
1149 if (!status.ok()) {
1150 return base::ErrStatus("%s", status.c_message());
1151 }
1152 return base::OkStatus();
1153 }
1154
RunQueriesFromFile(const std::string & query_file_path,bool expect_output)1155 base::Status RunQueriesFromFile(const std::string& query_file_path,
1156 bool expect_output) {
1157 std::string queries;
1158 if (!base::ReadFile(query_file_path, &queries)) {
1159 return base::ErrStatus("Unable to read file %s", query_file_path.c_str());
1160 }
1161 return RunQueries(queries, expect_output);
1162 }
1163
ParseSingleMetricExtensionPath(bool dev,const std::string & raw_extension,MetricExtension & parsed_extension)1164 base::Status ParseSingleMetricExtensionPath(bool dev,
1165 const std::string& raw_extension,
1166 MetricExtension& parsed_extension) {
1167 // We cannot easily use ':' as a path separator because windows paths can have
1168 // ':' in them (e.g. C:\foo\bar).
1169 std::vector<std::string> parts = base::SplitString(raw_extension, "@");
1170 if (parts.size() != 2 || parts[0].length() == 0 || parts[1].length() == 0) {
1171 return base::ErrStatus(
1172 "--metric-extension-dir must be of format disk_path@virtual_path");
1173 }
1174
1175 parsed_extension.SetDiskPath(std::move(parts[0]));
1176 parsed_extension.SetVirtualPath(std::move(parts[1]));
1177
1178 if (parsed_extension.virtual_path() == "/") {
1179 if (!dev) {
1180 return base::ErrStatus(
1181 "Local development features must be enabled (using the "
1182 "--dev flag) to override built-in metrics");
1183 }
1184 parsed_extension.SetVirtualPath("");
1185 }
1186
1187 if (parsed_extension.virtual_path() == "shell/") {
1188 return base::Status(
1189 "Cannot have 'shell/' as metric extension virtual path.");
1190 }
1191 return base::OkStatus();
1192 }
1193
CheckForDuplicateMetricExtension(const std::vector<MetricExtension> & metric_extensions)1194 base::Status CheckForDuplicateMetricExtension(
1195 const std::vector<MetricExtension>& metric_extensions) {
1196 std::unordered_set<std::string> disk_paths;
1197 std::unordered_set<std::string> virtual_paths;
1198 for (const auto& extension : metric_extensions) {
1199 auto ret = disk_paths.insert(extension.disk_path());
1200 if (!ret.second) {
1201 return base::ErrStatus(
1202 "Another metric extension is already using disk path %s",
1203 extension.disk_path().c_str());
1204 }
1205 ret = virtual_paths.insert(extension.virtual_path());
1206 if (!ret.second) {
1207 return base::ErrStatus(
1208 "Another metric extension is already using virtual path %s",
1209 extension.virtual_path().c_str());
1210 }
1211 }
1212 return base::OkStatus();
1213 }
1214
ParseMetricExtensionPaths(bool dev,const std::vector<std::string> & raw_metric_extensions,std::vector<MetricExtension> & metric_extensions)1215 base::Status ParseMetricExtensionPaths(
1216 bool dev,
1217 const std::vector<std::string>& raw_metric_extensions,
1218 std::vector<MetricExtension>& metric_extensions) {
1219 for (const auto& raw_extension : raw_metric_extensions) {
1220 metric_extensions.push_back({});
1221 RETURN_IF_ERROR(ParseSingleMetricExtensionPath(dev, raw_extension,
1222 metric_extensions.back()));
1223 }
1224 return CheckForDuplicateMetricExtension(metric_extensions);
1225 }
1226
IncludeSqlModule(std::string root,bool allow_override)1227 base::Status IncludeSqlModule(std::string root, bool allow_override) {
1228 // Remove trailing slash
1229 if (root.back() == '/')
1230 root = root.substr(0, root.length() - 1);
1231
1232 if (!base::FileExists(root))
1233 return base::ErrStatus("Directory %s does not exist.", root.c_str());
1234
1235 // Get module name
1236 size_t last_slash = root.rfind('/');
1237 if ((last_slash == std::string::npos) ||
1238 (root.find('.') != std::string::npos))
1239 return base::ErrStatus("Module path must point to the directory: %s",
1240 root.c_str());
1241
1242 std::string module_name = root.substr(last_slash + 1);
1243
1244 std::vector<std::string> paths;
1245 RETURN_IF_ERROR(base::ListFilesRecursive(root, paths));
1246 sql_modules::NameToPackage modules;
1247 for (const auto& path : paths) {
1248 if (base::GetFileExtension(path) != ".sql")
1249 continue;
1250
1251 std::string filename = root + "/" + path;
1252 std::string file_contents;
1253 if (!base::ReadFile(filename, &file_contents))
1254 return base::ErrStatus("Cannot read file %s", filename.c_str());
1255
1256 std::string import_key =
1257 module_name + "." + sql_modules::GetIncludeKey(path);
1258 modules.Insert(module_name, {})
1259 .first->push_back({import_key, file_contents});
1260 }
1261 for (auto module_it = modules.GetIterator(); module_it; ++module_it) {
1262 auto status = g_tp->RegisterSqlPackage({/*name=*/module_it.key(),
1263 /*files=*/module_it.value(),
1264 /*allow_override=*/allow_override});
1265 if (!status.ok())
1266 return status;
1267 }
1268
1269 return base::OkStatus();
1270 }
1271
LoadOverridenStdlib(std::string root)1272 base::Status LoadOverridenStdlib(std::string root) {
1273 // Remove trailing slash
1274 if (root.back() == '/') {
1275 root = root.substr(0, root.length() - 1);
1276 }
1277
1278 if (!base::FileExists(root)) {
1279 return base::ErrStatus("Directory '%s' does not exist.", root.c_str());
1280 }
1281
1282 std::vector<std::string> paths;
1283 RETURN_IF_ERROR(base::ListFilesRecursive(root, paths));
1284 sql_modules::NameToPackage packages;
1285 for (const auto& path : paths) {
1286 if (base::GetFileExtension(path) != ".sql") {
1287 continue;
1288 }
1289 std::string filename = root + "/" + path;
1290 std::string module_file;
1291 if (!base::ReadFile(filename, &module_file)) {
1292 return base::ErrStatus("Cannot read file '%s'", filename.c_str());
1293 }
1294 std::string module_name = sql_modules::GetIncludeKey(path);
1295 std::string package_name = sql_modules::GetPackageName(module_name);
1296 packages.Insert(package_name, {})
1297 .first->push_back({module_name, module_file});
1298 }
1299 for (auto package = packages.GetIterator(); package; ++package) {
1300 g_tp->RegisterSqlPackage({/*name=*/package.key(),
1301 /*files=*/package.value(),
1302 /*allow_override=*/true});
1303 }
1304
1305 return base::OkStatus();
1306 }
1307
LoadMetricExtensionProtos(const std::string & proto_root,const std::string & mount_path,google::protobuf::DescriptorPool & pool)1308 base::Status LoadMetricExtensionProtos(const std::string& proto_root,
1309 const std::string& mount_path,
1310 google::protobuf::DescriptorPool& pool) {
1311 if (!base::FileExists(proto_root)) {
1312 return base::ErrStatus(
1313 "Directory %s does not exist. Metric extension directory must contain "
1314 "a 'sql/' and 'protos/' subdirectory.",
1315 proto_root.c_str());
1316 }
1317 std::vector<std::string> proto_files;
1318 RETURN_IF_ERROR(base::ListFilesRecursive(proto_root, proto_files));
1319
1320 google::protobuf::FileDescriptorSet parsed_protos;
1321 for (const auto& file_path : proto_files) {
1322 if (base::GetFileExtension(file_path) != ".proto")
1323 continue;
1324 auto* file_desc = parsed_protos.add_file();
1325 ParseToFileDescriptorProto(proto_root + file_path, file_desc);
1326 file_desc->set_name(mount_path + file_path);
1327 }
1328
1329 std::vector<uint8_t> serialized_filedescset;
1330 serialized_filedescset.resize(parsed_protos.ByteSizeLong());
1331 parsed_protos.SerializeToArray(
1332 serialized_filedescset.data(),
1333 static_cast<int>(serialized_filedescset.size()));
1334
1335 // Extend the pool for any subsequent reflection-based operations
1336 // (e.g. output json)
1337 ExtendPoolWithBinaryDescriptor(
1338 pool, serialized_filedescset.data(),
1339 static_cast<int>(serialized_filedescset.size()), {});
1340 RETURN_IF_ERROR(g_tp->ExtendMetricsProto(serialized_filedescset.data(),
1341 serialized_filedescset.size()));
1342
1343 return base::OkStatus();
1344 }
1345
LoadMetricExtensionSql(const std::string & sql_root,const std::string & mount_path)1346 base::Status LoadMetricExtensionSql(const std::string& sql_root,
1347 const std::string& mount_path) {
1348 if (!base::FileExists(sql_root)) {
1349 return base::ErrStatus(
1350 "Directory %s does not exist. Metric extension directory must contain "
1351 "a 'sql/' and 'protos/' subdirectory.",
1352 sql_root.c_str());
1353 }
1354
1355 std::vector<std::string> sql_files;
1356 RETURN_IF_ERROR(base::ListFilesRecursive(sql_root, sql_files));
1357 for (const auto& file_path : sql_files) {
1358 if (base::GetFileExtension(file_path) != ".sql")
1359 continue;
1360 std::string file_contents;
1361 if (!base::ReadFile(sql_root + file_path, &file_contents)) {
1362 return base::ErrStatus("Cannot read file %s", file_path.c_str());
1363 }
1364 RETURN_IF_ERROR(
1365 g_tp->RegisterMetric(mount_path + file_path, file_contents));
1366 }
1367
1368 return base::OkStatus();
1369 }
1370
LoadMetricExtension(const MetricExtension & extension,google::protobuf::DescriptorPool & pool)1371 base::Status LoadMetricExtension(const MetricExtension& extension,
1372 google::protobuf::DescriptorPool& pool) {
1373 const std::string& disk_path = extension.disk_path();
1374 const std::string& virtual_path = extension.virtual_path();
1375
1376 if (!base::FileExists(disk_path)) {
1377 return base::ErrStatus("Metric extension directory %s does not exist",
1378 disk_path.c_str());
1379 }
1380
1381 // Note: Proto files must be loaded first, because we determine whether an SQL
1382 // file is a metric or not by checking if the name matches a field of the root
1383 // TraceMetrics proto.
1384 RETURN_IF_ERROR(LoadMetricExtensionProtos(
1385 disk_path + "protos/", kMetricProtoRoot + virtual_path, pool));
1386 RETURN_IF_ERROR(LoadMetricExtensionSql(disk_path + "sql/", virtual_path));
1387
1388 return base::OkStatus();
1389 }
1390
PopulateDescriptorPool(google::protobuf::DescriptorPool & pool,const std::vector<MetricExtension> & metric_extensions)1391 base::Status PopulateDescriptorPool(
1392 google::protobuf::DescriptorPool& pool,
1393 const std::vector<MetricExtension>& metric_extensions) {
1394 // TODO(b/182165266): There is code duplication here with trace_processor_impl
1395 // SetupMetrics. This will be removed when we switch the output formatter to
1396 // use internal DescriptorPool.
1397 std::vector<std::string> skip_prefixes;
1398 skip_prefixes.reserve(metric_extensions.size());
1399 for (const auto& ext : metric_extensions) {
1400 skip_prefixes.push_back(kMetricProtoRoot + ext.virtual_path());
1401 }
1402 ExtendPoolWithBinaryDescriptor(pool, kMetricsDescriptor.data(),
1403 kMetricsDescriptor.size(), skip_prefixes);
1404 ExtendPoolWithBinaryDescriptor(pool, kAllChromeMetricsDescriptor.data(),
1405 kAllChromeMetricsDescriptor.size(),
1406 skip_prefixes);
1407 ExtendPoolWithBinaryDescriptor(pool, kAllWebviewMetricsDescriptor.data(),
1408 kAllWebviewMetricsDescriptor.size(),
1409 skip_prefixes);
1410 return base::OkStatus();
1411 }
1412
LoadMetrics(const std::string & raw_metric_names,google::protobuf::DescriptorPool & pool,std::vector<MetricNameAndPath> & name_and_path)1413 base::Status LoadMetrics(const std::string& raw_metric_names,
1414 google::protobuf::DescriptorPool& pool,
1415 std::vector<MetricNameAndPath>& name_and_path) {
1416 std::vector<std::string> split;
1417 for (base::StringSplitter ss(raw_metric_names, ','); ss.Next();) {
1418 split.emplace_back(ss.cur_token());
1419 }
1420
1421 // For all metrics which are files, register them and extend the metrics
1422 // proto.
1423 for (const std::string& metric_or_path : split) {
1424 // If there is no extension, we assume it is a builtin metric.
1425 auto ext_idx = metric_or_path.rfind('.');
1426 if (ext_idx == std::string::npos) {
1427 name_and_path.emplace_back(
1428 MetricNameAndPath{metric_or_path, std::nullopt});
1429 continue;
1430 }
1431
1432 std::string no_ext_path = metric_or_path.substr(0, ext_idx);
1433
1434 // The proto must be extended before registering the metric.
1435 base::Status status = ExtendMetricsProto(no_ext_path + ".proto", &pool);
1436 if (!status.ok()) {
1437 return base::ErrStatus("Unable to extend metrics proto %s: %s",
1438 metric_or_path.c_str(), status.c_message());
1439 }
1440
1441 status = RegisterMetric(no_ext_path + ".sql");
1442 if (!status.ok()) {
1443 return base::ErrStatus("Unable to register metric %s: %s",
1444 metric_or_path.c_str(), status.c_message());
1445 }
1446 name_and_path.emplace_back(
1447 MetricNameAndPath{BaseName(no_ext_path), no_ext_path});
1448 }
1449 return base::OkStatus();
1450 }
1451
ParseOutputFormat(const CommandLineOptions & options)1452 OutputFormat ParseOutputFormat(const CommandLineOptions& options) {
1453 if (!options.query_file_path.empty())
1454 return OutputFormat::kNone;
1455 if (options.metric_output == "binary")
1456 return OutputFormat::kBinaryProto;
1457 if (options.metric_output == "json")
1458 return OutputFormat::kJson;
1459 return OutputFormat::kTextProto;
1460 }
1461
LoadMetricsAndExtensionsSql(const std::vector<MetricNameAndPath> & metrics,const std::vector<MetricExtension> & extensions)1462 base::Status LoadMetricsAndExtensionsSql(
1463 const std::vector<MetricNameAndPath>& metrics,
1464 const std::vector<MetricExtension>& extensions) {
1465 for (const MetricExtension& extension : extensions) {
1466 const std::string& disk_path = extension.disk_path();
1467 const std::string& virtual_path = extension.virtual_path();
1468
1469 RETURN_IF_ERROR(LoadMetricExtensionSql(disk_path + "sql/", virtual_path));
1470 }
1471
1472 for (const MetricNameAndPath& metric : metrics) {
1473 // Ignore builtin metrics.
1474 if (!metric.no_ext_path.has_value())
1475 continue;
1476 RETURN_IF_ERROR(RegisterMetric(metric.no_ext_path.value() + ".sql"));
1477 }
1478 return base::OkStatus();
1479 }
1480
PrintShellUsage()1481 void PrintShellUsage() {
1482 PERFETTO_ELOG(
1483 "Available commands:\n"
1484 ".quit, .q Exit the shell.\n"
1485 ".help This text.\n"
1486 ".dump FILE Export the trace as a sqlite database.\n"
1487 ".read FILE Executes the queries in the FILE.\n"
1488 ".reset Destroys all tables/view created by the user.\n"
1489 ".load-metrics-sql Reloads SQL from extension and custom metric paths\n"
1490 " specified in command line args.\n"
1491 ".run-metrics Runs metrics specified in command line args\n"
1492 " and prints the result.\n"
1493 ".width WIDTH Changes the column width of interactive query\n"
1494 " output.");
1495 }
1496
1497 struct InteractiveOptions {
1498 uint32_t column_width;
1499 OutputFormat metric_format;
1500 std::vector<MetricExtension> extensions;
1501 std::vector<MetricNameAndPath> metrics;
1502 const google::protobuf::DescriptorPool* pool;
1503 };
1504
StartInteractiveShell(const InteractiveOptions & options)1505 base::Status StartInteractiveShell(const InteractiveOptions& options) {
1506 SetupLineEditor();
1507
1508 uint32_t column_width = options.column_width;
1509 for (;;) {
1510 ScopedLine line = GetLine("> ");
1511 if (!line)
1512 break;
1513 if (strcmp(line.get(), "") == 0) {
1514 printf("If you want to quit either type .q or press CTRL-D (EOF)\n");
1515 continue;
1516 }
1517 if (line.get()[0] == '.') {
1518 char command[32] = {};
1519 char arg[1024] = {};
1520 sscanf(line.get() + 1, "%31s %1023s", command, arg);
1521 if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
1522 break;
1523 }
1524 if (strcmp(command, "help") == 0) {
1525 PrintShellUsage();
1526 } else if (strcmp(command, "dump") == 0 && strlen(arg)) {
1527 if (!ExportTraceToDatabase(arg).ok())
1528 PERFETTO_ELOG("Database export failed");
1529 } else if (strcmp(command, "reset") == 0) {
1530 g_tp->RestoreInitialTables();
1531 } else if (strcmp(command, "read") == 0 && strlen(arg)) {
1532 base::Status status = RunQueriesFromFile(arg, true);
1533 if (!status.ok()) {
1534 PERFETTO_ELOG("%s", status.c_message());
1535 }
1536 } else if (strcmp(command, "width") == 0 && strlen(arg)) {
1537 std::optional<uint32_t> width = base::CStringToUInt32(arg);
1538 if (!width) {
1539 PERFETTO_ELOG("Invalid column width specified");
1540 continue;
1541 }
1542 column_width = *width;
1543 } else if (strcmp(command, "load-metrics-sql") == 0) {
1544 base::Status status =
1545 LoadMetricsAndExtensionsSql(options.metrics, options.extensions);
1546 if (!status.ok()) {
1547 PERFETTO_ELOG("%s", status.c_message());
1548 }
1549 } else if (strcmp(command, "run-metrics") == 0) {
1550 if (options.metrics.empty()) {
1551 PERFETTO_ELOG("No metrics specified on command line");
1552 continue;
1553 }
1554
1555 base::Status status =
1556 RunMetrics(options.metrics, options.metric_format);
1557 if (!status.ok()) {
1558 fprintf(stderr, "%s\n", status.c_message());
1559 }
1560 } else {
1561 PrintShellUsage();
1562 }
1563 continue;
1564 }
1565
1566 base::TimeNanos t_start = base::GetWallTimeNs();
1567 auto it = g_tp->ExecuteQuery(line.get());
1568 PrintQueryResultInteractively(&it, t_start, column_width);
1569 }
1570 return base::OkStatus();
1571 }
1572
MaybeWriteMetatrace(const std::string & metatrace_path)1573 base::Status MaybeWriteMetatrace(const std::string& metatrace_path) {
1574 if (metatrace_path.empty()) {
1575 return base::OkStatus();
1576 }
1577 std::vector<uint8_t> serialized;
1578 base::Status status = g_tp->DisableAndReadMetatrace(&serialized);
1579 if (!status.ok())
1580 return status;
1581
1582 auto file = base::OpenFile(metatrace_path, O_CREAT | O_RDWR | O_TRUNC, 0600);
1583 if (!file)
1584 return base::ErrStatus("Unable to open metatrace file");
1585
1586 ssize_t res = base::WriteAll(*file, serialized.data(), serialized.size());
1587 if (res < 0)
1588 return base::ErrStatus("Error while writing metatrace file");
1589 return base::OkStatus();
1590 }
1591
MaybeUpdateSqlModules(const CommandLineOptions & options)1592 base::Status MaybeUpdateSqlModules(const CommandLineOptions& options) {
1593 if (!options.override_stdlib_path.empty()) {
1594 if (!options.dev)
1595 return base::ErrStatus("Overriding stdlib requires --dev flag");
1596
1597 auto status = LoadOverridenStdlib(options.override_stdlib_path);
1598 if (!status.ok())
1599 return base::ErrStatus("Couldn't override stdlib: %s",
1600 status.c_message());
1601 }
1602
1603 if (!options.override_sql_module_paths.empty()) {
1604 for (const auto& override_sql_module_path :
1605 options.override_sql_module_paths) {
1606 auto status = IncludeSqlModule(override_sql_module_path, true);
1607 if (!status.ok())
1608 return base::ErrStatus("Couldn't override stdlib module: %s",
1609 status.c_message());
1610 }
1611 }
1612
1613 if (!options.sql_module_path.empty()) {
1614 auto status = IncludeSqlModule(options.sql_module_path, false);
1615 if (!status.ok())
1616 return base::ErrStatus("Couldn't add SQL module: %s", status.c_message());
1617 }
1618 return base::OkStatus();
1619 }
1620
TraceProcessorMain(int argc,char ** argv)1621 base::Status TraceProcessorMain(int argc, char** argv) {
1622 CommandLineOptions options = ParseCommandLineOptions(argc, argv);
1623
1624 Config config;
1625 config.sorting_mode = options.force_full_sort
1626 ? SortingMode::kForceFullSort
1627 : SortingMode::kDefaultHeuristics;
1628 config.ingest_ftrace_in_raw_table = !options.no_ftrace_raw;
1629 config.analyze_trace_proto_content = options.analyze_trace_proto_content;
1630 config.drop_track_event_data_before =
1631 options.crop_track_events
1632 ? DropTrackEventDataBefore::kTrackEventRangeOfInterest
1633 : DropTrackEventDataBefore::kNoDrop;
1634
1635 std::vector<MetricExtension> metric_extensions;
1636 RETURN_IF_ERROR(ParseMetricExtensionPaths(
1637 options.dev, options.raw_metric_extensions, metric_extensions));
1638
1639 for (const auto& extension : metric_extensions) {
1640 config.skip_builtin_metric_paths.push_back(extension.virtual_path());
1641 }
1642
1643 if (options.dev) {
1644 config.enable_dev_features = true;
1645 for (const auto& flag_pair : options.dev_flags) {
1646 auto kv = base::SplitString(flag_pair, "=");
1647 if (kv.size() != 2) {
1648 PERFETTO_ELOG("Ignoring unknown dev flag format %s", flag_pair.c_str());
1649 continue;
1650 }
1651 config.dev_flags.emplace(kv[0], kv[1]);
1652 }
1653 }
1654
1655 if (options.extra_checks) {
1656 config.enable_extra_checks = true;
1657 }
1658
1659 std::unique_ptr<TraceProcessor> tp = TraceProcessor::CreateInstance(config);
1660 g_tp = tp.get();
1661
1662 {
1663 base::Status status = MaybeUpdateSqlModules(options);
1664 if (!status.ok()) {
1665 return status;
1666 }
1667 }
1668
1669 // Enable metatracing as soon as possible.
1670 if (!options.metatrace_path.empty()) {
1671 metatrace::MetatraceConfig metatrace_config;
1672 metatrace_config.override_buffer_size = options.metatrace_buffer_capacity;
1673 metatrace_config.categories = options.metatrace_categories;
1674 tp->EnableMetatrace(metatrace_config);
1675 }
1676
1677 // Descriptor pool used for printing output as textproto. Building on top of
1678 // generated pool so default protos in google.protobuf.descriptor.proto are
1679 // available.
1680 // For some insane reason, the descriptor pool is not movable so we need to
1681 // create it here so we can create references and pass it everywhere.
1682 google::protobuf::DescriptorPool pool(
1683 google::protobuf::DescriptorPool::generated_pool());
1684 RETURN_IF_ERROR(PopulateDescriptorPool(pool, metric_extensions));
1685
1686 // We load all the metric extensions even when --run-metrics arg is not there,
1687 // because we want the metrics to be available in interactive mode or when
1688 // used in UI using httpd.
1689 // Metric extensions are also used to populate the descriptor pool.
1690 for (const auto& extension : metric_extensions) {
1691 RETURN_IF_ERROR(LoadMetricExtension(extension, pool));
1692 }
1693
1694 base::TimeNanos t_load{};
1695 if (!options.trace_file_path.empty()) {
1696 base::TimeNanos t_load_start = base::GetWallTimeNs();
1697 double size_mb = 0;
1698 RETURN_IF_ERROR(LoadTrace(options.trace_file_path, &size_mb));
1699 t_load = base::GetWallTimeNs() - t_load_start;
1700
1701 double t_load_s = static_cast<double>(t_load.count()) / 1E9;
1702 PERFETTO_ILOG("Trace loaded: %.2f MB in %.2fs (%.1f MB/s)", size_mb,
1703 t_load_s, size_mb / t_load_s);
1704
1705 RETURN_IF_ERROR(PrintStats());
1706 }
1707
1708 #if PERFETTO_HAS_SIGNAL_H()
1709 // Set up interrupt signal to allow the user to abort query.
1710 signal(SIGINT, [](int) { g_tp->InterruptQuery(); });
1711 #endif
1712
1713 base::TimeNanos t_query_start = base::GetWallTimeNs();
1714 if (!options.pre_metrics_path.empty()) {
1715 RETURN_IF_ERROR(RunQueriesFromFile(options.pre_metrics_path, false));
1716 }
1717
1718 std::vector<MetricNameAndPath> metrics;
1719 if (!options.metric_names.empty()) {
1720 RETURN_IF_ERROR(LoadMetrics(options.metric_names, pool, metrics));
1721 }
1722
1723 OutputFormat metric_format = ParseOutputFormat(options);
1724 if (!metrics.empty()) {
1725 RETURN_IF_ERROR(RunMetrics(metrics, metric_format));
1726 }
1727
1728 if (!options.query_file_path.empty()) {
1729 base::Status status = RunQueriesFromFile(options.query_file_path, true);
1730 if (!status.ok()) {
1731 // Write metatrace if needed before exiting.
1732 RETURN_IF_ERROR(MaybeWriteMetatrace(options.metatrace_path));
1733 return status;
1734 }
1735 }
1736
1737 if (!options.query_string.empty()) {
1738 base::Status status = RunQueries(options.query_string, true);
1739 if (!status.ok()) {
1740 // Write metatrace if needed before exiting.
1741 RETURN_IF_ERROR(MaybeWriteMetatrace(options.metatrace_path));
1742 return status;
1743 }
1744 }
1745
1746 base::TimeNanos t_query = base::GetWallTimeNs() - t_query_start;
1747
1748 if (!options.sqlite_file_path.empty()) {
1749 RETURN_IF_ERROR(ExportTraceToDatabase(options.sqlite_file_path));
1750 }
1751
1752 if (options.enable_httpd) {
1753 #if PERFETTO_HAS_SIGNAL_H()
1754 if (options.metatrace_path.empty()) {
1755 // Restore the default signal handler to allow the user to terminate
1756 // httpd server via Ctrl-C.
1757 signal(SIGINT, SIG_DFL);
1758 } else {
1759 // Write metatrace to file before exiting.
1760 static std::string* metatrace_path = &options.metatrace_path;
1761 signal(SIGINT, [](int) {
1762 MaybeWriteMetatrace(*metatrace_path);
1763 exit(1);
1764 });
1765 }
1766 #endif
1767
1768 #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
1769 RunHttpRPCServer(std::move(tp), options.port_number);
1770 PERFETTO_FATAL("Should never return");
1771 #else
1772 PERFETTO_FATAL("HTTP not available");
1773 #endif
1774 }
1775
1776 if (options.enable_stdiod) {
1777 return RunStdioRpcServer(std::move(tp));
1778 }
1779
1780 if (options.launch_shell) {
1781 RETURN_IF_ERROR(StartInteractiveShell(
1782 InteractiveOptions{options.wide ? 40u : 20u, metric_format,
1783 metric_extensions, metrics, &pool}));
1784 } else if (!options.perf_file_path.empty()) {
1785 RETURN_IF_ERROR(PrintPerfFile(options.perf_file_path, t_load, t_query));
1786 }
1787
1788 RETURN_IF_ERROR(MaybeWriteMetatrace(options.metatrace_path));
1789
1790 return base::OkStatus();
1791 }
1792
1793 } // namespace
1794
1795 } // namespace perfetto::trace_processor
1796
main(int argc,char ** argv)1797 int main(int argc, char** argv) {
1798 auto status = perfetto::trace_processor::TraceProcessorMain(argc, argv);
1799 if (!status.ok()) {
1800 fprintf(stderr, "%s\n", status.c_message());
1801 return 1;
1802 }
1803 return 0;
1804 }
1805