perfetto_cmd: Add cmdline flags
Add an atrace style interface to the perfetto command line.
This allows users to take a ftrace/atrace trace without writing
out a config.
Bug: 117592083
Change-Id: I07efcbf2feee309190cb97cd0eedde0ced4106aa
diff --git a/Android.bp b/Android.bp
index f265d74..147384b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -343,6 +343,7 @@
"src/ipc/host_impl.cc",
"src/ipc/service_proxy.cc",
"src/ipc/virtual_destructors.cc",
+ "src/perfetto_cmd/config.cc",
"src/perfetto_cmd/main.cc",
"src/perfetto_cmd/pbtxt_to_pb.cc",
"src/perfetto_cmd/perfetto_cmd.cc",
@@ -2283,6 +2284,8 @@
"src/ipc/service_proxy.cc",
"src/ipc/test/ipc_integrationtest.cc",
"src/ipc/virtual_destructors.cc",
+ "src/perfetto_cmd/config.cc",
+ "src/perfetto_cmd/config_unittest.cc",
"src/perfetto_cmd/pbtxt_to_pb.cc",
"src/perfetto_cmd/pbtxt_to_pb_unittest.cc",
"src/perfetto_cmd/perfetto_cmd.cc",
diff --git a/src/perfetto_cmd/BUILD.gn b/src/perfetto_cmd/BUILD.gn
index 175d2b0..768dc17 100644
--- a/src/perfetto_cmd/BUILD.gn
+++ b/src/perfetto_cmd/BUILD.gn
@@ -29,6 +29,8 @@
"../tracing:ipc_consumer",
]
sources = [
+ "config.cc",
+ "config.h",
"pbtxt_to_pb.cc",
"pbtxt_to_pb.h",
"perfetto_cmd.cc",
@@ -61,6 +63,7 @@
"../../protos/perfetto/config:lite",
]
sources = [
+ "config_unittest.cc",
"pbtxt_to_pb_unittest.cc",
"rate_limiter_unittest.cc",
]
diff --git a/src/perfetto_cmd/config.cc b/src/perfetto_cmd/config.cc
new file mode 100644
index 0000000..106d3ff
--- /dev/null
+++ b/src/perfetto_cmd/config.cc
@@ -0,0 +1,133 @@
+/*
+ * 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/config.h"
+
+#include <stdlib.h>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace {
+using protos::TraceConfig;
+using ValueUnit = std::pair<uint64_t, std::string>;
+using UnitMultipler = std::pair<const char*, uint64_t>;
+
+bool SplitValueAndUnit(const std::string& arg, ValueUnit* out) {
+ char* end;
+ if (!arg.size())
+ return false;
+ out->first = strtoull(arg.c_str(), &end, 10);
+ if (end == arg.data())
+ return false;
+ std::string unit = arg.substr(static_cast<size_t>(end - arg.data()));
+ out->second = std::move(unit);
+ return true;
+}
+
+bool ConvertValue(const std::string& arg,
+ std::vector<UnitMultipler> units,
+ uint64_t* out) {
+ if (arg.empty()) {
+ *out = 0;
+ return true;
+ }
+
+ ValueUnit value_unit{};
+ if (!SplitValueAndUnit(arg, &value_unit))
+ return false;
+
+ for (const auto& unit_multiplier : units) {
+ if (value_unit.second != unit_multiplier.first)
+ continue;
+ *out = value_unit.first * unit_multiplier.second;
+ return true;
+ }
+ return false;
+}
+
+bool ConvertTimeToMs(const std::string& arg, uint64_t* out) {
+ return ConvertValue(
+ arg,
+ {
+ {"ms", 1}, {"s", 1000}, {"m", 1000 * 60}, {"h", 1000 * 60 * 60},
+ },
+ out);
+}
+
+bool ConvertSizeToKb(const std::string& arg, uint64_t* out) {
+ return ConvertValue(arg,
+ {
+ {"kb", 1},
+ {"mb", 1024},
+ {"gb", 1024 * 1024},
+ {"k", 1},
+ {"m", 1024},
+ {"g", 1024 * 1024},
+ },
+ out);
+}
+
+} // namespace
+
+bool CreateConfigFromOptions(const ConfigOptions& options,
+ TraceConfig* config) {
+ uint64_t duration_ms = 0;
+ if (!ConvertTimeToMs(options.time, &duration_ms))
+ return false;
+
+ uint64_t buffer_size_kb = 0;
+ if (!ConvertSizeToKb(options.buffer_size, &buffer_size_kb))
+ return false;
+
+ uint64_t max_file_size_kb = 0;
+ if (!ConvertSizeToKb(options.max_file_size, &max_file_size_kb))
+ return false;
+
+ std::vector<std::string> ftrace_events;
+ std::vector<std::string> atrace_categories;
+ std::vector<std::string> atrace_apps = options.atrace_apps;
+
+ for (const auto& category : options.categories) {
+ if (category.find("/") == std::string::npos) {
+ atrace_categories.push_back(category);
+ } else {
+ ftrace_events.push_back(category);
+ }
+ }
+
+ config->set_duration_ms(static_cast<unsigned int>(duration_ms));
+ config->set_max_file_size_bytes(static_cast<unsigned int>(max_file_size_kb));
+ if (max_file_size_kb)
+ config->set_write_into_file(true);
+ config->add_buffers()->set_size_kb(static_cast<unsigned int>(buffer_size_kb));
+ auto* ds_config = config->add_data_sources()->mutable_config();
+ ds_config->set_name("linux.ftrace");
+ for (const auto& evt : ftrace_events)
+ ds_config->mutable_ftrace_config()->add_ftrace_events(evt);
+ for (const auto& cat : atrace_categories)
+ ds_config->mutable_ftrace_config()->add_atrace_categories(cat);
+ for (const auto& app : atrace_apps)
+ ds_config->mutable_ftrace_config()->add_atrace_apps(app);
+
+ auto* ps_config = config->add_data_sources()->mutable_config();
+ ps_config->set_name("linux.process_stats");
+ ps_config->set_target_buffer(0);
+
+ return true;
+}
+
+} // namespace perfetto
diff --git a/src/perfetto_cmd/config.h b/src/perfetto_cmd/config.h
new file mode 100644
index 0000000..6d4b48c
--- /dev/null
+++ b/src/perfetto_cmd/config.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_PERFETTO_CMD_CONFIG_H_
+#define SRC_PERFETTO_CMD_CONFIG_H_
+
+#include <string>
+#include <utility>
+
+#include "perfetto/config/trace_config.pb.h"
+
+namespace perfetto {
+
+// As an alternative to setting a full proto/pbtxt config users may also
+// pass a handful of flags for the most common settings. Turning those
+// options in a proto is the job of CreateConfigFromOptions.
+
+struct ConfigOptions {
+ std::string time;
+ std::string max_file_size;
+ std::string buffer_size = "32mb";
+ std::vector<std::string> atrace_apps;
+ std::vector<std::string> categories;
+};
+
+bool CreateConfigFromOptions(const ConfigOptions& options,
+ perfetto::protos::TraceConfig* trace_config_proto);
+
+} // namespace perfetto
+
+#endif // SRC_PERFETTO_CMD_CONFIG_H_
diff --git a/src/perfetto_cmd/config_unittest.cc b/src/perfetto_cmd/config_unittest.cc
new file mode 100644
index 0000000..527e9da
--- /dev/null
+++ b/src/perfetto_cmd/config_unittest.cc
@@ -0,0 +1,128 @@
+/*
+ * 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/config.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "perfetto/config/trace_config.pb.h"
+
+namespace perfetto {
+namespace {
+
+using testing::Contains;
+using protos::TraceConfig;
+
+class CreateConfigFromOptionsTest : public ::testing::Test {
+ public:
+ TraceConfig config;
+ ConfigOptions options;
+};
+
+TEST_F(CreateConfigFromOptionsTest, Default) {
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_FALSE(config.write_into_file());
+}
+
+TEST_F(CreateConfigFromOptionsTest, MilliSeconds) {
+ options.time = "2ms";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.duration_ms(), 2);
+}
+
+TEST_F(CreateConfigFromOptionsTest, Seconds) {
+ options.time = "100s";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.duration_ms(), 100 * 1000);
+}
+
+TEST_F(CreateConfigFromOptionsTest, Minutes) {
+ options.time = "2m";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.duration_ms(), 2 * 60 * 1000);
+}
+
+TEST_F(CreateConfigFromOptionsTest, Hours) {
+ options.time = "2h";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.duration_ms(), 2 * 60 * 60 * 1000);
+}
+
+TEST_F(CreateConfigFromOptionsTest, Kilobyte) {
+ options.buffer_size = "2kb";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.buffers().Get(0).size_kb(), 2);
+}
+
+TEST_F(CreateConfigFromOptionsTest, Megabyte) {
+ options.buffer_size = "2mb";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.buffers().Get(0).size_kb(), 2 * 1024);
+}
+
+TEST_F(CreateConfigFromOptionsTest, Gigabyte) {
+ options.buffer_size = "2gb";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.buffers().Get(0).size_kb(), 2 * 1024 * 1024);
+}
+
+TEST_F(CreateConfigFromOptionsTest, BadTrailingSpace) {
+ options.buffer_size = "2gb ";
+ ASSERT_FALSE(CreateConfigFromOptions(options, &config));
+}
+
+TEST_F(CreateConfigFromOptionsTest, UnmatchedUnit) {
+ options.buffer_size = "2ly";
+ ASSERT_FALSE(CreateConfigFromOptions(options, &config));
+}
+
+TEST_F(CreateConfigFromOptionsTest, OnlyNumber) {
+ options.buffer_size = "2";
+ ASSERT_FALSE(CreateConfigFromOptions(options, &config));
+}
+
+TEST_F(CreateConfigFromOptionsTest, OnlyUnit) {
+ options.buffer_size = "kb";
+ ASSERT_FALSE(CreateConfigFromOptions(options, &config));
+}
+
+TEST_F(CreateConfigFromOptionsTest, Empty) {
+ options.buffer_size = "";
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+}
+
+TEST_F(CreateConfigFromOptionsTest, FullConfig) {
+ options.buffer_size = "100mb";
+ options.max_file_size = "1gb";
+ options.time = "1h";
+ options.categories.push_back("sw");
+ options.categories.push_back("sched/sched_switch");
+ options.atrace_apps.push_back("com.android.chrome");
+ ASSERT_TRUE(CreateConfigFromOptions(options, &config));
+ EXPECT_EQ(config.duration_ms(), 60 * 60 * 1000);
+ EXPECT_EQ(config.max_file_size_bytes(), 1 * 1024 * 1024);
+ EXPECT_EQ(config.buffers().Get(0).size_kb(), 100 * 1024);
+ EXPECT_EQ(config.data_sources().Get(0).config().name(), "linux.ftrace");
+ EXPECT_EQ(config.data_sources().Get(0).config().target_buffer(), 0);
+ auto ftrace = config.data_sources().Get(0).config().ftrace_config();
+ EXPECT_THAT(ftrace.ftrace_events(), Contains("sched/sched_switch"));
+ EXPECT_THAT(ftrace.atrace_categories(), Contains("sw"));
+ EXPECT_THAT(ftrace.atrace_apps(), Contains("com.android.chrome"));
+}
+
+} // namespace
+} // namespace perfetto
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 84d8bf0..48a3740 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -40,6 +40,7 @@
#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/config.h"
#include "src/perfetto_cmd/pbtxt_to_pb.h"
#include "perfetto/config/trace_config.pb.h"
@@ -132,15 +133,25 @@
int PerfettoCmd::PrintUsage(const char* argv0) {
PERFETTO_ELOG(R"(
Usage: %s
- --background -b : Exits immediately and continues tracing in background
+ --background -d : 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).
+ --dropbox TAG : Upload trace into DropBox using tag TAG (default: %s)
+ --no-guardrails : Ignore guardrails triggered when using --dropbox (for testing).
+ --txt : Parse config as pbtxt. Not a stable API. Not for production use.
--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
+
+light configuration flags: (only when NOT using -c/--config)
+ --time -t : Trace duration N[s,m,h] (default: 10s)
+ --buffer -b : Ring buffer size N[mb,gb] (default: 32mb)
+ --size -s : Maximum trace size N[mb,gb] (default: 100mb)
+ ATRACE_CAT : Record ATRACE_CAT (e.g. wm)
+ FTRACE_GROUP/FTRACE_NAME : Record ftrace event (e.g. sched/sched_switch)
+ FTRACE_GROUP/* : Record all events in group (e.g. sched/*)
+
+
statsd-specific flags:
--alert-id : ID of the alert that triggered this trace.
--config-id : ID of the triggering config.
@@ -156,20 +167,28 @@
OPT_CONFIG_ID,
OPT_CONFIG_UID,
OPT_RESET_GUARDRAILS,
+ OPT_PBTXT_CONFIG,
+ OPT_DROPBOX,
+ OPT_ATRACE_APP,
+ OPT_IGNORE_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'},
+ {"background", no_argument, nullptr, 'd'},
+ {"time", required_argument, nullptr, 't'},
+ {"buffer", required_argument, nullptr, 'b'},
+ {"size", required_argument, nullptr, 's'},
+ {"no-guardrails", optional_argument, nullptr, OPT_IGNORE_GUARDRAILS},
+ {"txt", optional_argument, nullptr, OPT_PBTXT_CONFIG},
+ {"dropbox", optional_argument, nullptr, OPT_DROPBOX},
{"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},
+ {"app", required_argument, nullptr, OPT_ATRACE_APP},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@@ -181,9 +200,12 @@
perfetto::protos::TraceConfig::StatsdMetadata statsd_metadata;
RateLimiter limiter;
+ ConfigOptions config_options;
+ bool has_config_options = false;
+
for (;;) {
int option =
- getopt_long(argc, argv, "c:o:bd::nt", long_options, &option_index);
+ getopt_long(argc, argv, "c:o:dt:b:s:", long_options, &option_index);
if (option == -1)
break; // EOF.
@@ -220,6 +242,28 @@
}
if (option == 'd') {
+ background = true;
+ continue;
+ }
+ if (option == 't') {
+ has_config_options = true;
+ config_options.time = std::string(optarg);
+ continue;
+ }
+
+ if (option == 'b') {
+ has_config_options = true;
+ config_options.buffer_size = std::string(optarg);
+ continue;
+ }
+
+ if (option == 's') {
+ has_config_options = true;
+ config_options.max_file_size = std::string(optarg);
+ continue;
+ }
+
+ if (option == OPT_DROPBOX) {
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
dropbox_tag_ = optarg ? optarg : kDefaultDropBoxTag;
continue;
@@ -229,21 +273,16 @@
#endif
}
- if (option == 'b') {
- background = true;
- continue;
- }
-
- if (option == 'n') {
- ignore_guardrails = true;
- continue;
- }
-
- if (option == 't') {
+ if (option == OPT_PBTXT_CONFIG) {
parse_as_pbtxt = true;
continue;
}
+ if (option == OPT_IGNORE_GUARDRAILS) {
+ ignore_guardrails = true;
+ continue;
+ }
+
if (option == OPT_RESET_GUARDRAILS) {
PERFETTO_CHECK(limiter.ClearState());
PERFETTO_ILOG("Guardrail state cleared");
@@ -265,9 +304,20 @@
continue;
}
+ if (option == OPT_ATRACE_APP) {
+ config_options.atrace_apps.push_back(std::string(optarg));
+ has_config_options = true;
+ continue;
+ }
+
return PrintUsage(argv[0]);
}
+ for (ssize_t i = optind; i < argc; i++) {
+ has_config_options = true;
+ config_options.categories.push_back(argv[i]);
+ }
+
if (!trace_out_path_.empty() && !dropbox_tag_.empty()) {
PERFETTO_ELOG(
"Can't log to a file (--out) and DropBox (--dropbox) at the same "
@@ -279,25 +329,37 @@
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);
+ if (has_config_options) {
+ if (!trace_config_raw.empty()) {
+ PERFETTO_ELOG(
+ "Cannot specify both -c/--config and any of --time, --size, "
+ "--buffer, --app, ATRACE_CAT, FTRACE_EVENT");
+ return 1;
+ }
+ parsed = CreateConfigFromOptions(config_options, &trace_config_proto);
} else {
- parsed = trace_config_proto.ParseFromString(trace_config_raw);
+ if (trace_config_raw.empty()) {
+ PERFETTO_ELOG("The TraceConfig is empty");
+ return 1;
+ }
+
+ PERFETTO_DLOG("Parsing TraceConfig, %zu bytes", trace_config_raw.size());
+ 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;
+ }
}
- 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);