| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "perfetto/ext/trace_processor/trace_processor_shell.h" |
| |
| #include <cctype> |
| #include <cerrno> |
| #include <cstddef> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include <google/protobuf/compiler/parser.h> |
| #include <google/protobuf/descriptor.pb.h> |
| #include <google/protobuf/dynamic_message.h> |
| #include <google/protobuf/io/tokenizer.h> |
| #include <google/protobuf/io/zero_copy_stream_impl.h> |
| #include <google/protobuf/text_format.h> |
| |
| #include "perfetto/base/build_config.h" |
| #include "perfetto/base/compiler.h" |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/status.h" |
| #include "perfetto/ext/base/file_utils.h" |
| #include "perfetto/ext/base/getopt.h" // IWYU pragma: keep |
| #include "perfetto/ext/base/status_macros.h" |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/ext/base/version.h" |
| #include "perfetto/trace_processor/basic_types.h" |
| #include "perfetto/trace_processor/metatrace_config.h" |
| #include "perfetto/trace_processor/read_trace.h" |
| #include "perfetto/trace_processor/trace_processor.h" |
| #include "src/trace_processor/read_trace_internal.h" |
| #include "src/trace_processor/rpc/rpc.h" |
| #include "src/trace_processor/rpc/stdiod.h" |
| #include "src/trace_processor/shell/common_flags.h" |
| #include "src/trace_processor/shell/convert_subcommand.h" |
| #include "src/trace_processor/shell/export_subcommand.h" |
| #include "src/trace_processor/shell/interactive_subcommand.h" |
| #include "src/trace_processor/shell/metatrace.h" |
| #include "src/trace_processor/shell/metrics_subcommand.h" |
| #include "src/trace_processor/shell/query_subcommand.h" |
| #include "src/trace_processor/shell/server_subcommand.h" |
| #include "src/trace_processor/shell/subcommand.h" |
| #include "src/trace_processor/shell/summarize_subcommand.h" |
| #include "src/trace_processor/util/symbolizer/symbolize_database.h" |
| |
| #include "protos/perfetto/trace_processor/trace_processor.pbzero.h" |
| |
| #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD) |
| #include "src/trace_processor/rpc/httpd.h" |
| #endif |
| |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ |
| PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \ |
| PERFETTO_BUILDFLAG(PERFETTO_OS_FREEBSD) || \ |
| PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) |
| #define PERFETTO_HAS_SIGNAL_H() 1 |
| #else |
| #define PERFETTO_HAS_SIGNAL_H() 0 |
| #endif |
| |
| #if PERFETTO_HAS_SIGNAL_H() |
| #include <signal.h> |
| #endif |
| |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) |
| #include <io.h> |
| #define ftruncate _chsize |
| #else |
| #include <dirent.h> |
| #include <unistd.h> |
| #endif |
| |
| namespace perfetto::trace_processor { |
| |
| namespace { |
| |
| struct CommandLineOptions { |
| std::string trace_file_path; |
| |
| bool enable_httpd = false; |
| std::string port_number; |
| std::string listen_ip; |
| std::vector<std::string> additional_cors_origins; |
| bool enable_stdiod = false; |
| bool launch_shell = false; |
| |
| bool force_full_sort = false; |
| bool no_ftrace_raw = false; |
| |
| std::string query_file_path; |
| std::string query_string; |
| std::vector<std::string> sql_package_paths; |
| std::vector<std::string> override_sql_package_paths; |
| |
| bool summary = false; |
| std::string summary_metrics_v2; |
| std::string summary_metadata_query; |
| std::vector<std::string> summary_specs; |
| std::string summary_output; |
| |
| std::string metatrace_path; |
| size_t metatrace_buffer_capacity = 0; |
| metatrace::MetatraceCategories metatrace_categories = |
| static_cast<metatrace::MetatraceCategories>( |
| metatrace::MetatraceCategories::QUERY_TIMELINE | |
| metatrace::MetatraceCategories::API_TIMELINE); |
| |
| bool dev = false; |
| std::vector<std::string> dev_flags; |
| bool extra_checks = false; |
| std::string export_file_path; |
| std::string perf_file_path; |
| bool wide = false; |
| bool analyze_trace_proto_content = false; |
| bool crop_track_events = false; |
| std::string register_files_dir; |
| std::string override_stdlib_path; |
| |
| std::string pre_metrics_v1_path; |
| std::string metric_v1_names; |
| std::string metric_v1_output; |
| std::vector<std::string> raw_metric_v1_extensions; |
| }; |
| |
| void PrintSubcommandHelp(const char* argv0) { |
| printf(R"(Perfetto Trace Processor. |
| Usage: %s [command] [flags] [trace_file] |
| |
| If no command is given, opens an interactive SQL shell on the trace file. |
| |
| trace_file can be a local path, an http(s):// URL, or a Perfetto UI share link |
| (https://ui.perfetto.dev/#!/?s=<hash>). |
| |
| Commands: |
| query Load a trace and run a SQL query. |
| interactive Interactive SQL shell (default if no command is given). |
| server Start an RPC server (http or stdio). |
| summarize Compute a trace summary from specs and/or built-in metrics. |
| export Export a trace to a database file. |
| metrics Run v1 metrics (deprecated; use 'summarize --metrics-v2'). |
| convert Convert trace format. |
| |
| Common flags (apply to all commands): |
| -h, --help Show help (per-command if after a command). |
| -v, --version Print version. |
| --full-sort Force full sort ignoring windowing. |
| --no-ftrace-raw Prevent ingestion of typed ftrace into raw table. |
| --add-sql-package PATH Register SQL files from a directory as a package. |
| -m, --metatrace FILE Enable metatracing, write to FILE. |
| |
| Run '%s help <command>' for per-command flags and details. |
| |
| Examples: |
| tp trace.pb Interactive shell. |
| tp query trace.pb "SELECT ts, dur FROM slice" Run a query. |
| tp query -f queries.sql trace.pb Run queries from file. |
| tp server http Start HTTP server. |
| tp summarize --metrics-v2 all trace.pb Summarize a trace. |
| tp convert json trace.pb out.json Convert to JSON. |
| tp query "https://.../trace.pftrace" "SELECT ..." Query a trace from a URL. |
| tp "https://ui.perfetto.dev/#!/?s=<hash>" Open a UI share link. |
| |
| Classic interface: |
| The previous flat-flag interface (-q, --httpd, --summary, -e, etc.) is |
| fully supported and will remain so. Existing scripts will continue to work. |
| Run '%s --help-classic' to see the classic flag reference. |
| )", |
| argv0, argv0, argv0); |
| } |
| |
| void PrintClassicUsage(char** argv) { |
| PERFETTO_ELOG(R"( |
| Interactive trace processor shell. |
| Usage: %s [FLAGS] trace_file.pb |
| |
| General purpose: |
| -h, --help Prints this guide. |
| -v, --version Prints the version of trace processor. |
| |
| Behavioural: |
| -D, --httpd Enables the HTTP RPC server. |
| --http-port PORT Specify what port to run HTTP RPC server. |
| --http-ip-address ip Specify what ip address to run HTTP RPC server. |
| --http-additional-cors-origins origin1,origin2,... |
| Specify a comma-separated list of |
| additional CORS allowed origins for the |
| HTTP RPC server. These are in addition to |
| the default origins: [https://ui.perfetto.dev, |
| http://localhost:10000, http://127.0.0.1:10000] |
| --stdiod Enables the stdio RPC server. |
| -i, --interactive Starts interactive mode even after |
| executing some other commands (-q, -Q, |
| --run-metrics, --summary). |
| |
| Parsing: |
| --full-sort Forces the trace processor into performing |
| a full sort ignoring any windowing |
| logic. |
| --no-ftrace-raw Prevents ingestion of typed ftrace events |
| into the raw table. This significantly |
| reduces the memory usage of trace |
| processor when loading traces containing |
| ftrace events. |
| |
| PerfettoSQL: |
| -q, --query-file FILE Read and execute an SQL query from a file. |
| If used with --run-metrics, the query is |
| executed after the selected metrics and |
| the metrics output is suppressed. |
| -Q, --query-string QUERY Execute the SQL query QUERY. |
| If used with --run-metrics, the query is |
| executed after the selected metrics and |
| the metrics output is suppressed. |
| --add-sql-package PATH[@PACKAGE] Registers SQL files from a directory as |
| a package for use with INCLUDE PERFETTO |
| MODULE statements. |
| |
| By default, the directory name becomes the |
| root package name. Use @PACKAGE to |
| override. |
| |
| Given a directory structure: |
| mydir/ |
| utils.sql |
| helpers/common.sql |
| |
| --add-sql-package ./mydir |
| Registers modules as: |
| mydir.utils |
| mydir.helpers.common |
| Usage: INCLUDE PERFETTO MODULE mydir.utils; |
| |
| --add-sql-package ./mydir@foo |
| Registers modules as: |
| foo.utils |
| foo.helpers.common |
| Usage: INCLUDE PERFETTO MODULE foo.utils; |
| |
| --add-sql-package ./mydir@foo.bar.baz |
| Registers modules as: |
| foo.bar.baz.utils |
| foo.bar.baz.helpers.common |
| Usage: INCLUDE PERFETTO MODULE foo.bar.*; |
| |
| |
| Trace summarization: |
| --summary Enables the trace summarization features of |
| trace processor. Required for any flags |
| starting with --summary-* to be meaningful. |
| --summary-format can be used to control the |
| output format. |
| --summary-metrics-v2 ID1,ID2,ID3 Specifies that the given v2 metrics (as |
| defined by a comma separated set of ids) |
| should be computed and returned as part of |
| the trace summary. The spec for every metric |
| must exist in one of the files passed to |
| --summary-spec. Specify `all` to execute all |
| available v2 metrics. |
| --summary-metadata-query ID Specifies that the given query id should be |
| used to populate the `metadata` field of the |
| trace summary. The spec for the query must |
| exist in one of the files passed to |
| --summary-spec. |
| --summary-spec SUMMARY_PATH Parses the spec at the specified path and |
| makes it available to all summarization |
| operators (--summary-metrics-v2). Spec |
| files must be instances of the |
| perfetto.protos.TraceSummarySpec proto. |
| If the file extension is `.textproto` then |
| the spec file will be parsed as a |
| textproto. If the file extension is `.pb` |
| then it will be parsed as a binary |
| protobuf. Otherwise, heureustics will be |
| used to determine the format. |
| --summary-format [text,binary] Controls the serialization format of trace |
| summarization proto |
| (perfetto.protos.TraceSummary). If |
| `binary`, then the output is a binary |
| protobuf. If unspecified or `text` then |
| the output is a textproto. |
| |
| Metatracing: |
| -m, --metatrace FILE Enables metatracing of trace processor |
| writing the resulting trace into FILE. |
| --metatrace-buffer-capacity N Sets metatrace event buffer to capture |
| last N events. |
| --metatrace-categories CATEGORIES A comma-separated list of metatrace |
| categories to enable. |
| |
| Advanced: |
| --dev Enables features which are reserved for |
| local development use only and |
| *should not* be enabled on production |
| builds. The features behind this flag can |
| break at any time without any warning. |
| --dev-flag KEY=VALUE Set a development flag to the given value. |
| Does not have any affect unless --dev is |
| specified. |
| --extra-checks Enables additional checks which can catch |
| more SQL errors, but which incur |
| additional runtime overhead. |
| -e, --export FILE Export the contents of trace processor |
| into an SQLite database after running any |
| metrics or queries specified. |
| -p, --perf-file FILE Writes the time taken to ingest the trace |
| and execute the queries to the given file. |
| Only valid with -q or --run-metrics and |
| the file will only be written if the |
| execution is successful. |
| -W, --wide Prints interactive output with double |
| column width. |
| --analyze-trace-proto-content Enables trace proto content analysis in |
| trace processor. |
| --crop-track-events Ignores track event outside of the |
| range of interest in trace processor. |
| --register-files-dir PATH The contents of all files in this |
| directory and subdirectories will be made |
| available to the trace processor runtime. |
| Some importers can use this data to |
| augment trace data (e.g. decode ETM |
| instruction streams). |
| --override-stdlib=[path_to_stdlib] Will override trace_processor/stdlib with |
| passed contents. The outer directory will |
| be ignored. Only allowed when --dev is |
| specified. |
| --override-sql-package PATH[@PKG] Same as --add-sql-package but allows |
| overriding existing user-registered |
| packages with the same name. This bypasses |
| checks trace processor makes around |
| packages already existing and clashing |
| with stdlib package names so should be |
| used with caution. |
| |
| Structured queries: |
| --structured-query-spec SPEC_PATH Parses the spec at the specified path and |
| makes queries available for execution. |
| Spec files must be instances of the |
| perfetto.protos.TraceSummarySpec proto. |
| If the file extension is `.textproto` then |
| the spec file will be parsed as a |
| textproto. If the file extension is `.pb` |
| then it will be parsed as a binary |
| protobuf. Otherwise, heuristics will be |
| used to determine the format. |
| --structured-query-id ID Specifies that the structured query with |
| the given ID should be executed. The spec |
| for the query must exist in one of the |
| files passed to --structured-query-spec. |
| |
| Metrics (v1): |
| |
| NOTE: the trace-based metrics system has been "soft" deprecated. Specifically, |
| all existing metrics will continue functioning but we will not be building |
| any new features nor developing any metrics there further. Please use the |
| metrics v2 system as part of trace summarization. |
| |
| --run-metrics x,y,z Runs a comma separated list of metrics and |
| prints the result as a TraceMetrics proto |
| to stdout. The specified can either be |
| in-built metrics or SQL/proto files of |
| extension metrics. |
| --pre-metrics FILE Read and execute an SQL query from a file. |
| This query is executed before the selected |
| metrics and can't output any results. |
| --metrics-output=[binary|text|json] Allows the output of --run-metrics to be |
| specified in either proto binary, proto |
| text format or JSON format (default: proto |
| text). |
| --metric-extension DISK_PATH@VIRTUAL_PATH |
| Loads metric proto and sql files from |
| DISK_PATH/protos and DISK_PATH/sql |
| respectively, and mounts them onto |
| VIRTUAL_PATH. |
| )", |
| argv[0]); |
| } |
| |
| enum LongOption { |
| OPT_HTTP_PORT = 1000, |
| OPT_HTTP_IP, |
| OPT_HTTP_ADDITIONAL_CORS_ORIGINS, |
| OPT_STDIOD, |
| |
| OPT_FORCE_FULL_SORT, |
| OPT_NO_FTRACE_RAW, |
| |
| OPT_ADD_SQL_PACKAGE, |
| OPT_OVERRIDE_SQL_PACKAGE, |
| OPT_STRUCTURED_QUERY_SPEC, |
| OPT_STRUCTURED_QUERY_ID, |
| |
| OPT_SUMMARY, |
| OPT_SUMMARY_METRICS_V2, |
| OPT_SUMMARY_METADATA_QUERY, |
| OPT_SUMMARY_SPEC, |
| OPT_SUMMARY_FORMAT, |
| |
| OPT_METATRACE_BUFFER_CAPACITY, |
| OPT_METATRACE_CATEGORIES, |
| |
| OPT_DEV, |
| OPT_DEV_FLAG, |
| OPT_EXTRA_CHECKS, |
| OPT_ANALYZE_TRACE_PROTO_CONTENT, |
| OPT_CROP_TRACK_EVENTS, |
| OPT_REGISTER_FILES_DIR, |
| OPT_OVERRIDE_STDLIB, |
| |
| OPT_RUN_METRICS, |
| OPT_PRE_METRICS, |
| OPT_METRICS_OUTPUT, |
| OPT_METRIC_EXTENSION, |
| |
| OPT_HELP_CLASSIC, |
| }; |
| |
| constexpr char kShortOptions[] = "hvWiDdm:p:q:Q:e:"; |
| |
| const option kLongOptions[] = { |
| {"help", no_argument, nullptr, 'h'}, |
| {"help-classic", no_argument, nullptr, OPT_HELP_CLASSIC}, |
| {"version", no_argument, nullptr, 'v'}, |
| |
| {"httpd", no_argument, nullptr, 'D'}, |
| {"http-port", required_argument, nullptr, OPT_HTTP_PORT}, |
| {"http-ip-address", required_argument, nullptr, OPT_HTTP_IP}, |
| {"http-additional-cors-origins", required_argument, nullptr, |
| OPT_HTTP_ADDITIONAL_CORS_ORIGINS}, |
| {"stdiod", no_argument, nullptr, OPT_STDIOD}, |
| {"interactive", no_argument, nullptr, 'i'}, |
| |
| {"full-sort", no_argument, nullptr, OPT_FORCE_FULL_SORT}, |
| {"no-ftrace-raw", no_argument, nullptr, OPT_NO_FTRACE_RAW}, |
| |
| {"query-file", required_argument, nullptr, 'q'}, |
| {"query-string", required_argument, nullptr, 'Q'}, |
| {"add-sql-package", required_argument, nullptr, OPT_ADD_SQL_PACKAGE}, |
| {"override-sql-package", required_argument, nullptr, |
| OPT_OVERRIDE_SQL_PACKAGE}, |
| |
| {"summary", no_argument, nullptr, OPT_SUMMARY}, |
| {"summary-metrics-v2", required_argument, nullptr, OPT_SUMMARY_METRICS_V2}, |
| {"summary-metadata-query", required_argument, nullptr, |
| OPT_SUMMARY_METADATA_QUERY}, |
| {"summary-spec", required_argument, nullptr, OPT_SUMMARY_SPEC}, |
| {"summary-format", required_argument, nullptr, OPT_SUMMARY_FORMAT}, |
| |
| {"metatrace", required_argument, nullptr, 'm'}, |
| {"metatrace-buffer-capacity", required_argument, nullptr, |
| OPT_METATRACE_BUFFER_CAPACITY}, |
| {"metatrace-categories", required_argument, nullptr, |
| OPT_METATRACE_CATEGORIES}, |
| |
| {"dev", no_argument, nullptr, OPT_DEV}, |
| {"dev-flag", required_argument, nullptr, OPT_DEV_FLAG}, |
| {"extra-checks", no_argument, nullptr, OPT_EXTRA_CHECKS}, |
| {"export", required_argument, nullptr, 'e'}, |
| {"perf-file", required_argument, nullptr, 'p'}, |
| {"wide", no_argument, nullptr, 'W'}, |
| {"analyze-trace-proto-content", no_argument, nullptr, |
| OPT_ANALYZE_TRACE_PROTO_CONTENT}, |
| {"crop-track-events", no_argument, nullptr, OPT_CROP_TRACK_EVENTS}, |
| {"register-files-dir", required_argument, nullptr, OPT_REGISTER_FILES_DIR}, |
| {"override-stdlib", required_argument, nullptr, OPT_OVERRIDE_STDLIB}, |
| |
| {"run-metrics", required_argument, nullptr, OPT_RUN_METRICS}, |
| {"pre-metrics", required_argument, nullptr, OPT_PRE_METRICS}, |
| {"metrics-output", required_argument, nullptr, OPT_METRICS_OUTPUT}, |
| {"metric-extension", required_argument, nullptr, OPT_METRIC_EXTENSION}, |
| |
| {nullptr, 0, nullptr, 0}}; |
| |
| CommandLineOptions ParseCommandLineOptions(int argc, char** argv) { |
| CommandLineOptions command_line_options; |
| |
| bool explicit_interactive = false; |
| for (;;) { |
| int option = getopt_long(argc, argv, kShortOptions, kLongOptions, nullptr); |
| |
| if (option == -1) |
| break; // EOF. |
| |
| if (option == 'v') { |
| printf("%s\n", base::GetVersionString()); |
| printf("Trace Processor RPC API version: %d\n", |
| protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION); |
| exit(0); |
| } |
| |
| if (option == 'W') { |
| command_line_options.wide = true; |
| continue; |
| } |
| |
| if (option == 'p') { |
| command_line_options.perf_file_path = optarg; |
| continue; |
| } |
| |
| if (option == 'q') { |
| command_line_options.query_file_path = optarg; |
| continue; |
| } |
| |
| if (option == 'Q') { |
| command_line_options.query_string = optarg; |
| continue; |
| } |
| |
| if (option == 'D') { |
| #if PERFETTO_BUILDFLAG(PERFETTO_TP_HTTPD) |
| command_line_options.enable_httpd = true; |
| #else |
| PERFETTO_FATAL("HTTP RPC module not supported in this build"); |
| #endif |
| continue; |
| } |
| |
| if (option == OPT_HTTP_PORT) { |
| command_line_options.port_number = optarg; |
| continue; |
| } |
| |
| if (option == OPT_HTTP_IP) { |
| command_line_options.listen_ip = optarg; |
| continue; |
| } |
| |
| if (option == OPT_HTTP_ADDITIONAL_CORS_ORIGINS) { |
| command_line_options.additional_cors_origins = |
| base::SplitString(optarg, ","); |
| continue; |
| } |
| |
| if (option == OPT_STDIOD) { |
| command_line_options.enable_stdiod = true; |
| continue; |
| } |
| |
| if (option == 'i') { |
| explicit_interactive = true; |
| continue; |
| } |
| |
| if (option == 'e') { |
| command_line_options.export_file_path = optarg; |
| continue; |
| } |
| |
| if (option == 'm') { |
| command_line_options.metatrace_path = optarg; |
| continue; |
| } |
| |
| if (option == OPT_METATRACE_BUFFER_CAPACITY) { |
| command_line_options.metatrace_buffer_capacity = |
| static_cast<size_t>(atoi(optarg)); |
| continue; |
| } |
| |
| if (option == OPT_METATRACE_CATEGORIES) { |
| command_line_options.metatrace_categories = |
| ParseMetatraceCategories(optarg); |
| continue; |
| } |
| |
| if (option == OPT_FORCE_FULL_SORT) { |
| command_line_options.force_full_sort = true; |
| continue; |
| } |
| |
| if (option == OPT_NO_FTRACE_RAW) { |
| command_line_options.no_ftrace_raw = true; |
| continue; |
| } |
| |
| if (option == OPT_ANALYZE_TRACE_PROTO_CONTENT) { |
| command_line_options.analyze_trace_proto_content = true; |
| continue; |
| } |
| |
| if (option == OPT_CROP_TRACK_EVENTS) { |
| command_line_options.crop_track_events = true; |
| continue; |
| } |
| |
| if (option == OPT_DEV) { |
| command_line_options.dev = true; |
| continue; |
| } |
| |
| if (option == OPT_EXTRA_CHECKS) { |
| command_line_options.extra_checks = true; |
| continue; |
| } |
| |
| if (option == OPT_ADD_SQL_PACKAGE) { |
| command_line_options.sql_package_paths.emplace_back(optarg); |
| continue; |
| } |
| |
| if (option == OPT_OVERRIDE_SQL_PACKAGE) { |
| command_line_options.override_sql_package_paths.emplace_back(optarg); |
| continue; |
| } |
| |
| if (option == OPT_OVERRIDE_STDLIB) { |
| command_line_options.override_stdlib_path = optarg; |
| continue; |
| } |
| |
| if (option == OPT_RUN_METRICS) { |
| command_line_options.metric_v1_names = optarg; |
| continue; |
| } |
| |
| if (option == OPT_PRE_METRICS) { |
| command_line_options.pre_metrics_v1_path = optarg; |
| continue; |
| } |
| |
| if (option == OPT_METRICS_OUTPUT) { |
| command_line_options.metric_v1_output = optarg; |
| continue; |
| } |
| |
| if (option == OPT_METRIC_EXTENSION) { |
| command_line_options.raw_metric_v1_extensions.emplace_back(optarg); |
| continue; |
| } |
| |
| if (option == OPT_DEV_FLAG) { |
| command_line_options.dev_flags.emplace_back(optarg); |
| continue; |
| } |
| |
| if (option == OPT_REGISTER_FILES_DIR) { |
| command_line_options.register_files_dir = optarg; |
| continue; |
| } |
| |
| if (option == OPT_SUMMARY) { |
| command_line_options.summary = true; |
| continue; |
| } |
| |
| if (option == OPT_SUMMARY_METRICS_V2) { |
| command_line_options.summary_metrics_v2 = optarg; |
| continue; |
| } |
| |
| if (option == OPT_SUMMARY_METADATA_QUERY) { |
| command_line_options.summary_metadata_query = optarg; |
| continue; |
| } |
| |
| if (option == OPT_SUMMARY_SPEC) { |
| command_line_options.summary_specs.emplace_back(optarg); |
| continue; |
| } |
| |
| if (option == OPT_SUMMARY_FORMAT) { |
| command_line_options.summary_output = optarg; |
| continue; |
| } |
| |
| if (option == OPT_HELP_CLASSIC) { |
| PrintClassicUsage(argv); |
| exit(0); |
| } |
| if (option == 'h') { |
| PrintSubcommandHelp(argv[0]); |
| exit(0); |
| } |
| PrintClassicUsage(argv); |
| exit(1); |
| } |
| |
| command_line_options.launch_shell = |
| explicit_interactive || (command_line_options.metric_v1_names.empty() && |
| command_line_options.query_file_path.empty() && |
| command_line_options.query_string.empty() && |
| command_line_options.export_file_path.empty() && |
| !command_line_options.summary); |
| |
| // Only allow non-interactive queries to emit perf data. |
| if (!command_line_options.perf_file_path.empty() && |
| command_line_options.launch_shell) { |
| PrintClassicUsage(argv); |
| exit(1); |
| } |
| |
| if (command_line_options.summary && |
| !command_line_options.metric_v1_names.empty()) { |
| PERFETTO_ELOG("Cannot specify both metrics v1 and trace summarization"); |
| exit(1); |
| } |
| |
| // The only case where we allow omitting the trace file path is when running |
| // in --httpd or --stdiod mode. In all other cases, the last argument must be |
| // the trace file. |
| if (optind == argc - 1 && argv[optind]) { |
| command_line_options.trace_file_path = argv[optind]; |
| } else if (!command_line_options.enable_httpd && |
| !command_line_options.enable_stdiod) { |
| PrintClassicUsage(argv); |
| exit(1); |
| } |
| |
| return command_line_options; |
| } |
| |
| class DefaultPlatformInterface : public TraceProcessorShell::PlatformInterface { |
| public: |
| ~DefaultPlatformInterface() override; |
| |
| Config DefaultConfig() const override { return {}; } |
| |
| base::Status OnTraceProcessorCreated(TraceProcessor*) override { |
| return base::OkStatus(); |
| } |
| |
| base::Status LoadTrace( |
| TraceProcessor* trace_processor, |
| const std::string& path, |
| std::function<void(size_t)> progress_callback) override { |
| // The shell opts into loading traces directly from http(s) URLs and from |
| // Perfetto UI share links; the user opts in by passing such a path. Bytes |
| // downloaded this way are cached on local disk so re-running on the same |
| // URL doesn't re-download. |
| ReadTraceArgs args; |
| args.allow_http = true; |
| args.allow_perfetto_ui_links = true; |
| args.cache_downloads = true; |
| return ReadTraceUnfinalized(trace_processor, path.c_str(), |
| progress_callback, args); |
| } |
| }; |
| |
| DefaultPlatformInterface::~DefaultPlatformInterface() = default; |
| |
| } // namespace |
| |
| TraceProcessorShell::TraceProcessorShell( |
| std::unique_ptr<PlatformInterface> platform_interface) |
| : platform_interface_(std::move(platform_interface)) {} |
| |
| std::unique_ptr<TraceProcessorShell> TraceProcessorShell::Create( |
| std::unique_ptr<PlatformInterface> platform_interface) { |
| return std::unique_ptr<TraceProcessorShell>( |
| new TraceProcessorShell(std::move(platform_interface))); |
| } |
| |
| std::unique_ptr<TraceProcessorShell> |
| TraceProcessorShell::CreateWithDefaultPlatform() { |
| return std::unique_ptr<TraceProcessorShell>( |
| new TraceProcessorShell(std::make_unique<DefaultPlatformInterface>())); |
| } |
| |
| base::Status TraceProcessorShell::Run(int argc, char** argv) { |
| // Subcommand dispatch: if a positional argument matches a known subcommand |
| // name, route to it. Otherwise fall through to classic path. |
| { |
| shell::QuerySubcommand query_subcommand; |
| shell::InteractiveSubcommand interactive_subcommand; |
| shell::ServerSubcommand server_subcommand; |
| shell::SummarizeSubcommand summarize_subcommand; |
| shell::ExportSubcommand export_subcommand; |
| shell::MetricsSubcommand metrics_subcommand; |
| shell::ConvertSubcommand convert_subcommand; |
| std::vector<shell::Subcommand*> subcommands = { |
| &query_subcommand, &interactive_subcommand, &server_subcommand, |
| &summarize_subcommand, &export_subcommand, &metrics_subcommand, |
| &convert_subcommand, |
| }; |
| |
| // Handle "help" pseudo-subcommand: `tp help <command>` or bare `tp help`. |
| for (int i = 1; i < argc; ++i) { |
| if (argv[i][0] == '-') |
| continue; |
| if (strcmp(argv[i], "help") == 0) { |
| if (i + 1 < argc) { |
| // `help <command>` -- find the named subcommand and print its usage. |
| for (auto* sc : subcommands) { |
| if (strcmp(sc->name(), argv[i + 1]) == 0) { |
| printf("%s", shell::FormatSubcommandUsage(argv[0], sc).c_str()); |
| return base::OkStatus(); |
| } |
| } |
| return base::ErrStatus("Unknown command '%s'.", argv[i + 1]); |
| } |
| // Bare `help` -- same as --help. |
| PrintSubcommandHelp(argv[0]); |
| return base::OkStatus(); |
| } |
| break; // First non-flag, non-help positional -> stop. |
| } |
| |
| // Build the set of flags that consume an argument, derived from the |
| // classic kLongOptions array, kShortOptions, and subcommand FlagSpecs. |
| std::unordered_set<std::string> flags_with_arg; |
| for (const auto* o = kLongOptions; o->name; ++o) { |
| if (o->has_arg == required_argument) |
| flags_with_arg.insert("--" + std::string(o->name)); |
| } |
| for (const char* p = kShortOptions; *p; ++p) { |
| if (*(p + 1) == ':') { |
| flags_with_arg.insert(std::string("-") + *p); |
| ++p; // skip ':' |
| } |
| } |
| for (auto* sc : subcommands) { |
| for (const auto& f : sc->GetFlags()) { |
| if (f.has_arg) { |
| flags_with_arg.insert("--" + std::string(f.long_name)); |
| if (f.short_name) |
| flags_with_arg.insert(std::string(1, '-') + f.short_name); |
| } |
| } |
| } |
| |
| auto result = |
| shell::FindSubcommandInArgs(argc, argv, subcommands, flags_with_arg); |
| if (result.subcommand) { |
| // If the matched word is also a file on disk, it's ambiguous -- the user |
| // might have meant it as a trace file. Emit a hint. |
| if (base::FileExists(argv[result.argv_index])) { |
| PERFETTO_ELOG( |
| "Note: '%s' matches both a subcommand and a file on disk. " |
| "Interpreting as subcommand. To use it as a trace file, " |
| "use './%s'.", |
| argv[result.argv_index], argv[result.argv_index]); |
| } |
| |
| // Remove the subcommand name from argv. |
| for (int i = result.argv_index; i < argc - 1; ++i) |
| argv[i] = argv[i + 1]; |
| argc--; |
| |
| shell::GlobalOptions global; |
| shell::SubcommandContext ctx; |
| ctx.platform = platform_interface_.get(); |
| ctx.global = &global; |
| |
| auto usage = shell::FormatSubcommandUsage(argv[0], result.subcommand); |
| { |
| auto parse_status = |
| shell::ParseFlags(result.subcommand, &ctx, argc, argv); |
| if (!parse_status.ok()) { |
| bool already_printed = |
| parse_status.GetPayload("perfetto.dev/has_printed_error") |
| .has_value(); |
| if (already_printed) { |
| return base::ErrStatus("\n%s", usage.c_str()); |
| } |
| return base::ErrStatus("%s\n\n%s", parse_status.c_message(), |
| usage.c_str()); |
| } |
| } |
| if (global.help) { |
| printf("%s", usage.c_str()); |
| return base::OkStatus(); |
| } |
| if (global.version) { |
| printf("%s\n", base::GetVersionString()); |
| printf("Trace Processor RPC API version: %d\n", |
| protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION); |
| return base::OkStatus(); |
| } |
| |
| // Parse metric extensions and populate their descriptor pool. The |
| // pool is always created (built-in metrics need it for output |
| // formatting); extensions just add to it. |
| RETURN_IF_ERROR(ParseMetricExtensionPaths(global.dev, |
| global.raw_metric_v1_extensions, |
| global.metric_extensions)); |
| global.metric_descriptor_pool = |
| std::make_unique<google::protobuf::DescriptorPool>( |
| google::protobuf::DescriptorPool::generated_pool()); |
| RETURN_IF_ERROR(PopulateDescriptorPool(*global.metric_descriptor_pool, |
| global.metric_extensions)); |
| |
| return result.subcommand->Run(ctx); |
| } |
| } |
| |
| // No arguments at all: show the subcommand-based help. |
| if (argc == 1) { |
| PrintSubcommandHelp(argv[0]); |
| return base::OkStatus(); |
| } |
| |
| // Classic flag path: translate classic flags into a subcommand invocation |
| // and re-dispatch through the subcommand machinery above. |
| CommandLineOptions options = ParseCommandLineOptions(argc, argv); |
| |
| // Build a synthetic argv for the target subcommand. |
| std::vector<std::string> args; |
| args.emplace_back(argv[0]); |
| |
| // Forward global flags. |
| auto add_global_flags = [&]() { |
| if (options.force_full_sort) |
| args.emplace_back("--full-sort"); |
| if (options.no_ftrace_raw) |
| args.emplace_back("--no-ftrace-raw"); |
| if (options.analyze_trace_proto_content) |
| args.emplace_back("--analyze-trace-proto-content"); |
| if (options.crop_track_events) |
| args.emplace_back("--crop-track-events"); |
| if (options.dev) |
| args.emplace_back("--dev"); |
| for (const auto& f : options.dev_flags) { |
| args.emplace_back("--dev-flag"); |
| args.emplace_back(f); |
| } |
| if (options.extra_checks) |
| args.emplace_back("--extra-checks"); |
| for (const auto& p : options.sql_package_paths) { |
| args.emplace_back("--add-sql-package"); |
| args.emplace_back(p); |
| } |
| for (const auto& p : options.override_sql_package_paths) { |
| args.emplace_back("--override-sql-package"); |
| args.emplace_back(p); |
| } |
| if (!options.override_stdlib_path.empty()) { |
| args.emplace_back("--override-stdlib"); |
| args.emplace_back(options.override_stdlib_path); |
| } |
| if (!options.register_files_dir.empty()) { |
| args.emplace_back("--register-files-dir"); |
| args.emplace_back(options.register_files_dir); |
| } |
| for (const auto& e : options.raw_metric_v1_extensions) { |
| args.emplace_back("--metric-extension"); |
| args.emplace_back(e); |
| } |
| if (!options.metatrace_path.empty()) { |
| args.emplace_back("--metatrace"); |
| args.emplace_back(options.metatrace_path); |
| } |
| if (options.metatrace_buffer_capacity > 0) { |
| args.emplace_back("--metatrace-buffer-capacity"); |
| args.emplace_back(std::to_string(options.metatrace_buffer_capacity)); |
| } |
| }; |
| |
| // Determine which subcommand to dispatch to. |
| // |
| // For subcommands that themselves consume a positional mode argument |
| // (e.g. "server http", "server stdio", "export sqlite"), emit all flags |
| // *before* the mode positional. Our Windows getopt_compat implementation |
| // does not GNU-style permute argv, so any flag appearing after a positional |
| // would be left as a positional, which would then be misinterpreted as the |
| // trace file by the subcommand. |
| if (options.enable_httpd) { |
| args.emplace_back("server"); |
| if (!options.port_number.empty()) { |
| args.emplace_back("--port"); |
| args.emplace_back(options.port_number); |
| } |
| if (!options.listen_ip.empty()) { |
| args.emplace_back("--ip-address"); |
| args.emplace_back(options.listen_ip); |
| } |
| for (const auto& o : options.additional_cors_origins) { |
| args.emplace_back("--additional-cors-origins"); |
| args.emplace_back(o); |
| } |
| add_global_flags(); |
| args.emplace_back("http"); |
| if (!options.trace_file_path.empty()) |
| args.emplace_back(options.trace_file_path); |
| } else if (options.enable_stdiod) { |
| args.emplace_back("server"); |
| add_global_flags(); |
| args.emplace_back("stdio"); |
| if (!options.trace_file_path.empty()) |
| args.emplace_back(options.trace_file_path); |
| } else if (options.summary) { |
| args.emplace_back("summarize"); |
| if (!options.summary_metrics_v2.empty()) { |
| args.emplace_back("--metrics-v2"); |
| args.emplace_back(options.summary_metrics_v2); |
| } |
| if (!options.summary_metadata_query.empty()) { |
| args.emplace_back("--metadata-query"); |
| args.emplace_back(options.summary_metadata_query); |
| } |
| if (!options.summary_output.empty()) { |
| args.emplace_back("--format"); |
| args.emplace_back(options.summary_output); |
| } |
| if (!options.query_file_path.empty()) { |
| args.emplace_back("--post-query"); |
| args.emplace_back(options.query_file_path); |
| } |
| if (!options.perf_file_path.empty()) { |
| args.emplace_back("--perf-file"); |
| args.emplace_back(options.perf_file_path); |
| } |
| if (options.launch_shell) |
| args.emplace_back("-i"); |
| add_global_flags(); |
| if (!options.trace_file_path.empty()) |
| args.emplace_back(options.trace_file_path); |
| for (const auto& s : options.summary_specs) |
| args.emplace_back(s); |
| } else if (!options.metric_v1_names.empty()) { |
| args.emplace_back("metrics"); |
| args.emplace_back("--run"); |
| args.emplace_back(options.metric_v1_names); |
| if (!options.pre_metrics_v1_path.empty()) { |
| args.emplace_back("--pre"); |
| args.emplace_back(options.pre_metrics_v1_path); |
| } |
| if (!options.metric_v1_output.empty()) { |
| args.emplace_back("--output"); |
| args.emplace_back(options.metric_v1_output); |
| } |
| if (!options.query_file_path.empty()) { |
| args.emplace_back("--post-query"); |
| args.emplace_back(options.query_file_path); |
| } |
| if (!options.perf_file_path.empty()) { |
| args.emplace_back("--perf-file"); |
| args.emplace_back(options.perf_file_path); |
| } |
| if (options.launch_shell) |
| args.emplace_back("-i"); |
| add_global_flags(); |
| if (!options.trace_file_path.empty()) |
| args.emplace_back(options.trace_file_path); |
| } else if (!options.export_file_path.empty()) { |
| if (!options.query_file_path.empty()) { |
| return base::ErrStatus( |
| "Cannot combine -e (export) with -q (query). " |
| "Use the 'export' subcommand directly."); |
| } |
| args.emplace_back("export"); |
| args.emplace_back("-o"); |
| args.emplace_back(options.export_file_path); |
| add_global_flags(); |
| args.emplace_back("sqlite"); |
| if (!options.trace_file_path.empty()) |
| args.emplace_back(options.trace_file_path); |
| } else if (!options.query_file_path.empty() || |
| !options.query_string.empty()) { |
| args.emplace_back("query"); |
| if (!options.query_file_path.empty()) { |
| args.emplace_back("-f"); |
| args.emplace_back(options.query_file_path); |
| } |
| if (options.launch_shell) |
| args.emplace_back("-i"); |
| if (options.wide) |
| args.emplace_back("-W"); |
| if (!options.perf_file_path.empty()) { |
| args.emplace_back("--perf-file"); |
| args.emplace_back(options.perf_file_path); |
| } |
| add_global_flags(); |
| if (!options.trace_file_path.empty()) |
| args.emplace_back(options.trace_file_path); |
| if (!options.query_string.empty()) |
| args.emplace_back(options.query_string); |
| } else { |
| // Default: interactive shell. |
| args.emplace_back("interactive"); |
| if (options.wide) |
| args.emplace_back("-W"); |
| add_global_flags(); |
| if (!options.trace_file_path.empty()) |
| args.emplace_back(options.trace_file_path); |
| } |
| |
| // Convert to argc/argv and re-enter Run() which will match the subcommand. |
| std::vector<char*> new_argv; |
| new_argv.reserve(args.size()); |
| for (auto& a : args) |
| new_argv.emplace_back(a.data()); |
| return Run(static_cast<int>(new_argv.size()), new_argv.data()); |
| } |
| |
| TraceProcessorShell_PlatformInterface:: |
| ~TraceProcessorShell_PlatformInterface() = default; |
| |
| int PERFETTO_EXPORT_ENTRYPOINT TraceProcessorShellMain(int argc, char** argv) { |
| auto shell = TraceProcessorShell::CreateWithDefaultPlatform(); |
| auto status = shell->Run(argc, argv); |
| if (!status.ok()) { |
| fprintf(stderr, "%s\n", status.c_message()); |
| return 1; |
| } |
| return 0; |
| } |
| |
| } // namespace perfetto::trace_processor |