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);