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();