1 /*
2 * Copyright (C) 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/perfetto_sql/generator/structured_query_generator.h"
18
19 #include <algorithm>
20 #include <cstddef>
21 #include <cstdint>
22 #include <cstring>
23 #include <memory>
24 #include <optional>
25 #include <string>
26 #include <utility>
27 #include <vector>
28
29 #include "perfetto/base/logging.h"
30 #include "perfetto/base/status.h"
31 #include "perfetto/ext/base/flat_hash_map.h"
32 #include "perfetto/ext/base/status_or.h"
33 #include "perfetto/ext/base/string_utils.h"
34 #include "perfetto/protozero/field.h"
35 #include "perfetto/protozero/proto_decoder.h"
36 #include "protos/perfetto/perfetto_sql/structured_query.pbzero.h"
37 #include "src/trace_processor/util/status_macros.h"
38
39 namespace perfetto::trace_processor::perfetto_sql::generator {
40
41 namespace {
42
43 using StructuredQuery = protos::pbzero::PerfettoSqlStructuredQuery;
44
45 enum QueryType : uint8_t {
46 kRoot,
47 kShared,
48 kNested,
49 };
50
51 struct QueryState {
QueryStateperfetto::trace_processor::perfetto_sql::generator::__anon8c2575f40111::QueryState52 QueryState(QueryType _type, protozero::ConstBytes _bytes, size_t index)
53 : type(_type), bytes(_bytes) {
54 protozero::ProtoDecoder decoder(bytes);
55 std::string prefix = type == QueryType::kShared ? "shared_sq_" : "sq_";
56 if (auto id = decoder.FindField(StructuredQuery::kIdFieldNumber); id) {
57 id_from_proto = id.as_std_string();
58 table_name = prefix + *id_from_proto;
59 } else {
60 table_name = prefix + std::to_string(index);
61 }
62 }
63
64 QueryType type;
65 protozero::ConstBytes bytes;
66 std::optional<std::string> id_from_proto;
67 std::string table_name;
68
69 std::string sql;
70 };
71
72 using Query = StructuredQueryGenerator::Query;
73 using QueryProto = StructuredQueryGenerator::QueryProto;
74
75 class GeneratorImpl {
76 public:
GeneratorImpl(base::FlatHashMap<std::string,QueryProto> & protos,std::vector<Query> & queries,base::FlatHashMap<std::string,std::nullptr_t> & modules,std::vector<std::string> & preambles)77 GeneratorImpl(base::FlatHashMap<std::string, QueryProto>& protos,
78 std::vector<Query>& queries,
79 base::FlatHashMap<std::string, std::nullptr_t>& modules,
80 std::vector<std::string>& preambles)
81 : query_protos_(protos),
82 queries_(queries),
83 referenced_modules_(modules),
84 preambles_(preambles) {}
85
86 base::StatusOr<std::string> Generate(protozero::ConstBytes);
87
88 private:
89 using RepeatedString =
90 protozero::RepeatedFieldIterator<protozero::ConstChars>;
91 using RepeatedProto = protozero::RepeatedFieldIterator<protozero::ConstBytes>;
92
93 base::StatusOr<std::string> GenerateImpl();
94
95 // Base sources
96 base::StatusOr<std::string> Table(const StructuredQuery::Table::Decoder&);
97 base::StatusOr<std::string> SimpleSlices(
98 const StructuredQuery::SimpleSlices::Decoder&);
99 base::StatusOr<std::string> SqlSource(const StructuredQuery::Sql::Decoder&);
100
101 // Nested sources
102 std::string NestedSource(protozero::ConstBytes);
103 base::StatusOr<std::string> ReferencedSharedQuery(
104 protozero::ConstChars raw_id);
105
106 base::StatusOr<std::string> IntervalIntersect(
107 const StructuredQuery::IntervalIntersect::Decoder&);
108
109 // Filtering.
110 static base::StatusOr<std::string> Filters(RepeatedProto filters);
111
112 // Aggregation.
113 static base::StatusOr<std::string> GroupBy(RepeatedString group_by);
114 static base::StatusOr<std::string> SelectColumnsAggregates(
115 RepeatedString group_by,
116 RepeatedProto aggregates,
117 RepeatedProto select_cols);
118 static base::StatusOr<std::string> SelectColumnsNoAggregates(
119 RepeatedProto select_columns);
120
121 // Helpers.
122 static base::StatusOr<std::string> OperatorToString(
123 StructuredQuery::Filter::Operator op);
124 static base::StatusOr<std::string> AggregateToString(
125 StructuredQuery::GroupBy::Aggregate::Op op,
126 protozero::ConstChars column_name);
127
128 // Index of the current query we are processing in the `state_` vector.
129 size_t state_index_ = 0;
130 std::vector<QueryState> state_;
131 base::FlatHashMap<std::string, QueryProto>& query_protos_;
132 std::vector<Query>& queries_;
133 base::FlatHashMap<std::string, std::nullptr_t>& referenced_modules_;
134 std::vector<std::string>& preambles_;
135 };
136
Generate(protozero::ConstBytes bytes)137 base::StatusOr<std::string> GeneratorImpl::Generate(
138 protozero::ConstBytes bytes) {
139 state_.emplace_back(QueryType::kRoot, bytes, state_.size());
140 for (; state_index_ < state_.size(); ++state_index_) {
141 base::StatusOr<std::string> sql = GenerateImpl();
142 if (!sql.ok()) {
143 return base::ErrStatus(
144 "Failed to generate SQL for query (id=%s, idx=%zu): %s",
145 state_[state_index_].id_from_proto.value_or("unknown").c_str(),
146 state_index_, sql.status().c_message());
147 }
148 state_[state_index_].sql = *sql;
149 }
150 std::string sql = "WITH ";
151 for (size_t i = 0; i < state_.size(); ++i) {
152 QueryState& state = state_[state_.size() - i - 1];
153 if (state.type == QueryType::kShared) {
154 queries_.emplace_back(
155 Query{state.id_from_proto.value(), state.table_name, state.sql});
156 continue;
157 }
158 sql += state.table_name + " AS (" + state.sql + ")";
159 if (i < state_.size() - 1) {
160 sql += ", ";
161 }
162 }
163 sql += " SELECT * FROM " + state_[0].table_name;
164 return sql;
165 }
166
GenerateImpl()167 base::StatusOr<std::string> GeneratorImpl::GenerateImpl() {
168 StructuredQuery::Decoder q(state_[state_index_].bytes);
169
170 // Warning: do *not* keep a reference to elemenets in `state_` across any of
171 // these functions: `state_` can be modified by them.
172 std::string source;
173 {
174 if (q.has_table()) {
175 StructuredQuery::Table::Decoder table(q.table());
176 ASSIGN_OR_RETURN(source, Table(table));
177 } else if (q.has_simple_slices()) {
178 StructuredQuery::SimpleSlices::Decoder slices(q.simple_slices());
179 ASSIGN_OR_RETURN(source, SimpleSlices(slices));
180 } else if (q.has_interval_intersect()) {
181 StructuredQuery::IntervalIntersect::Decoder ii(q.interval_intersect());
182 ASSIGN_OR_RETURN(source, IntervalIntersect(ii));
183 } else if (q.has_sql()) {
184 StructuredQuery::Sql::Decoder sql_source(q.sql());
185 ASSIGN_OR_RETURN(source, SqlSource(sql_source));
186 } else if (q.has_inner_query()) {
187 source = NestedSource(q.inner_query());
188 } else if (q.has_inner_query_id()) {
189 ASSIGN_OR_RETURN(source, ReferencedSharedQuery(q.inner_query_id()));
190 } else {
191 return base::ErrStatus("Query must specify a source");
192 }
193 }
194
195 ASSIGN_OR_RETURN(std::string filters, Filters(q.filters()));
196
197 std::string select;
198 std::string group_by;
199 if (q.has_group_by()) {
200 StructuredQuery::GroupBy::Decoder gb(q.group_by());
201 ASSIGN_OR_RETURN(group_by, GroupBy(gb.column_names()));
202 ASSIGN_OR_RETURN(select,
203 SelectColumnsAggregates(gb.column_names(), gb.aggregates(),
204 q.select_columns()));
205 } else {
206 ASSIGN_OR_RETURN(select, SelectColumnsNoAggregates(q.select_columns()));
207 }
208
209 std::string sql = "SELECT " + select + " FROM " + source;
210 if (!filters.empty()) {
211 sql += " WHERE " + filters;
212 }
213 if (!group_by.empty()) {
214 sql += " " + group_by;
215 }
216 return sql;
217 }
218
Table(const StructuredQuery::Table::Decoder & table)219 base::StatusOr<std::string> GeneratorImpl::Table(
220 const StructuredQuery::Table::Decoder& table) {
221 if (table.table_name().size == 0) {
222 return base::ErrStatus("Table must specify a table name");
223 }
224 if (table.module_name().size > 0) {
225 referenced_modules_.Insert(table.module_name().ToStdString(), nullptr);
226 }
227 return table.table_name().ToStdString();
228 }
229
SqlSource(const StructuredQuery::Sql::Decoder & sql)230 base::StatusOr<std::string> GeneratorImpl::SqlSource(
231 const StructuredQuery::Sql::Decoder& sql) {
232 if (sql.sql().size == 0) {
233 return base::ErrStatus("Sql field must be specified");
234 }
235 if (sql.column_names()->size() == 0) {
236 return base::ErrStatus("Sql must specify columns");
237 }
238
239 if (sql.has_preamble()) {
240 preambles_.push_back(sql.preamble().ToStdString());
241 }
242
243 std::vector<std::string> cols;
244 for (auto it = sql.column_names(); it; ++it) {
245 cols.push_back(it->as_std_string());
246 }
247 std::string join_str = base::Join(cols, ", ");
248
249 return "(SELECT " + join_str + " FROM (" + sql.sql().ToStdString() + "))";
250 }
251
SimpleSlices(const StructuredQuery::SimpleSlices::Decoder & slices)252 base::StatusOr<std::string> GeneratorImpl::SimpleSlices(
253 const StructuredQuery::SimpleSlices::Decoder& slices) {
254 referenced_modules_.Insert("slices.with_context", nullptr);
255
256 std::string sql =
257 "SELECT id, ts, dur, name AS slice_name, thread_name, process_name, "
258 "track_name FROM thread_or_process_slice";
259
260 std::vector<std::string> conditions;
261 if (slices.has_slice_name_glob()) {
262 conditions.push_back("slice_name GLOB '" +
263 slices.slice_name_glob().ToStdString() + "'");
264 }
265 if (slices.has_thread_name_glob()) {
266 conditions.push_back("thread_name GLOB '" +
267 slices.thread_name_glob().ToStdString() + "'");
268 }
269 if (slices.has_process_name_glob()) {
270 conditions.push_back("process_name GLOB '" +
271 slices.process_name_glob().ToStdString() + "'");
272 }
273 if (slices.has_track_name_glob()) {
274 conditions.push_back("track_name GLOB '" +
275 slices.track_name_glob().ToStdString() + "'");
276 }
277 if (!conditions.empty()) {
278 sql += " WHERE " + conditions[0];
279 for (size_t i = 1; i < conditions.size(); ++i) {
280 sql += " AND " + conditions[i];
281 }
282 }
283 return "(" + sql + ")";
284 }
285
IntervalIntersect(const StructuredQuery::IntervalIntersect::Decoder & interval)286 base::StatusOr<std::string> GeneratorImpl::IntervalIntersect(
287 const StructuredQuery::IntervalIntersect::Decoder& interval) {
288 if (interval.base().size == 0) {
289 return base::ErrStatus("IntervalIntersect must specify a base query");
290 }
291 if (!interval.interval_intersect()) {
292 return base::ErrStatus(
293 "IntervalIntersect must specify at least one interval query");
294 }
295 referenced_modules_.Insert("intervals.intersect", nullptr);
296
297 std::string sql =
298 "(WITH iibase AS (SELECT * FROM " + NestedSource(interval.base()) + ")";
299 auto ii = interval.interval_intersect();
300 for (size_t i = 0; ii; ++ii, ++i) {
301 sql += ", iisource" + std::to_string(i) + " AS (SELECT * FROM " +
302 NestedSource(*ii) + ") ";
303 }
304
305 sql += "SELECT ii.ts, ii.dur, iibase.*";
306 ii = interval.interval_intersect();
307 for (size_t i = 0; ii; ++ii) {
308 sql += ", iisource" + std::to_string(i) + ".*";
309 }
310 sql += " FROM _interval_intersect!((iibase";
311 ii = interval.interval_intersect();
312 for (size_t i = 0; ii; ++ii) {
313 sql += ", iisource" + std::to_string(i);
314 }
315 sql += "), ()) ii JOIN iibase ON ii.id_0 = iibase.id";
316
317 ii = interval.interval_intersect();
318 for (size_t i = 0; ii; ++ii) {
319 sql += " JOIN iisource" + std::to_string(i) + " ON ii.id_" +
320 std::to_string(i + 1) + " = iisource" + std::to_string(i) + ".id";
321 }
322 sql += ")";
323 return sql;
324 }
325
ReferencedSharedQuery(protozero::ConstChars raw_id)326 base::StatusOr<std::string> GeneratorImpl::ReferencedSharedQuery(
327 protozero::ConstChars raw_id) {
328 std::string id = raw_id.ToStdString();
329 auto* it = query_protos_.Find(id);
330 if (!it) {
331 return base::ErrStatus("Shared query with id '%s' not found", id.c_str());
332 }
333 auto sq = std::find_if(queries_.begin(), queries_.end(),
334 [&](const Query& sq) { return id == sq.id; });
335 if (sq != queries_.end()) {
336 return sq->table_name;
337 }
338 state_.emplace_back(QueryType::kShared,
339 protozero::ConstBytes{it->data.get(), it->size},
340 state_.size());
341 return state_.back().table_name;
342 }
343
NestedSource(protozero::ConstBytes bytes)344 std::string GeneratorImpl::NestedSource(protozero::ConstBytes bytes) {
345 state_.emplace_back(QueryType::kNested, bytes, state_.size());
346 return state_.back().table_name;
347 }
348
Filters(protozero::RepeatedFieldIterator<protozero::ConstBytes> filters)349 base::StatusOr<std::string> GeneratorImpl::Filters(
350 protozero::RepeatedFieldIterator<protozero::ConstBytes> filters) {
351 std::string sql;
352 for (auto it = filters; it; ++it) {
353 StructuredQuery::Filter::Decoder filter(*it);
354 if (!sql.empty()) {
355 sql += " AND ";
356 }
357
358 std::string column_name = filter.column_name().ToStdString();
359 auto op = static_cast<StructuredQuery::Filter::Operator>(filter.op());
360 ASSIGN_OR_RETURN(std::string op_str, OperatorToString(op));
361
362 if (op == StructuredQuery::Filter::Operator::IS_NULL ||
363 op == StructuredQuery::Filter::Operator::IS_NOT_NULL) {
364 sql += column_name + " " + op_str;
365 continue;
366 }
367
368 sql += column_name + " " + op_str + " ";
369
370 if (auto srhs = filter.string_rhs(); srhs) {
371 sql += "'" + (*srhs++).ToStdString() + "'";
372 for (; srhs; ++srhs) {
373 sql += " OR " + column_name + " " + op_str + " '" +
374 (*srhs).ToStdString() + "'";
375 }
376 } else if (auto drhs = filter.double_rhs(); drhs) {
377 sql += std::to_string((*drhs++));
378 for (; drhs; ++drhs) {
379 sql +=
380 " OR " + column_name + " " + op_str + " " + std::to_string(*drhs);
381 }
382 } else if (auto irhs = filter.int64_rhs(); irhs) {
383 sql += std::to_string(*irhs++);
384 for (; irhs; ++irhs) {
385 sql +=
386 " OR " + column_name + " " + op_str + " " + std::to_string(*irhs);
387 }
388 } else {
389 return base::ErrStatus("Filter must specify a right-hand side");
390 }
391 }
392 return sql;
393 }
394
GroupBy(protozero::RepeatedFieldIterator<protozero::ConstChars> group_by)395 base::StatusOr<std::string> GeneratorImpl::GroupBy(
396 protozero::RepeatedFieldIterator<protozero::ConstChars> group_by) {
397 std::string sql;
398 for (auto it = group_by; it; ++it) {
399 if (sql.empty()) {
400 sql += "GROUP BY ";
401 } else {
402 sql += ", ";
403 }
404 sql += (*it).ToStdString();
405 }
406 return sql;
407 }
408
SelectColumnsAggregates(protozero::RepeatedFieldIterator<protozero::ConstChars> group_by_cols,protozero::RepeatedFieldIterator<protozero::ConstBytes> aggregates,protozero::RepeatedFieldIterator<protozero::ConstBytes> select_cols)409 base::StatusOr<std::string> GeneratorImpl::SelectColumnsAggregates(
410 protozero::RepeatedFieldIterator<protozero::ConstChars> group_by_cols,
411 protozero::RepeatedFieldIterator<protozero::ConstBytes> aggregates,
412 protozero::RepeatedFieldIterator<protozero::ConstBytes> select_cols) {
413 base::FlatHashMap<std::string, std::optional<std::string>> output;
414 if (select_cols) {
415 for (auto it = select_cols; it; ++it) {
416 StructuredQuery::SelectColumn::Decoder select(*it);
417 std::string selected_col_name = select.column_name().ToStdString();
418 output.Insert(select.column_name().ToStdString(),
419 select.has_alias()
420 ? std::make_optional(select.alias().ToStdString())
421 : std::nullopt);
422 }
423 } else {
424 for (auto it = group_by_cols; it; ++it) {
425 output.Insert((*it).ToStdString(), std::nullopt);
426 }
427 for (auto it = aggregates; it; ++it) {
428 StructuredQuery::GroupBy::Aggregate::Decoder aggregate(*it);
429 output.Insert(aggregate.result_column_name().ToStdString(), std::nullopt);
430 }
431 }
432
433 std::string sql;
434 auto itg = group_by_cols;
435 for (; itg; ++itg) {
436 std::string column_name = (*itg).ToStdString();
437 auto* o = output.Find(column_name);
438 if (!o) {
439 continue;
440 }
441 if (!sql.empty()) {
442 sql += ", ";
443 }
444 if (o->has_value()) {
445 sql += column_name + " AS " + o->value();
446 } else {
447 sql += column_name;
448 }
449 }
450
451 for (auto ita = aggregates; ita; ++ita) {
452 StructuredQuery::GroupBy::Aggregate::Decoder aggregate(*ita);
453 std::string res_column_name = aggregate.result_column_name().ToStdString();
454 auto* o = output.Find(res_column_name);
455 if (!o) {
456 continue;
457 }
458 if (!sql.empty()) {
459 sql += ", ";
460 }
461 ASSIGN_OR_RETURN(
462 std::string agg,
463 AggregateToString(static_cast<StructuredQuery::GroupBy::Aggregate::Op>(
464 aggregate.op()),
465 aggregate.column_name()));
466 if (o->has_value()) {
467 sql += agg + " AS " + o->value();
468 } else {
469 sql += agg + " AS " + res_column_name;
470 }
471 }
472 return sql;
473 }
474
SelectColumnsNoAggregates(protozero::RepeatedFieldIterator<protozero::ConstBytes> select_columns)475 base::StatusOr<std::string> GeneratorImpl::SelectColumnsNoAggregates(
476 protozero::RepeatedFieldIterator<protozero::ConstBytes> select_columns) {
477 if (!select_columns) {
478 return std::string("*");
479 }
480 std::string sql;
481 for (auto it = select_columns; it; ++it) {
482 StructuredQuery::SelectColumn::Decoder column(*it);
483 if (!sql.empty()) {
484 sql += ", ";
485 }
486 if (column.has_alias()) {
487 sql += column.column_name().ToStdString() + " AS " +
488 column.alias().ToStdString();
489 } else {
490 sql += column.column_name().ToStdString();
491 }
492 }
493 return sql;
494 }
495
OperatorToString(StructuredQuery::Filter::Operator op)496 base::StatusOr<std::string> GeneratorImpl::OperatorToString(
497 StructuredQuery::Filter::Operator op) {
498 switch (op) {
499 case StructuredQuery::Filter::EQUAL:
500 return std::string("=");
501 case StructuredQuery::Filter::NOT_EQUAL:
502 return std::string("!=");
503 case StructuredQuery::Filter::LESS_THAN:
504 return std::string("<");
505 case StructuredQuery::Filter::LESS_THAN_EQUAL:
506 return std::string("<=");
507 case StructuredQuery::Filter::GREATER_THAN:
508 return std::string(">");
509 case StructuredQuery::Filter::GREATER_THAN_EQUAL:
510 return std::string(">=");
511 case StructuredQuery::Filter::GLOB:
512 return std::string("GLOB");
513 case StructuredQuery::Filter::IS_NULL:
514 return std::string("IS NULL");
515 case StructuredQuery::Filter::IS_NOT_NULL:
516 return std::string("IS NOT NULL");
517 case StructuredQuery::Filter::UNKNOWN:
518 return base::ErrStatus("Invalid filter operator %d", op);
519 }
520 PERFETTO_FATAL("For GCC");
521 }
522
AggregateToString(StructuredQuery::GroupBy::Aggregate::Op op,protozero::ConstChars raw_column_name)523 base::StatusOr<std::string> GeneratorImpl::AggregateToString(
524 StructuredQuery::GroupBy::Aggregate::Op op,
525 protozero::ConstChars raw_column_name) {
526 std::string column_name = raw_column_name.ToStdString();
527 switch (op) {
528 case StructuredQuery::GroupBy::Aggregate::COUNT:
529 return "COUNT(" + column_name + ")";
530 case StructuredQuery::GroupBy::Aggregate::SUM:
531 return "SUM(" + column_name + ")";
532 case StructuredQuery::GroupBy::Aggregate::MIN:
533 return "MIN(" + column_name + ")";
534 case StructuredQuery::GroupBy::Aggregate::MAX:
535 return "MAX(" + column_name + ")";
536 case StructuredQuery::GroupBy::Aggregate::MEAN:
537 return "AVG(" + column_name + ")";
538 case StructuredQuery::GroupBy::Aggregate::MEDIAN:
539 return "MEDIAN(" + column_name + ")";
540 case StructuredQuery::GroupBy::Aggregate::DURATION_WEIGHTED_MEAN:
541 return "SUM(cast_double!(" + column_name +
542 " * dur)) / cast_double!(SUM(dur))";
543 case StructuredQuery::GroupBy::Aggregate::UNSPECIFIED:
544 return base::ErrStatus("Invalid aggregate operator %d", op);
545 }
546 PERFETTO_FATAL("For GCC");
547 }
548
549 } // namespace
550
Generate(const uint8_t * data,size_t size)551 base::StatusOr<std::string> StructuredQueryGenerator::Generate(
552 const uint8_t* data,
553 size_t size) {
554 GeneratorImpl impl(query_protos_, referenced_queries_, referenced_modules_,
555 preambles_);
556 ASSIGN_OR_RETURN(std::string sql,
557 impl.Generate(protozero::ConstBytes{data, size}));
558 return sql;
559 }
560
GenerateById(const std::string & id)561 base::StatusOr<std::string> StructuredQueryGenerator::GenerateById(
562 const std::string& id) {
563 auto* ptr = query_protos_.Find(id);
564 if (!ptr) {
565 return base::ErrStatus("Query with id %s not found", id.c_str());
566 }
567 return Generate(ptr->data.get(), ptr->size);
568 }
569
AddQuery(const uint8_t * data,size_t size)570 base::Status StructuredQueryGenerator::AddQuery(const uint8_t* data,
571 size_t size) {
572 protozero::ProtoDecoder decoder(data, size);
573 auto field = decoder.FindField(
574 protos::pbzero::PerfettoSqlStructuredQuery::kIdFieldNumber);
575 if (!field) {
576 return base::ErrStatus(
577 "Unable to find id for shared query: all shared queries must have an "
578 "id specified");
579 }
580 std::string id = field.as_std_string();
581 auto ptr = std::make_unique<uint8_t[]>(size);
582 memcpy(ptr.get(), data, size);
583 auto [it, inserted] =
584 query_protos_.Insert(id, QueryProto{std::move(ptr), size});
585 if (!inserted) {
586 return base::ErrStatus("Multiple shared queries specified with the ids %s",
587 id.c_str());
588 }
589 return base::OkStatus();
590 }
591
ComputeReferencedModules() const592 std::vector<std::string> StructuredQueryGenerator::ComputeReferencedModules()
593 const {
594 std::vector<std::string> modules;
595 for (auto it = referenced_modules_.GetIterator(); it; ++it) {
596 modules.emplace_back(it.key());
597 }
598 return modules;
599 }
600
601 } // namespace perfetto::trace_processor::perfetto_sql::generator
602