blob: ec5a1cec1fc23acf61285d84c16d54517ca9717b [file] [log] [blame]
/*
* Copyright (C) 2018 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/perfetto_cmd/perfetto_cmd.h"
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include "perfetto/base/file_utils.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/string_view.h"
#include "perfetto/base/time.h"
#include "perfetto/base/utils.h"
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/traced/traced.h"
#include "perfetto/tracing/core/data_source_config.h"
#include "perfetto/tracing/core/data_source_descriptor.h"
#include "perfetto/tracing/core/trace_config.h"
#include "perfetto/tracing/core/trace_packet.h"
#include "src/perfetto_cmd/pbtxt_to_pb.h"
#include "perfetto/config/trace_config.pb.h"
#include "src/tracing/ipc/default_socket.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
#include <android/os/DropBoxManager.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>
#endif // PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
namespace perfetto {
namespace {
constexpr char kDefaultDropBoxTag[] = "perfetto";
perfetto::PerfettoCmd* g_consumer_cmd;
class LoggingErrorReporter : public ErrorReporter {
public:
LoggingErrorReporter(std::string file_name, const char* config)
: file_name_(file_name), config_(config) {}
void AddError(size_t row,
size_t column,
size_t length,
const std::string& message) override {
parsed_successfully_ = false;
std::string line = ExtractLine(row - 1).ToStdString();
if (!line.empty() && line[line.length() - 1] == '\n') {
line.erase(line.length() - 1);
}
std::string guide(column + length, ' ');
for (size_t i = column; i < column + length; i++) {
guide[i - 1] = i == column ? '^' : '~';
}
fprintf(stderr, "%s:%zu:%zu error: %s\n", file_name_.c_str(), row, column,
message.c_str());
fprintf(stderr, "%s\n", line.c_str());
fprintf(stderr, "%s\n", guide.c_str());
}
bool Success() const { return parsed_successfully_; }
private:
base::StringView ExtractLine(size_t line) {
const char* start = config_;
const char* end = config_;
for (size_t i = 0; i < line + 1; i++) {
start = end;
char c;
while ((c = *end++) && c != '\n')
;
}
return base::StringView(start, static_cast<size_t>(end - start));
}
bool parsed_successfully_ = true;
std::string file_name_;
const char* config_;
};
bool ParseTraceConfigPbtxt(const std::string& file_name,
const std::string& pbtxt,
protos::TraceConfig* config) {
LoggingErrorReporter reporter(file_name, pbtxt.c_str());
std::vector<uint8_t> buf = PbtxtToPb(pbtxt, &reporter);
if (!reporter.Success())
return false;
if (!config->ParseFromArray(buf.data(), static_cast<int>(buf.size())))
return false;
return true;
}
} // namespace
// Temporary directory for DropBox traces. Note that this is automatically
// created by the system by setting setprop persist.traced.enable=1.
const char* kTempDropBoxTraceDir = "/data/misc/perfetto-traces";
using protozero::proto_utils::WriteVarInt;
using protozero::proto_utils::MakeTagLengthDelimited;
int PerfettoCmd::PrintUsage(const char* argv0) {
PERFETTO_ELOG(R"(
Usage: %s
--background -b : Exits immediately and continues tracing in background
--config -c : /path/to/trace/config/file or - for stdin
--out -o : /path/to/out/trace/file or - for stdout
--dropbox -d TAG : Upload trace into DropBox using tag TAG (default: %s)
--no-guardrails -n : Ignore guardrails triggered when using --dropbox (for testing).
--reset-guardrails : Resets the state of the guardails and exits (for testing).
--txt -t : Parse config as pbtxt. Not a stable API. Not for production use.
--help -h
statsd-specific flags:
--alert-id : ID of the alert that triggered this trace.
--config-id : ID of the triggering config.
--config-uid : UID of app which registered the config.
)",
argv0, kDefaultDropBoxTag);
return 1;
}
int PerfettoCmd::Main(int argc, char** argv) {
enum LongOption {
OPT_ALERT_ID = 1000,
OPT_CONFIG_ID,
OPT_CONFIG_UID,
OPT_RESET_GUARDRAILS,
};
static const struct option long_options[] = {
// |option_index| relies on the order of options, don't reshuffle them.
{"help", required_argument, nullptr, 'h'},
{"config", required_argument, nullptr, 'c'},
{"out", required_argument, nullptr, 'o'},
{"background", no_argument, nullptr, 'b'},
{"dropbox", optional_argument, nullptr, 'd'},
{"no-guardrails", optional_argument, nullptr, 'n'},
{"txt", optional_argument, nullptr, 't'},
{"alert-id", required_argument, nullptr, OPT_ALERT_ID},
{"config-id", required_argument, nullptr, OPT_CONFIG_ID},
{"config-uid", required_argument, nullptr, OPT_CONFIG_UID},
{"reset-guardrails", no_argument, nullptr, OPT_RESET_GUARDRAILS},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
std::string config_file_name;
std::string trace_config_raw;
bool background = false;
bool ignore_guardrails = false;
bool parse_as_pbtxt = false;
perfetto::protos::TraceConfig::StatsdMetadata statsd_metadata;
RateLimiter limiter;
for (;;) {
int option =
getopt_long(argc, argv, "c:o:bd::nt", long_options, &option_index);
if (option == -1)
break; // EOF.
if (option == 'c') {
config_file_name = std::string(optarg);
if (strcmp(optarg, "-") == 0) {
std::istreambuf_iterator<char> begin(std::cin), end;
trace_config_raw.assign(begin, end);
} else if (strcmp(optarg, ":test") == 0) {
// TODO(primiano): temporary for testing only.
perfetto::protos::TraceConfig test_config;
test_config.add_buffers()->set_size_kb(4096);
test_config.set_duration_ms(2000);
auto* ds_config = test_config.add_data_sources()->mutable_config();
ds_config->set_name("linux.ftrace");
ds_config->mutable_ftrace_config()->add_ftrace_events("sched_switch");
ds_config->mutable_ftrace_config()->add_ftrace_events("cpu_idle");
ds_config->mutable_ftrace_config()->add_ftrace_events("cpu_frequency");
ds_config->set_target_buffer(0);
test_config.SerializeToString(&trace_config_raw);
} else {
if (!base::ReadFile(optarg, &trace_config_raw)) {
PERFETTO_PLOG("Could not open %s", optarg);
return 1;
}
}
continue;
}
if (option == 'o') {
trace_out_path_ = optarg;
continue;
}
if (option == 'd') {
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
dropbox_tag_ = optarg ? optarg : kDefaultDropBoxTag;
continue;
#else
PERFETTO_ELOG("DropBox is only supported with Android tree builds");
return 1;
#endif
}
if (option == 'b') {
background = true;
continue;
}
if (option == 'n') {
ignore_guardrails = true;
continue;
}
if (option == 't') {
parse_as_pbtxt = true;
continue;
}
if (option == OPT_RESET_GUARDRAILS) {
PERFETTO_CHECK(limiter.ClearState());
PERFETTO_ILOG("Guardrail state cleared");
return 0;
}
if (option == OPT_ALERT_ID) {
statsd_metadata.set_triggering_alert_id(atoll(optarg));
continue;
}
if (option == OPT_CONFIG_ID) {
statsd_metadata.set_triggering_config_id(atoll(optarg));
continue;
}
if (option == OPT_CONFIG_UID) {
statsd_metadata.set_triggering_config_uid(atoi(optarg));
continue;
}
return PrintUsage(argv[0]);
}
if (!trace_out_path_.empty() && !dropbox_tag_.empty()) {
PERFETTO_ELOG(
"Can't log to a file (--out) and DropBox (--dropbox) at the same "
"time");
return 1;
}
if (trace_out_path_.empty() && dropbox_tag_.empty()) {
return PrintUsage(argv[0]);
}
if (trace_config_raw.empty()) {
PERFETTO_ELOG("The TraceConfig is empty");
return 1;
}
perfetto::protos::TraceConfig trace_config_proto;
PERFETTO_DLOG("Parsing TraceConfig, %zu bytes", trace_config_raw.size());
bool parsed;
if (parse_as_pbtxt) {
parsed = ParseTraceConfigPbtxt(config_file_name, trace_config_raw,
&trace_config_proto);
} else {
parsed = trace_config_proto.ParseFromString(trace_config_raw);
}
if (!parsed) {
PERFETTO_ELOG("Could not parse TraceConfig proto");
return 1;
}
*trace_config_proto.mutable_statsd_metadata() = std::move(statsd_metadata);
trace_config_.reset(new TraceConfig());
trace_config_->FromProto(trace_config_proto);
trace_config_raw.clear();
if (!OpenOutputFile())
return 1;
if (background) {
pid_t pid;
switch (pid = fork()) {
case -1:
PERFETTO_FATAL("fork");
case 0: {
PERFETTO_CHECK(setsid() != -1);
// daemon also ignores chdir errors.
chdir("/");
base::ScopedFile null = base::OpenFile("/dev/null", O_RDONLY);
PERFETTO_CHECK(null);
PERFETTO_CHECK(dup2(*null, STDIN_FILENO) != -1);
PERFETTO_CHECK(dup2(*null, STDOUT_FILENO) != -1);
PERFETTO_CHECK(dup2(*null, STDERR_FILENO) != -1);
// Do not accidentally close stdin/stdout/stderr.
if (*null <= 2)
null.release();
break;
}
default:
printf("%d\n", pid);
exit(0);
}
}
RateLimiter::Args args{};
args.is_dropbox = !dropbox_tag_.empty();
args.current_time = base::GetWallTimeS();
args.ignore_guardrails = ignore_guardrails;
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_USERDEBUG_BUILD)
args.max_upload_bytes_override =
trace_config_->guardrail_overrides().max_upload_per_day_bytes();
#endif
if (!limiter.ShouldTrace(args))
return 1;
consumer_endpoint_ =
ConsumerIPCClient::Connect(GetConsumerSocket(), this, &task_runner_);
SetupCtrlCSignalHandler();
task_runner_.Run();
return limiter.OnTraceDone(args, did_process_full_trace_, bytes_written_) ? 0
: 1;
}
void PerfettoCmd::OnConnect() {
PERFETTO_LOG(
"Connected to the Perfetto traced service, starting tracing for %d ms",
trace_config_->duration_ms());
PERFETTO_DCHECK(trace_config_);
trace_config_->set_enable_extra_guardrails(!dropbox_tag_.empty());
base::ScopedFile optional_fd;
if (trace_config_->write_into_file())
optional_fd.reset(dup(fileno(*trace_out_stream_)));
consumer_endpoint_->EnableTracing(*trace_config_, std::move(optional_fd));
// Failsafe mechanism to avoid waiting indefinitely if the service hangs.
if (trace_config_->duration_ms()) {
task_runner_.PostDelayedTask(std::bind(&PerfettoCmd::OnTimeout, this),
trace_config_->duration_ms() + 10000);
}
}
void PerfettoCmd::OnDisconnect() {
PERFETTO_LOG("Disconnected from the Perfetto traced service");
task_runner_.Quit();
}
void PerfettoCmd::OnTimeout() {
PERFETTO_ELOG("Timed out while waiting for trace from the service, aborting");
task_runner_.Quit();
}
void PerfettoCmd::OnTraceData(std::vector<TracePacket> packets, bool has_more) {
for (TracePacket& packet : packets) {
uint8_t preamble[16];
uint8_t* pos = preamble;
// ID of the |packet| field in trace.proto. Hardcoded as this we not depend
// on protos/trace:lite for binary size saving reasons.
static constexpr uint32_t kPacketFieldNumber = 1;
pos = WriteVarInt(MakeTagLengthDelimited(kPacketFieldNumber), pos);
pos = WriteVarInt(static_cast<uint32_t>(packet.size()), pos);
bytes_written_ +=
fwrite(reinterpret_cast<const char*>(preamble), 1,
static_cast<size_t>(pos - preamble), trace_out_stream_.get());
for (const Slice& slice : packet.slices()) {
bytes_written_ += fwrite(reinterpret_cast<const char*>(slice.start), 1,
slice.size, trace_out_stream_.get());
}
}
if (!has_more)
FinalizeTraceAndExit(); // Reached end of trace.
}
void PerfettoCmd::OnTracingDisabled() {
if (trace_config_->write_into_file()) {
// If write_into_file == true, at this point the passed file contains
// already all the packets.
return FinalizeTraceAndExit();
}
// This will cause a bunch of OnTraceData callbacks. The last one will
// save the file and exit.
consumer_endpoint_->ReadBuffers();
}
void PerfettoCmd::FinalizeTraceAndExit() {
fflush(*trace_out_stream_);
if (dropbox_tag_.empty()) {
trace_out_stream_.reset();
did_process_full_trace_ = true;
if (trace_config_->write_into_file()) {
PERFETTO_ILOG("Streamed output to %s", trace_out_path_ == "-"
? "stdout"
: trace_out_path_.c_str());
} else {
PERFETTO_ILOG(
"Wrote %" PRIu64 " bytes into %s", bytes_written_,
trace_out_path_ == "-" ? "stdout" : trace_out_path_.c_str());
}
} else {
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
android::sp<android::os::DropBoxManager> dropbox =
new android::os::DropBoxManager();
fseek(*trace_out_stream_, 0, SEEK_SET);
// DropBox takes ownership of the file descriptor, so give it a duplicate.
// Also we need to give it a read-only copy of the fd or will hit a SELinux
// violation (about system_server ending up with a writable FD to our dir).
char fdpath[64];
sprintf(fdpath, "/proc/self/fd/%d", fileno(*trace_out_stream_));
base::ScopedFile read_only_fd(base::OpenFile(fdpath, O_RDONLY));
PERFETTO_CHECK(read_only_fd);
trace_out_stream_.reset();
android::binder::Status status =
dropbox->addFile(android::String16(dropbox_tag_.c_str()),
read_only_fd.release(), 0 /* flags */);
if (status.isOk()) {
// TODO(hjd): Account for compression.
did_process_full_trace_ = true;
PERFETTO_ILOG("Uploaded %" PRIu64 " bytes into DropBox with tag %s",
bytes_written_, dropbox_tag_.c_str());
} else {
PERFETTO_ELOG("DropBox upload failed: %s", status.toString8().c_str());
}
#endif // PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
}
task_runner_.Quit();
}
bool PerfettoCmd::OpenOutputFile() {
base::ScopedFile fd;
if (!dropbox_tag_.empty()) {
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
// If we are tracing to DropBox, there's no need to make a
// filesystem-visible temporary file.
// TODO(skyostil): Fall back to base::TempFile for older devices.
fd = base::OpenFile(kTempDropBoxTraceDir, O_TMPFILE | O_RDWR, 0600);
if (!fd) {
PERFETTO_ELOG("Could not create a temporary trace file in %s",
kTempDropBoxTraceDir);
return false;
}
#else
PERFETTO_FATAL("Tracing to Dropbox requires the Android build.");
#endif
} else if (trace_out_path_ == "-") {
fd.reset(dup(STDOUT_FILENO));
} else {
fd = base::OpenFile(trace_out_path_, O_RDWR | O_CREAT | O_TRUNC, 0600);
}
trace_out_stream_.reset(fdopen(fd.release(), "wb"));
PERFETTO_CHECK(trace_out_stream_);
return true;
}
void PerfettoCmd::SetupCtrlCSignalHandler() {
// Setup the pipe used to deliver the CTRL-C notification from signal handler.
ctrl_c_pipe_ = base::Pipe::Create();
// Setup signal handler.
struct sigaction sa {};
// Glibc headers for sa_sigaction trigger this.
#pragma GCC diagnostic push
#if defined(__clang__)
#pragma GCC diagnostic ignored "-Wdisabled-macro-expansion"
#endif
sa.sa_handler = [](int) {
PERFETTO_LOG("SIGINT received: disabling tracing");
char one = '1';
PERFETTO_CHECK(base::WriteAll(g_consumer_cmd->ctrl_c_pipe_wr(), &one,
sizeof(one)) == 1);
};
sa.sa_flags = static_cast<decltype(sa.sa_flags)>(SA_RESETHAND | SA_RESTART);
#pragma GCC diagnostic pop
sigaction(SIGINT, &sa, nullptr);
task_runner_.AddFileDescriptorWatch(
*ctrl_c_pipe_.rd, [this] { consumer_endpoint_->DisableTracing(); });
}
int __attribute__((visibility("default")))
PerfettoCmdMain(int argc, char** argv) {
g_consumer_cmd = new perfetto::PerfettoCmd();
return g_consumer_cmd->Main(argc, argv);
}
} // namespace perfetto