blob: 142a4262a5bba2f43e781ddd72758ee75feef247 [file]
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/trace_processor/shell/query.h"
#include <chrono>
#include <cinttypes>
#include <cstdint>
#include <cstdio>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/status_macros.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/iterator.h"
#include "perfetto/trace_processor/trace_processor.h"
namespace perfetto::trace_processor {
base::StatusOr<QueryResult> ExtractQueryResult(Iterator* it, bool has_more) {
QueryResult result;
for (uint32_t c = 0; c < it->ColumnCount(); c++) {
result.column_names.push_back(it->GetColumnName(c));
}
for (; has_more; has_more = it->Next()) {
std::vector<std::string> row;
for (uint32_t c = 0; c < it->ColumnCount(); c++) {
SqlValue value = it->Get(c);
std::string str_value;
switch (value.type) {
case SqlValue::Type::kNull:
str_value = "\"[NULL]\"";
break;
case SqlValue::Type::kDouble:
str_value =
base::StackString<256>("%f", value.double_value).ToStdString();
break;
case SqlValue::Type::kLong:
str_value = base::StackString<256>("%" PRIi64, value.long_value)
.ToStdString();
break;
case SqlValue::Type::kString:
str_value = '"' + std::string(value.string_value) + '"';
break;
case SqlValue::Type::kBytes:
str_value = "\"<raw bytes>\"";
break;
}
row.push_back(std::move(str_value));
}
result.rows.push_back(std::move(row));
}
RETURN_IF_ERROR(it->Status());
return result;
}
void PrintQueryResultAsCsv(const QueryResult& result, FILE* output) {
for (uint32_t c = 0; c < result.column_names.size(); c++) {
if (c > 0)
fprintf(output, ",");
fprintf(output, "\"%s\"", result.column_names[c].c_str());
}
fprintf(output, "\n");
for (const auto& row : result.rows) {
for (uint32_t c = 0; c < result.column_names.size(); c++) {
if (c > 0)
fprintf(output, ",");
fprintf(output, "%s", row[c].c_str());
}
fprintf(output, "\n");
}
}
base::Status RunQueriesWithoutOutput(TraceProcessor* trace_processor,
const std::string& sql_query) {
auto it = trace_processor->ExecuteQuery(sql_query);
if (it.StatementWithOutputCount() > 0)
return base::ErrStatus("Unexpected result from a query.");
RETURN_IF_ERROR(it.Status());
return it.Next() ? base::ErrStatus("Unexpected result from a query.")
: it.Status();
}
base::Status RunQueriesAndPrintResult(TraceProcessor* trace_processor,
const std::string& sql_query,
FILE* output) {
PERFETTO_DLOG("Executing query: %s", sql_query.c_str());
// Statements are executed one at a time and every statement's result set
// is printed as CSV, with consecutive result sets separated by a single
// blank line. Since our CSV writer quotes all strings, a blank line is
// unambiguously a boundary between result sets. Statements with no output
// print nothing, matching the sqlite3/duckdb shells.
std::chrono::nanoseconds exec_dur{0};
uint32_t offset = 0;
bool executed_any_statement = false;
bool printed_any_result = false;
for (;;) {
auto query_start = std::chrono::steady_clock::now();
std::optional<Iterator> it =
trace_processor->ExecuteNextStatement(sql_query, &offset);
if (!it.has_value()) {
break;
}
RETURN_IF_ERROR(it->Status());
executed_any_statement = true;
bool has_more = it->Next();
RETURN_IF_ERROR(it->Status());
// Statements without a result set (e.g. CREATE TABLE) print nothing.
if (it->ColumnCount() == 0) {
PERFETTO_DCHECK(!has_more);
exec_dur += std::chrono::steady_clock::now() - query_start;
continue;
}
// Statements with rows which nonetheless count as having no output are
// those whose output is explicitly ignored (a single column named
// `suppress_query_output`, void functions): step through them for their
// side effects but print nothing.
if (has_more && it->StatementWithOutputCount() == 0) {
for (; has_more; has_more = it->Next()) {
}
RETURN_IF_ERROR(it->Status());
exec_dur += std::chrono::steady_clock::now() - query_start;
continue;
}
// A zero-row result set still prints its header, with one exception: the
// `suppress_query_output` escape hatch must stay silent whether or not
// any row matched. (A zero-row void-function statement can't be detected
// here: its VOID marker lives on a row's value and there is no row.)
if (!has_more && it->ColumnCount() == 1 &&
it->GetColumnName(0) == "suppress_query_output") {
exec_dur += std::chrono::steady_clock::now() - query_start;
continue;
}
auto query_result = ExtractQueryResult(&*it, has_more);
RETURN_IF_ERROR(query_result.status());
// We want to include the query iteration time (as it's a part of
// executing SQL and can be non-trivial), and we want to exclude the time
// spent printing the result (which can be significant for large results),
// so we materialise the results first, then take the measurement, then
// print them.
exec_dur += std::chrono::steady_clock::now() - query_start;
if (printed_any_result) {
fprintf(output, "\n");
}
printed_any_result = true;
PrintQueryResultAsCsv(query_result.value(), output);
}
if (!executed_any_statement) {
return base::ErrStatus("No valid SQL to run");
}
PERFETTO_ILOG(
"Query execution time: %" PRIi64 " ms",
static_cast<int64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(exec_dur)
.count()));
return base::OkStatus();
}
base::Status PrintPerfFile(const std::string& perf_file_path,
base::TimeNanos t_load,
base::TimeNanos t_run) {
char buf[128];
size_t count = base::SprintfTrunc(buf, sizeof(buf), "%" PRId64 ",%" PRId64,
static_cast<int64_t>(t_load.count()),
static_cast<int64_t>(t_run.count()));
if (count == 0) {
return base::ErrStatus("Failed to write perf data");
}
auto fd(base::OpenFile(perf_file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666));
if (!fd) {
return base::ErrStatus("Failed to open perf file");
}
base::WriteAll(fd.get(), buf, count);
return base::OkStatus();
}
base::Status RunQueries(TraceProcessor* trace_processor,
const std::string& queries,
bool expect_output) {
if (expect_output) {
return RunQueriesAndPrintResult(trace_processor, queries, stdout);
}
return RunQueriesWithoutOutput(trace_processor, queries);
}
base::Status RunQueriesFromFile(TraceProcessor* trace_processor,
const std::string& query_file_path,
bool expect_output) {
std::string queries;
if (!base::ReadFile(query_file_path, &queries)) {
return base::ErrStatus(
"Unable to read file %s. If you're passing an SQL query, did you mean "
"to use the -Q flag instead?",
query_file_path.c_str());
}
return RunQueries(trace_processor, queries, expect_output);
}
} // namespace perfetto::trace_processor