Skip to content

Commit c8c6133

Browse files
authored
tp: add structured query mode to query subcommand (#5198)
## Summary - Add `--structured-query-id` and `--summary-spec` flags to the `query` subcommand, using the Summarizer API to materialize and print a single structured query result. Intended for debugging/testing. - Remove the legacy `ExecuteStructuredQuery()` from summary.{h,cc} and the old `--structured-query-spec`/`--structured-query-id` classic flags. - Migrate diff test infra to use the new subcommand syntax. ## Stack - **#5198 tp: add structured query mode to query subcommand** (this PR) - #5149 tp: classic-to-subcommand translation layer ## Test plan - [x] `perfetto_integrationtests --gtest_filter="TraceProcessorShellIntegrationTest.*"` (45 tests pass) - [x] `tools/diff_test_trace_processor.py --name-filter=".*structured.*"` (4 tests pass)
1 parent 5e3b0f0 commit c8c6133

File tree

7 files changed

+138
-189
lines changed

7 files changed

+138
-189
lines changed

python/generators/diff_tests/test_executor.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,11 @@ def run(self, test: TestCase, trace_path: str) -> TestResult:
318318
try:
319319
cmd = [
320320
self.trace_processor_path,
321+
'query',
321322
'--analyze-trace-proto-content',
322323
'--crop-track-events',
323324
'--extra-checks',
324-
'--structured-query-spec',
325+
'--summary-spec',
325326
spec_file_path,
326327
'--structured-query-id',
327328
test.blueprint.query.query_id,

src/trace_processor/shell/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@ source_set("shell") {
5151
"../../../protos/perfetto/trace_processor:zero",
5252
"../../base",
5353
"../../base:version",
54+
"../../protozero/text_to_proto:text_to_proto",
5455
"../metrics",
5556
"../rpc",
5657
"../rpc:stdiod",
5758
"../trace_summary",
59+
"../trace_summary:gen_cc_trace_summary_descriptor",
5860
"../util:stdlib",
5961
"../util/deobfuscation:deobfuscator",
6062
"../util/symbolizer",

src/trace_processor/shell/query_subcommand.cc

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
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"
@@ -25,10 +29,14 @@
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>
@@ -41,6 +49,63 @@
4149
#endif
4250

4351
namespace 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

45110
const 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
65130
Multiple 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

69140
std::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
}

src/trace_processor/shell/query_subcommand.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ class QuerySubcommand : public Subcommand {
3535
base::Status Run(const SubcommandContext& ctx) override;
3636

3737
private:
38+
base::Status RunStructuredQuery(const SubcommandContext& ctx,
39+
const std::string& trace_file);
40+
3841
std::string query_file_;
42+
std::string structured_query_id_;
43+
std::vector<std::string> structured_query_specs_;
3944
bool interactive_ = false;
4045
bool wide_ = false;
4146
std::string perf_file_;

src/trace_processor/trace_processor_shell.cc

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@
7272
#include "src/trace_processor/shell/sql_packages.h"
7373
#include "src/trace_processor/shell/subcommand.h"
7474
#include "src/trace_processor/shell/summarize_subcommand.h"
75-
#include "src/trace_processor/trace_summary/summary.h"
7675
#include "src/trace_processor/util/deobfuscation/deobfuscator.h"
7776
#include "src/trace_processor/util/symbolizer/symbolize_database.h"
7877

@@ -127,8 +126,6 @@ struct CommandLineOptions {
127126

128127
std::string query_file_path;
129128
std::string query_string;
130-
std::vector<std::string> structured_query_specs;
131-
std::string structured_query_id;
132129
std::vector<std::string> sql_package_paths;
133130
std::vector<std::string> override_sql_package_paths;
134131

@@ -467,10 +464,6 @@ const option kLongOptions[] = {
467464

468465
{"query-file", required_argument, nullptr, 'q'},
469466
{"query-string", required_argument, nullptr, 'Q'},
470-
{"structured-query-spec", required_argument, nullptr,
471-
OPT_STRUCTURED_QUERY_SPEC},
472-
{"structured-query-id", required_argument, nullptr,
473-
OPT_STRUCTURED_QUERY_ID},
474467
{"add-sql-package", required_argument, nullptr, OPT_ADD_SQL_PACKAGE},
475468
{"override-sql-package", required_argument, nullptr,
476469
OPT_OVERRIDE_SQL_PACKAGE},
@@ -641,16 +634,6 @@ CommandLineOptions ParseCommandLineOptions(int argc, char** argv) {
641634
continue;
642635
}
643636

644-
if (option == OPT_STRUCTURED_QUERY_SPEC) {
645-
command_line_options.structured_query_specs.emplace_back(optarg);
646-
continue;
647-
}
648-
649-
if (option == OPT_STRUCTURED_QUERY_ID) {
650-
command_line_options.structured_query_id = optarg;
651-
continue;
652-
}
653-
654637
if (option == OPT_OVERRIDE_STDLIB) {
655638
command_line_options.override_stdlib_path = optarg;
656639
continue;
@@ -724,13 +707,11 @@ CommandLineOptions ParseCommandLineOptions(int argc, char** argv) {
724707
}
725708

726709
command_line_options.launch_shell =
727-
explicit_interactive ||
728-
(command_line_options.metric_v1_names.empty() &&
729-
command_line_options.query_file_path.empty() &&
730-
command_line_options.query_string.empty() &&
731-
command_line_options.structured_query_id.empty() &&
732-
command_line_options.export_file_path.empty() &&
733-
!command_line_options.summary);
710+
explicit_interactive || (command_line_options.metric_v1_names.empty() &&
711+
command_line_options.query_file_path.empty() &&
712+
command_line_options.query_string.empty() &&
713+
command_line_options.export_file_path.empty() &&
714+
!command_line_options.summary);
734715

735716
// Only allow non-interactive queries to emit perf data.
736717
if (!command_line_options.perf_file_path.empty() &&
@@ -1262,44 +1243,6 @@ base::Status TraceProcessorShell::Run(int argc, char** argv) {
12621243
}
12631244
}
12641245

1265-
if (!options.structured_query_id.empty()) {
1266-
// Load spec files.
1267-
std::vector<std::string> spec_content;
1268-
spec_content.reserve(options.structured_query_specs.size());
1269-
for (const auto& s : options.structured_query_specs) {
1270-
spec_content.emplace_back();
1271-
if (!base::ReadFile(s, &spec_content.back())) {
1272-
return base::ErrStatus("Unable to read structured query spec file %s",
1273-
s.c_str());
1274-
}
1275-
}
1276-
1277-
// Convert to TraceSummarySpecBytes.
1278-
std::vector<TraceSummarySpecBytes> specs;
1279-
specs.reserve(options.structured_query_specs.size());
1280-
for (uint32_t i = 0; i < options.structured_query_specs.size(); ++i) {
1281-
specs.emplace_back(TraceSummarySpecBytes{
1282-
reinterpret_cast<const uint8_t*>(spec_content[i].data()),
1283-
spec_content[i].size(),
1284-
GuessSummarySpecFormat(options.structured_query_specs[i],
1285-
spec_content[i]),
1286-
});
1287-
}
1288-
1289-
// Execute the structured query.
1290-
std::string output;
1291-
base::Status status = summary::ExecuteStructuredQuery(
1292-
tp.get(), specs, options.structured_query_id, &output);
1293-
if (!status.ok()) {
1294-
// Write metatrace if needed before exiting.
1295-
RETURN_IF_ERROR(MaybeWriteMetatrace(tp.get(), options.metatrace_path));
1296-
return status;
1297-
}
1298-
1299-
// Print the result.
1300-
fprintf(stdout, "%s", output.c_str());
1301-
}
1302-
13031246
base::TimeNanos t_query = base::GetWallTimeNs() - t_query_start;
13041247

13051248
if (!options.export_file_path.empty()) {

0 commit comments

Comments
 (0)