tp: improve help text for subcommand CLI (#5152)
Add usage_args() and detailed_help() to Subcommand base class so each
subcommand provides its own positional arg description and detailed
help text. Rework the top-level help to use a clean command table,
common flags table, examples section, and classic interface note.
Fix PrintSubcommandUsage to use per-command usage_args instead of
hardcoded "[SQL]", and add FILE* parameter for output stream control.
Update metrics description to reference summarize --metrics-v2.
diff --git a/src/trace_processor/shell/common_flags.cc b/src/trace_processor/shell/common_flags.cc
index 11d7e59..aef06a6 100644
--- a/src/trace_processor/shell/common_flags.cc
+++ b/src/trace_processor/shell/common_flags.cc
@@ -50,33 +50,42 @@
namespace {
-void PrintFlagList(const std::vector<FlagSpec>& flags) {
+void AppendFlagList(std::string* out, const std::vector<FlagSpec>& flags) {
+ char buf[256];
for (const auto& f : flags) {
if (f.short_name) {
- fprintf(stderr, " -%c, --%-24s %s\n", f.short_name, f.long_name, f.help);
+ snprintf(buf, sizeof(buf), " -%c, --%-24s %s\n", f.short_name,
+ f.long_name, f.help);
} else {
- fprintf(stderr, " --%-24s %s\n", f.long_name, f.help);
+ snprintf(buf, sizeof(buf), " --%-24s %s\n", f.long_name, f.help);
}
+ *out += buf;
}
}
} // namespace
-void PrintSubcommandUsage(const char* argv0, Subcommand* cmd) {
- fprintf(stderr, "Usage: %s %s [FLAGS] <trace_file> [SQL]\n\n", argv0,
- cmd->name());
- fprintf(stderr, "%s\n\n", cmd->description());
+std::string FormatSubcommandUsage(const char* argv0, Subcommand* cmd) {
+ std::string out;
+ base::StackString<256> buf("Usage: %s %s [FLAGS] %s\n\n", argv0, cmd->name(),
+ cmd->usage_args());
+ out += buf.c_str();
+ out += cmd->description();
+ out += "\n\n";
+ out += cmd->detailed_help();
+ out += "\n\n";
auto sub_flags = cmd->GetFlags();
if (!sub_flags.empty()) {
- fprintf(stderr, "Subcommand flags:\n");
- PrintFlagList(sub_flags);
- fprintf(stderr, "\n");
+ out += "Subcommand flags:\n";
+ AppendFlagList(&out, sub_flags);
+ out += "\n";
}
GlobalOptions dummy;
- fprintf(stderr, "Global flags:\n");
- PrintFlagList(GetGlobalFlagSpecs(&dummy));
+ out += "Global flags:\n";
+ AppendFlagList(&out, GetGlobalFlagSpecs(&dummy));
+ return out;
}
std::vector<FlagSpec> GetGlobalFlagSpecs(GlobalOptions* opts) {
@@ -223,7 +232,11 @@
break;
if (opt == '?') {
- return base::ErrStatus("Unknown flag. Use --help for usage.");
+ // getopt_long already printed a diagnostic to stderr; mark the
+ // status so callers know not to print the message again.
+ base::Status s = base::ErrStatus("Unknown flag");
+ s.SetPayload("perfetto.dev/has_printed_error", "1");
+ return s;
}
bool found = false;
diff --git a/src/trace_processor/shell/common_flags.h b/src/trace_processor/shell/common_flags.h
index 1f43ca2..267ce69 100644
--- a/src/trace_processor/shell/common_flags.h
+++ b/src/trace_processor/shell/common_flags.h
@@ -68,8 +68,8 @@
// Returns the FlagSpec entries for all global options.
std::vector<FlagSpec> GetGlobalFlagSpecs(GlobalOptions* opts);
-// Prints usage for a subcommand to stderr.
-void PrintSubcommandUsage(const char* argv0, Subcommand* cmd);
+// Returns the formatted usage string for a subcommand.
+std::string FormatSubcommandUsage(const char* argv0, Subcommand* cmd);
// Parses flags for a subcommand. Combines the subcommand's flags with
// the global flags, then parses argv using getopt_long.
diff --git a/src/trace_processor/shell/export_subcommand.cc b/src/trace_processor/shell/export_subcommand.cc
index a1794c4..a980569 100644
--- a/src/trace_processor/shell/export_subcommand.cc
+++ b/src/trace_processor/shell/export_subcommand.cc
@@ -35,6 +35,18 @@
return "Export trace to a database file.";
}
+const char* ExportSubcommand::usage_args() const {
+ return "<format> -o FILE <trace_file>";
+}
+
+const char* ExportSubcommand::detailed_help() const {
+ return R"(Load a trace and export it to a database file.
+
+Currently the only supported format is "sqlite", which exports all trace
+processor tables to a SQLite database. The format is the first positional
+argument, and -o specifies the output path.)";
+}
+
std::vector<FlagSpec> ExportSubcommand::GetFlags() {
return {
StringFlag("output", 'o', "FILE", "Output file path.", &output_path_),
diff --git a/src/trace_processor/shell/export_subcommand.h b/src/trace_processor/shell/export_subcommand.h
index 93813e8..d1d345b 100644
--- a/src/trace_processor/shell/export_subcommand.h
+++ b/src/trace_processor/shell/export_subcommand.h
@@ -29,6 +29,8 @@
public:
const char* name() const override;
const char* description() const override;
+ const char* usage_args() const override;
+ const char* detailed_help() const override;
std::vector<FlagSpec> GetFlags() override;
base::Status Run(const SubcommandContext& ctx) override;
diff --git a/src/trace_processor/shell/find_subcommand_unittest.cc b/src/trace_processor/shell/find_subcommand_unittest.cc
index 62463b4..cad40ee 100644
--- a/src/trace_processor/shell/find_subcommand_unittest.cc
+++ b/src/trace_processor/shell/find_subcommand_unittest.cc
@@ -31,6 +31,8 @@
explicit FakeSubcommand(const char* n) : name_(n) {}
const char* name() const override { return name_; }
const char* description() const override { return ""; }
+ const char* usage_args() const override { return ""; }
+ const char* detailed_help() const override { return ""; }
std::vector<FlagSpec> GetFlags() override { return {}; }
base::Status Run(const SubcommandContext&) override {
return base::OkStatus();
diff --git a/src/trace_processor/shell/interactive_subcommand.cc b/src/trace_processor/shell/interactive_subcommand.cc
index c7c4c13..2e1f64c 100644
--- a/src/trace_processor/shell/interactive_subcommand.cc
+++ b/src/trace_processor/shell/interactive_subcommand.cc
@@ -35,6 +35,15 @@
return "Interactive SQL shell.";
}
+const char* InteractiveSubcommand::usage_args() const {
+ return "<trace_file>";
+}
+
+const char* InteractiveSubcommand::detailed_help() const {
+ return R"(Open a REPL for running SQL queries interactively against a trace file.
+This is the default when no subcommand is specified.)";
+}
+
std::vector<FlagSpec> InteractiveSubcommand::GetFlags() {
return {
BoolFlag("wide", 'W', "Double column width for output.", &wide_),
diff --git a/src/trace_processor/shell/interactive_subcommand.h b/src/trace_processor/shell/interactive_subcommand.h
index a0e9db3..a5ab707 100644
--- a/src/trace_processor/shell/interactive_subcommand.h
+++ b/src/trace_processor/shell/interactive_subcommand.h
@@ -28,6 +28,8 @@
public:
const char* name() const override;
const char* description() const override;
+ const char* usage_args() const override;
+ const char* detailed_help() const override;
std::vector<FlagSpec> GetFlags() override;
base::Status Run(const SubcommandContext& ctx) override;
diff --git a/src/trace_processor/shell/metrics_subcommand.cc b/src/trace_processor/shell/metrics_subcommand.cc
index e028d14..813bff0 100644
--- a/src/trace_processor/shell/metrics_subcommand.cc
+++ b/src/trace_processor/shell/metrics_subcommand.cc
@@ -37,7 +37,19 @@
}
const char* MetricsSubcommand::description() const {
- return "Run v1 metrics (deprecated).";
+ return "Run v1 metrics (deprecated; use 'summarize --metrics-v2').";
+}
+
+const char* MetricsSubcommand::usage_args() const {
+ return "<trace_file>";
+}
+
+const char* MetricsSubcommand::detailed_help() const {
+ return R"(Run v1 trace processor metrics. This system is deprecated; prefer
+'summarize --metrics-v2' for new workflows.
+
+Metrics are specified by name with --run (comma-separated). Use
+--output to control the format (text proto, binary proto, or JSON).)";
}
std::vector<FlagSpec> MetricsSubcommand::GetFlags() {
diff --git a/src/trace_processor/shell/metrics_subcommand.h b/src/trace_processor/shell/metrics_subcommand.h
index a919b99..7194b31 100644
--- a/src/trace_processor/shell/metrics_subcommand.h
+++ b/src/trace_processor/shell/metrics_subcommand.h
@@ -29,6 +29,8 @@
public:
const char* name() const override;
const char* description() const override;
+ const char* usage_args() const override;
+ const char* detailed_help() const override;
std::vector<FlagSpec> GetFlags() override;
base::Status Run(const SubcommandContext& ctx) override;
diff --git a/src/trace_processor/shell/query_subcommand.cc b/src/trace_processor/shell/query_subcommand.cc
index 97d6ca4..b874aec 100644
--- a/src/trace_processor/shell/query_subcommand.cc
+++ b/src/trace_processor/shell/query_subcommand.cc
@@ -46,6 +46,22 @@
return "Load a trace and run a SQL query.";
}
+const char* QuerySubcommand::usage_args() const {
+ return "<trace_file> [SQL]";
+}
+
+const char* QuerySubcommand::detailed_help() const {
+ return R"(Run one or more SQL queries against a loaded trace file and print results.
+
+SQL can be provided in three ways:
+ 1. Positional argument: tp query trace.pb "SELECT ts FROM slice LIMIT 10"
+ 2. From a file: tp query -f queries.sql trace.pb
+ 3. From stdin: cat q.sql | tp query trace.pb
+
+Multiple semicolon-separated statements are supported. Use -i to drop into
+an interactive shell after the queries complete.)";
+}
+
std::vector<FlagSpec> QuerySubcommand::GetFlags() {
return {
StringFlag("query-file", 'f', "FILE",
diff --git a/src/trace_processor/shell/query_subcommand.h b/src/trace_processor/shell/query_subcommand.h
index d483c9a..f4dffb6 100644
--- a/src/trace_processor/shell/query_subcommand.h
+++ b/src/trace_processor/shell/query_subcommand.h
@@ -29,6 +29,8 @@
public:
const char* name() const override;
const char* description() const override;
+ const char* usage_args() const override;
+ const char* detailed_help() const override;
std::vector<FlagSpec> GetFlags() override;
base::Status Run(const SubcommandContext& ctx) override;
diff --git a/src/trace_processor/shell/server_subcommand.cc b/src/trace_processor/shell/server_subcommand.cc
index 08f84d8..0bcc617 100644
--- a/src/trace_processor/shell/server_subcommand.cc
+++ b/src/trace_processor/shell/server_subcommand.cc
@@ -47,6 +47,23 @@
return "Start an RPC server.";
}
+const char* ServerSubcommand::usage_args() const {
+ return "<mode> [trace_file]";
+}
+
+const char* ServerSubcommand::detailed_help() const {
+ return R"(Start an RPC server for remote trace processor access.
+
+Modes:
+ http Start an HTTP server (default port 9001). This is what the
+ Perfetto UI (ui.perfetto.dev) connects to. Configure with
+ --port and --ip-address.
+ stdio Communicate via stdin/stdout using length-prefixed RPC protocol.
+ Used by tooling that embeds trace processor as a subprocess.
+
+The trace file is optional; in http mode, traces can be loaded remotely.)";
+}
+
std::vector<FlagSpec> ServerSubcommand::GetFlags() {
return {
StringFlag("port", '\0', "PORT", "HTTP port.", &port_number_),
diff --git a/src/trace_processor/shell/server_subcommand.h b/src/trace_processor/shell/server_subcommand.h
index e712d01..1c4795e 100644
--- a/src/trace_processor/shell/server_subcommand.h
+++ b/src/trace_processor/shell/server_subcommand.h
@@ -29,6 +29,8 @@
public:
const char* name() const override;
const char* description() const override;
+ const char* usage_args() const override;
+ const char* detailed_help() const override;
std::vector<FlagSpec> GetFlags() override;
base::Status Run(const SubcommandContext& ctx) override;
diff --git a/src/trace_processor/shell/subcommand.h b/src/trace_processor/shell/subcommand.h
index 77fe456..3941353 100644
--- a/src/trace_processor/shell/subcommand.h
+++ b/src/trace_processor/shell/subcommand.h
@@ -86,6 +86,12 @@
// A short one-line description shown in help output.
virtual const char* description() const = 0;
+ // Positional args shown in usage line, e.g. "<trace_file> [SQL]".
+ virtual const char* usage_args() const = 0;
+
+ // Multi-line detailed help shown in per-subcommand help.
+ virtual const char* detailed_help() const = 0;
+
// Returns the flags this subcommand accepts.
virtual std::vector<FlagSpec> GetFlags() = 0;
diff --git a/src/trace_processor/shell/summarize_subcommand.cc b/src/trace_processor/shell/summarize_subcommand.cc
index d3fb941..903e549 100644
--- a/src/trace_processor/shell/summarize_subcommand.cc
+++ b/src/trace_processor/shell/summarize_subcommand.cc
@@ -43,6 +43,19 @@
return "Run trace summarization.";
}
+const char* SummarizeSubcommand::usage_args() const {
+ return "<trace_file> [spec_file ...]";
+}
+
+const char* SummarizeSubcommand::detailed_help() const {
+ return R"(Compute a trace summary using spec files and/or built-in metrics.
+
+Spec files (textproto or binary proto) define which summary computations
+to run. They are passed as positional arguments after the trace file.
+Use --metrics-v2 to run built-in v2 metrics (pass "all" or comma-separated
+IDs). Output defaults to text proto; use --format binary for binary proto.)";
+}
+
std::vector<FlagSpec> SummarizeSubcommand::GetFlags() {
// Note: --spec is multi-valued but FlagSpec handlers are called per
// occurrence, so we accumulate in a string with comma separation and
diff --git a/src/trace_processor/shell/summarize_subcommand.h b/src/trace_processor/shell/summarize_subcommand.h
index dfb5685..1286e1d 100644
--- a/src/trace_processor/shell/summarize_subcommand.h
+++ b/src/trace_processor/shell/summarize_subcommand.h
@@ -29,6 +29,8 @@
public:
const char* name() const override;
const char* description() const override;
+ const char* usage_args() const override;
+ const char* detailed_help() const override;
std::vector<FlagSpec> GetFlags() override;
base::Status Run(const SubcommandContext& ctx) override;
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 27127c1..64ac912 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -163,54 +163,41 @@
};
void PrintSubcommandHelp(const char* argv0) {
- printf(R"(
-Perfetto Trace Processor.
+ 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.
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').
- query Run SQL queries against a trace.
- %s query -c "SELECT ts, dur FROM slice" trace.pb
- %s query -f query.sql trace.pb
- Flags: -f FILE, -c STRING, -i (interactive after), -W (wide)
+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.
- interactive Interactive SQL shell (default).
- %s trace.pb
- %s interactive trace.pb
- Flags: -W (wide)
+Run '%s help <command>' for per-command flags and details.
- server Start an RPC server.
- %s server http trace.pb
- %s server http --port 9001 trace.pb
- %s server stdio
- Modes: http, stdio
+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.
- summarize Run trace summarization.
- %s summarize --metrics-v2 all --spec spec.textproto trace.pb
- Flags: --spec PATH, --metrics-v2 IDS, --format [text|binary]
-
- metrics Run v1 metrics (deprecated).
- %s metrics --run android_cpu trace.pb
- Flags: --run NAMES, --output [binary|text|json]
-
- export Export trace to a database file.
- %s export sqlite -o out.db trace.pb
- Formats: sqlite
-
-Global flags (apply to all commands):
- --dev, --full-sort, --no-ftrace-raw, --metatrace FILE, ...
- Run '%s help <command>' for full flag details.
-
-Previous versions of trace_processor_shell used a flat flag interface
-(e.g. -q file.sql, --httpd, --summary, -e output.db). This interface
-is fully supported and will remain so permanently. If you have existing
-scripts or are following older documentation that uses these flags, they
-will continue to work exactly as before.
- Run '%s --help-classic' to see the flat flag reference.
+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, argv0, argv0, argv0, argv0, argv0, argv0, argv0,
argv0, argv0, argv0);
}
@@ -994,17 +981,15 @@
// `help <command>` -- find the named subcommand and print its usage.
for (auto* sc : subcommands) {
if (strcmp(sc->name(), argv[i + 1]) == 0) {
- shell::PrintSubcommandUsage(argv[0], sc);
- exit(0);
+ printf("%s", shell::FormatSubcommandUsage(argv[0], sc).c_str());
+ return base::OkStatus();
}
}
- PERFETTO_ELOG("Unknown command '%s'.", argv[i + 1]);
- PrintSubcommandHelp(argv[0]);
- exit(1);
+ return base::ErrStatus("Unknown command '%s'.", argv[i + 1]);
}
// Bare `help` -- same as --help.
PrintSubcommandHelp(argv[0]);
- exit(0);
+ return base::OkStatus();
}
break; // First non-flag, non-help positional -> stop.
}
@@ -1055,9 +1040,23 @@
ctx.platform = platform_interface_.get();
ctx.global = &global;
- RETURN_IF_ERROR(shell::ParseFlags(result.subcommand, &ctx, argc, argv));
+ 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) {
- shell::PrintSubcommandUsage(argv[0], result.subcommand);
+ printf("%s", usage.c_str());
return base::OkStatus();
}
if (global.version) {
@@ -1066,10 +1065,23 @@
protos::pbzero::TRACE_PROCESSOR_CURRENT_API_VERSION);
return base::OkStatus();
}
- return result.subcommand->Run(ctx);
+ {
+ auto run_status = result.subcommand->Run(ctx);
+ if (!run_status.ok()) {
+ return base::ErrStatus("%s\n\n%s", run_status.c_message(),
+ usage.c_str());
+ }
+ return base::OkStatus();
+ }
}
}
+ // No arguments at all: show the subcommand-based help.
+ if (argc == 1) {
+ PrintSubcommandHelp(argv[0]);
+ return base::OkStatus();
+ }
+
CommandLineOptions options = ParseCommandLineOptions(argc, argv);
Config config = platform_interface_->DefaultConfig();