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