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