blob: 6fcb2e8119c910196b286e8e1ddca09b6997bbec [file] [log] [blame] [edit]
/*
* 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/interactive.h"
#include <cerrno>
#include <cinttypes>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include "perfetto/base/build_config.h"
#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/string_utils.h"
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/iterator.h"
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/shell/metrics.h"
#include "src/trace_processor/shell/query.h"
#include "src/trace_processor/shell/shell_utils.h"
#if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE)
#include <linenoise.h>
#include <pwd.h>
#include <sys/types.h>
#endif
namespace perfetto::trace_processor {
namespace {
#if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE)
bool EnsureDir(const std::string& path) {
return base::Mkdir(path) || errno == EEXIST;
}
bool EnsureFile(const std::string& path) {
return base::OpenFile(path, O_RDONLY | O_CREAT, 0644).get() != -1;
}
std::string GetConfigPath() {
const char* homedir = getenv("HOME");
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_FREEBSD) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
if (homedir == nullptr)
homedir = getpwuid(getuid())->pw_dir;
#elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
if (homedir == nullptr)
homedir = getenv("USERPROFILE");
#endif
if (homedir == nullptr)
return "";
return std::string(homedir) + "/.config";
}
std::string GetPerfettoPath() {
std::string config = GetConfigPath();
if (config.empty())
return "";
return config + "/perfetto";
}
std::string GetHistoryPath() {
std::string perfetto = GetPerfettoPath();
if (perfetto.empty())
return "";
return perfetto + "/.trace_processor_shell_history";
}
void SetupLineEditor() {
linenoiseSetMultiLine(true);
linenoiseHistorySetMaxLen(1000);
bool success = !GetHistoryPath().empty();
success = success && EnsureDir(GetConfigPath());
success = success && EnsureDir(GetPerfettoPath());
success = success && EnsureFile(GetHistoryPath());
success = success && linenoiseHistoryLoad(GetHistoryPath().c_str()) != -1;
if (!success) {
PERFETTO_PLOG("Could not load history from %s", GetHistoryPath().c_str());
}
}
struct LineDeleter {
void operator()(char* p) const {
linenoiseHistoryAdd(p);
linenoiseHistorySave(GetHistoryPath().c_str());
linenoiseFree(p);
}
};
using ScopedLine = std::unique_ptr<char, LineDeleter>;
ScopedLine GetLine(const char* prompt) {
errno = 0;
auto line = ScopedLine(linenoise(prompt));
// linenoise returns a nullptr both for CTRL-C and CTRL-D, however in the
// former case it sets errno to EAGAIN.
// If the user press CTRL-C return "" instead of nullptr. We don't want the
// main loop to quit in that case as that is inconsistent with the behavior
// "CTRL-C interrupts the current query" and frustrating when hitting that
// a split second after the query is done.
if (!line && errno == EAGAIN)
return ScopedLine(strdup(""));
return line;
}
#else
void SetupLineEditor() {}
using ScopedLine = std::unique_ptr<char>;
ScopedLine GetLine(const char* prompt) {
printf("\r%80s\r%s", "", prompt);
fflush(stdout);
ScopedLine line(new char[1024]);
if (!fgets(line.get(), 1024 - 1, stdin))
return nullptr;
if (strlen(line.get()) > 0)
line.get()[strlen(line.get()) - 1] = 0;
return line;
}
#endif // PERFETTO_TP_LINENOISE
void PrintQueryResultInteractively(Iterator* it,
base::TimeNanos t_start,
uint32_t column_width) {
base::TimeNanos t_end = base::GetWallTimeNs();
for (uint32_t rows = 0; it->Next(); rows++) {
if (rows % 32 == 0) {
if (rows == 0) {
t_end = base::GetWallTimeNs();
} else {
fprintf(stderr, "...\nType 'q' to stop, Enter for more records: ");
fflush(stderr);
char input[32];
if (!fgets(input, sizeof(input) - 1, stdin))
exit(0);
if (input[0] == 'q')
break;
}
for (uint32_t i = 0; i < it->ColumnCount(); i++)
printf("%-*.*s ", column_width, column_width,
it->GetColumnName(i).c_str());
printf("\n");
std::string divider(column_width, '-');
for (uint32_t i = 0; i < it->ColumnCount(); i++) {
printf("%-*s ", column_width, divider.c_str());
}
printf("\n");
}
for (uint32_t c = 0; c < it->ColumnCount(); c++) {
auto value = it->Get(c);
switch (value.type) {
case SqlValue::Type::kNull:
printf("%-*s", column_width, "[NULL]");
break;
case SqlValue::Type::kDouble:
printf("%*f", column_width, value.double_value);
break;
case SqlValue::Type::kLong:
printf("%*" PRIi64, column_width, value.long_value);
break;
case SqlValue::Type::kString:
printf("%-*.*s", column_width, column_width, value.string_value);
break;
case SqlValue::Type::kBytes:
printf("%-*s", column_width, "<raw bytes>");
break;
}
printf(" ");
}
printf("\n");
}
base::Status status = it->Status();
if (!status.ok()) {
fprintf(stderr, "%s\n", status.c_message());
}
printf("\nQuery executed in %.3f ms\n\n",
static_cast<double>((t_end - t_start).count()) / 1E6);
}
void PrintShellUsage() {
PERFETTO_ELOG(R"(
Available commands:
.quit, .q Exit the shell.
.help This text.
.dump FILE Export the trace as a sqlite database.
.read FILE Executes the queries in the FILE.
.reset Destroys all tables/view created by the user.
.load-metrics-sql Reloads SQL from extension and custom metric paths
specified in command line args.
.run-metrics Runs metrics specified in command line args
and prints the result.
.width WIDTH Changes the column width of interactive query
output.
)");
}
} // namespace
base::Status StartInteractiveShell(TraceProcessor* trace_processor,
const InteractiveOptions& options) {
SetupLineEditor();
uint32_t column_width = options.column_width;
for (;;) {
ScopedLine line = GetLine("> ");
if (!line)
break;
if (strcmp(line.get(), "") == 0) {
printf("If you want to quit either type .q or press CTRL-D (EOF)\n");
continue;
}
if (line.get()[0] == '.') {
char command[32] = {};
char arg[1024] = {};
sscanf(line.get() + 1, "%31s %1023s", command, arg);
if (strcmp(command, "quit") == 0 || strcmp(command, "q") == 0) {
break;
}
if (strcmp(command, "help") == 0) {
PrintShellUsage();
} else if (strcmp(command, "dump") == 0 && strlen(arg)) {
if (!ExportTraceToDatabase(trace_processor, arg).ok())
PERFETTO_ELOG("Database export failed");
} else if (strcmp(command, "reset") == 0) {
trace_processor->RestoreInitialTables();
} else if (strcmp(command, "read") == 0 && strlen(arg)) {
base::Status status = RunQueriesFromFile(trace_processor, arg, true);
if (!status.ok()) {
PERFETTO_ELOG("%s", status.c_message());
}
} else if (strcmp(command, "width") == 0 && strlen(arg)) {
std::optional<uint32_t> width = base::CStringToUInt32(arg);
if (!width) {
PERFETTO_ELOG("Invalid column width specified");
continue;
}
column_width = *width;
} else if (strcmp(command, "load-metrics-sql") == 0) {
base::Status status = LoadMetricsAndExtensionsSql(
trace_processor, options.metrics, options.extensions);
if (!status.ok()) {
PERFETTO_ELOG("%s", status.c_message());
}
} else if (strcmp(command, "run-metrics") == 0) {
if (options.metrics.empty()) {
PERFETTO_ELOG("No metrics specified on command line");
continue;
}
base::Status status = RunMetrics(trace_processor, options.metrics,
options.metric_v1_format);
if (!status.ok()) {
fprintf(stderr, "%s\n", status.c_message());
}
} else {
PrintShellUsage();
}
continue;
}
base::TimeNanos t_start = base::GetWallTimeNs();
auto it = trace_processor->ExecuteQuery(line.get());
PrintQueryResultInteractively(&it, t_start, column_width);
}
return base::OkStatus();
}
} // namespace perfetto::trace_processor