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