OSDN Git Service

simpleperf: allow recording events in different speed.
authorYabin Cui <yabinc@google.com>
Thu, 3 Aug 2017 22:54:43 +0000 (15:54 -0700)
committerYabin Cui <yabinc@google.com>
Mon, 7 Aug 2017 17:26:22 +0000 (10:26 -0700)
Currently record command interface only allows one sample freq or sample period
for all events. This is not convenient when recording both non tracepoint events
and tracepoint events. This CL allows setting different sample speed for
different events. For example, for "-f 1000 -e cpu-cycles -c 1 sched:sched_switch",
"-f 1000" applies to cpu-cycles, and "-c 1" applies to sched:sched_switch.

It also fixes a bug about trace-offcpu: if "-f 1000 --trace-offcpu" is used,
the sched:sched_switch is be samples with "-f 1000". But we want to sample it
with "-c 1".

Also change the order of options in the help msg of record cmd to make it more
readable. Remove -F option. Because adding it seems not useful.

Bug: http://b/37572306
Test: run simpleperf_unit_test.

Change-Id: Ifdbd27c8f9fec49aade0e0e6ce624d8114042020

simpleperf/cmd_record.cpp
simpleperf/cmd_record_test.cpp
simpleperf/event_selection_set.cpp
simpleperf/event_selection_set.h

