blob: 82ae923a1ef0336fa5894789ee789c051fe694b9 [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/summarize_subcommand.h"
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <optional>
#include <string>
#include <vector>
#include "perfetto/base/status.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/status_macros.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/shell/common_flags.h"
#include "src/trace_processor/shell/interactive.h"
#include "src/trace_processor/shell/metatrace.h"
#include "src/trace_processor/shell/query.h"
#include "src/trace_processor/shell/subcommand.h"
#include "src/trace_processor/trace_summary/summary.h" // no-include-violation-check
namespace perfetto::trace_processor::shell {
namespace {
TraceSummarySpecBytes::Format GuessSummarySpecFormat(
const std::string& path,
const std::string& content) {
if (base::EndsWith(path, ".pb")) {
return TraceSummarySpecBytes::Format::kBinaryProto;
}
if (base::EndsWith(path, ".textproto")) {
return TraceSummarySpecBytes::Format::kTextProto;
}
// Content-sniffing fallback: if the first 128 bytes are all printable or
// whitespace, assume text proto; otherwise binary.
std::string_view prefix(content.c_str(),
std::min<size_t>(content.size(), 128));
if (std::all_of(prefix.begin(), prefix.end(),
[](char c) { return std::isspace(c) || std::isprint(c); })) {
return TraceSummarySpecBytes::Format::kTextProto;
}
return TraceSummarySpecBytes::Format::kBinaryProto;
}
} // namespace
const char* SummarizeSubcommand::name() const {
return "summarize";
}
const char* SummarizeSubcommand::description() const {
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
// split later. This matches the old getopt behavior where --spec could
// be repeated.
return {
StringFlag("metrics-v2", '\0', "IDS", "Metric IDs, or \"all\".",
&metrics_v2_),
StringFlag("metadata-query", '\0', "ID", "Metadata query ID.",
&metadata_query_),
StringFlag("format", '\0', "FORMAT", "Output format (text or binary).",
&output_format_),
StringFlag("post-query", '\0', "FILE",
"SQL file to run after summarization.", &post_query_path_),
StringFlag("perf-file", '\0', "FILE", "Write perf timing data to FILE.",
&perf_file_),
BoolFlag("interactive", 'i',
"Start interactive shell after summarization.", &interactive_),
};
}
base::Status SummarizeSubcommand::Run(const SubcommandContext& ctx) {
if (ctx.positional_args.empty()) {
return base::ErrStatus("summarize: trace file is required");
}
std::string trace_file = ctx.positional_args[0];
// Collect spec paths from remaining positional args (if any).
std::vector<std::string> spec_paths;
for (size_t i = 1; i < ctx.positional_args.size(); ++i) {
spec_paths.push_back(ctx.positional_args[i]);
}
auto config = BuildConfig(*ctx.global, ctx.platform);
ASSIGN_OR_RETURN(auto tp,
SetupTraceProcessor(*ctx.global, config, ctx.platform));
ASSIGN_OR_RETURN(auto t_load,
LoadTraceFile(tp.get(), ctx.platform, trace_file));
// Load spec files.
std::vector<std::string> spec_content;
spec_content.reserve(spec_paths.size());
for (const auto& s : spec_paths) {
spec_content.emplace_back();
if (!base::ReadFile(s, &spec_content.back())) {
return base::ErrStatus("Unable to read summary spec file %s", s.c_str());
}
}
std::vector<TraceSummarySpecBytes> specs;
specs.reserve(spec_paths.size());
for (uint32_t i = 0; i < spec_paths.size(); ++i) {
auto format = GuessSummarySpecFormat(spec_paths[i], spec_content[i]);
specs.emplace_back(TraceSummarySpecBytes{
reinterpret_cast<const uint8_t*>(spec_content[i].data()),
spec_content[i].size(),
format,
});
}
TraceSummaryComputationSpec computation_config;
if (metrics_v2_.empty()) {
computation_config.v2_metric_ids = std::vector<std::string>();
} else if (base::CaseInsensitiveEqual(metrics_v2_, "all")) {
computation_config.v2_metric_ids = std::nullopt;
} else {
computation_config.v2_metric_ids = base::SplitString(metrics_v2_, ",");
}
computation_config.metadata_query_id =
metadata_query_.empty() ? std::nullopt
: std::make_optional(metadata_query_);
TraceSummaryOutputSpec output_spec;
if (output_format_ == "binary") {
output_spec.format = TraceSummaryOutputSpec::Format::kBinaryProto;
} else {
output_spec.format = TraceSummaryOutputSpec::Format::kTextProto;
}
base::TimeNanos t_query_start = base::GetWallTimeNs();
std::vector<uint8_t> output;
RETURN_IF_ERROR(
tp->Summarize(computation_config, specs, &output, output_spec));
if (post_query_path_.empty()) {
fwrite(output.data(), sizeof(char), output.size(), stdout);
}
if (!post_query_path_.empty()) {
RETURN_IF_ERROR(RunQueriesFromFile(tp.get(), post_query_path_, true));
}
base::TimeNanos t_query = base::GetWallTimeNs() - t_query_start;
if (!perf_file_.empty()) {
RETURN_IF_ERROR(PrintPerfFile(perf_file_, t_load, t_query));
}
if (interactive_) {
RETURN_IF_ERROR(StartInteractiveShell(
tp.get(),
InteractiveOptions{20u, MetricV1OutputFormat::kNone, {}, {}, nullptr}));
}
RETURN_IF_ERROR(MaybeWriteMetatrace(tp.get(), ctx.global->metatrace_path));
return base::OkStatus();
}
} // namespace perfetto::trace_processor::shell