OSDN Git Service

simpleperf: Support event_source bus event types
authorNamhyung Kim <namhyung@google.com>
Wed, 23 Oct 2019 03:37:34 +0000 (12:37 +0900)
committerNamhyung Kim <namhyung@google.com>
Fri, 20 Dec 2019 08:23:59 +0000 (17:23 +0900)
The perf_event subsystem exports available PMUs and their events under
/sys/bus/event_source/devices directory.  This patch supports those
events using 'pmu/event/' format like in the mainline perf tool.

The "event" part above might have multiple terms if supported by PMU.
In that case PMU would have "format" directory for available terms.
They would be seperated by comma, so the enclosing "/"s are needed to
group those terms for an event.

But currently all PMUs seem to support a single term only so I left
the parser to split events by comma and match the whole string
(including "/"s).

For example you can use cpu-cycles events like below:

  # simpleperf stat -a -e armv8_pmuv3/cpu_cycles/ sleep 1
  Performance counter statistics:

    986,481,766  armv8_pmuv3/cpu_cycles/   #   (100%)

  Total test time: 1.007272 seconds.

Test: build and run simpleperf_unit_test
Change-Id: Ibdf1180e58d190f8108cdc8d8a096e6323b2a6f3

simpleperf/cmd_record_test.cpp
simpleperf/cmd_stat_test.cpp
simpleperf/event_type.cpp
simpleperf/event_type.h
simpleperf/test_util.h

index 14127c6..4093992 100644 (file)
@@ -281,6 +281,20 @@ bool HasHardwareCounter() {
   return has_hw_counter == 1;
 }
 
+bool HasPmuCounter() {
+  static int has_pmu_counter = -1;
+  if (has_pmu_counter == -1) {
+    has_pmu_counter = 0;
+    for (auto& event_type : GetAllEventTypes()) {
+      if (event_type.IsPmuEvent()) {
+        has_pmu_counter = 1;
+        break;
+      }
+    }
+  }
+  return has_pmu_counter == 1;
+}
+
 TEST(record_cmd, dwarf_callchain_sampling) {
   TEST_REQUIRE_HW_COUNTER();
   OMIT_TEST_ON_NON_NATIVE_ABIS();
@@ -977,4 +991,18 @@ TEST(record_cmd, include_filter_option) {
       ASSERT_EQ(dso, sleep_exec_path);
     }
   }
-}
\ No newline at end of file
+}
+
+TEST(record_cmd, pmu_event_option) {
+  TEST_REQUIRE_PMU_COUNTER();
+  std::string event_string;
+  if (GetBuildArch() == ARCH_X86_64) {
+    event_string = "cpu/cpu-cycles/";
+  } else if (GetBuildArch() == ARCH_ARM64) {
+    event_string = "armv8_pmuv3/cpu_cycles/";
+  } else {
+    GTEST_LOG_(INFO) << "Omit arch " << GetBuildArch();
+    return;
+  }
+  TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-e", event_string})));
+}
index 3668cb9..7b66ce5 100644 (file)
@@ -71,6 +71,21 @@ TEST(stat_cmd, rN_event) {
   ASSERT_TRUE(StatCmd()->Run({"-e", event_name, "sleep", "1"}));
 }
 
