1616
1717#include " src/trace_processor/shell/query_subcommand.h"
1818
19+ #include < algorithm>
20+ #include < cstdint>
21+ #include < memory>
1922#include < string>
23+ #include < string_view>
2024#include < vector>
2125
2226#include " perfetto/base/build_config.h"
2529#include " perfetto/base/time.h"
2630#include " perfetto/ext/base/file_utils.h"
2731#include " perfetto/ext/base/status_macros.h"
32+ #include " perfetto/ext/base/string_utils.h"
33+ #include " perfetto/trace_processor/summarizer.h"
34+ #include " src/protozero/text_to_proto/text_to_proto.h"
2835#include " src/trace_processor/shell/common_flags.h"
2936#include " src/trace_processor/shell/metatrace.h"
3037#include " src/trace_processor/shell/query.h"
3138#include " src/trace_processor/shell/subcommand.h"
39+ #include " src/trace_processor/trace_summary/trace_summary.descriptor.h"
3240
3341#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
3442#include < io.h>
4149#endif
4250
4351namespace perfetto ::trace_processor::shell {
52+ namespace {
53+
54+ // Returns true if the file at |path| with |content| should be treated as
55+ // textproto rather than binary proto. Uses the same heuristic as the
56+ // classic codepath: .pb → binary, .textproto → text, otherwise
57+ // content-sniff the first 128 bytes (all printable/whitespace → text).
58+ bool IsTextproto (const std::string& path, const std::string& content) {
59+ if (base::EndsWith (path, " .pb" )) {
60+ return false ;
61+ }
62+ if (base::EndsWith (path, " .textproto" )) {
63+ return true ;
64+ }
65+ std::string_view prefix (content.c_str (),
66+ std::min<size_t >(content.size (), 128 ));
67+ return std::all_of (prefix.begin (), prefix.end (),
68+ [](char c) { return std::isspace (c) || std::isprint (c); });
69+ }
70+
71+ // Reads a TraceSummarySpec file and passes it to |summarizer|, converting
72+ // textproto to binary proto when necessary.
73+ base::Status LoadSpecIntoSummarizer (Summarizer* summarizer,
74+ const std::string& path) {
75+ std::string content;
76+ if (!base::ReadFile (path, &content)) {
77+ return base::ErrStatus (" Unable to read spec file %s" , path.c_str ());
78+ }
79+
80+ const uint8_t * spec_data;
81+ size_t spec_size;
82+ std::vector<uint8_t > binary_proto;
83+ if (IsTextproto (path, content)) {
84+ ASSIGN_OR_RETURN (binary_proto,
85+ protozero::TextToProto (kTraceSummaryDescriptor .data (),
86+ kTraceSummaryDescriptor .size (),
87+ " .perfetto.protos.TraceSummarySpec" ,
88+ " -" , std::string_view (content)));
89+ spec_data = binary_proto.data ();
90+ spec_size = binary_proto.size ();
91+ } else {
92+ spec_data = reinterpret_cast <const uint8_t *>(content.data ());
93+ spec_size = content.size ();
94+ }
95+
96+ SummarizerUpdateSpecResult update_result;
97+ RETURN_IF_ERROR (summarizer->UpdateSpec (spec_data, spec_size, &update_result));
98+ for (const auto & q : update_result.queries ) {
99+ if (q.error .has_value ()) {
100+ return base::ErrStatus (" Error in query '%s' from spec '%s': %s" ,
101+ q.query_id .c_str (), path.c_str (),
102+ q.error ->c_str ());
103+ }
104+ }
105+ return base::OkStatus ();
106+ }
107+
108+ } // namespace
44109
45110const char * QuerySubcommand::name () const {
46111 return " query" ;
@@ -63,13 +128,25 @@ SQL can be provided in three ways:
63128 3. From stdin: cat q.sql | tp query trace.pb
64129
65130Multiple semicolon-separated statements are supported. Use -i to drop into
66- an interactive shell after the queries complete.)" ;
131+ an interactive shell after the queries complete.
132+
133+ Advanced (for debugging/testing structured queries):
134+ --structured-query-id ID --summary-spec FILE [...]
135+ Executes a single structured query by ID from the given summary spec
136+ files. The spec files replace -f/stdin/positional SQL. Output is the
137+ query result table.)" ;
67138}
68139
69140std::vector<FlagSpec> QuerySubcommand::GetFlags () {
70141 return {
71142 StringFlag (" query-file" , ' f' , " FILE" ,
72143 " Read SQL from FILE (use '-' for stdin)." , &query_file_),
144+ StringFlag (" structured-query-id" , ' \0 ' , " ID" ,
145+ " [Advanced] Run a single structured query by ID." ,
146+ &structured_query_id_),
147+ {" summary-spec" , ' \0 ' , true , " FILE" ,
148+ " [Advanced] Summary spec file for structured queries (repeatable)." ,
149+ [this ](const char * v) { structured_query_specs_.emplace_back (v); }},
73150 BoolFlag (" interactive" , ' i' , " Start interactive shell after query." ,
74151 &interactive_),
75152 BoolFlag (" wide" , ' W' , " Double column width for output." , &wide_),
@@ -84,6 +161,11 @@ base::Status QuerySubcommand::Run(const SubcommandContext& ctx) {
84161 }
85162 std::string trace_file = ctx.positional_args [0 ];
86163
164+ // Advanced: structured query mode.
165+ if (!structured_query_id_.empty ()) {
166+ return RunStructuredQuery (ctx, trace_file);
167+ }
168+
87169 // Determine SQL source:
88170 // 1. Positional: query trace.pb "SELECT ..."
89171 // 2. File: query -f file.sql trace.pb
@@ -112,8 +194,6 @@ base::Status QuerySubcommand::Run(const SubcommandContext& ctx) {
112194 ASSIGN_OR_RETURN (auto t_load,
113195 LoadTraceFile (tp.get (), ctx.platform , trace_file));
114196
115- // If we have a file, read it into sql. After this point, sql always has
116- // the SQL to execute.
117197 if (!query_file_.empty ()) {
118198 if (!base::ReadFile (query_file_, &sql)) {
119199 return base::ErrStatus (" query: unable to read file '%s'" ,
@@ -128,12 +208,51 @@ base::Status QuerySubcommand::Run(const SubcommandContext& ctx) {
128208 MaybeWriteMetatrace (tp.get (), ctx.global ->metatrace_path );
129209 return status;
130210 }
131-
132211 base::TimeNanos t_query = base::GetWallTimeNs () - t_query_start;
133212
134- if (!perf_file_.empty ())
213+ if (!perf_file_.empty ()) {
135214 RETURN_IF_ERROR (PrintPerfFile (perf_file_, t_load, t_query));
215+ }
216+ RETURN_IF_ERROR (MaybeWriteMetatrace (tp.get (), ctx.global ->metatrace_path ));
217+ return base::OkStatus ();
218+ }
219+
220+ base::Status QuerySubcommand::RunStructuredQuery (
221+ const SubcommandContext& ctx,
222+ const std::string& trace_file) {
223+ if (structured_query_specs_.empty ()) {
224+ return base::ErrStatus (
225+ " query: --structured-query-id requires at least one --summary-spec" );
226+ }
227+
228+ auto config = BuildConfig (*ctx.global , ctx.platform );
229+ ASSIGN_OR_RETURN (auto tp,
230+ SetupTraceProcessor (*ctx.global , config, ctx.platform ));
231+ ASSIGN_OR_RETURN (auto t_load,
232+ LoadTraceFile (tp.get (), ctx.platform , trace_file));
136233
234+ std::unique_ptr<Summarizer> summarizer;
235+ RETURN_IF_ERROR (tp->CreateSummarizer (&summarizer));
236+ for (const auto & path : structured_query_specs_) {
237+ RETURN_IF_ERROR (LoadSpecIntoSummarizer (summarizer.get (), path));
238+ }
239+
240+ base::TimeNanos t_query_start = base::GetWallTimeNs ();
241+ SummarizerQueryResult query_result;
242+ RETURN_IF_ERROR (summarizer->Query (structured_query_id_, &query_result));
243+ if (!query_result.exists ) {
244+ return base::ErrStatus (
245+ " Structured query ID '%s' not found in the provided spec files" ,
246+ structured_query_id_.c_str ());
247+ }
248+
249+ RETURN_IF_ERROR (
250+ RunQueries (tp.get (), " SELECT * FROM " + query_result.table_name , true ));
251+ base::TimeNanos t_query = base::GetWallTimeNs () - t_query_start;
252+
253+ if (!perf_file_.empty ()) {
254+ RETURN_IF_ERROR (PrintPerfFile (perf_file_, t_load, t_query));
255+ }
137256 RETURN_IF_ERROR (MaybeWriteMetatrace (tp.get (), ctx.global ->metatrace_path ));
138257 return base::OkStatus ();
139258}
0 commit comments