index 493f094..487e219 100644 (file)
@@ -78,26 +78,18 @@ class RecordCommand : public Command {
 "       Gather sampling information of running [command]. And -a/-p/-t option\n"
 "       can be used to change target of sampling information.\n"
 "       The default options are: -e cpu-cycles -f 4000 -o perf.data.\n"
+"Select monitored threads:\n"
 "-a     System-wide collection.\n"
 #if defined(__ANDROID__)
 "--app package_name    Profile the process of an Android application.\n"
 "                      On non-rooted devices, the app must be debuggable,\n"
 "                      because we use run-as to switch to the app's context.\n"
 #endif
-"-b     Enable take branch stack sampling. Same as '-j any'\n"
-"-c count     Set event sample period. It means recording one sample when\n"
-"             [count] events happen. Can't be used with -f/-F option.\n"
-"             For tracepoint events, the default option is -c 1.\n"
-"--call-graph fp | dwarf[,<dump_stack_size>]\n"
-"             Enable call graph recording. Use frame pointer or dwarf debug\n"
-"             frame as the method to parse call graph in stack.\n"
-"             Default is dwarf,65528.\n"
-"--cpu cpu_item1,cpu_item2,...\n"
-"             Collect samples only on the selected cpus. cpu_item can be cpu\n"
-"             number like 1, or cpu range like 0-3.\n"
-"--duration time_in_sec  Monitor for time_in_sec seconds instead of running\n"
-"                        [command]. Here time_in_sec may be any positive\n"
-"                        floating point number.\n"
+"-p pid1,pid2,...       Record events on existing processes. Mutually exclusive\n"
+"                       with -a.\n"
+"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
+"\n"
+"Select monitored event types:\n"
 "-e event1[:modifier1],event2[:modifier2],...\n"
 "             Select the event list to sample. Use `simpleperf list` to find\n"
 "             all possible event names. Modifiers can be added to define how\n"
@@ -105,15 +97,34 @@ class RecordCommand : public Command {
 "             Possible modifiers are:\n"
 "                u - monitor user space events only\n"
 "                k - monitor kernel space events only\n"
-"-f freq      Set event sample frequency. It means recording at most [freq]\n"
-"             samples every second. For non-tracepoint events, the default\n"
-"             option is -f 4000.\n"
-"-F freq      Same as '-f freq'.\n"
-"-g           Same as '--call-graph dwarf'.\n"
 "--group event1[:modifier],event2[:modifier2],...\n"
 "             Similar to -e option. But events specified in the same --group\n"
 "             option are monitored as a group, and scheduled in and out at the\n"
 "             same time.\n"
+"--trace-offcpu   Generate samples when threads are scheduled off cpu.\n"
+"                 Similar to \"-c 1 -e sched:sched_switch\".\n"
+"\n"
+"Select monitoring options:\n"
+"-f freq      Set event sample frequency. It means recording at most [freq]\n"
+"             samples every second. For non-tracepoint events, the default\n"
+"             option is -f 4000. A -f/-c option affects all event types\n"
+"             following it until meeting another -f/-c option. For example,"
+"             for \"-f 1000 cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
+"             has sample freq 1000, sched:sched_switch event has sample period 1.\n"
+"-c count     Set event sample period. It means recording one sample when\n"
+"             [count] events happen. For tracepoint events, the default option\n"
+"             is -c 1.\n"
+"--call-graph fp | dwarf[,<dump_stack_size>]\n"
+"             Enable call graph recording. Use frame pointer or dwarf debug\n"
+"             frame as the method to parse call graph in stack.\n"
+"             Default is dwarf,65528.\n"
+"-g           Same as '--call-graph dwarf'.\n"
+"--cpu cpu_item1,cpu_item2,...\n"
+"             Collect samples only on the selected cpus. cpu_item can be cpu\n"
+"             number like 1, or cpu range like 0-3.\n"
+"--duration time_in_sec  Monitor for time_in_sec seconds instead of running\n"
+"                        [command]. Here time_in_sec may be any positive\n"
+"                        floating point number.\n"
 "-j branch_filter1,branch_filter2,...\n"
 "             Enable taken branch stack sampling. Each sample captures a series\n"
 "             of consecutive taken branches.\n"
@@ -126,6 +137,7 @@ class RecordCommand : public Command {
 "                k: only when the branch target is in the kernel\n"
 "             This option requires at least one branch type among any, any_call,\n"
 "             any_ret, ind_call.\n"
+"-b           Enable taken branch stack sampling. Same as '-j any'.\n"
 "-m mmap_pages   Set the size of the buffer used to receiving sample data from\n"
 "                the kernel. It should be a power of 2. If not set, the max\n"
 "                possible value <= 1024 will be used.\n"
@@ -139,8 +151,6 @@ class RecordCommand : public Command {
 "              will be unwound by default. Use this option to disable the\n"
 "              unwinding of the user's stack.\n"
 "-o record_file_name    Set record file name, default is perf.data.\n"
-"-p pid1,pid2,...       Record events on existing processes. Mutually exclusive\n"
-"                       with -a.\n"
 "--post-unwind  If `--call-graph dwarf` option is used, then the user's stack\n"
 "               will be unwound while recording by default. But it may lose\n"
 "               records as stacking unwinding can be time consuming. Use this\n"
@@ -150,8 +160,6 @@ class RecordCommand : public Command {
 "--symfs <dir>    Look for files with symbols relative to this directory.\n"
 "                 This option is used to provide files with symbol table and\n"
 "                 debug information, which are used for unwinding and dumping symbols.\n"
-"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
-"--trace-offcpu   Generate samples when threads are scheduled off cpu.\n"
 #if 0
 // Below options are only used internally and shouldn't be visible to the public.
 "--in-app         We are already running in the app's context.\n"
@@ -159,10 +167,6 @@ class RecordCommand : public Command {
 #endif
             // clang-format on
             ),
-        use_sample_freq_(false),
-        sample_freq_(0),
-        use_sample_period_(false),
-        sample_period_(0),
         system_wide_collection_(false),
         branch_sampling_(0),
         fp_callchain_sampling_(false),
@@ -218,11 +222,7 @@ class RecordCommand : public Command {
   bool DumpMetaInfoFeature();
   void CollectHitFileInfo(const SampleRecord& r);
 
-  bool use_sample_freq_;
-  uint64_t sample_freq_;  // Sample 'sample_freq_' times per second.
-  bool use_sample_period_;
-  uint64_t sample_period_;  // Sample once when 'sample_period_' events occur.
-
+  std::unique_ptr<SampleSpeed> sample_speed_;
   bool system_wide_collection_;
   uint64_t branch_sampling_;
   bool fp_callchain_sampling_;
@@ -274,9 +274,13 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
     }
   }
   if (event_selection_set_.empty()) {
-    if (!event_selection_set_.AddEventType(default_measured_event_type)) {
+    size_t group_id;
+    if (!event_selection_set_.AddEventType(default_measured_event_type, &group_id)) {
       return false;
     }
+    if (sample_speed_) {
+      event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
+    }
   }
   exclude_kernel_callchain_ = event_selection_set_.ExcludeKernel();
   if (trace_offcpu_ && !TraceOffCpu()) {
@@ -422,6 +426,7 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
 
 bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
                                  std::vector<std::string>* non_option_args) {
+  std::vector<size_t> wait_setting_speed_event_groups_;
   size_t i;
   for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
     if (args[i] == "-a") {
@@ -433,17 +438,26 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
       app_package_name_ = args[i];
     } else if (args[i] == "-b") {
       branch_sampling_ = branch_sampling_type_map["any"];
-    } else if (args[i] == "-c") {
+    } else if (args[i] == "-c" || args[i] == "-f") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
       char* endptr;
-      sample_period_ = strtoull(args[i].c_str(), &endptr, 0);
-      if (*endptr != '\0' || sample_period_ == 0) {
-        LOG(ERROR) << "Invalid sample period: '" << args[i] << "'";
+      uint64_t value = strtoull(args[i].c_str(), &endptr, 0);
+      if (*endptr != '\0' || value == 0) {
+        LOG(ERROR) << "Invalid option for " << args[i-1] << ": '" << args[i] << "'";
         return false;
       }
-      use_sample_period_ = true;
+      if (args[i-1] == "-c") {
+        sample_speed_.reset(new SampleSpeed(0, value));
+      } else {
+        sample_speed_.reset(new SampleSpeed(AdjustSampleFrequency(value), 0));
+      }
+      for (auto group_id : wait_setting_speed_event_groups_) {
+        event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
+      }
+      wait_setting_speed_event_groups_.clear();
+
     } else if (args[i] == "--call-graph") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -501,20 +515,16 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
       }
       std::vector<std::string> event_types = android::base::Split(args[i], ",");
       for (auto& event_type : event_types) {
-        if (!event_selection_set_.AddEventType(event_type)) {
+        size_t group_id;
+        if (!event_selection_set_.AddEventType(event_type, &group_id)) {
           return false;
         }
+        if (sample_speed_) {
+          event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
+        } else {
+          wait_setting_speed_event_groups_.push_back(group_id);
+        }
       }
-    } else if (args[i] == "-f" || args[i] == "-F") {
-      if (!NextArgumentOrError(args, &i)) {
-        return false;
-      }
-      if (!android::base::ParseUint(args[i].c_str(), &sample_freq_)) {
-        LOG(ERROR) << "Invalid sample frequency: " << args[i];
-        return false;
-      }
-      sample_freq_ = AdjustSampleFrequency(sample_freq_);
-      use_sample_freq_ = true;
     } else if (args[i] == "-g") {
       fp_callchain_sampling_ = false;
       dwarf_callchain_sampling_ = true;
@@ -523,9 +533,15 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
         return false;
       }
       std::vector<std::string> event_types = android::base::Split(args[i], ",");
-      if (!event_selection_set_.AddEventGroup(event_types)) {
+      size_t group_id;
+      if (!event_selection_set_.AddEventGroup(event_types, &group_id)) {
         return false;
       }
+      if (sample_speed_) {
+        event_selection_set_.SetSampleSpeed(group_id, *sample_speed_);
+      } else {
+        wait_setting_speed_event_groups_.push_back(group_id);
+      }
     } else if (args[i] == "--in-app") {
       in_app_context_ = true;
     } else if (args[i] == "-j") {
@@ -616,11 +632,6 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
     }
   }
 
-  if (use_sample_freq_ && use_sample_period_) {
-    LOG(ERROR) << "-f option can't be used with -c option.";
-    return false;
-  }
-
   if (!dwarf_callchain_sampling_) {
     if (!unwind_dwarf_callchain_) {
       LOG(ERROR)
@@ -690,13 +701,6 @@ bool RecordCommand::TraceOffCpu() {
 }
 
 bool RecordCommand::SetEventSelectionFlags() {
-  if (use_sample_freq_) {
-    event_selection_set_.SetSampleFreq(sample_freq_);
-  } else if (use_sample_period_) {
-    event_selection_set_.SetSamplePeriod(sample_period_);
-  } else {
-    event_selection_set_.UseDefaultSampleFreq();
-  }
   event_selection_set_.SampleIdAll();
   if (!event_selection_set_.SetBranchSampling(branch_sampling_)) {
     return false;
index 5f42137..4873230 100644 (file)
@@ -61,8 +61,32 @@ TEST(record_cmd, system_wide_option) {
   TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a"})));
 }
 
+void CheckEventType(const std::string& record_file, const std::string event_type,
+                    uint64_t sample_period, uint64_t sample_freq) {
+  const EventType* type = FindEventTypeByName(event_type);
+  ASSERT_TRUE(type != nullptr);
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_file);
+  ASSERT_TRUE(reader);
+  std::vector<EventAttrWithId> attrs = reader->AttrSection();
+  for (auto& attr : attrs) {
+    if (attr.attr->type == type->type && attr.attr->config == type->config) {
+      if (attr.attr->freq == 0) {
+        ASSERT_EQ(sample_period, attr.attr->sample_period);
+        ASSERT_EQ(sample_freq, 0u);
+      } else {
+        ASSERT_EQ(sample_period, 0u);
+        ASSERT_EQ(sample_freq, attr.attr->sample_freq);
+      }
+      return;
+    }
+  }
+  FAIL();
+}
+
 TEST(record_cmd, sample_period_option) {
-  ASSERT_TRUE(RunRecordCmd({"-c", "100000"}));
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({"-c", "100000"}, tmpfile.path));
+  CheckEventType(tmpfile.path, "cpu-cycles", 100000u, 0);
 }
 
 TEST(record_cmd, event_option) {
@@ -70,11 +94,22 @@ TEST(record_cmd, event_option) {
 }
 
 TEST(record_cmd, freq_option) {
-  ASSERT_TRUE(RunRecordCmd({"-f", "99"}));
-  ASSERT_TRUE(RunRecordCmd({"-F", "99"}));
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({"-f", "99"}, tmpfile.path));
+  CheckEventType(tmpfile.path, "cpu-cycles", 0, 99u);
+  ASSERT_TRUE(RunRecordCmd({"-e", "cpu-clock", "-f", "99"}, tmpfile.path));
+  CheckEventType(tmpfile.path, "cpu-clock", 0, 99u);
   ASSERT_TRUE(RunRecordCmd({"-f", std::to_string(UINT_MAX)}));
 }
 
+TEST(record_cmd, multiple_freq_or_sample_period_option) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({"-f", "99", "-e", "cpu-cycles", "-c", "1000000", "-e",
+                            "cpu-clock"}, tmpfile.path));
+  CheckEventType(tmpfile.path, "cpu-cycles", 0, 99u);
+  CheckEventType(tmpfile.path, "cpu-clock", 1000000u, 0u);
+}
+
 TEST(record_cmd, output_file_option) {
   TemporaryFile tmpfile;
   ASSERT_TRUE(RecordCmd()->Run({"-o", tmpfile.path, "sleep", SLEEP_SEC}));
@@ -415,7 +450,7 @@ TEST(record_cmd, record_meta_info_feature) {
   TemporaryFile tmpfile;
   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
-  ASSERT_TRUE(reader != nullptr);
+  ASSERT_TRUE(reader);
   std::unordered_map<std::string, std::string> info_map;
   ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
   ASSERT_NE(info_map.find("simpleperf_version"), info_map.end());
@@ -443,10 +478,11 @@ TEST(record_cmd, trace_offcpu_option) {
   // On linux host, we need root privilege to read tracepoint events.
   TEST_REQUIRE_HOST_ROOT();
   TemporaryFile tmpfile;
-  ASSERT_TRUE(RunRecordCmd({"--trace-offcpu"}, tmpfile.path));
+  ASSERT_TRUE(RunRecordCmd({"--trace-offcpu", "-f", "1000"}, tmpfile.path));
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
-  ASSERT_TRUE(reader != nullptr);
+  ASSERT_TRUE(reader);
   std::unordered_map<std::string, std::string> info_map;
   ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map));
   ASSERT_EQ(info_map["trace_offcpu"], "true");
+  CheckEventType(tmpfile.path, "sched:sched_switch", 1u, 0u);
 }
index de04d3d..e16a27d 100644 (file)
@@ -128,6 +128,14 @@ bool EventSelectionSet::BuildAndCheckEventSelection(
   selection->event_attr.exclude_host = event_type->exclude_host;
   selection->event_attr.exclude_guest = event_type->exclude_guest;
   selection->event_attr.precise_ip = event_type->precise_ip;
+  if (event_type->event_type.type == PERF_TYPE_TRACEPOINT) {
+    selection->event_attr.freq = 0;
+    selection->event_attr.sample_period = DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
+  } else {
+    selection->event_attr.freq = 1;
+    selection->event_attr.sample_freq =
+        AdjustSampleFrequency(DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+  }
   if (!IsEventAttrSupported(selection->event_attr)) {
     LOG(ERROR) << "Event type '" << event_type->name
                << "' is not supported on the device";
@@ -147,12 +155,12 @@ bool EventSelectionSet::BuildAndCheckEventSelection(
   return true;
 }
 
-bool EventSelectionSet::AddEventType(const std::string& event_name) {
-  return AddEventGroup(std::vector<std::string>(1, event_name));
+bool EventSelectionSet::AddEventType(const std::string& event_name, size_t* group_id) {
+  return AddEventGroup(std::vector<std::string>(1, event_name), group_id);
 }
 
 bool EventSelectionSet::AddEventGroup(
-    const std::vector<std::string>& event_names) {
+    const std::vector<std::string>& event_names, size_t* group_id) {
   EventSelectionGroup group;
   for (const auto& event_name : event_names) {
     EventSelection selection;
@@ -163,6 +171,9 @@ bool EventSelectionSet::AddEventGroup(
   }
   groups_.push_back(std::move(group));
   UnionSampleType();
+  if (group_id != nullptr) {
+    *group_id = groups_.size() - 1;
+  }
   return true;
 }
 
@@ -284,37 +295,15 @@ void EventSelectionSet::SampleIdAll() {
   }
 }
 
-void EventSelectionSet::SetSampleFreq(uint64_t sample_freq) {
-  for (auto& group : groups_) {
-    for (auto& selection : group) {
+void EventSelectionSet::SetSampleSpeed(size_t group_id, const SampleSpeed& speed) {
+  CHECK_LT(group_id, groups_.size());
+  for (auto& selection : groups_[group_id]) {
+    if (speed.UseFreq()) {
       selection.event_attr.freq = 1;
-      selection.event_attr.sample_freq = sample_freq;
-    }
-  }
-}
-
-void EventSelectionSet::SetSamplePeriod(uint64_t sample_period) {
-  for (auto& group : groups_) {
-    for (auto& selection : group) {
+      selection.event_attr.sample_freq = speed.sample_freq;
+    } else {
       selection.event_attr.freq = 0;
-      selection.event_attr.sample_period = sample_period;
-    }
-  }
-}
-
-void EventSelectionSet::UseDefaultSampleFreq() {
-  for (auto& group : groups_) {
-    for (auto& selection : group) {
-      if (selection.event_type_modifier.event_type.type ==
-          PERF_TYPE_TRACEPOINT) {
-        selection.event_attr.freq = 0;
-        selection.event_attr.sample_period =
-            DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT;
-      } else {
-        selection.event_attr.freq = 1;
-        selection.event_attr.sample_freq =
-            DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT;
-      }
+      selection.event_attr.sample_period = speed.sample_period;
     }
   }
 }
index 3a3bd46..cc972be 100644 (file)
@@ -49,6 +49,20 @@ struct CountersInfo {
   std::vector<CounterInfo> counters;
 };
 
+struct SampleSpeed {
+  // There are two ways to set sample speed:
+  // 1. sample_freq: take [sample_freq] samples every second.
+  // 2. sample_period: take one sample every [sample_period] events happen.
+  uint64_t sample_freq;
+  uint64_t sample_period;
+  SampleSpeed(uint64_t freq = 0, uint64_t period = 0) : sample_freq(freq), sample_period(period) {}
+  bool UseFreq() const {
+    // Only use one way to set sample speed.
+    CHECK_NE(sample_freq != 0u, sample_period != 0u);
+    return sample_freq != 0u;
+  }
+};
+
 // EventSelectionSet helps to monitor events. It is used in following steps:
 // 1. Create an EventSelectionSet, and add event types to monitor by calling
 //    AddEventType() or AddEventGroup().
@@ -71,8 +85,8 @@ class EventSelectionSet {
 
   bool empty() const { return groups_.empty(); }
 
-  bool AddEventType(const std::string& event_name);
-  bool AddEventGroup(const std::vector<std::string>& event_names);
+  bool AddEventType(const std::string& event_name, size_t* group_id = nullptr);
+  bool AddEventGroup(const std::vector<std::string>& event_names, size_t* group_id = nullptr);
   std::vector<const EventType*> GetEvents() const;
   std::vector<const EventType*> GetTracepointEvents() const;
   bool ExcludeKernel() const;
@@ -82,9 +96,7 @@ class EventSelectionSet {
   void SetEnableOnExec(bool enable);
   bool GetEnableOnExec();
   void SampleIdAll();
-  void SetSampleFreq(uint64_t sample_freq);
-  void SetSamplePeriod(uint64_t sample_period);
-  void UseDefaultSampleFreq();
+  void SetSampleSpeed(size_t group_id, const SampleSpeed& speed);
   bool SetBranchSampling(uint64_t branch_sample_type);
   void EnableFpCallChainSampling();
   bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);