summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2015-04-20 18:07:17 -0700
committerYabin Cui <yabinc@google.com>2015-04-23 14:45:23 -0700
commit323e945313b190373b3fcfe578e25ee8390a76d3 (patch)
treee5019d88e283415b0f3d6e14c1c91df740110145
parent67d3abd7b26a741347b33402ad32f5c6735ca0bd (diff)
downloadextras-323e945313b190373b3fcfe578e25ee8390a76d3.tar.gz
Implement simpleperf stat subcommand.
Also add some simple unit-tests. Change-Id: Ic30a2d4a879e028a8c82babbaf82e322fc49a838
-rw-r--r--simpleperf/Android.mk73
-rw-r--r--simpleperf/cmd_list.cpp26
-rw-r--r--simpleperf/cmd_list_test.cpp25
-rw-r--r--simpleperf/cmd_stat.cpp345
-rw-r--r--simpleperf/cmd_stat_test.cpp52
-rw-r--r--simpleperf/command.cpp9
-rw-r--r--simpleperf/command.h2
-rw-r--r--simpleperf/command_test.cpp47
-rw-r--r--simpleperf/environment.cpp67
-rw-r--r--simpleperf/environment.h26
-rw-r--r--simpleperf/environment_test.cpp25
-rw-r--r--simpleperf/event_attr.cpp3
-rw-r--r--simpleperf/event_fd.cpp9
-rw-r--r--simpleperf/event_fd.h11
-rw-r--r--simpleperf/gtest_main.cpp25
-rw-r--r--simpleperf/main.cpp10
-rw-r--r--simpleperf/utils.cpp19
-rw-r--r--simpleperf/utils.h32
-rw-r--r--simpleperf/workload.cpp147
-rw-r--r--simpleperf/workload.h76
-rw-r--r--simpleperf/workload_test.cpp43
21 files changed, 1045 insertions, 27 deletions
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index 22bfd77c..a360896a 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -16,22 +16,48 @@
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
diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp
index dd35c299..224c795a 100644
--- a/simpleperf/cmd_list.cpp
+++ b/simpleperf/cmd_list.cpp
@@ -24,6 +24,17 @@
#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
index 00000000..d7e2afcc
--- /dev/null
+++ b/simpleperf/cmd_list_test.cpp
@@ -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
index 00000000..9ba4a561
--- /dev/null
+++ b/simpleperf/cmd_stat.cpp
@@ -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
index 00000000..acf668f8
--- /dev/null
+++ b/simpleperf/cmd_stat_test.cpp
@@ -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"}));
+}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index d26576a3..8b911fdc 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -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;
+ }
+ }
+}
diff --git a/simpleperf/command.h b/simpleperf/command.h
index a2e1923a..46b49cbe 100644
--- a/simpleperf/command.h
+++ b/simpleperf/command.h
@@ -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
index 00000000..dc2e4a6a
--- /dev/null
+++ b/simpleperf/command_test.cpp
@@ -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
index 00000000..14c256a5
--- /dev/null
+++ b/simpleperf/environment.cpp
@@ -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
index 00000000..b03e4896
--- /dev/null
+++ b/simpleperf/environment.h
@@ -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
index 00000000..a53f635d
--- /dev/null
+++ b/simpleperf/environment_test.cpp
@@ -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}));
+}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index a1ee3d94..418bf443 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -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);
}
diff --git a/simpleperf/event_fd.cpp b/simpleperf/event_fd.cpp
index 7c4ea44c..b7c1b4ce 100644
--- a/simpleperf/event_fd.cpp
+++ b/simpleperf/event_fd.cpp
@@ -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;
+}
diff --git a/simpleperf/event_fd.h b/simpleperf/event_fd.h
index 96286fb3..1fc97134 100644
--- a/simpleperf/event_fd.h
+++ b/simpleperf/event_fd.h
@@ -24,8 +24,17 @@
#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
index 00000000..33ec32fd
--- /dev/null
+++ b/simpleperf/gtest_main.cpp
@@ -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();
+}
diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp
index 017e4952..1f7c7daa 100644
--- a/simpleperf/main.cpp
+++ b/simpleperf/main.cpp
@@ -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;
}
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index 2b02bb16..f7819cbe 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -16,8 +16,12 @@
#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;
+}
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index dcbe74d6..b73dccd6 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -18,7 +18,39 @@
#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
index 00000000..46dfc404
--- /dev/null
+++ b/simpleperf/workload.cpp
@@ -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
index 00000000..dea8030f
--- /dev/null
+++ b/simpleperf/workload.h
@@ -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
index 00000000..5f0645f2
--- /dev/null
+++ b/simpleperf/workload_test.cpp
@@ -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());
+}