+TEST(stat_cmd, pmu_event) {
+  TEST_REQUIRE_PMU_COUNTER();
+  std::string event_string;
+  if (GetBuildArch() == ARCH_X86_64) {
+    event_string = "cpu/instructions/";
+  } else if (GetBuildArch() == ARCH_ARM64) {
+    event_string = "armv8_pmuv3/inst_retired/";
+  } else {
+    GTEST_LOG_(INFO) << "Omit arch " << GetBuildArch();
+    return;
+  }
+  TEST_IN_ROOT(ASSERT_TRUE(
+      StatCmd()->Run({"-a", "-e", event_string, "sleep", "1"})));
+}
+
 TEST(stat_cmd, event_modifier) {
   TEST_REQUIRE_HW_COUNTER();
   ASSERT_TRUE(
index ec7ec8e..556e104 100644 (file)
 
 using namespace simpleperf;
 
+struct EventFormat {
+  EventFormat(const std::string& name, const std::string& attr, int shift)
+      : name(name), attr(attr), shift(shift) {
+  }
+
+  std::string name;
+  std::string attr;
+  int shift;
+};
+
 #define EVENT_TYPE_TABLE_ENTRY(name, type, config, description, limited_arch) \
           {name, type, config, description, limited_arch},
 
@@ -115,6 +125,100 @@ static std::vector<EventType> GetTracepointEventTypes() {
   return result;
 }
 
+static std::vector<EventFormat> ParseEventFormats(const std::string& evtdev_path) {
+  std::vector<EventFormat> v;
+  std::string formats_dirname = evtdev_path + "/format/";
+  for (const auto& format_name : GetEntriesInDir(formats_dirname)) {
+    std::string format_path = formats_dirname + format_name;
+    std::string format_content;
+    if (!android::base::ReadFileToString(format_path, &format_content)) {
+      continue;
+    }
+
+    // format files look like below (currently only 'config' is supported) :
+    //   # cat armv8_pmuv3/format/event
+    //   config:0-15
+    int shift;
+    if (sscanf(format_content.c_str(), "config:%d", &shift) != 1) {
+      LOG(DEBUG) << "Invalid or unsupported event format: " << format_content;
+      continue;
+    }
+
+    v.emplace_back(EventFormat(format_name, "config", shift));
+  }
+  return v;
+}
+
+static uint64_t MakeEventConfig(const std::string& event_str, std::vector<EventFormat>& formats) {
+  uint64_t config = 0;
+
+  // event files might have multiple terms, but usually have a term like:
+  //   # cat armv8_pmuv3/events/cpu_cycles
+  //   event=0x011
+  for (auto& s : android::base::Split(event_str, ",")) {
+    auto pos = s.find("=");
+    if (pos == std::string::npos)
+      continue;
+
+    auto format = s.substr(0, pos);
+    long val;
+    if (!android::base::ParseInt(android::base::Trim(s.substr(pos+1)), &val)) {
+      LOG(DEBUG) << "Invalid event format '" << s << "'";
+      continue;
+    }
+
+    for (auto& f : formats) {
+      if (f.name == format) {
+        if (f.attr != "config") {
+          LOG(DEBUG) << "cannot support other attribute: " << s;
+          return ~0ULL;
+        }
+
+        config |= val << f.shift;
+        break;
+      }
+    }
+  }
+  return config;
+}
+
+static std::vector<EventType> GetPmuEventTypes() {
+  std::vector<EventType> result;
+  const std::string evtsrc_dirname = "/sys/bus/event_source/devices/";
+  for (const auto& device_name : GetSubDirs(evtsrc_dirname)) {
+    std::string evtdev_path = evtsrc_dirname + device_name;
+    std::string type_path = evtdev_path + "/type";
+    std::string type_content;
+
+    if (!android::base::ReadFileToString(type_path, &type_content)) {
+      LOG(DEBUG) << "cannot read event type: " << device_name;
+      continue;
+    }
+    uint64_t type_id = strtoull(type_content.c_str(), NULL, 10);
+
+    std::vector<EventFormat> formats = ParseEventFormats(evtdev_path);
+
+    std::string events_dirname = evtdev_path + "/events/";
+    for (const auto& event_name : GetEntriesInDir(events_dirname)) {
+      std::string event_path = events_dirname + event_name;
+      std::string event_content;
+      if (!android::base::ReadFileToString(event_path, &event_content)) {
+        LOG(DEBUG) << "cannot read event content in " << event_name;
+        continue;
+      }
+
+      uint64_t config = MakeEventConfig(event_content, formats);
+      if (config == ~0ULL) {
+        LOG(DEBUG) << "cannot handle config format in " << event_name;
+        continue;
+      }
+      result.emplace_back(EventType(device_name + "/" + event_name + "/",
+                                    type_id, config, "", ""));
+    }
+  }
+  return result;
+}
+
 std::string ScopedEventTypes::BuildString(const std::vector<const EventType*>& event_types) {
   std::string result;
   for (auto type : event_types) {
@@ -153,6 +257,8 @@ const std::set<EventType>& GetAllEventTypes() {
     g_event_types.insert(static_event_type_array.begin(), static_event_type_array.end());
     std::vector<EventType> tracepoint_array = GetTracepointEventTypes();
     g_event_types.insert(tracepoint_array.begin(), tracepoint_array.end());
+    std::vector<EventType> pmu_array = GetPmuEventTypes();
+    g_event_types.insert(pmu_array.begin(), pmu_array.end());
 #if defined(__linux__)
     std::unique_ptr<EventType> etm_type = ETMRecorder::GetInstance().BuildEventType();
     if (etm_type) {
@@ -262,4 +368,4 @@ std::unique_ptr<EventTypeAndModifier> ParseEventType(const std::string& event_ty
 
 bool IsEtmEventType(uint32_t type) {
   return g_etm_event_type != 0 && type == g_etm_event_type;
-}
\ No newline at end of file
+}
index 4421dfc..caedbac 100644 (file)
@@ -51,6 +51,10 @@ struct EventType {
     return strcasecmp(name.c_str(), other.name.c_str()) < 0;
   }
 
+  bool IsPmuEvent() const {
+    return name.find("/") != std::string::npos;
+  }
+
   std::string name;
   uint32_t type;
   uint64_t config;
index 660994a..81a6815 100644 (file)
@@ -69,6 +69,15 @@ bool HasHardwareCounter();
     } \
   } while (0)
 
+bool HasPmuCounter();
+#define TEST_REQUIRE_PMU_COUNTER() \
+  do { \
+    if (!HasPmuCounter()) { \
+      GTEST_LOG_(INFO) << "Skip this test as the machine doesn't have low-level PMU counters."; \
+      return; \
+    } \
+  } while (0)
+
 #if defined(IN_CTS_TEST)
 #define TEST_REQUIRE_APPS()
 #else