OSDN Git Service

Implement simpleperf stat subcommand.
authorYabin Cui <yabinc@google.com>
Tue, 21 Apr 2015 01:07:17 +0000 (18:07 -0700)
committerYabin Cui <yabinc@google.com>
Thu, 23 Apr 2015 21:45:23 +0000 (14:45 -0700)
Also add some simple unit-tests.

Change-Id: Ic30a2d4a879e028a8c82babbaf82e322fc49a838

21 files changed:
simpleperf/Android.mk
simpleperf/cmd_list.cpp
simpleperf/cmd_list_test.cpp [new file with mode: 0644]
simpleperf/cmd_stat.cpp [new file with mode: 0644]
simpleperf/cmd_stat_test.cpp [new file with mode: 0644]
simpleperf/command.cpp
simpleperf/command.h
simpleperf/command_test.cpp [new file with mode: 0644]
simpleperf/environment.cpp [new file with mode: 0644]
simpleperf/environment.h [new file with mode: 0644]
simpleperf/environment_test.cpp [new file with mode: 0644]
simpleperf/event_attr.cpp
simpleperf/event_fd.cpp
simpleperf/event_fd.h
simpleperf/gtest_main.cpp [new file with mode: 0644]
simpleperf/main.cpp
simpleperf/utils.cpp
simpleperf/utils.h
simpleperf/workload.cpp [new file with mode: 0644]
simpleperf/workload.h [new file with mode: 0644]
simpleperf/workload_test.cpp [new file with mode: 0644]

index 22bfd77..a360896 100644 (file)
 
 LOCAL_PATH := $(call my-dir)
 
-simpleperf_src_files := \
+simpleperf_common_cppflags := -std=c++11 -Wall -Wextra -Werror -Wunused
+
+libsimpleperf_src_files := \
   cmd_help.cpp \
   cmd_list.cpp \
+  cmd_stat.cpp \
   command.cpp \
+  environment.cpp \
   event_attr.cpp \
   event_fd.cpp \
   event_type.cpp \
-  main.cpp \
   utils.cpp \
+  workload.cpp \
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := $(libsimpleperf_src_files)
+LOCAL_STATIC_LIBRARIES := libbase libcutils liblog
+LOCAL_MODULE := libsimpleperf
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_STATIC_LIBRARY)
 
-simpleperf_cppflags := -std=c++11 -Wall -Wextra -Werror -Wunused
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := $(libsimpleperf_src_files)
+LOCAL_STATIC_LIBRARIES := libbase libcutils liblog
+LOCAL_LDLIBS := -lrt
+LOCAL_MODULE := libsimpleperf
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_STATIC_LIBRARY)
+endif
 
 include $(CLEAR_VARS)
 LOCAL_CLANG := true
-LOCAL_CPPFLAGS := $(simpleperf_cppflags)
-LOCAL_SRC_FILES := $(simpleperf_src_files)
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := main.cpp
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
 LOCAL_STATIC_LIBRARIES := libbase libcutils liblog
 LOCAL_MODULE := simpleperf
 LOCAL_MODULE_TAGS := optional
@@ -41,8 +67,9 @@ include $(BUILD_EXECUTABLE)
 ifeq ($(HOST_OS),linux)
 include $(CLEAR_VARS)
 LOCAL_CLANG := true
-LOCAL_CPPFLAGS := $(simpleperf_cppflags)
-LOCAL_SRC_FILES := $(simpleperf_src_files)
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := main.cpp
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
 LOCAL_STATIC_LIBRARIES := libbase libcutils liblog
 LOCAL_LDLIBS := -lrt
 LOCAL_MODULE := simpleperf
@@ -50,3 +77,35 @@ LOCAL_MODULE_TAGS := optional
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 include $(BUILD_HOST_EXECUTABLE)
 endif
+
+simpleperf_unit_test_src_files := \
+  cmd_list_test.cpp \
+  cmd_stat_test.cpp \
+  command_test.cpp \
+  environment_test.cpp \
+  gtest_main.cpp \
+  workload_test.cpp \
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := $(simpleperf_unit_test_src_files)
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
+LOCAL_STATIC_LIBRARIES := libbase libcutils liblog
+LOCAL_MODULE := simpleperf_unit_test
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_NATIVE_TEST)
+
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_CPPFLAGS := $(simpleperf_common_cppflags)
+LOCAL_SRC_FILES := $(simpleperf_unit_test_src_files)
+LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf
+LOCAL_STATIC_LIBRARIES := libbase libcutils liblog
+LOCAL_MODULE := simpleperf_unit_test
+LOCAL_MODULE_TAGS := optional
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_NATIVE_TEST)
+endif
index dd35c29..224c795 100644 (file)
 #include "event_type.h"
 #include "perf_event.h"
 
