diff options
author | Yabin Cui <yabinc@google.com> | 2015-04-20 18:07:17 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2015-04-23 14:45:23 -0700 |
commit | 323e945313b190373b3fcfe578e25ee8390a76d3 (patch) | |
tree | e5019d88e283415b0f3d6e14c1c91df740110145 | |
parent | 67d3abd7b26a741347b33402ad32f5c6735ca0bd (diff) | |
download | extras-323e945313b190373b3fcfe578e25ee8390a76d3.tar.gz |
Implement simpleperf stat subcommand.
Also add some simple unit-tests.
Change-Id: Ic30a2d4a879e028a8c82babbaf82e322fc49a838
-rw-r--r-- | simpleperf/Android.mk | 73 | ||||
-rw-r--r-- | simpleperf/cmd_list.cpp | 26 | ||||
-rw-r--r-- | simpleperf/cmd_list_test.cpp | 25 | ||||
-rw-r--r-- | simpleperf/cmd_stat.cpp | 345 | ||||
-rw-r--r-- | simpleperf/cmd_stat_test.cpp | 52 | ||||
-rw-r--r-- | simpleperf/command.cpp | 9 | ||||
-rw-r--r-- | simpleperf/command.h | 2 | ||||
-rw-r--r-- | simpleperf/command_test.cpp | 47 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 67 | ||||
-rw-r--r-- | simpleperf/environment.h | 26 | ||||
-rw-r--r-- | simpleperf/environment_test.cpp | 25 | ||||
-rw-r--r-- | simpleperf/event_attr.cpp | 3 | ||||
-rw-r--r-- | simpleperf/event_fd.cpp | 9 | ||||
-rw-r--r-- | simpleperf/event_fd.h | 11 | ||||
-rw-r--r-- | simpleperf/gtest_main.cpp | 25 | ||||
-rw-r--r-- | simpleperf/main.cpp | 10 | ||||
-rw-r--r-- | simpleperf/utils.cpp | 19 | ||||
-rw-r--r-- | simpleperf/utils.h | 32 | ||||
-rw-r--r-- | simpleperf/workload.cpp | 147 | ||||
-rw-r--r-- | simpleperf/workload.h | 76 | ||||
-rw-r--r-- | simpleperf/workload_test.cpp | 43 |
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()); +} |