• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <inttypes.h>
19 #include <stdio.h>
20 #include <sys/stat.h>
21 
22 #include <functional>
23 #include <iostream>
24 #include <vector>
25 
26 #include <google/protobuf/compiler/parser.h>
27 #include <google/protobuf/dynamic_message.h>
28 #include <google/protobuf/io/zero_copy_stream_impl.h>
29 #include <google/protobuf/text_format.h>
30 
31 #include "perfetto/base/build_config.h"
32 #include "perfetto/base/logging.h"
33 #include "perfetto/base/time.h"
34 #include "perfetto/ext/base/file_utils.h"
35 #include "perfetto/ext/base/getopt.h"
36 #include "perfetto/ext/base/scoped_file.h"
37 #include "perfetto/ext/base/string_splitter.h"
38 #include "perfetto/ext/base/string_utils.h"
39 #include "perfetto/ext/base/version.h"
40 
41 #include "perfetto/trace_processor/read_trace.h"
42 #include "perfetto/trace_processor/trace_processor.h"
43 #include "src/trace_processor/metrics/chrome/all_chrome_metrics.descriptor.h"
44 #include "src/trace_processor/metrics/metrics.descriptor.h"
45 #include "src/trace_processor/util/proto_to_json.h"
46 #include "src/trace_processor/util/status_macros.h"
47 
48 #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
49 #include "src/trace_processor/rpc/httpd.h"
50 #endif
51 #include "src/profiling/deobfuscator.h"
52 #include "src/profiling/symbolizer/local_symbolizer.h"
53 #include "src/profiling/symbolizer/symbolize_database.h"
54 #include "src/profiling/symbolizer/symbolizer.h"
55 
56 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
57     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
58     PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
59 #define PERFETTO_HAS_SIGNAL_H() 1
60 #else
61 #define PERFETTO_HAS_SIGNAL_H() 0
62 #endif
63 
64 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE)
65 #include <linenoise.h>
66 #include <pwd.h>
67 #include <sys/types.h>
68 #endif
69 
70 #if PERFETTO_HAS_SIGNAL_H()
71 #include <signal.h>
72 #endif
73 
74 #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
75 #include <io.h>
76 #define ftruncate _chsize
77 #else
78 #include <dirent.h>
79 #endif
80 
81 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE) && \
82     !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
83 #include <unistd.h>  // For getuid() in GetConfigPath().
84 #endif
85 
86 namespace perfetto {
87 namespace trace_processor {
88 
89 namespace {
90 TraceProcessor* g_tp;
91 
92 #if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE)
93 
EnsureDir(const std::string & path)94 bool EnsureDir(const std::string& path) {
95   return base::Mkdir(path) || errno == EEXIST;
96 }
97 
EnsureFile(const std::string & path)98 bool EnsureFile(const std::string& path) {
99   return base::OpenFile(path, O_RDONLY | O_CREAT, 0644).get() != -1;
100 }
101 
GetConfigPath()102 std::string GetConfigPath() {
103   const char* homedir = getenv("HOME");
104 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
105     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
106     PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
107   if (homedir == nullptr)
108     homedir = getpwuid(getuid())->pw_dir;
109 #elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
110   if (homedir == nullptr)
111     homedir = getenv("USERPROFILE");
112 #endif
113   if (homedir == nullptr)
114     return "";
115   return std::string(homedir) + "/.config";
116 }
117 
GetPerfettoPath()118 std::string GetPerfettoPath() {
119   std::string config = GetConfigPath();
120   if (config.empty())
121     return "";
122   return config + "/perfetto";
123 }
124 
GetHistoryPath()125 std::string GetHistoryPath() {
126   std::string perfetto = GetPerfettoPath();
127   if (perfetto.empty())
128     return "";
129   return perfetto + "/.trace_processor_shell_history";
130 }
131 
SetupLineEditor()132 void SetupLineEditor() {
133   linenoiseSetMultiLine(true);
134   linenoiseHistorySetMaxLen(1000);
135 
136   bool success = !GetHistoryPath().empty();
137   success = success && EnsureDir(GetConfigPath());
138   success = success && EnsureDir(GetPerfettoPath());
139   success = success && EnsureFile(GetHistoryPath());
140   success = success && linenoiseHistoryLoad(GetHistoryPath().c_str()) != -1;
141   if (!success) {
142     PERFETTO_PLOG("Could not load history from %s", GetHistoryPath().c_str());
143   }
144 }
145 
146 struct LineDeleter {
operator ()perfetto::trace_processor::__anon412e3b3d0111::LineDeleter147   void operator()(char* p) const {
148     linenoiseHistoryAdd(p);
149     linenoiseHistorySave(GetHistoryPath().c_str());
150     linenoiseFree(p);
151   }
152 };
153 
154 using ScopedLine = std::unique_ptr<char, LineDeleter>;
155 
GetLine(const char * prompt)156 ScopedLine GetLine(const char* prompt) {
157   errno = 0;
158   auto line = ScopedLine(linenoise(prompt));
159   // linenoise returns a nullptr both for CTRL-C and CTRL-D, however in the
160   // former case it sets errno to EAGAIN.
161   // If the user press CTRL-C return "" instead of nullptr. We don't want the
162   // main loop to quit in that case as that is inconsistent with the behavior
163   // "CTRL-C interrupts the current query" and frustrating when hitting that
164   // a split second after the query is done.
165   if (!line && errno == EAGAIN)
166     return ScopedLine(strdup(""));
167   return line;
168 }
169 
170 #else
171 
SetupLineEditor()172 void SetupLineEditor() {}
173 
174 using ScopedLine = std::unique_ptr<char>;
175 
GetLine(const char * prompt)176 ScopedLine GetLine(const char* prompt) {
177   printf("\r%80s\r%s", "", prompt);
178   fflush(stdout);
179   ScopedLine line(new char[1024]);
180   if (!fgets(line.get(), 1024 - 1, stdin))
181     return nullptr;
182   if (strlen(line.get()) > 0)
183     line.get()[strlen(line.get()) - 1] = 0;
184   return line;
185 }
186 
187 #endif  // PERFETTO_TP_LINENOISE
188 
PrintStats()189 util::Status PrintStats() {
190   auto it = g_tp->ExecuteQuery(
191       "SELECT name, idx, source, value from stats "
192       "where severity IN ('error', 'data_loss') and value > 0");
193 
194   bool first = true;
195   for (uint32_t rows = 0; it.Next(); rows++) {
196     if (first) {
197       fprintf(stderr, "Error stats for this trace:\n");
198 
199       for (uint32_t i = 0; i < it.ColumnCount(); i++)
200         fprintf(stderr, "%40s ", it.GetColumnName(i).c_str());
201       fprintf(stderr, "\n");
202 
203       for (uint32_t i = 0; i < it.ColumnCount(); i++)
204         fprintf(stderr, "%40s ", "----------------------------------------");
205       fprintf(stderr, "\n");
206 
207       first = false;
208     }
209 
210     for (uint32_t c = 0; c < it.ColumnCount(); c++) {
211       auto value = it.Get(c);
212       switch (value.type) {
213         case SqlValue::Type::kNull:
214           fprintf(stderr, "%-40.40s", "[NULL]");
215           break;
216         case SqlValue::Type::kDouble:
217           fprintf(stderr, "%40f", value.double_value);
218           break;
219         case SqlValue::Type::kLong:
220           fprintf(stderr, "%40" PRIi64, value.long_value);
221           break;
222         case SqlValue::Type::kString:
223           fprintf(stderr, "%-40.40s", value.string_value);
224           break;
225         case SqlValue::Type::kBytes:
226           printf("%-40.40s", "<raw bytes>");
227           break;
228       }
229       fprintf(stderr, " ");
230     }
231     fprintf(stderr, "\n");
232   }
233 
234   util::Status status = it.Status();
235   if (!status.ok()) {
236     return util::ErrStatus("Error while iterating stats (%s)",
237                            status.c_message());
238   }
239   return util::OkStatus();
240 }
241 
ExportTraceToDatabase(const std::string & output_name)242 util::Status ExportTraceToDatabase(const std::string& output_name) {
243   PERFETTO_CHECK(output_name.find('\'') == std::string::npos);
244   {
245     base::ScopedFile fd(base::OpenFile(output_name, O_CREAT | O_RDWR, 0600));
246     if (!fd)
247       return util::ErrStatus("Failed to create file: %s", output_name.c_str());
248     int res = ftruncate(fd.get(), 0);
249     PERFETTO_CHECK(res == 0);
250   }
251 
252   std::string attach_sql =
253       "ATTACH DATABASE '" + output_name + "' AS perfetto_export";
254   auto attach_it = g_tp->ExecuteQuery(attach_sql);
255   bool attach_has_more = attach_it.Next();
256   PERFETTO_DCHECK(!attach_has_more);
257 
258   util::Status status = attach_it.Status();
259   if (!status.ok())
260     return util::ErrStatus("SQLite error: %s", status.c_message());
261 
262   // Export real and virtual tables.
263   auto tables_it = g_tp->ExecuteQuery(
264       "SELECT name FROM perfetto_tables UNION "
265       "SELECT name FROM sqlite_master WHERE type='table'");
266   for (uint32_t rows = 0; tables_it.Next(); rows++) {
267     std::string table_name = tables_it.Get(0).string_value;
268     PERFETTO_CHECK(!base::Contains(table_name, '\''));
269     std::string export_sql = "CREATE TABLE perfetto_export." + table_name +
270                              " AS SELECT * FROM " + table_name;
271 
272     auto export_it = g_tp->ExecuteQuery(export_sql);
273     bool export_has_more = export_it.Next();
274     PERFETTO_DCHECK(!export_has_more);
275 
276     status = export_it.Status();
277     if (!status.ok())
278       return util::ErrStatus("SQLite error: %s", status.c_message());
279   }
280   status = tables_it.Status();
281   if (!status.ok())
282     return util::ErrStatus("SQLite error: %s", status.c_message());
283 
284   // Export views.
285   auto views_it =
286       g_tp->ExecuteQuery("SELECT sql FROM sqlite_master WHERE type='view'");
287   for (uint32_t rows = 0; views_it.Next(); rows++) {
288     std::string sql = views_it.Get(0).string_value;
289     // View statements are of the form "CREATE VIEW name AS stmt". We need to
290     // rewrite name to point to the exported db.
291     const std::string kPrefix = "CREATE VIEW ";
292     PERFETTO_CHECK(sql.find(kPrefix) == 0);
293     sql = sql.substr(0, kPrefix.size()) + "perfetto_export." +
294           sql.substr(kPrefix.size());
295 
296     auto export_it = g_tp->ExecuteQuery(sql);
297     bool export_has_more = export_it.Next();
298     PERFETTO_DCHECK(!export_has_more);
299 
300     status = export_it.Status();
301     if (!status.ok())
302       return util::ErrStatus("SQLite error: %s", status.c_message());
303   }
304   status = views_it.Status();
305   if (!status.ok())
306     return util::ErrStatus("SQLite error: %s", status.c_message());
307 
308   auto detach_it = g_tp->ExecuteQuery("DETACH DATABASE perfetto_export");
309   bool detach_has_more = attach_it.Next();
310   PERFETTO_DCHECK(!detach_has_more);
311   status = detach_it.Status();
312   return status.ok() ? util::OkStatus()
313                      : util::ErrStatus("SQLite error: %s", status.c_message());
314 }
315 
316 class ErrorPrinter : public google::protobuf::io::ErrorCollector {
AddError(int line,int col,const std::string & msg)317   void AddError(int line, int col, const std::string& msg) override {
318     PERFETTO_ELOG("%d:%d: %s", line, col, msg.c_str());
319   }
320 
AddWarning(int line,int col,const std::string & msg)321   void AddWarning(int line, int col, const std::string& msg) override {
322     PERFETTO_ILOG("%d:%d: %s", line, col, msg.c_str());
323   }
324 };
325 
326 // This function returns an indentifier for a metric suitable for use
327 // as an SQL table name (i.e. containing no forward or backward slashes).
BaseName(std::string metric_path)328 std::string BaseName(std::string metric_path) {
329   std::replace(metric_path.begin(), metric_path.end(), '\\', '/');
330   auto slash_idx = metric_path.rfind('/');
331   return slash_idx == std::string::npos ? metric_path
332                                         : metric_path.substr(slash_idx + 1);
333 }
334 
RegisterMetric(const std::string & register_metric)335 util::Status RegisterMetric(const std::string& register_metric) {
336   std::string sql;
337   base::ReadFile(register_metric, &sql);
338 
339   std::string path = "shell/" + BaseName(register_metric);
340 
341   return g_tp->RegisterMetric(path, sql);
342 }
343 
ExtendMetricsProto(const std::string & extend_metrics_proto,google::protobuf::DescriptorPool * pool)344 util::Status ExtendMetricsProto(const std::string& extend_metrics_proto,
345                                 google::protobuf::DescriptorPool* pool) {
346   google::protobuf::FileDescriptorSet desc_set;
347 
348   base::ScopedFile file(base::OpenFile(extend_metrics_proto, O_RDONLY));
349   if (file.get() == -1) {
350     return util::ErrStatus("Failed to open proto file %s",
351                            extend_metrics_proto.c_str());
352   }
353 
354   google::protobuf::io::FileInputStream stream(file.get());
355   ErrorPrinter printer;
356   google::protobuf::io::Tokenizer tokenizer(&stream, &printer);
357 
358   auto* file_desc = desc_set.add_file();
359   google::protobuf::compiler::Parser parser;
360   parser.Parse(&tokenizer, file_desc);
361 
362   file_desc->set_name(BaseName(extend_metrics_proto));
363   pool->BuildFile(*file_desc);
364 
365   std::vector<uint8_t> metric_proto;
366   metric_proto.resize(desc_set.ByteSizeLong());
367   desc_set.SerializeToArray(metric_proto.data(),
368                             static_cast<int>(metric_proto.size()));
369 
370   return g_tp->ExtendMetricsProto(metric_proto.data(), metric_proto.size());
371 }
372 
373 enum OutputFormat {
374   kBinaryProto,
375   kTextProto,
376   kJson,
377   kNone,
378 };
379 
RunMetrics(const std::vector<std::string> & metric_names,OutputFormat format,const google::protobuf::DescriptorPool & pool)380 util::Status RunMetrics(const std::vector<std::string>& metric_names,
381                         OutputFormat format,
382                         const google::protobuf::DescriptorPool& pool) {
383   std::vector<uint8_t> metric_result;
384   util::Status status = g_tp->ComputeMetric(metric_names, &metric_result);
385   if (!status.ok()) {
386     return util::ErrStatus("Error when computing metrics: %s",
387                            status.c_message());
388   }
389   if (format == OutputFormat::kNone) {
390     return util::OkStatus();
391   }
392   if (format == OutputFormat::kBinaryProto) {
393     fwrite(metric_result.data(), sizeof(uint8_t), metric_result.size(), stdout);
394     return util::OkStatus();
395   }
396 
397   google::protobuf::DynamicMessageFactory factory(&pool);
398   auto* descriptor = pool.FindMessageTypeByName("perfetto.protos.TraceMetrics");
399   std::unique_ptr<google::protobuf::Message> metrics(
400       factory.GetPrototype(descriptor)->New());
401   metrics->ParseFromArray(metric_result.data(),
402                           static_cast<int>(metric_result.size()));
403 
404   switch (format) {
405     case OutputFormat::kTextProto: {
406       std::string out;
407       google::protobuf::TextFormat::PrintToString(*metrics, &out);
408       fwrite(out.c_str(), sizeof(char), out.size(), stdout);
409       break;
410     }
411     case OutputFormat::kJson: {
412       // We need to instantiate field options from dynamic message factory
413       // because otherwise it cannot parse our custom extensions.
414       const google::protobuf::Message* field_options_prototype =
415           factory.GetPrototype(
416               pool.FindMessageTypeByName("google.protobuf.FieldOptions"));
417       auto out = proto_to_json::MessageToJsonWithAnnotations(
418           *metrics, field_options_prototype, 0);
419       fwrite(out.c_str(), sizeof(char), out.size(), stdout);
420       break;
421     }
422     case OutputFormat::kBinaryProto:
423     case OutputFormat::kNone:
424       PERFETTO_FATAL("Unsupported output format.");
425   }
426   return util::OkStatus();
427 }
428 
PrintQueryResultInteractively(Iterator * it,base::TimeNanos t_start,uint32_t column_width)429 void PrintQueryResultInteractively(Iterator* it,
430                                    base::TimeNanos t_start,
431                                    uint32_t column_width) {
432   base::TimeNanos t_end = t_start;
433   for (uint32_t rows = 0; it->Next(); rows++) {
434     if (rows % 32 == 0) {
435       if (rows > 0) {
436         fprintf(stderr, "...\nType 'q' to stop, Enter for more records: ");
437         fflush(stderr);
438         char input[32];
439         if (!fgets(input, sizeof(input) - 1, stdin))
440           exit(0);
441         if (input[0] == 'q')
442           break;
443       } else {
444         t_end = base::GetWallTimeNs();
445       }
446       for (uint32_t i = 0; i < it->ColumnCount(); i++)
447         printf("%-*.*s ", column_width, column_width,
448                it->GetColumnName(i).c_str());
449       printf("\n");
450 
451       std::string divider(column_width, '-');
452       for (uint32_t i = 0; i < it->ColumnCount(); i++) {
453         printf("%-*s ", column_width, divider.c_str());
454       }
455       printf("\n");
456     }
457 
458     for (uint32_t c = 0; c < it->ColumnCount(); c++) {
459       auto value = it->Get(c);
460       switch (value.type) {
461         case SqlValue::Type::kNull:
462           printf("%-*s", column_width, "[NULL]");
463           break;
464         case SqlValue::Type::kDouble:
465           printf("%*f", column_width, value.double_value);
466           break;
467         case SqlValue::Type::kLong:
468           printf("%*" PRIi64, column_width, value.long_value);
469           break;
470         case SqlValue::Type::kString:
471           printf("%-*.*s", column_width, column_width, value.string_value);
472           break;
473         case SqlValue::Type::kBytes:
474           printf("%-*s", column_width, "<raw bytes>");
475           break;
476       }
477       printf(" ");
478     }
479     printf("\n");
480   }
481 
482   util::Status status = it->Status();
483   if (!status.ok()) {
484     PERFETTO_ELOG("SQLite error: %s", status.c_message());
485   }
486   printf("\nQuery executed in %.3f ms\n\n",
487          static_cast<double>((t_end - t_start).count()) / 1E6);
488 }
489 
PrintQueryResultAsCsv(Iterator * it,FILE * output)490 util::Status PrintQueryResultAsCsv(Iterator* it, FILE* output) {
491   for (uint32_t c = 0; c < it->ColumnCount(); c++) {
492     if (c > 0)
493       fprintf(output, ",");
494     fprintf(output, "\"%s\"", it->GetColumnName(c).c_str());
495   }
496   fprintf(output, "\n");
497 
498   for (uint32_t rows = 0; it->Next(); rows++) {
499     for (uint32_t c = 0; c < it->ColumnCount(); c++) {
500       if (c > 0)
501         fprintf(output, ",");
502 
503       auto value = it->Get(c);
504       switch (value.type) {
505         case SqlValue::Type::kNull:
506           fprintf(output, "\"%s\"", "[NULL]");
507           break;
508         case SqlValue::Type::kDouble:
509           fprintf(output, "%f", value.double_value);
510           break;
511         case SqlValue::Type::kLong:
512           fprintf(output, "%" PRIi64, value.long_value);
513           break;
514         case SqlValue::Type::kString:
515           fprintf(output, "\"%s\"", value.string_value);
516           break;
517         case SqlValue::Type::kBytes:
518           fprintf(output, "\"%s\"", "<raw bytes>");
519           break;
520       }
521     }
522     fprintf(output, "\n");
523   }
524   return it->Status();
525 }
526 
IsBlankLine(const std::string & buffer)527 bool IsBlankLine(const std::string& buffer) {
528   return buffer == "\n" || buffer == "\r\n";
529 }
530 
IsCommentLine(const std::string & buffer)531 bool IsCommentLine(const std::string& buffer) {
532   return base::StartsWith(buffer, "--");
533 }
534 
HasEndOfQueryDelimiter(const std::string & buffer)535 bool HasEndOfQueryDelimiter(const std::string& buffer) {
536   return base::EndsWith(buffer, ";\n") || base::EndsWith(buffer, ";") ||
537          base::EndsWith(buffer, ";\r\n");
538 }
539 
LoadQueries(FILE * input,std::vector<std::string> * output)540 util::Status LoadQueries(FILE* input, std::vector<std::string>* output) {
541   char buffer[4096];
542   while (!feof(input) && !ferror(input)) {
543     std::string sql_query;
544     while (fgets(buffer, sizeof(buffer), input)) {
545       std::string line = base::TrimLeading(buffer);
546       if (IsBlankLine(line))
547         break;
548 
549       if (IsCommentLine(line))
550         continue;
551 
552       sql_query.append(line);
553 
554       if (HasEndOfQueryDelimiter(line))
555         break;
556     }
557     if (!sql_query.empty() && sql_query.back() == '\n')
558       sql_query.resize(sql_query.size() - 1);
559 
560     // If we have a new line at the end of the file or an extra new line
561     // somewhere in the file, we'll end up with an empty query which we should
562     // just ignore.
563     if (sql_query.empty())
564       continue;
565 
566     output->push_back(sql_query);
567   }
568   if (ferror(input)) {
569     return util::ErrStatus("Error reading query file");
570   }
571   return util::OkStatus();
572 }
573 
RunQueriesWithoutOutput(const std::vector<std::string> & queries)574 util::Status RunQueriesWithoutOutput(const std::vector<std::string>& queries) {
575   for (const auto& sql_query : queries) {
576     PERFETTO_DLOG("Executing query: %s", sql_query.c_str());
577 
578     auto it = g_tp->ExecuteQuery(sql_query);
579     RETURN_IF_ERROR(it.Status());
580     if (it.Next()) {
581       return util::ErrStatus("Unexpected result from a query.");
582     }
583   }
584   return util::OkStatus();
585 }
586 
RunQueriesAndPrintResult(const std::vector<std::string> & queries,FILE * output)587 util::Status RunQueriesAndPrintResult(const std::vector<std::string>& queries,
588                                       FILE* output) {
589   bool is_first_query = true;
590   bool has_output = false;
591   for (const auto& sql_query : queries) {
592     // Add an extra newline separator between query results.
593     if (!is_first_query)
594       fprintf(output, "\n");
595     is_first_query = false;
596 
597     PERFETTO_ILOG("Executing query: %s", sql_query.c_str());
598 
599     auto it = g_tp->ExecuteQuery(sql_query);
600     RETURN_IF_ERROR(it.Status());
601     if (it.ColumnCount() == 0) {
602       bool it_has_more = it.Next();
603       PERFETTO_DCHECK(!it_has_more);
604       continue;
605     }
606 
607     // If we have a single column with the name |suppress_query_output| that's
608     // a hint to shell that it should not treat the query as having real
609     // meaning.
610     if (it.ColumnCount() == 1 &&
611         it.GetColumnName(0) == "suppress_query_output") {
612       // We should only see a single null value as this feature is usually used
613       // as SELECT RUN_METRIC(<metric file>) as suppress_query_output and
614       // RUN_METRIC returns a single null.
615       bool has_next = it.Next();
616       RETURN_IF_ERROR(it.Status());
617       PERFETTO_DCHECK(has_next);
618       PERFETTO_DCHECK(it.Get(0).is_null());
619 
620       has_next = it.Next();
621       RETURN_IF_ERROR(it.Status());
622       PERFETTO_DCHECK(!has_next);
623       continue;
624     }
625 
626     if (has_output) {
627       return util::ErrStatus(
628           "More than one query generated result rows. This is unsupported.");
629     }
630     has_output = true;
631     RETURN_IF_ERROR(PrintQueryResultAsCsv(&it, output));
632   }
633   return util::OkStatus();
634 }
635 
PrintPerfFile(const std::string & perf_file_path,base::TimeNanos t_load,base::TimeNanos t_run)636 util::Status PrintPerfFile(const std::string& perf_file_path,
637                            base::TimeNanos t_load,
638                            base::TimeNanos t_run) {
639   char buf[128];
640   int count = snprintf(buf, sizeof(buf), "%" PRId64 ",%" PRId64,
641                        static_cast<int64_t>(t_load.count()),
642                        static_cast<int64_t>(t_run.count()));
643   if (count < 0) {
644     return util::ErrStatus("Failed to write perf data");
645   }
646 
647   auto fd(base::OpenFile(perf_file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666));
648   if (!fd) {
649     return util::ErrStatus("Failed to open perf file");
650   }
651   base::WriteAll(fd.get(), buf, static_cast<size_t>(count));
652   return util::OkStatus();
653 }
654 
655 struct CommandLineOptions {
656   std::string perf_file_path;
657   std::string query_file_path;
658   std::string pre_metrics_path;
659   std::string sqlite_file_path;
660   std::string metric_names;
661   std::string metric_output;
662   std::string trace_file_path;
663   std::string port_number;
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 };
670 
PrintUsage(char ** argv)671 void PrintUsage(char** argv) {
672   PERFETTO_ELOG(R"(
673 Interactive trace processor shell.
674 Usage: %s [OPTIONS] trace_file.pb
675 
676 Options:
677  -h, --help                           Prints this guide.
678  -v, --version                        Prints the version of trace processor.
679  -d, --debug                          Enable virtual table debugging.
680  -W, --wide                           Prints interactive output with double
681                                       column width.
682  -p, --perf-file FILE                 Writes the time taken to ingest the trace
683                                       and execute the queries to the given file.
684                                       Only valid with -q or --run-metrics and
685                                       the file will only be written if the
686                                       execution is successful.
687  -q, --query-file FILE                Read and execute an SQL query from a file.
688                                       If used with --run-metrics, the query is
689                                       executed after the selected metrics and
690                                       the metrics output is suppressed.
691  --pre-metrics FILE                   Read and execute an SQL query from a file.
692                                       This query is executed before the selected
693                                       metrics and can't output any results.
694  -D, --httpd                          Enables the HTTP RPC server.
695  --http-port PORT                     Specify what port to run HTTP RPC server.
696  -i, --interactive                    Starts interactive mode even after a query
697                                       file is specified with -q or
698                                       --run-metrics.
699  -e, --export FILE                    Export the contents of trace processor
700                                       into an SQLite database after running any
701                                       metrics or queries specified.
702  --run-metrics x,y,z                  Runs a comma separated list of metrics and
703                                       prints the result as a TraceMetrics proto
704                                       to stdout. The specified can either be
705                                       in-built metrics or SQL/proto files of
706                                       extension metrics.
707  --metrics-output=[binary|text|json]  Allows the output of --run-metrics to be
708                                       specified in either proto binary, proto
709                                       text format or JSON format (default: proto
710                                       text).
711  -m, --metatrace FILE                 Enables metatracing of trace processor
712                                       writing the resulting trace into FILE.
713  --full-sort                          Forces the trace processor into performing
714                                       a full sort ignoring any windowing
715                                       logic.)",
716                 argv[0]);
717 }
718 
ParseCommandLineOptions(int argc,char ** argv)719 CommandLineOptions ParseCommandLineOptions(int argc, char** argv) {
720   CommandLineOptions command_line_options;
721   enum LongOption {
722     OPT_RUN_METRICS = 1000,
723     OPT_PRE_METRICS,
724     OPT_METRICS_OUTPUT,
725     OPT_FORCE_FULL_SORT,
726     OPT_HTTP_PORT,
727   };
728 
729   static const option long_options[] = {
730       {"help", no_argument, nullptr, 'h'},
731       {"version", no_argument, nullptr, 'v'},
732       {"wide", no_argument, nullptr, 'W'},
733       {"httpd", no_argument, nullptr, 'D'},
734       {"interactive", no_argument, nullptr, 'i'},
735       {"debug", no_argument, nullptr, 'd'},
736       {"perf-file", required_argument, nullptr, 'p'},
737       {"query-file", required_argument, nullptr, 'q'},
738       {"export", required_argument, nullptr, 'e'},
739       {"metatrace", required_argument, nullptr, 'm'},
740       {"run-metrics", required_argument, nullptr, OPT_RUN_METRICS},
741       {"pre-metrics", required_argument, nullptr, OPT_PRE_METRICS},
742       {"metrics-output", required_argument, nullptr, OPT_METRICS_OUTPUT},
743       {"full-sort", no_argument, nullptr, OPT_FORCE_FULL_SORT},
744       {"http-port", required_argument, nullptr, OPT_HTTP_PORT},
745       {nullptr, 0, nullptr, 0}};
746 
747   bool explicit_interactive = false;
748   for (;;) {
749     int option =
750         getopt_long(argc, argv, "hvWiDdm:p:q:e:", long_options, nullptr);
751 
752     if (option == -1)
753       break;  // EOF.
754 
755     if (option == 'v') {
756       printf("%s\n", base::GetVersionString());
757       exit(0);
758     }
759 
760     if (option == 'i') {
761       explicit_interactive = true;
762       continue;
763     }
764 
765     if (option == 'D') {
766 #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
767       command_line_options.enable_httpd = true;
768 #else
769       PERFETTO_FATAL("HTTP RPC module not supported in this build");
770 #endif
771       continue;
772     }
773 
774     if (option == 'W') {
775       command_line_options.wide = true;
776       continue;
777     }
778 
779     if (option == 'd') {
780       EnableSQLiteVtableDebugging();
781       continue;
782     }
783 
784     if (option == 'p') {
785       command_line_options.perf_file_path = optarg;
786       continue;
787     }
788 
789     if (option == 'q') {
790       command_line_options.query_file_path = optarg;
791       continue;
792     }
793 
794     if (option == 'e') {
795       command_line_options.sqlite_file_path = optarg;
796       continue;
797     }
798 
799     if (option == 'm') {
800       command_line_options.metatrace_path = optarg;
801       continue;
802     }
803 
804     if (option == OPT_PRE_METRICS) {
805       command_line_options.pre_metrics_path = optarg;
806       continue;
807     }
808 
809     if (option == OPT_RUN_METRICS) {
810       command_line_options.metric_names = optarg;
811       continue;
812     }
813 
814     if (option == OPT_METRICS_OUTPUT) {
815       command_line_options.metric_output = optarg;
816       continue;
817     }
818 
819     if (option == OPT_FORCE_FULL_SORT) {
820       command_line_options.force_full_sort = true;
821       continue;
822     }
823 
824     if (option == OPT_HTTP_PORT) {
825       command_line_options.port_number = optarg;
826       continue;
827     }
828 
829     PrintUsage(argv);
830     exit(option == 'h' ? 0 : 1);
831   }
832 
833   command_line_options.launch_shell =
834       explicit_interactive || (command_line_options.pre_metrics_path.empty() &&
835                                command_line_options.metric_names.empty() &&
836                                command_line_options.query_file_path.empty() &&
837                                command_line_options.sqlite_file_path.empty());
838 
839   // Only allow non-interactive queries to emit perf data.
840   if (!command_line_options.perf_file_path.empty() &&
841       command_line_options.launch_shell) {
842     PrintUsage(argv);
843     exit(1);
844   }
845 
846   // The only case where we allow omitting the trace file path is when running
847   // in --http mode. In all other cases, the last argument must be the trace
848   // file.
849   if (optind == argc - 1 && argv[optind]) {
850     command_line_options.trace_file_path = argv[optind];
851   } else if (!command_line_options.enable_httpd) {
852     PrintUsage(argv);
853     exit(1);
854   }
855   return command_line_options;
856 }
857 
ExtendPoolWithBinaryDescriptor(google::protobuf::DescriptorPool & pool,const void * data,int size)858 void ExtendPoolWithBinaryDescriptor(google::protobuf::DescriptorPool& pool,
859                                     const void* data,
860                                     int size) {
861   google::protobuf::FileDescriptorSet desc_set;
862   desc_set.ParseFromArray(data, size);
863   for (const auto& desc : desc_set.file()) {
864     pool.BuildFile(desc);
865   }
866 }
867 
LoadTrace(const std::string & trace_file_path,double * size_mb)868 util::Status LoadTrace(const std::string& trace_file_path, double* size_mb) {
869   util::Status read_status =
870       ReadTrace(g_tp, trace_file_path.c_str(), [&size_mb](size_t parsed_size) {
871         *size_mb = static_cast<double>(parsed_size) / 1E6;
872         fprintf(stderr, "\rLoading trace: %.2f MB\r", *size_mb);
873       });
874   if (!read_status.ok()) {
875     return util::ErrStatus("Could not read trace file (path: %s): %s",
876                            trace_file_path.c_str(), read_status.c_message());
877   }
878 
879   std::unique_ptr<profiling::Symbolizer> symbolizer =
880       profiling::LocalSymbolizerOrDie(profiling::GetPerfettoBinaryPath(),
881                                       getenv("PERFETTO_SYMBOLIZER_MODE"));
882 
883   if (symbolizer) {
884     profiling::SymbolizeDatabase(
885         g_tp, symbolizer.get(), [](const std::string& trace_proto) {
886           std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
887           memcpy(buf.get(), trace_proto.data(), trace_proto.size());
888           auto status = g_tp->Parse(std::move(buf), trace_proto.size());
889           if (!status.ok()) {
890             PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
891                                     status.message().c_str());
892             return;
893           }
894         });
895     g_tp->NotifyEndOfFile();
896   }
897 
898   auto maybe_map = profiling::GetPerfettoProguardMapPath();
899   if (!maybe_map.empty()) {
900     profiling::ReadProguardMapsToDeobfuscationPackets(
901         maybe_map, [](const std::string& trace_proto) {
902           std::unique_ptr<uint8_t[]> buf(new uint8_t[trace_proto.size()]);
903           memcpy(buf.get(), trace_proto.data(), trace_proto.size());
904           auto status = g_tp->Parse(std::move(buf), trace_proto.size());
905           if (!status.ok()) {
906             PERFETTO_DFATAL_OR_ELOG("Failed to parse: %s",
907                                     status.message().c_str());
908             return;
909           }
910         });
911   }
912   return util::OkStatus();
913 }
914 
RunQueries(const std::string & query_file_path,bool expect_output)915 util::Status RunQueries(const std::string& query_file_path,
916                         bool expect_output) {
917   std::vector<std::string> queries;
918   base::ScopedFstream file(fopen(query_file_path.c_str(), "r"));
919   if (!file) {
920     return util::ErrStatus("Could not open query file (path: %s)",
921                            query_file_path.c_str());
922   }
923   RETURN_IF_ERROR(LoadQueries(file.get(), &queries));
924 
925   util::Status status;
926   if (expect_output) {
927     status = RunQueriesAndPrintResult(queries, stdout);
928   } else {
929     status = RunQueriesWithoutOutput(queries);
930   }
931   if (!status.ok()) {
932     return util::ErrStatus("Encountered error while running queries: %s",
933                            status.c_message());
934   }
935   return util::OkStatus();
936 }
937 
RunMetrics(const CommandLineOptions & options)938 util::Status RunMetrics(const CommandLineOptions& options) {
939   // Descriptor pool used for printing output as textproto.
940   // Building on top of generated pool so default protos in
941   // google.protobuf.descriptor.proto are available.
942   google::protobuf::DescriptorPool pool(
943       google::protobuf::DescriptorPool::generated_pool());
944   ExtendPoolWithBinaryDescriptor(pool, kMetricsDescriptor.data(),
945                                  kMetricsDescriptor.size());
946   ExtendPoolWithBinaryDescriptor(pool, kAllChromeMetricsDescriptor.data(),
947                                  kAllChromeMetricsDescriptor.size());
948 
949   std::vector<std::string> metrics;
950   for (base::StringSplitter ss(options.metric_names, ','); ss.Next();) {
951     metrics.emplace_back(ss.cur_token());
952   }
953 
954   // For all metrics which are files, register them and extend the metrics
955   // proto.
956   for (size_t i = 0; i < metrics.size(); ++i) {
957     const std::string& metric_or_path = metrics[i];
958 
959     // If there is no extension, we assume it is a builtin metric.
960     auto ext_idx = metric_or_path.rfind('.');
961     if (ext_idx == std::string::npos)
962       continue;
963 
964     std::string no_ext_name = metric_or_path.substr(0, ext_idx);
965 
966     // The proto must be extended before registering the metric.
967     util::Status status = ExtendMetricsProto(no_ext_name + ".proto", &pool);
968     if (!status.ok()) {
969       return util::ErrStatus("Unable to extend metrics proto %s: %s",
970                              metric_or_path.c_str(), status.c_message());
971     }
972 
973     status = RegisterMetric(no_ext_name + ".sql");
974     if (!status.ok()) {
975       return util::ErrStatus("Unable to register metric %s: %s",
976                              metric_or_path.c_str(), status.c_message());
977     }
978 
979     metrics[i] = BaseName(no_ext_name);
980   }
981 
982   OutputFormat format;
983   if (!options.query_file_path.empty()) {
984     format = OutputFormat::kNone;
985   } else if (options.metric_output == "binary") {
986     format = OutputFormat::kBinaryProto;
987   } else if (options.metric_output == "json") {
988     format = OutputFormat::kJson;
989   } else {
990     format = OutputFormat::kTextProto;
991   }
992   return RunMetrics(std::move(metrics), format, pool);
993 }
994 
PrintShellUsage()995 void PrintShellUsage() {
996   PERFETTO_ELOG(
997       "Available commands:\n"
998       ".quit, .q    Exit the shell.\n"
999       ".help        This text.\n"
1000       ".dump FILE   Export the trace as a sqlite database.\n"
1001       ".read FILE   Executes the queries in the FILE.\n"
1002       ".reset       Destroys all tables/view created by the user.\n");
1003 }
1004 
StartInteractiveShell(uint32_t column_width)1005 util::Status StartInteractiveShell(uint32_t column_width) {
1006   SetupLineEditor();
1007 
1008   for (;;) {
1009     ScopedLine line = GetLine("> ");
1010     if (!line)
1011       break;
1012     if (strcmp(line.get(), "") == 0) {
1013       printf("If you want to quit either type .q or press CTRL-D (EOF)\n");
1014       continue;
1015     }
1016     if (line.get()[0] == '.') {
1017       char command[32] = {};
1018       char arg[1024] = {};
1019       sscanf(line.get() + 1, "%31s %1023s", command, arg);
1020       if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
1021         break;
1022       } else if (strcmp(command, "help") == 0) {
1023         PrintShellUsage();
1024       } else if (strcmp(command, "dump") == 0 && strlen(arg)) {
1025         if (!ExportTraceToDatabase(arg).ok())
1026           PERFETTO_ELOG("Database export failed");
1027       } else if (strcmp(command, "reset") == 0) {
1028         g_tp->RestoreInitialTables();
1029       } else if (strcmp(command, "read") == 0 && strlen(arg)) {
1030         util::Status status = RunQueries(arg, true);
1031         if (!status.ok()) {
1032           PERFETTO_ELOG("%s", status.c_message());
1033         }
1034       } else {
1035         PrintShellUsage();
1036       }
1037       continue;
1038     }
1039 
1040     base::TimeNanos t_start = base::GetWallTimeNs();
1041     auto it = g_tp->ExecuteQuery(line.get());
1042     PrintQueryResultInteractively(&it, t_start, column_width);
1043   }
1044   return util::OkStatus();
1045 }
1046 
TraceProcessorMain(int argc,char ** argv)1047 util::Status TraceProcessorMain(int argc, char** argv) {
1048   CommandLineOptions options = ParseCommandLineOptions(argc, argv);
1049 
1050   Config config;
1051   config.sorting_mode = options.force_full_sort
1052                             ? SortingMode::kForceFullSort
1053                             : SortingMode::kDefaultHeuristics;
1054 
1055   std::unique_ptr<TraceProcessor> tp = TraceProcessor::CreateInstance(config);
1056   g_tp = tp.get();
1057 
1058   // Enable metatracing as soon as possible.
1059   if (!options.metatrace_path.empty()) {
1060     tp->EnableMetatrace();
1061   }
1062 
1063   base::TimeNanos t_load{};
1064   if (!options.trace_file_path.empty()) {
1065     base::TimeNanos t_load_start = base::GetWallTimeNs();
1066     double size_mb = 0;
1067     RETURN_IF_ERROR(LoadTrace(options.trace_file_path, &size_mb));
1068     t_load = base::GetWallTimeNs() - t_load_start;
1069 
1070     double t_load_s = static_cast<double>(t_load.count()) / 1E9;
1071     PERFETTO_ILOG("Trace loaded: %.2f MB (%.1f MB/s)", size_mb,
1072                   size_mb / t_load_s);
1073 
1074     RETURN_IF_ERROR(PrintStats());
1075   }
1076 
1077 #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD)
1078   if (options.enable_httpd) {
1079     RunHttpRPCServer(std::move(tp), options.port_number);
1080     PERFETTO_FATAL("Should never return");
1081   }
1082 #endif
1083 
1084 #if PERFETTO_HAS_SIGNAL_H()
1085   signal(SIGINT, [](int) { g_tp->InterruptQuery(); });
1086 #endif
1087 
1088   base::TimeNanos t_query_start = base::GetWallTimeNs();
1089   if (!options.pre_metrics_path.empty()) {
1090     RETURN_IF_ERROR(RunQueries(options.pre_metrics_path, false));
1091   }
1092 
1093   if (!options.metric_names.empty()) {
1094     RETURN_IF_ERROR(RunMetrics(options));
1095   }
1096 
1097   if (!options.query_file_path.empty()) {
1098     RETURN_IF_ERROR(RunQueries(options.query_file_path, true));
1099   }
1100   base::TimeNanos t_query = base::GetWallTimeNs() - t_query_start;
1101 
1102   if (!options.sqlite_file_path.empty()) {
1103     RETURN_IF_ERROR(ExportTraceToDatabase(options.sqlite_file_path));
1104   }
1105 
1106   if (options.launch_shell) {
1107     RETURN_IF_ERROR(StartInteractiveShell(options.wide ? 40 : 20));
1108   } else if (!options.perf_file_path.empty()) {
1109     RETURN_IF_ERROR(PrintPerfFile(options.perf_file_path, t_load, t_query));
1110   }
1111 
1112   if (!options.metatrace_path.empty()) {
1113     std::vector<uint8_t> serialized;
1114     util::Status status = g_tp->DisableAndReadMetatrace(&serialized);
1115     if (!status.ok())
1116       return status;
1117 
1118     auto file =
1119         base::OpenFile(options.metatrace_path, O_CREAT | O_RDWR | O_TRUNC);
1120     if (!file)
1121       return util::ErrStatus("Unable to open metatrace file");
1122 
1123     ssize_t res = base::WriteAll(*file, serialized.data(), serialized.size());
1124     if (res < 0)
1125       return util::ErrStatus("Error while writing metatrace file");
1126   }
1127 
1128   return util::OkStatus();
1129 }
1130 
1131 }  // namespace
1132 
1133 }  // namespace trace_processor
1134 }  // namespace perfetto
1135 
main(int argc,char ** argv)1136 int main(int argc, char** argv) {
1137   auto status = perfetto::trace_processor::TraceProcessorMain(argc, argv);
1138   if (!status.ok()) {
1139     PERFETTO_ELOG("%s", status.c_message());
1140     return 1;
1141   }
1142   return 0;
1143 }
1144