+static void PrintEventTypesOfType(uint32_t type, const char* type_name,
+                                  const std::vector<const EventType>& event_types) {
+  printf("List of %s:\n", type_name);
+  for (auto& event_type : event_types) {
+    if (event_type.type == type && event_type.IsSupportedByKernel()) {
+      printf("  %s\n", event_type.name);
+    }
+  }
+  printf("\n");
+}
+
 class ListCommand : public Command {
  public:
   ListCommand()
@@ -33,10 +44,6 @@ class ListCommand : public Command {
   }
 
   bool Run(const std::vector<std::string>& args) override;
-
- private:
-  void PrintEventTypesOfType(uint32_t type, const char* type_name,
-                             const std::vector<const EventType>& event_types);
 };
 
 bool ListCommand::Run(const std::vector<std::string>& args) {
@@ -53,15 +60,4 @@ bool ListCommand::Run(const std::vector<std::string>& args) {
   return true;
 }
 
-void ListCommand::PrintEventTypesOfType(uint32_t type, const char* type_name,
-                                        const std::vector<const EventType>& event_types) {
-  printf("List of %s:\n", type_name);
-  for (auto& event_type : event_types) {
-    if (event_type.type == type && event_type.IsSupportedByKernel()) {
-      printf("  %s\n", event_type.name);
-    }
-  }
-  printf("\n");
-}
-
 ListCommand list_command;
diff --git a/simpleperf/cmd_list_test.cpp b/simpleperf/cmd_list_test.cpp
new file mode 100644 (file)
index 0000000..d7e2afc
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <command.h>
+
+TEST(cmd_list, smoke) {
+  Command* list_cmd = Command::FindCommandByName("list");
+  ASSERT_TRUE(list_cmd != nullptr);
+  ASSERT_TRUE(list_cmd->Run({}));
+}
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
new file mode 100644 (file)
index 0000000..9ba4a56
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2015 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 <inttypes.h>
+#include <stdio.h>
+#include <chrono>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings.h>
+
+#include "command.h"
+#include "environment.h"
+#include "event_attr.h"
+#include "event_fd.h"
+#include "event_type.h"
+#include "perf_event.h"
+#include "utils.h"
+#include "workload.h"
+
+static std::vector<std::string> default_measured_event_types{
+    "cpu-cycles", "stalled-cycles-frontend", "stalled-cycles-backend", "instructions",
+    "branch-instructions", "branch-misses", "task-clock", "context-switches", "page-faults",
+};
+
+class StatCommandImpl {
+ public:
+  StatCommandImpl() : verbose_mode_(false), system_wide_collection_(false) {
+  }
+
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
+  bool AddMeasuredEventType(const std::string& event_type_name,
+                            bool report_unsupported_types = true);
+  bool AddDefaultMeasuredEventTypes();
+  bool OpenEventFilesForCpus(const std::vector<int>& cpus);
+  bool OpenEventFilesForProcess(pid_t pid);
+  bool StartCounting();
+  bool StopCounting();
+  bool ReadCounters();
+  bool ShowCounters(std::chrono::steady_clock::duration counting_duration);
+
+  struct EventElem {
+    const EventType* const event_type;
+    std::vector<std::unique_ptr<EventFd>> event_fds;
+    std::vector<PerfCounter> event_counters;
+    PerfCounter sum_counter;
+
+    EventElem(const EventType* event_type) : event_type(event_type) {
+    }
+  };
+
+  std::vector<EventElem> measured_events_;
+  bool verbose_mode_;
+  bool system_wide_collection_;
+};
+
+bool StatCommandImpl::Run(const std::vector<std::string>& args) {
+  // 1. Parse options.
+  std::vector<std::string> workload_args;
+  if (!ParseOptions(args, &workload_args)) {
+    return false;
+  }
+
+  // 2. Add default measured event types.
+  if (measured_events_.empty()) {
+    if (!AddDefaultMeasuredEventTypes()) {
+      return false;
+    }
+  }
+
+  // 3. Create workload.
+  if (workload_args.empty()) {
+    workload_args = std::vector<std::string>({"sleep", "1"});
+  }
+  std::unique_ptr<Workload> workload = Workload::CreateWorkload(workload_args);
+  if (workload == nullptr) {
+    return false;
+  }
+
+  // 4. Open perf_event_files.
+  if (system_wide_collection_) {
+    std::vector<int> cpus = GetOnlineCpus();
+    if (cpus.empty() || !OpenEventFilesForCpus(cpus)) {
+      return false;
+    }
+  } else {
+    if (!OpenEventFilesForProcess(workload->GetWorkPid())) {
+      return false;
+    }
+  }
+
+  // 5. Count events while workload running.
+  auto start_time = std::chrono::steady_clock::now();
+  if (!StartCounting()) {
+    return false;
+  }
+  if (!workload->Start()) {
+    return false;
+  }
+  workload->WaitFinish();
+  if (!StopCounting()) {
+    return false;
+  }
+  auto end_time = std::chrono::steady_clock::now();
+
+  // 6. Read and print counters.
+  if (!ReadCounters()) {
+    return false;
+  }
+  if (!ShowCounters(end_time - start_time)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool StatCommandImpl::ParseOptions(const std::vector<std::string>& args,
+                                   std::vector<std::string>* non_option_args) {
+  size_t i;
+  for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
+    if (args[i] == "-a") {
+      system_wide_collection_ = true;
+    } else if (args[i] == "-e") {
+      if (i + 1 == args.size()) {
+        LOG(ERROR) << "No event list following -e option. Try `simpleperf help stat`";
+        return false;
+      }
+      ++i;
+      std::vector<std::string> event_types = android::base::Split(args[i], ",");
+      for (auto& event_type : event_types) {
+        if (!AddMeasuredEventType(event_type)) {
+          return false;
+        }
+      }
+    } else if (args[i] == "--verbose") {
+      verbose_mode_ = true;
+    } else {
+      LOG(ERROR) << "Unknown option for stat command: " << args[i];
+      LOG(ERROR) << "Try `simpleperf help stat`";
+      return false;
+    }
+  }
+
+  if (non_option_args != nullptr) {
+    non_option_args->clear();
+    for (; i < args.size(); ++i) {
+      non_option_args->push_back(args[i]);
+    }
+  }
+  return true;
+}
+
+bool StatCommandImpl::AddMeasuredEventType(const std::string& event_type_name,
+                                           bool report_unsupported_types) {
+  const EventType* event_type = EventTypeFactory::FindEventTypeByName(event_type_name);
+  if (event_type == nullptr) {
+    LOG(ERROR) << "Unknown event_type: " << event_type_name;
+    LOG(ERROR) << "Try `simpleperf help list` to list all possible event type names";
+    return false;
+  }
+  if (!event_type->IsSupportedByKernel()) {
+    (report_unsupported_types ? LOG(ERROR) : LOG(DEBUG)) << "Event type " << event_type->name
+                                                         << " is not supported by the kernel";
+    return false;
+  }
+  measured_events_.push_back(EventElem(event_type));
+  return true;
+}
+
+bool StatCommandImpl::AddDefaultMeasuredEventTypes() {
+  for (auto& name : default_measured_event_types) {
+    // It is not an error when some event types in the default list are not supported by the kernel.
+    AddMeasuredEventType(name, false);
+  }
+  if (measured_events_.empty()) {
+    LOG(ERROR) << "Failed to add any supported default measured types";
+    return false;
+  }
+  return true;
+}
+
+bool StatCommandImpl::OpenEventFilesForCpus(const std::vector<int>& cpus) {
+  for (auto& elem : measured_events_) {
+    EventAttr attr = EventAttr::CreateDefaultAttrToMonitorEvent(*elem.event_type);
+    std::vector<std::unique_ptr<EventFd>> event_fds;
+    for (auto& cpu : cpus) {
+      auto event_fd = EventFd::OpenEventFileForCpu(attr, cpu);
+      if (event_fd != nullptr) {
+        event_fds.push_back(std::move(event_fd));
+      }
+    }
+    // As the online cpus can be enabled or disabled at runtime, we may not open perf_event_file
+    // for all cpus successfully. But we should open at least one cpu successfully for each event
+    // type.
+    if (event_fds.empty()) {
+      LOG(ERROR) << "failed to open perf_event_files for event_type " << elem.event_type->name
+                 << " on all cpus";
+      return false;
+    }
+    elem.event_fds = std::move(event_fds);
+  }
+  return true;
+}
+
+bool StatCommandImpl::OpenEventFilesForProcess(pid_t pid) {
+  for (auto& elem : measured_events_) {
+    EventAttr attr = EventAttr::CreateDefaultAttrToMonitorEvent(*elem.event_type);
+    std::vector<std::unique_ptr<EventFd>> event_fds;
+    auto event_fd = EventFd::OpenEventFileForProcess(attr, pid);
+    if (event_fd == nullptr) {
+      PLOG(ERROR) << "failed to open perf_event_file for event_type " << elem.event_type->name
+                  << " on pid " << pid;
+      return false;
+    }
+    event_fds.push_back(std::move(event_fd));
+    elem.event_fds = std::move(event_fds);
+  }
+  return true;
+}
+
+bool StatCommandImpl::StartCounting() {
+  for (auto& elem : measured_events_) {
+    for (auto& event_fd : elem.event_fds) {
+      if (!event_fd->EnableEvent()) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool StatCommandImpl::StopCounting() {
+  for (auto& elem : measured_events_) {
+    for (auto& event_fd : elem.event_fds) {
+      if (!event_fd->DisableEvent()) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool StatCommandImpl::ReadCounters() {
+  for (auto& elem : measured_events_) {
+    std::vector<PerfCounter> event_counters;
+    for (auto& event_fd : elem.event_fds) {
+      PerfCounter counter;
+      if (!event_fd->ReadCounter(&counter)) {
+        return false;
+      }
+      event_counters.push_back(counter);
+    }
+    PerfCounter sum_counter = event_counters.front();
+    for (size_t i = 1; i < event_counters.size(); ++i) {
+      sum_counter.value += event_counters[i].value;
+      sum_counter.time_enabled += event_counters[i].time_enabled;
+      sum_counter.time_running += event_counters[i].time_running;
+    }
+    elem.event_counters = event_counters;
+    elem.sum_counter = sum_counter;
+  }
+  return true;
+}
+
+bool StatCommandImpl::ShowCounters(std::chrono::steady_clock::duration counting_duration) {
+  printf("Performance counter statistics:\n\n");
+  for (auto& elem : measured_events_) {
+    std::string event_type_name = elem.event_type->name;
+
+    if (verbose_mode_) {
+      auto& event_fds = elem.event_fds;
+      auto& counters = elem.event_counters;
+      for (size_t i = 0; i < elem.event_fds.size(); ++i) {
+        printf("%s: value %'" PRId64 ", time_enabled %" PRId64 ", time_disabled %" PRId64
+               ", id %" PRId64 "\n",
+               event_fds[i]->Name().c_str(), counters[i].value, counters[i].time_enabled,
+               counters[i].time_running, counters[i].id);
+      }
+    }
+
+    auto& counter = elem.sum_counter;
+    bool scaled = false;
+    int64_t scaled_count = counter.value;
+    if (counter.time_running < counter.time_enabled) {
+      if (counter.time_running == 0) {
+        scaled_count = 0;
+      } else {
+        scaled = true;
+        scaled_count = static_cast<int64_t>(static_cast<double>(counter.value) *
+                                            counter.time_enabled / counter.time_running);
+      }
+    }
+    printf("%'30" PRId64 "%s  %s\n", scaled_count, scaled ? "(scaled)" : "       ",
+           event_type_name.c_str());
+  }
+  printf("\n");
+  printf("Total test time: %lf seconds.\n",
+         std::chrono::duration_cast<std::chrono::duration<double>>(counting_duration).count());
+  return true;
+}
+
+class StatCommand : public Command {
+ public:
+  StatCommand()
+      : Command("stat", "gather performance counter information",
+                "Usage: simpleperf stat [options] [command [command-args]]\n"
+                "    Gather performance counter information of running [command]. If [command]\n"
+                "    is not specified, sleep 1 is used instead.\n\n"
+                "    -a           Collect system-wide information.\n"
+                "    -e event1,event2,... Select the event list to count. Use `simpleperf list`\n"
+                "                         to find all possible event names.\n"
+                "    --verbose    Show result in verbose mode.\n"
+                "    --help       Print this help information.\n") {
+  }
+
+  bool Run(const std::vector<std::string>& args) override {
+    for (auto& arg : args) {
+      if (arg == "--help") {
+        printf("%s\n", LongHelpString().c_str());
+        return true;
+      }
+    }
+    StatCommandImpl impl;
+    return impl.Run(args);
+  }
+};
+
+StatCommand stat_command;
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
new file mode 100644 (file)
index 0000000..acf668f
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <chrono>
+
+#include <command.h>
+
+class StatCommandTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    stat_cmd = Command::FindCommandByName("stat");
+    ASSERT_TRUE(stat_cmd != nullptr);
+  }
+
+ protected:
+  Command* stat_cmd;
+};
+
+TEST_F(StatCommandTest, no_options) {
+  ASSERT_TRUE(stat_cmd->Run({}));
+}
+
+TEST_F(StatCommandTest, event_option) {
+  ASSERT_TRUE(stat_cmd->Run({"-e", "cpu-clock,task-clock"}));
+}
+
+TEST_F(StatCommandTest, system_wide_option) {
+  ASSERT_TRUE(stat_cmd->Run({"-a"}));
+}
+
+TEST_F(StatCommandTest, verbose_option) {
+  ASSERT_TRUE(stat_cmd->Run({"--verbose"}));
+}
+
+TEST_F(StatCommandTest, help_option) {
+  ASSERT_TRUE(stat_cmd->Run({"--help"}));
+}
index d26576a..8b911fd 100644 (file)
@@ -48,3 +48,12 @@ const std::vector<Command*>& Command::GetAllCommands() {
 void Command::RegisterCommand(Command* cmd) {
   Commands().push_back(cmd);
 }
+
+void Command::UnRegisterCommand(Command* cmd) {
+  for (auto it = Commands().begin(); it != Commands().end(); ++it) {
+    if (*it == cmd) {
+      Commands().erase(it);
+      break;
+    }
+  }
+}
index a2e1923..46b49cb 100644 (file)
@@ -31,6 +31,7 @@ class Command {
   }
 
   virtual ~Command() {
+    UnRegisterCommand(this);
   }
 
   const std::string& Name() const {
@@ -56,6 +57,7 @@ class Command {
   const std::string long_help_string_;
 
   static void RegisterCommand(Command* cmd);
+  static void UnRegisterCommand(Command* cmd);
 
   DISALLOW_COPY_AND_ASSIGN(Command);
 };
diff --git a/simpleperf/command_test.cpp b/simpleperf/command_test.cpp
new file mode 100644 (file)
index 0000000..dc2e4a6
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <command.h>
+
+class MockCommand : public Command {
+ public:
+  MockCommand(const std::string& name) : Command(name, name + "_short_help", name + "_long_help") {
+  }
+
+  bool Run(const std::vector<std::string>&) override {
+    return true;
+  }
+};
+
+TEST(command, FindCommandByName) {
+  ASSERT_EQ(Command::FindCommandByName("mock1"), nullptr);
+  {
+    MockCommand mock1("mock1");
+    ASSERT_EQ(Command::FindCommandByName("mock1"), &mock1);
+  }
+  ASSERT_EQ(Command::FindCommandByName("mock1"), nullptr);
+}
+
+TEST(command, GetAllCommands) {
+  size_t command_count = Command::GetAllCommands().size();
+  {
+    MockCommand mock1("mock1");
+    ASSERT_EQ(command_count + 1, Command::GetAllCommands().size());
+  }
+  ASSERT_EQ(command_count, Command::GetAllCommands().size());
+}
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
new file mode 100644 (file)
index 0000000..14c256a
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 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 "environment.h"
+
+#include <stdlib.h>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "utils.h"
+
+std::vector<int> GetOnlineCpus() {
+  std::vector<int> result;
+  FILE* fp = fopen("/sys/devices/system/cpu/online", "re");
+  if (fp == nullptr) {
+    PLOG(ERROR) << "can't open online cpu information";
+    return result;
+  }
+
+  LineReader reader(fp);
+  char* line;
+  if ((line = reader.ReadLine()) != nullptr) {
+    result = GetOnlineCpusFromString(line);
+  }
+  CHECK(!result.empty()) << "can't get online cpu information";
+  return result;
+}
+
+std::vector<int> GetOnlineCpusFromString(const std::string& s) {
+  std::vector<int> result;
+  bool have_dash = false;
+  const char* p = s.c_str();
+  char* endp;
+  long cpu;
+  // Parse line like: 0,1-3, 5, 7-8
+  while ((cpu = strtol(p, &endp, 10)) != 0 || endp != p) {
+    if (have_dash && result.size() > 0) {
+      for (int t = result.back() + 1; t < cpu; ++t) {
+        result.push_back(t);
+      }
+    }
+    have_dash = false;
+    result.push_back(cpu);
+    p = endp;
+    while (!isdigit(*p) && *p != '\0') {
+      if (*p == '-') {
+        have_dash = true;
+      }
+      ++p;
+    }
+  }
+  return result;
+}
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
new file mode 100644 (file)
index 0000000..b03e489
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 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 SIMPLE_PERF_ENVIRONMENT_H_
+#define SIMPLE_PERF_ENVIRONMENT_H_
+
+#include <string>
+#include <vector>
+
+std::vector<int> GetOnlineCpus();
+std::vector<int> GetOnlineCpusFromString(const std::string& s);
+
+#endif  // SIMPLE_PERF_ENVIRONMENT_H_
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
new file mode 100644 (file)
index 0000000..a53f635
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <environment.h>
+
+TEST(environment, GetOnlineCpusFromString) {
+  ASSERT_EQ(GetOnlineCpusFromString(""), std::vector<int>());
+  ASSERT_EQ(GetOnlineCpusFromString("0-2"), std::vector<int>({0, 1, 2}));
+  ASSERT_EQ(GetOnlineCpusFromString("0,2-3"), std::vector<int>({0, 2, 3}));
+}
index a1ee3d9..418bf44 100644 (file)
@@ -66,9 +66,12 @@ EventAttr EventAttr::CreateDefaultAttrToMonitorEvent(const EventType& event_type
   attr.config = event_type.config;
   attr.mmap = 1;
   attr.comm = 1;
+  // Changing read_format affects the layout of the data read from perf_event_file, namely
+  // PerfCounter in event_fd.h.
   attr.read_format =
       PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID;
   attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_PERIOD;
+  attr.disabled = 1;
   return EventAttr(attr);
 }
 
index 7c4ea44..b7c1b4c 100644 (file)
@@ -94,3 +94,12 @@ bool EventFd::DisableEvent() {
   }
   return true;
 }
+
+bool EventFd::ReadCounter(PerfCounter* counter) {
+  CHECK(counter != nullptr);
+  if (!ReadNBytesFromFile(perf_event_fd_, counter, sizeof(*counter))) {
+    PLOG(ERROR) << "ReadCounter from " << Name() << " failed";
+    return false;
+  }
+  return true;
+}
index 96286fb..1fc9713 100644 (file)
 
 #include <base/macros.h>
 
+#include "perf_event.h"
+
 class EventAttr;
 
+struct PerfCounter {
+  uint64_t value;         // The value of the event specified by the perf_event_file.
+  uint64_t time_enabled;  // The enabled time.
+  uint64_t time_running;  // The running time.
+  uint64_t id;            // The id of the perf_event_file.
+};
+
 // EventFd represents an opened perf_event_file.
 class EventFd {
  public:
@@ -44,6 +53,8 @@ class EventFd {
   // It tells the kernel to stop counting and recording events specified by this file.
   bool DisableEvent();
 
+  bool ReadCounter(PerfCounter* counter);
+
  private:
   EventFd(int perf_event_fd, const std::string& event_name, pid_t pid, int cpu)
       : perf_event_fd_(perf_event_fd), event_name_(event_name), pid_(pid), cpu_(cpu) {
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
new file mode 100644 (file)
index 0000000..33ec32f
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <base/logging.h>
+
+int main(int argc, char** argv) {
+  InitLogging(argv, android::base::StderrLogger);
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
index 017e495..1f7c7da 100644 (file)
@@ -39,12 +39,12 @@ int main(int argc, char** argv) {
     LOG(ERROR) << "malformed command line: unknown command " << args[0];
     return 1;
   }
+  std::string command_name = args[0];
   args.erase(args.begin());
+
+  LOG(DEBUG) << "command '" << command_name << "' starts running";
   bool result = command->Run(args);
-  if (result == true) {
-    LOG(DEBUG) << "run command " << args[0] << " successfully";
-  } else {
-    LOG(DEBUG) << "run command " << args[0] << "unsuccessfully";
-  }
+  LOG(DEBUG) << "command '" << command_name << "' "
+             << (result ? "finished successfully" : "failed");
   return result ? 0 : 1;
 }
index 2b02bb1..f7819cb 100644 (file)
 
 #include "utils.h"
 
+#include <errno.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <unistd.h>
+
+#include <base/logging.h>
 
 void PrintIndented(size_t indent, const char* fmt, ...) {
   va_list ap;
@@ -26,3 +30,18 @@ void PrintIndented(size_t indent, const char* fmt, ...) {
   vprintf(fmt, ap);
   va_end(ap);
 }
+
+bool ReadNBytesFromFile(int fd, void* buf, size_t nbytes) {
+  char* p = reinterpret_cast<char*>(buf);
+  size_t bytes_left = nbytes;
+  while (bytes_left > 0) {
+    ssize_t nread = TEMP_FAILURE_RETRY(read(fd, p, bytes_left));
+    if (nread <= 0) {
+      return false;
+    } else {
+      p += nread;
+      bytes_left -= nread;
+    }
+  }
+  return true;
+}
index dcbe74d..b73dccd 100644 (file)
 #define SIMPLE_PERF_UTILS_H_
 
 #include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
 
 void PrintIndented(size_t indent, const char* fmt, ...);
 
+class LineReader {
+ public:
+  LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
+  }
+
+  ~LineReader() {
+    free(buf_);
+    fclose(fp_);
+  }
+
+  char* ReadLine() {
+    if (getline(&buf_, &bufsize_, fp_) != -1) {
+      return buf_;
+    }
+    return nullptr;
+  }
+
+  size_t MaxLineSize() {
+    return bufsize_;
+  }
+
+ private:
+  FILE* fp_;
+  char* buf_;
+  size_t bufsize_;
+};
+
+bool ReadNBytesFromFile(int fd, void* buf, size_t nbytes);
+
 #endif  // SIMPLE_PERF_UTILS_H_
diff --git a/simpleperf/workload.cpp b/simpleperf/workload.cpp
new file mode 100644 (file)
index 0000000..46dfc40
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 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 "workload.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+std::unique_ptr<Workload> Workload::CreateWorkload(const std::vector<std::string>& args) {
+  std::unique_ptr<Workload> workload(new Workload(args));
+  if (workload != nullptr && workload->CreateNewProcess()) {
+    return workload;
+  }
+  return nullptr;
+}
+
+static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd, int exec_child_fd);
+
+bool Workload::CreateNewProcess() {
+  CHECK_EQ(work_state_, NotYetCreateNewProcess);
+
+  int start_signal_pipe[2];
+  if (pipe2(start_signal_pipe, O_CLOEXEC) != 0) {
+    PLOG(ERROR) << "pipe2() failed";
+    return false;
+  }
+
+  int exec_child_pipe[2];
+  if (pipe2(exec_child_pipe, O_CLOEXEC) != 0) {
+    PLOG(ERROR) << "pipe2() failed";
+    close(start_signal_pipe[0]);
+    close(start_signal_pipe[1]);
+    return false;
+  }
+
+  pid_t pid = fork();
+  if (pid == -1) {
+    PLOG(ERROR) << "fork() failed";
+    close(start_signal_pipe[0]);
+    close(start_signal_pipe[1]);
+    close(exec_child_pipe[0]);
+    close(exec_child_pipe[1]);
+    return false;
+  } else if (pid == 0) {
+    // In child process.
+    close(start_signal_pipe[1]);
+    close(exec_child_pipe[0]);
+    ChildProcessFn(args_, start_signal_pipe[0], exec_child_pipe[1]);
+  }
+  // In parent process.
+  close(start_signal_pipe[0]);
+  close(exec_child_pipe[1]);
+  start_signal_fd_ = start_signal_pipe[1];
+  exec_child_fd_ = exec_child_pipe[0];
+  work_pid_ = pid;
+  work_state_ = NotYetStartNewProcess;
+  return true;
+}
+
+static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd, int exec_child_fd) {
+  std::vector<char*> argv(args.size() + 1);
+  for (size_t i = 0; i < args.size(); ++i) {
+    argv[i] = &args[i][0];
+  }
+  argv[args.size()] = nullptr;
+
+  char start_signal = 0;
+  ssize_t nread = TEMP_FAILURE_RETRY(read(start_signal_fd, &start_signal, 1));
+  if (nread == 1 && start_signal == 1) {
+    close(start_signal_fd);
+    execvp(argv[0], argv.data());
+    // If execvp() succeed, we will not arrive here. But if it failed, we need to
+    // report the failure to the parent process by writing 1 to exec_child_fd.
+    int saved_errno = errno;
+    char exec_child_failed = 1;
+    TEMP_FAILURE_RETRY(write(exec_child_fd, &exec_child_failed, 1));
+    close(exec_child_fd);
+    errno = saved_errno;
+    PLOG(FATAL) << "execvp() failed";
+  } else {
+    PLOG(FATAL) << "child process failed to receive start_signal";
+  }
+}
+
+bool Workload::Start() {
+  CHECK_EQ(work_state_, NotYetStartNewProcess);
+  char start_signal = 1;
+  ssize_t nwrite = TEMP_FAILURE_RETRY(write(start_signal_fd_, &start_signal, 1));
+  if (nwrite != 1) {
+    PLOG(ERROR) << "write start signal failed";
+    return false;
+  }
+  char exec_child_failed;
+  ssize_t nread = TEMP_FAILURE_RETRY(read(exec_child_fd_, &exec_child_failed, 1));
+  if (nread != 0) {
+    LOG(ERROR) << "exec child failed";
+    return false;
+  }
+  work_state_ = Started;
+  return true;
+}
+
+bool Workload::IsFinished() {
+  if (work_state_ == Started) {
+    WaitChildProcess(true);
+  }
+  return work_state_ == Finished;
+}
+
+void Workload::WaitFinish() {
+  CHECK(work_state_ == Started || work_state_ == Finished);
+  if (work_state_ == Started) {
+    WaitChildProcess(false);
+  }
+}
+
+void Workload::WaitChildProcess(bool no_hang) {
+  int status;
+  pid_t result = TEMP_FAILURE_RETRY(waitpid(work_pid_, &status, (no_hang ? WNOHANG : 0)));
+  if (result == work_pid_) {
+    work_state_ = Finished;
+    if (WIFSIGNALED(status)) {
+      LOG(ERROR) << "work process was terminated by signal " << strsignal(WTERMSIG(status));
+    } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+      LOG(ERROR) << "work process exited with exit code " << WEXITSTATUS(status);
+    }
+  } else if (result == -1) {
+    PLOG(FATAL) << "waitpid() failed";
+  }
+}
diff --git a/simpleperf/workload.h b/simpleperf/workload.h
new file mode 100644 (file)
index 0000000..dea8030
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 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 SIMPLE_PERF_WORKLOAD_H_
+#define SIMPLE_PERF_WORKLOAD_H_
+
+#include <sys/types.h>
+#include <chrono>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+class Workload {
+ private:
+  enum WorkState {
+    NotYetCreateNewProcess,
+    NotYetStartNewProcess,
+    Started,
+    Finished,
+  };
+
+ public:
+  static std::unique_ptr<Workload> CreateWorkload(const std::vector<std::string>& args);
+
+  ~Workload() {
+    if (start_signal_fd_ != -1) {
+      close(start_signal_fd_);
+    }
+    if (exec_child_fd_ != -1) {
+      close(exec_child_fd_);
+    }
+  }
+
+  bool Start();
+  bool IsFinished();
+  void WaitFinish();
+  pid_t GetWorkPid() {
+    return work_pid_;
+  }
+
+ private:
+  Workload(const std::vector<std::string>& args)
+      : work_state_(NotYetCreateNewProcess),
+        args_(args),
+        work_pid_(-1),
+        start_signal_fd_(-1),
+        exec_child_fd_(-1) {
+  }
+
+  bool CreateNewProcess();
+  void WaitChildProcess(bool no_hang);
+
+  WorkState work_state_;
+  std::vector<std::string> args_;
+  pid_t work_pid_;
+  int start_signal_fd_;  // The parent process writes 1 to start workload in the child process.
+  int exec_child_fd_;    // The child process writes 1 to notify that execvp() failed.
+
+  DISALLOW_COPY_AND_ASSIGN(Workload);
+};
+
+#endif  // SIMPLE_PERF_WORKLOAD_H_
diff --git a/simpleperf/workload_test.cpp b/simpleperf/workload_test.cpp
new file mode 100644 (file)
index 0000000..5f0645f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 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 <gtest/gtest.h>
+
+#include <workload.h>
+
+#include <chrono>
+
+using namespace std::chrono;
+
+TEST(workload, smoke) {
+  auto workload = Workload::CreateWorkload({"sleep", "1"});
+  ASSERT_TRUE(workload != nullptr);
+  ASSERT_FALSE(workload->IsFinished());
+  ASSERT_TRUE(workload->GetWorkPid() != 0);
+  auto start_time = steady_clock::now();
+  ASSERT_TRUE(workload->Start());
+  ASSERT_FALSE(workload->IsFinished());
+  workload->WaitFinish();
+  ASSERT_TRUE(workload->IsFinished());
+  auto end_time = steady_clock::now();
+  ASSERT_TRUE(end_time >= start_time + seconds(1));
+}
+
+TEST(workload, execvp_failure) {
+  auto workload = Workload::CreateWorkload({"/dev/null"});
+  ASSERT_TRUE(workload != nullptr);
+  ASSERT_FALSE(workload->Start());
+}