From ed91cd9ab32a3130bc2a4b15b0ac3cfca58c259f Mon Sep 17 00:00:00 2001 From: Yabin Cui Date: Tue, 28 Apr 2015 15:54:13 -0700 Subject: [PATCH] Implement simpleperf record/dumprecord subcommands. (cherry picked from commit 9759e1b1ce76185aa539aeea2fb1cbd8382156e7) Bug: 19483574 Change-Id: Id879713a75c2d3a6289d8847b95ee0bb4a2cc8a0 --- simpleperf/Android.mk | 25 ++- simpleperf/cmd_dumprecord.cpp | 179 +++++++++++++++++++++ simpleperf/cmd_dumprecord_test.cpp | 42 +++++ simpleperf/cmd_help.cpp | 4 +- simpleperf/cmd_list.cpp | 2 +- simpleperf/cmd_list_test.cpp | 2 +- simpleperf/cmd_record.cpp | 259 ++++++++++++++++++++++++++++++ simpleperf/cmd_record_test.cpp | 54 +++++++ simpleperf/cmd_stat.cpp | 211 +++++++----------------- simpleperf/cmd_stat_test.cpp | 14 +- simpleperf/command.cpp | 2 +- simpleperf/environment.h | 2 + simpleperf/environment_test.cpp | 2 +- simpleperf/event_attr.cpp | 96 ++++++----- simpleperf/event_attr.h | 43 +---- simpleperf/event_fd.cpp | 87 +++++++++- simpleperf/event_fd.h | 44 ++++- simpleperf/event_selection_set.cpp | 208 ++++++++++++++++++++++++ simpleperf/event_selection_set.h | 82 ++++++++++ simpleperf/event_type.cpp | 25 ++- simpleperf/event_type.h | 3 +- simpleperf/main.cpp | 9 +- simpleperf/record.cpp | 321 +++++++++++++++++++++++++++++++++++++ simpleperf/record.h | 180 +++++++++++++++++++++ simpleperf/record_file.cpp | 263 ++++++++++++++++++++++++++++++ simpleperf/record_file.h | 100 ++++++++++++ simpleperf/record_file_format.h | 85 ++++++++++ simpleperf/record_file_test.cpp | 74 +++++++++ simpleperf/utils.cpp | 23 ++- simpleperf/utils.h | 7 +- simpleperf/workload.cpp | 4 +- simpleperf/workload.h | 2 +- simpleperf/workload_test.cpp | 2 +- 33 files changed, 2155 insertions(+), 301 deletions(-) create mode 100644 simpleperf/cmd_dumprecord.cpp create mode 100644 simpleperf/cmd_dumprecord_test.cpp create mode 100644 simpleperf/cmd_record.cpp create mode 100644 simpleperf/cmd_record_test.cpp create mode 100644 simpleperf/event_selection_set.cpp create mode 100644 simpleperf/event_selection_set.h create mode 100644 simpleperf/record.cpp create mode 100644 simpleperf/record.h create mode 100644 simpleperf/record_file.cpp create mode 100644 simpleperf/record_file.h create mode 100644 simpleperf/record_file_format.h create mode 100644 simpleperf/record_file_test.cpp diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk index df37a223..80da24a6 100644 --- a/simpleperf/Android.mk +++ b/simpleperf/Android.mk @@ -18,15 +18,25 @@ LOCAL_PATH := $(call my-dir) simpleperf_common_cppflags := -std=c++11 -Wall -Wextra -Werror -Wunused +simpleperf_common_static_libraries := \ + libbase \ + libcutils \ + liblog \ + libsimpleperf_src_files := \ + cmd_dumprecord.cpp \ cmd_help.cpp \ cmd_list.cpp \ + cmd_record.cpp \ cmd_stat.cpp \ command.cpp \ environment.cpp \ event_attr.cpp \ event_fd.cpp \ + event_selection_set.cpp \ event_type.cpp \ + record.cpp \ + record_file.cpp \ utils.cpp \ workload.cpp \ @@ -34,7 +44,7 @@ include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := $(libsimpleperf_src_files) -LOCAL_STATIC_LIBRARIES := libbase libcutils liblog +LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) LOCAL_MODULE := libsimpleperf LOCAL_MODULE_TAGS := debug LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) @@ -46,7 +56,7 @@ include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := $(libsimpleperf_src_files) -LOCAL_STATIC_LIBRARIES := libbase libcutils liblog +LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) LOCAL_LDLIBS := -lrt LOCAL_MODULE := libsimpleperf LOCAL_MODULE_TAGS := optional @@ -59,7 +69,7 @@ LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := main.cpp LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf -LOCAL_STATIC_LIBRARIES := libbase libcutils liblog +LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) LOCAL_MODULE := simpleperf LOCAL_MODULE_TAGS := debug LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) @@ -72,7 +82,7 @@ LOCAL_CLANG := true LOCAL_CPPFLAGS := $(simpleperf_common_cppflags) LOCAL_SRC_FILES := main.cpp LOCAL_WHOLE_STATIC_LIBRARIES := libsimpleperf -LOCAL_STATIC_LIBRARIES := libbase libcutils liblog +LOCAL_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) LOCAL_LDLIBS := -lrt LOCAL_MODULE := simpleperf LOCAL_MODULE_TAGS := optional @@ -81,11 +91,14 @@ include $(BUILD_HOST_EXECUTABLE) endif simpleperf_unit_test_src_files := \ + cmd_dumprecord_test.cpp \ cmd_list_test.cpp \ + cmd_record_test.cpp \ cmd_stat_test.cpp \ command_test.cpp \ environment_test.cpp \ gtest_main.cpp \ + record_file_test.cpp \ workload_test.cpp \ include $(CLEAR_VARS) @@ -93,7 +106,7 @@ 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_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) LOCAL_MODULE := simpleperf_unit_test LOCAL_MODULE_TAGS := optional LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk @@ -105,7 +118,7 @@ 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_STATIC_LIBRARIES := $(simpleperf_common_static_libraries) LOCAL_MODULE := simpleperf_unit_test LOCAL_MODULE_TAGS := optional LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp new file mode 100644 index 00000000..4ee93942 --- /dev/null +++ b/simpleperf/cmd_dumprecord.cpp @@ -0,0 +1,179 @@ +/* + * 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 + +#include +#include +#include + +#include +#include + +#include "command.h" +#include "event_attr.h" +#include "record.h" +#include "record_file.h" + +using namespace PerfFileFormat; + +class DumpRecordCommandImpl { + public: + DumpRecordCommandImpl() : record_filename_("perf.data") { + } + + bool Run(const std::vector& args); + + private: + bool ParseOptions(const std::vector& args); + void DumpFileHeader(); + void DumpAttrSection(); + void DumpDataSection(); + + std::string record_filename_; + std::unique_ptr record_file_reader_; + + std::vector features_; +}; + +bool DumpRecordCommandImpl::Run(const std::vector& args) { + if (!ParseOptions(args)) { + return false; + } + record_file_reader_ = RecordFileReader::CreateInstance(record_filename_); + if (record_file_reader_ == nullptr) { + return false; + } + DumpFileHeader(); + DumpAttrSection(); + DumpDataSection(); + + return true; +} + +bool DumpRecordCommandImpl::ParseOptions(const std::vector& args) { + if (args.size() == 2) { + record_filename_ = args[1]; + } + return true; +} + +static const std::string GetFeatureName(int feature); + +void DumpRecordCommandImpl::DumpFileHeader() { + const FileHeader* header = record_file_reader_->FileHeader(); + printf("magic: "); + for (size_t i = 0; i < 8; ++i) { + printf("%c", header->magic[i]); + } + printf("\n"); + printf("header_size: %" PRId64 "\n", header->header_size); + if (header->header_size != sizeof(*header)) { + PLOG(WARNING) << "record file header size doesn't match expected header size " + << sizeof(*header); + } + printf("attr_size: %" PRId64 "\n", header->attr_size); + if (header->attr_size != sizeof(FileAttr)) { + PLOG(WARNING) << "record file attr size doesn't match expected attr size " << sizeof(FileAttr); + } + printf("attrs[file section]: offset %" PRId64 ", size %" PRId64 "\n", header->attrs.offset, + header->attrs.size); + printf("data[file section]: offset %" PRId64 ", size %" PRId64 "\n", header->data.offset, + header->data.size); + printf("event_types[file section]: offset %" PRId64 ", size %" PRId64 "\n", + header->event_types.offset, header->event_types.size); + + features_.clear(); + for (size_t i = 0; i < FEAT_MAX_NUM; ++i) { + size_t j = i / 8; + size_t k = i % 8; + if ((header->features[j] & (1 << k)) != 0) { + features_.push_back(i); + } + } + for (auto& feature : features_) { + printf("feature: %s\n", GetFeatureName(feature).c_str()); + } +} + +static const std::string GetFeatureName(int feature) { + static std::map feature_name_map = { + {FEAT_TRACING_DATA, "tracing_data"}, + {FEAT_BUILD_ID, "build_id"}, + {FEAT_HOSTNAME, "hostname"}, + {FEAT_OSRELEASE, "osrelease"}, + {FEAT_VERSION, "version"}, + {FEAT_ARCH, "arch"}, + {FEAT_NRCPUS, "nrcpus"}, + {FEAT_CPUDESC, "cpudesc"}, + {FEAT_CPUID, "cpuid"}, + {FEAT_TOTAL_MEM, "total_mem"}, + {FEAT_CMDLINE, "cmdline"}, + {FEAT_EVENT_DESC, "event_desc"}, + {FEAT_CPU_TOPOLOGY, "cpu_topology"}, + {FEAT_NUMA_TOPOLOGY, "numa_topology"}, + {FEAT_BRANCH_STACK, "branck_stack"}, + {FEAT_PMU_MAPPINGS, "pmu_mappings"}, + {FEAT_GROUP_DESC, "group_desc"}, + }; + auto it = feature_name_map.find(feature); + if (it != feature_name_map.end()) { + return it->second; + } + return android::base::StringPrintf("unknown_feature(%d)", feature); +} + +void DumpRecordCommandImpl::DumpAttrSection() { + std::vector attrs = record_file_reader_->AttrSection(); + for (size_t i = 0; i < attrs.size(); ++i) { + auto& attr = attrs[i]; + printf("file_attr %zu:\n", i + 1); + DumpPerfEventAttr(attr->attr, 1); + printf(" ids[file_section]: offset %" PRId64 ", size %" PRId64 "\n", attr->ids.offset, + attr->ids.size); + std::vector ids = record_file_reader_->IdsForAttr(attr); + if (ids.size() > 0) { + printf(" ids:"); + for (auto& id : ids) { + printf(" %" PRId64, id); + } + printf("\n"); + } + } +} + +void DumpRecordCommandImpl::DumpDataSection() { + std::vector> records = record_file_reader_->DataSection(); + for (auto& record : records) { + record->Dump(); + } +} + +class DumpRecordCommand : public Command { + public: + DumpRecordCommand() + : Command("dump", "dump perf record file", + "Usage: simpleperf dumprecord [options] [perf_record_file]\n" + " Dump different parts of a perf record file. Default file is perf.data.\n") { + } + + bool Run(const std::vector& args) override { + DumpRecordCommandImpl impl; + return impl.Run(args); + } +}; + +DumpRecordCommand dumprecord_cmd; diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp new file mode 100644 index 00000000..90772eb4 --- /dev/null +++ b/simpleperf/cmd_dumprecord_test.cpp @@ -0,0 +1,42 @@ +/* + * 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 + +#include + +class DumpRecordCommandTest : public ::testing::Test { + protected: + virtual void SetUp() { + record_cmd = Command::FindCommandByName("record"); + ASSERT_TRUE(record_cmd != nullptr); + dumprecord_cmd = Command::FindCommandByName("dump"); + ASSERT_TRUE(dumprecord_cmd != nullptr); + } + + Command* record_cmd; + Command* dumprecord_cmd; +}; + +TEST_F(DumpRecordCommandTest, no_options) { + ASSERT_TRUE(record_cmd->Run({"record", "-a", "sleep", "1"})); + ASSERT_TRUE(dumprecord_cmd->Run({"dump"})); +} + +TEST_F(DumpRecordCommandTest, record_file_option) { + ASSERT_TRUE(record_cmd->Run({"record", "-a", "-o", "perf2.data", "sleep", "1"})); + ASSERT_TRUE(dumprecord_cmd->Run({"dump", "perf2.data"})); +} diff --git a/simpleperf/cmd_help.cpp b/simpleperf/cmd_help.cpp index bf08dba5..0f3839b3 100644 --- a/simpleperf/cmd_help.cpp +++ b/simpleperf/cmd_help.cpp @@ -39,10 +39,10 @@ class HelpCommand : public Command { }; bool HelpCommand::Run(const std::vector& args) { - if (args.empty()) { + if (args.size() == 1) { PrintShortHelp(); } else { - Command* cmd = Command::FindCommandByName(args[0]); + Command* cmd = Command::FindCommandByName(args[1]); if (cmd == nullptr) { LOG(ERROR) << "malformed command line: can't find help string for unknown command " << args[0]; LOG(ERROR) << "try using \"--help\""; diff --git a/simpleperf/cmd_list.cpp b/simpleperf/cmd_list.cpp index 224c795a..923a884f 100644 --- a/simpleperf/cmd_list.cpp +++ b/simpleperf/cmd_list.cpp @@ -47,7 +47,7 @@ class ListCommand : public Command { }; bool ListCommand::Run(const std::vector& args) { - if (!args.empty()) { + if (args.size() != 1) { LOG(ERROR) << "malformed command line: list subcommand needs no argument"; LOG(ERROR) << "try using \"help list\""; return false; diff --git a/simpleperf/cmd_list_test.cpp b/simpleperf/cmd_list_test.cpp index d7e2afcc..2368e0fb 100644 --- a/simpleperf/cmd_list_test.cpp +++ b/simpleperf/cmd_list_test.cpp @@ -21,5 +21,5 @@ TEST(cmd_list, smoke) { Command* list_cmd = Command::FindCommandByName("list"); ASSERT_TRUE(list_cmd != nullptr); - ASSERT_TRUE(list_cmd->Run({})); + ASSERT_TRUE(list_cmd->Run({"list"})); } diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp new file mode 100644 index 00000000..e835acd6 --- /dev/null +++ b/simpleperf/cmd_record.cpp @@ -0,0 +1,259 @@ +/* + * 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 +#include +#include +#include + +#include + +#include "command.h" +#include "environment.h" +#include "event_selection_set.h" +#include "event_type.h" +#include "record_file.h" +#include "utils.h" +#include "workload.h" + +static std::string default_measured_event_type = "cpu-cycles"; + +class RecordCommandImpl { + public: + RecordCommandImpl() + : use_sample_freq_(true), + sample_freq_(1000), + system_wide_collection_(false), + measured_event_type_(nullptr), + perf_mmap_pages_(256), + record_filename_("perf.data") { + // We need signal SIGCHLD to break poll(). + saved_sigchild_handler_ = signal(SIGCHLD, [](int) {}); + } + + ~RecordCommandImpl() { + signal(SIGCHLD, saved_sigchild_handler_); + } + + bool Run(const std::vector& args); + + static bool ReadMmapDataCallback(const char* data, size_t size); + + private: + bool ParseOptions(const std::vector& args, std::vector* non_option_args); + bool SetMeasuredEventType(const std::string& event_type_name); + void SetEventSelection(); + bool WriteData(const char* data, size_t size); + + bool use_sample_freq_; // Use sample_freq_ when true, otherwise using sample_period_. + uint64_t sample_freq_; // Sample 'sample_freq_' times per second. + uint64_t sample_period_; // Sample once when 'sample_period_' events occur. + + bool system_wide_collection_; + const EventType* measured_event_type_; + EventSelectionSet event_selection_set_; + + // mmap pages used by each perf event file, should be power of 2. + const size_t perf_mmap_pages_; + + std::string record_filename_; + std::unique_ptr record_file_writer_; + + sighandler_t saved_sigchild_handler_; +}; + +bool RecordCommandImpl::Run(const std::vector& args) { + // 1. Parse options, and use default measured event type if not given. + std::vector workload_args; + if (!ParseOptions(args, &workload_args)) { + return false; + } + if (measured_event_type_ == nullptr) { + if (!SetMeasuredEventType(default_measured_event_type)) { + return false; + } + } + SetEventSelection(); + + // 2. Create workload. + if (workload_args.empty()) { + // TODO: change default workload to sleep 99999, and run record until Ctrl-C. + workload_args = std::vector({"sleep", "1"}); + } + std::unique_ptr workload = Workload::CreateWorkload(workload_args); + if (workload == nullptr) { + return false; + } + + // 3. Open perf_event_files, create memory mapped buffers for perf_event_files, add prepare poll + // for perf_event_files. + if (system_wide_collection_) { + if (!event_selection_set_.OpenEventFilesForAllCpus()) { + return false; + } + } else { + event_selection_set_.EnableOnExec(); + if (!event_selection_set_.OpenEventFilesForProcess(workload->GetPid())) { + return false; + } + } + if (!event_selection_set_.MmapEventFiles(perf_mmap_pages_)) { + return false; + } + std::vector pollfds; + event_selection_set_.PreparePollForEventFiles(&pollfds); + + // 4. Open record file writer. + record_file_writer_ = RecordFileWriter::CreateInstance( + record_filename_, event_selection_set_.FindEventAttrByType(*measured_event_type_), + event_selection_set_.FindEventFdsByType(*measured_event_type_)); + if (record_file_writer_ == nullptr) { + return false; + } + + // 5. Dump records in mmap buffers of perf_event_files to output file while workload is running. + + // If monitoring only one process, we use the enable_on_exec flag, and don't need to start + // recording manually. + if (system_wide_collection_) { + if (!event_selection_set_.EnableEvents()) { + return false; + } + } + if (!workload->Start()) { + return false; + } + auto callback = + std::bind(&RecordCommandImpl::WriteData, this, std::placeholders::_1, std::placeholders::_2); + while (true) { + if (!event_selection_set_.ReadMmapEventData(callback)) { + return false; + } + if (workload->IsFinished()) { + break; + } + poll(&pollfds[0], pollfds.size(), -1); + } + + // 6. Close record file. + if (!record_file_writer_->Close()) { + return false; + } + return true; +} + +bool RecordCommandImpl::ParseOptions(const std::vector& args, + std::vector* non_option_args) { + size_t i; + for (i = 1; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) { + if (args[i] == "-a") { + system_wide_collection_ = true; + } else if (args[i] == "-c") { + if (!NextArgumentOrError(args, &i)) { + return false; + } + char* endptr; + sample_period_ = strtoull(args[i].c_str(), &endptr, 0); + if (*endptr != '\0' || sample_period_ == 0) { + LOG(ERROR) << "Invalid sample period: '" << args[i] << "'"; + return false; + } + use_sample_freq_ = false; + } else if (args[i] == "-e") { + if (!NextArgumentOrError(args, &i)) { + return false; + } + if (!SetMeasuredEventType(args[i])) { + return false; + } + } else if (args[i] == "-f" || args[i] == "-F") { + if (!NextArgumentOrError(args, &i)) { + return false; + } + char* endptr; + sample_freq_ = strtoull(args[i].c_str(), &endptr, 0); + if (*endptr != '\0' || sample_freq_ == 0) { + LOG(ERROR) << "Invalid sample frequency: '" << args[i] << "'"; + return false; + } + use_sample_freq_ = true; + } else if (args[i] == "-o") { + if (!NextArgumentOrError(args, &i)) { + return false; + } + record_filename_ = args[i]; + } else { + LOG(ERROR) << "Unknown option for record command: '" << args[i] << "'\n"; + LOG(ERROR) << "Try `simpleperf help record`"; + 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 RecordCommandImpl::SetMeasuredEventType(const std::string& event_type_name) { + const EventType* event_type = EventTypeFactory::FindEventTypeByName(event_type_name); + if (event_type == nullptr) { + return false; + } + measured_event_type_ = event_type; + return true; +} + +void RecordCommandImpl::SetEventSelection() { + event_selection_set_.AddEventType(*measured_event_type_); + if (use_sample_freq_) { + event_selection_set_.SetSampleFreq(sample_freq_); + } else { + event_selection_set_.SetSamplePeriod(sample_period_); + } + event_selection_set_.SampleIdAll(); +} + +bool RecordCommandImpl::WriteData(const char* data, size_t size) { + return record_file_writer_->WriteData(data, size); +} + +class RecordCommand : public Command { + public: + RecordCommand() + : Command("record", "record sampling info in perf.data", + "Usage: simpleperf record [options] [command [command-args]]\n" + " Gather sampling information when running [command]. If [command]\n" + " is not specified, sleep 1 is used instead.\n" + " -a System-wide collection.\n" + " -c count Set event sample period.\n" + " -e event Select the event to sample (Use `simpleperf list`)\n" + " to find all possible event names.\n" + " -f freq Set event sample frequency.\n" + " -F freq Same as '-f freq'.\n" + " -o record_file_name Set record file name, default is perf.data.\n") { + } + + bool Run(const std::vector& args) override { + RecordCommandImpl impl; + return impl.Run(args); + } +}; + +RecordCommand record_command; diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp new file mode 100644 index 00000000..14fa0614 --- /dev/null +++ b/simpleperf/cmd_record_test.cpp @@ -0,0 +1,54 @@ +/* + * 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 + +#include + +class RecordCommandTest : public ::testing::Test { + protected: + virtual void SetUp() { + record_cmd = Command::FindCommandByName("record"); + ASSERT_TRUE(record_cmd != nullptr); + } + + Command* record_cmd; +}; + +TEST_F(RecordCommandTest, no_options) { + ASSERT_TRUE(record_cmd->Run({"record", "sleep", "1"})); +} + +TEST_F(RecordCommandTest, system_wide_option) { + ASSERT_TRUE(record_cmd->Run({"record", "-a", "sleep", "1"})); +} + +TEST_F(RecordCommandTest, sample_period_option) { + ASSERT_TRUE(record_cmd->Run({"record", "-c", "100000", "sleep", "1"})); +} + +TEST_F(RecordCommandTest, event_option) { + ASSERT_TRUE(record_cmd->Run({"record", "-e", "cpu-clock", "sleep", "1"})); +} + +TEST_F(RecordCommandTest, freq_option) { + ASSERT_TRUE(record_cmd->Run({"record", "-f", "99", "sleep", "1"})); + ASSERT_TRUE(record_cmd->Run({"record", "-F", "99", "sleep", "1"})); +} + +TEST_F(RecordCommandTest, output_file_option) { + ASSERT_TRUE(record_cmd->Run({"record", "-o", "perf2.data", "sleep", "1"})); +} diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp index 9ba4a561..c8e59d97 100644 --- a/simpleperf/cmd_stat.cpp +++ b/simpleperf/cmd_stat.cpp @@ -25,8 +25,7 @@ #include "command.h" #include "environment.h" -#include "event_attr.h" -#include "event_fd.h" +#include "event_selection_set.h" #include "event_type.h" #include "perf_event.h" #include "utils.h" @@ -46,27 +45,12 @@ class StatCommandImpl { private: bool ParseOptions(const std::vector& args, std::vector* non_option_args); - bool AddMeasuredEventType(const std::string& event_type_name, - bool report_unsupported_types = true); + bool AddMeasuredEventType(const std::string& event_type_name, bool report_unsupported_type = true); bool AddDefaultMeasuredEventTypes(); - bool OpenEventFilesForCpus(const std::vector& cpus); - bool OpenEventFilesForProcess(pid_t pid); - bool StartCounting(); - bool StopCounting(); - bool ReadCounters(); - bool ShowCounters(std::chrono::steady_clock::duration counting_duration); + bool ShowCounters(const std::map>& counters_map, + std::chrono::steady_clock::duration counting_duration); - struct EventElem { - const EventType* const event_type; - std::vector> event_fds; - std::vector event_counters; - PerfCounter sum_counter; - - EventElem(const EventType* event_type) : event_type(event_type) { - } - }; - - std::vector measured_events_; + EventSelectionSet event_selection_set_; bool verbose_mode_; bool system_wide_collection_; }; @@ -79,7 +63,7 @@ bool StatCommandImpl::Run(const std::vector& args) { } // 2. Add default measured event types. - if (measured_events_.empty()) { + if (event_selection_set_.Empty()) { if (!AddDefaultMeasuredEventTypes()) { return false; } @@ -87,6 +71,7 @@ bool StatCommandImpl::Run(const std::vector& args) { // 3. Create workload. if (workload_args.empty()) { + // TODO: change default workload to sleep 99999, and run stat until Ctrl-C. workload_args = std::vector({"sleep", "1"}); } std::unique_ptr workload = Workload::CreateWorkload(workload_args); @@ -96,53 +81,52 @@ bool StatCommandImpl::Run(const std::vector& args) { // 4. Open perf_event_files. if (system_wide_collection_) { - std::vector cpus = GetOnlineCpus(); - if (cpus.empty() || !OpenEventFilesForCpus(cpus)) { + if (!event_selection_set_.OpenEventFilesForAllCpus()) { return false; } } else { - if (!OpenEventFilesForProcess(workload->GetWorkPid())) { + event_selection_set_.EnableOnExec(); + if (!event_selection_set_.OpenEventFilesForProcess(workload->GetPid())) { return false; } } // 5. Count events while workload running. auto start_time = std::chrono::steady_clock::now(); - if (!StartCounting()) { - return false; + // If monitoring only one process, we use the enable_on_exec flag, and don't need to start + // counting manually. + if (system_wide_collection_) { + if (!event_selection_set_.EnableEvents()) { + 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()) { + std::map> counters_map; + if (!event_selection_set_.ReadCounters(&counters_map)) { return false; } - if (!ShowCounters(end_time - start_time)) { + if (!ShowCounters(counters_map, end_time - start_time)) { return false; } - return true; } bool StatCommandImpl::ParseOptions(const std::vector& args, std::vector* non_option_args) { size_t i; - for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) { + for (i = 1; 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`"; + if (!NextArgumentOrError(args, &i)) { return false; } - ++i; std::vector event_types = android::base::Split(args[i], ","); for (auto& event_type : event_types) { if (!AddMeasuredEventType(event_type)) { @@ -168,19 +152,13 @@ bool StatCommandImpl::ParseOptions(const std::vector& args, } bool StatCommandImpl::AddMeasuredEventType(const std::string& event_type_name, - bool report_unsupported_types) { - const EventType* event_type = EventTypeFactory::FindEventTypeByName(event_type_name); + bool report_unsupported_type) { + const EventType* event_type = + EventTypeFactory::FindEventTypeByName(event_type_name, report_unsupported_type); 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)); + event_selection_set_.AddEventType(*event_type); return true; } @@ -189,129 +167,52 @@ bool StatCommandImpl::AddDefaultMeasuredEventTypes() { // 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()) { + if (event_selection_set_.Empty()) { LOG(ERROR) << "Failed to add any supported default measured types"; return false; } return true; } -bool StatCommandImpl::OpenEventFilesForCpus(const std::vector& cpus) { - for (auto& elem : measured_events_) { - EventAttr attr = EventAttr::CreateDefaultAttrToMonitorEvent(*elem.event_type); - std::vector> 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> 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 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) { +bool StatCommandImpl::ShowCounters( + const std::map>& counters_map, + 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; + for (auto& pair : counters_map) { + auto& event_type = pair.first; + auto& counters = pair.second; 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 + for (auto& counter : counters) { + printf("%s: value %'" PRId64 ", time_enabled %" PRId64 ", time_running %" PRId64 ", id %" PRId64 "\n", - event_fds[i]->Name().c_str(), counters[i].value, counters[i].time_enabled, - counters[i].time_running, counters[i].id); + event_selection_set_.FindEventFileNameById(counter.id).c_str(), counter.value, + counter.time_enabled, counter.time_running, counter.id); } } - auto& counter = elem.sum_counter; + PerfCounter sum_counter; + memset(&sum_counter, 0, sizeof(sum_counter)); + for (auto& counter : counters) { + sum_counter.value += counter.value; + sum_counter.time_enabled += counter.time_enabled; + sum_counter.time_running += counter.time_running; + } bool scaled = false; - int64_t scaled_count = counter.value; - if (counter.time_running < counter.time_enabled) { - if (counter.time_running == 0) { + int64_t scaled_count = sum_counter.value; + if (sum_counter.time_running < sum_counter.time_enabled) { + if (sum_counter.time_running == 0) { scaled_count = 0; } else { scaled = true; - scaled_count = static_cast(static_cast(counter.value) * - counter.time_enabled / counter.time_running); + scaled_count = static_cast(static_cast(sum_counter.value) * + sum_counter.time_enabled / sum_counter.time_running); } } printf("%'30" PRId64 "%s %s\n", scaled_count, scaled ? "(scaled)" : " ", - event_type_name.c_str()); + event_type->name); } - printf("\n"); - printf("Total test time: %lf seconds.\n", + printf("\nTotal test time: %lf seconds.\n", std::chrono::duration_cast>(counting_duration).count()); return true; } @@ -326,17 +227,13 @@ class StatCommand : public Command { " -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") { + " --verbose Show result in verbose mode.\n") { } bool Run(const std::vector& args) override { - for (auto& arg : args) { - if (arg == "--help") { - printf("%s\n", LongHelpString().c_str()); - return true; - } - } + // Keep the implementation in StatCommandImpl, so the resources used are cleaned up when the + // command finishes. This is useful when we need to call some commands multiple times, like + // in unit tests. StatCommandImpl impl; return impl.Run(args); } diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp index acf668f8..bcbb1854 100644 --- a/simpleperf/cmd_stat_test.cpp +++ b/simpleperf/cmd_stat_test.cpp @@ -16,8 +16,6 @@ #include -#include - #include class StatCommandTest : public ::testing::Test { @@ -32,21 +30,17 @@ class StatCommandTest : public ::testing::Test { }; TEST_F(StatCommandTest, no_options) { - ASSERT_TRUE(stat_cmd->Run({})); + ASSERT_TRUE(stat_cmd->Run({"stat", "sleep", "1"})); } TEST_F(StatCommandTest, event_option) { - ASSERT_TRUE(stat_cmd->Run({"-e", "cpu-clock,task-clock"})); + ASSERT_TRUE(stat_cmd->Run({"stat", "-e", "cpu-clock,task-clock", "sleep", "1"})); } TEST_F(StatCommandTest, system_wide_option) { - ASSERT_TRUE(stat_cmd->Run({"-a"})); + ASSERT_TRUE(stat_cmd->Run({"stat", "-a", "sleep", "1"})); } TEST_F(StatCommandTest, verbose_option) { - ASSERT_TRUE(stat_cmd->Run({"--verbose"})); -} - -TEST_F(StatCommandTest, help_option) { - ASSERT_TRUE(stat_cmd->Run({"--help"})); + ASSERT_TRUE(stat_cmd->Run({"stat", "--verbose", "sleep", "1"})); } diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp index 8b911fdc..79cbc446 100644 --- a/simpleperf/command.cpp +++ b/simpleperf/command.cpp @@ -28,7 +28,7 @@ static std::vector& Commands() { } Command* Command::FindCommandByName(const std::string& cmd_name) { - for (auto command : Commands()) { + for (auto& command : Commands()) { if (command->Name() == cmd_name) { return command; } diff --git a/simpleperf/environment.h b/simpleperf/environment.h index b03e4896..fbc8cfb1 100644 --- a/simpleperf/environment.h +++ b/simpleperf/environment.h @@ -21,6 +21,8 @@ #include std::vector GetOnlineCpus(); + +// Expose the following functions for unit tests. std::vector GetOnlineCpusFromString(const std::string& s); #endif // SIMPLE_PERF_ENVIRONMENT_H_ diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp index a53f635d..398554d3 100644 --- a/simpleperf/environment_test.cpp +++ b/simpleperf/environment_test.cpp @@ -16,7 +16,7 @@ #include -#include +#include "environment.h" TEST(environment, GetOnlineCpusFromString) { ASSERT_EQ(GetOnlineCpusFromString(""), std::vector()); diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp index 418bf443..2b059312 100644 --- a/simpleperf/event_attr.cpp +++ b/simpleperf/event_attr.cpp @@ -26,8 +26,26 @@ #include "event_type.h" #include "utils.h" +static std::string BitsToString(const std::string& name, uint64_t bits, + const std::vector>& bit_names) { + std::string result; + for (auto& p : bit_names) { + if (bits & p.first) { + bits &= ~p.first; + if (!result.empty()) { + result += ", "; + } + result += p.second; + } + } + if (bits != 0) { + LOG(DEBUG) << "unknown " << name << " bits: " << std::hex << bits; + } + return result; +} + static std::string SampleTypeToString(uint64_t sample_type) { - std::unordered_map map = { + static std::vector> sample_type_names = { {PERF_SAMPLE_IP, "ip"}, {PERF_SAMPLE_TID, "tid"}, {PERF_SAMPLE_TIME, "time"}, @@ -40,25 +58,20 @@ static std::string SampleTypeToString(uint64_t sample_type) { {PERF_SAMPLE_STREAM_ID, "stream_id"}, {PERF_SAMPLE_RAW, "raw"}, }; + return BitsToString("sample_type", sample_type, sample_type_names); +} - std::string result; - for (auto p : map) { - if (sample_type & p.first) { - sample_type &= ~p.first; - if (!result.empty()) { - result += ", "; - } - result += p.second; - } - } - if (sample_type != 0) { - LOG(DEBUG) << "unknown sample_type bits: " << std::hex << sample_type; - } - - return result; +static std::string ReadFormatToString(uint64_t read_format) { + static std::vector> read_format_names = { + {PERF_FORMAT_TOTAL_TIME_ENABLED, "total_time_enabled"}, + {PERF_FORMAT_TOTAL_TIME_RUNNING, "total_time_running"}, + {PERF_FORMAT_ID, "id"}, + {PERF_FORMAT_GROUP, "group"}, + }; + return BitsToString("read_format", read_format, read_format_names); } -EventAttr EventAttr::CreateDefaultAttrToMonitorEvent(const EventType& event_type) { +perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type) { perf_event_attr attr; memset(&attr, 0, sizeof(attr)); attr.size = sizeof(perf_event_attr); @@ -66,52 +79,53 @@ EventAttr EventAttr::CreateDefaultAttrToMonitorEvent(const EventType& event_type attr.config = event_type.config; attr.mmap = 1; attr.comm = 1; + attr.disabled = 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); + return attr; } -void EventAttr::Dump(size_t indent) const { +void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) { std::string event_name = "unknown"; - const EventType* event_type = EventTypeFactory::FindEventTypeByConfig(attr_.type, attr_.config); + const EventType* event_type = EventTypeFactory::FindEventTypeByConfig(attr.type, attr.config); if (event_type != nullptr) { event_name = event_type->name; } - PrintIndented(indent, "event_attr_: for event %s\n", event_name.c_str()); + PrintIndented(indent, "event_attr: for event %s\n", event_name.c_str()); - PrintIndented(indent + 2, "type %u, size %u, config %llu\n", attr_.type, attr_.size, attr_.config); + PrintIndented(indent + 1, "type %u, size %u, config %llu\n", attr.type, attr.size, attr.config); - if (attr_.freq != 0) { - PrintIndented(indent + 2, "sample_freq %llu\n", attr_.sample_freq); + if (attr.freq != 0) { + PrintIndented(indent + 1, "sample_freq %llu\n", attr.sample_freq); } else { - PrintIndented(indent + 2, "sample_period %llu\n", attr_.sample_period); + PrintIndented(indent + 1, "sample_period %llu\n", attr.sample_period); } - PrintIndented(indent + 2, "sample_type (0x%llx) %s\n", attr_.sample_type, - SampleTypeToString(attr_.sample_type).c_str()); + PrintIndented(indent + 1, "sample_type (0x%llx) %s\n", attr.sample_type, + SampleTypeToString(attr.sample_type).c_str()); - PrintIndented(indent + 2, "read_format (0x%llx)\n", attr_.read_format); + PrintIndented(indent + 1, "read_format (0x%llx) %s\n", attr.read_format, + ReadFormatToString(attr.read_format).c_str()); - PrintIndented(indent + 2, "disabled %llu, inherit %llu, pinned %llu, exclusive %llu\n", - attr_.disabled, attr_.inherit, attr_.pinned, attr_.exclusive); + PrintIndented(indent + 1, "disabled %llu, inherit %llu, pinned %llu, exclusive %llu\n", + attr.disabled, attr.inherit, attr.pinned, attr.exclusive); - PrintIndented(indent + 2, "exclude_user %llu, exclude_kernel %llu, exclude_hv %llu\n", - attr_.exclude_user, attr_.exclude_kernel, attr_.exclude_hv); + PrintIndented(indent + 1, "exclude_user %llu, exclude_kernel %llu, exclude_hv %llu\n", + attr.exclude_user, attr.exclude_kernel, attr.exclude_hv); - PrintIndented(indent + 2, "exclude_idle %llu, mmap %llu, comm %llu, freq %llu\n", - attr_.exclude_idle, attr_.mmap, attr_.comm, attr_.freq); + PrintIndented(indent + 1, "exclude_idle %llu, mmap %llu, comm %llu, freq %llu\n", + attr.exclude_idle, attr.mmap, attr.comm, attr.freq); - PrintIndented(indent + 2, "inherit_stat %llu, enable_on_exec %llu, task %llu\n", - attr_.inherit_stat, attr_.enable_on_exec, attr_.task); + PrintIndented(indent + 1, "inherit_stat %llu, enable_on_exec %llu, task %llu\n", + attr.inherit_stat, attr.enable_on_exec, attr.task); - PrintIndented(indent + 2, "watermark %llu, precise_ip %llu, mmap_data %llu\n", attr_.watermark, - attr_.precise_ip, attr_.mmap_data); + PrintIndented(indent + 1, "watermark %llu, precise_ip %llu, mmap_data %llu\n", attr.watermark, + attr.precise_ip, attr.mmap_data); - PrintIndented(indent + 2, "sample_id_all %llu, exclude_host %llu, exclude_guest %llu\n", - attr_.sample_id_all, attr_.exclude_host, attr_.exclude_guest); + PrintIndented(indent + 1, "sample_id_all %llu, exclude_host %llu, exclude_guest %llu\n", + attr.sample_id_all, attr.exclude_host, attr.exclude_guest); } diff --git a/simpleperf/event_attr.h b/simpleperf/event_attr.h index 30052f14..52f4aca7 100644 --- a/simpleperf/event_attr.h +++ b/simpleperf/event_attr.h @@ -24,46 +24,7 @@ struct EventType; -// EventAttr manages perf_event_attr, which provides detailed configuration information when -// opening a perf_event_file. The configuration information tells the kernel how to count and -// record events. -class EventAttr { - public: - static EventAttr CreateDefaultAttrToMonitorEvent(const EventType& event_type); - - EventAttr(const perf_event_attr& attr) : attr_(attr) { - } - - perf_event_attr Attr() const { - return attr_; - } - - uint64_t SampleType() const { - return attr_.sample_type; - } - - void EnableOnExec() { - attr_.enable_on_exec = 1; - } - - void SetSampleFreq(uint64_t freq) { - attr_.freq = 1; - attr_.sample_freq = freq; - } - - void SetSamplePeriod(uint64_t period) { - attr_.freq = 0; - attr_.sample_period = period; - } - - void SetSampleAll() { - attr_.sample_id_all = 1; - } - - void Dump(size_t indent = 0) const; - - private: - perf_event_attr attr_; -}; +perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type); +void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent = 0); #endif // SIMPLE_PERF_EVENT_ATTR_H_ diff --git a/simpleperf/event_fd.cpp b/simpleperf/event_fd.cpp index b7c1b4ce..386685c2 100644 --- a/simpleperf/event_fd.cpp +++ b/simpleperf/event_fd.cpp @@ -19,15 +19,16 @@ #include #include #include +#include #include #include #include +#include #include #include #include "event_type.h" -#include "event_attr.h" #include "perf_event.h" #include "utils.h" @@ -36,16 +37,16 @@ static int perf_event_open(perf_event_attr* attr, pid_t pid, int cpu, int group_ return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags); } -std::unique_ptr EventFd::OpenEventFileForProcess(const EventAttr& attr, pid_t pid) { +std::unique_ptr EventFd::OpenEventFileForProcess(const perf_event_attr& attr, pid_t pid) { return OpenEventFile(attr, pid, -1); } -std::unique_ptr EventFd::OpenEventFileForCpu(const EventAttr& attr, int cpu) { +std::unique_ptr EventFd::OpenEventFileForCpu(const perf_event_attr& attr, int cpu) { return OpenEventFile(attr, -1, cpu); } -std::unique_ptr EventFd::OpenEventFile(const EventAttr& attr, pid_t pid, int cpu) { - perf_event_attr perf_attr = attr.Attr(); +std::unique_ptr EventFd::OpenEventFile(const perf_event_attr& attr, pid_t pid, int cpu) { + perf_event_attr perf_attr = attr; std::string event_name = "unknown event"; const EventType* event_type = EventTypeFactory::FindEventTypeByConfig(perf_attr.type, perf_attr.config); @@ -69,6 +70,9 @@ std::unique_ptr EventFd::OpenEventFile(const EventAttr& attr, pid_t pid } EventFd::~EventFd() { + if (mmap_addr_ != nullptr) { + munmap(mmap_addr_, mmap_len_); + } close(perf_event_fd_); } @@ -77,6 +81,16 @@ std::string EventFd::Name() const { event_name_.c_str(), pid_, cpu_); } +uint64_t EventFd::Id() const { + if (id_ == 0) { + PerfCounter counter; + if (ReadCounter(&counter)) { + id_ = counter.id; + } + } + return id_; +} + bool EventFd::EnableEvent() { int result = ioctl(perf_event_fd_, PERF_EVENT_IOC_ENABLE, 0); if (result < 0) { @@ -95,11 +109,70 @@ bool EventFd::DisableEvent() { return true; } -bool EventFd::ReadCounter(PerfCounter* counter) { +bool EventFd::ReadCounter(PerfCounter* counter) const { CHECK(counter != nullptr); - if (!ReadNBytesFromFile(perf_event_fd_, counter, sizeof(*counter))) { + if (!android::base::ReadFully(perf_event_fd_, counter, sizeof(*counter))) { PLOG(ERROR) << "ReadCounter from " << Name() << " failed"; return false; } return true; } + +bool EventFd::MmapContent(size_t mmap_pages) { + CHECK(IsPowerOfTwo(mmap_pages)); + size_t page_size = sysconf(_SC_PAGE_SIZE); + size_t mmap_len = (mmap_pages + 1) * page_size; + void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, perf_event_fd_, 0); + if (mmap_addr == MAP_FAILED) { + PLOG(ERROR) << "mmap() failed for " << Name(); + return false; + } + mmap_addr_ = mmap_addr; + mmap_len_ = mmap_len; + mmap_metadata_page_ = reinterpret_cast(mmap_addr_); + mmap_data_buffer_ = reinterpret_cast(mmap_addr_) + page_size; + mmap_data_buffer_size_ = mmap_len_ - page_size; + return true; +} + +size_t EventFd::GetAvailableMmapData(char** pdata) { + // The mmap_data_buffer is used as a ring buffer like below. The kernel continuously writes + // records to the buffer, and the user continuously read records out. + // _________________________________________ + // buffer | can write | can read | can write | + // ^ ^ + // read_head write_head + // + // So the user can read records in [read_head, write_head), and the kernel can write records + // in [write_head, read_head). The kernel is responsible for updating write_head, and the user + // is responsible for updating read_head. + + uint64_t buf_mask = mmap_data_buffer_size_ - 1; + uint64_t write_head = mmap_metadata_page_->data_head & buf_mask; + uint64_t read_head = mmap_metadata_page_->data_tail & buf_mask; + + if (read_head == write_head) { + // No available data. + return 0; + } + + // Make sure we can see the data after the fence. + std::atomic_thread_fence(std::memory_order_acquire); + + *pdata = mmap_data_buffer_ + read_head; + if (read_head < write_head) { + return write_head - read_head; + } else { + return mmap_data_buffer_size_ - read_head; + } +} + +void EventFd::DiscardMmapData(size_t discard_size) { + mmap_metadata_page_->data_tail += discard_size; +} + +void EventFd::PreparePollForMmapData(pollfd* poll_fd) { + memset(poll_fd, 0, sizeof(pollfd)); + poll_fd->fd = perf_event_fd_; + poll_fd->events = POLLIN; +} diff --git a/simpleperf/event_fd.h b/simpleperf/event_fd.h index 1fc97134..36ea0cb0 100644 --- a/simpleperf/event_fd.h +++ b/simpleperf/event_fd.h @@ -17,6 +17,7 @@ #ifndef SIMPLE_PERF_EVENT_FD_H_ #define SIMPLE_PERF_EVENT_FD_H_ +#include #include #include @@ -26,8 +27,6 @@ #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. @@ -38,33 +37,64 @@ struct PerfCounter { // EventFd represents an opened perf_event_file. class EventFd { public: - static std::unique_ptr OpenEventFileForProcess(const EventAttr& attr, pid_t pid); - static std::unique_ptr OpenEventFileForCpu(const EventAttr& attr, int cpu); - static std::unique_ptr OpenEventFile(const EventAttr& attr, pid_t pid, int cpu); + static std::unique_ptr OpenEventFileForProcess(const perf_event_attr& attr, pid_t pid); + static std::unique_ptr OpenEventFileForCpu(const perf_event_attr& attr, int cpu); + static std::unique_ptr OpenEventFile(const perf_event_attr& attr, pid_t pid, int cpu); ~EventFd(); // Give information about this perf_event_file, like (event_name, pid, cpu). std::string Name() const; + uint64_t Id() const; + // It tells the kernel to start counting and recording events specified by this file. bool EnableEvent(); // It tells the kernel to stop counting and recording events specified by this file. bool DisableEvent(); - bool ReadCounter(PerfCounter* counter); + bool ReadCounter(PerfCounter* counter) const; + + // Call mmap() for this perf_event_file, so we can read sampled records from mapped area. + // mmap_pages should be power of 2. + bool MmapContent(size_t mmap_pages); + + // When the kernel writes new sampled records to the mapped area, we can get them by returning + // the start address and size of the data. + size_t GetAvailableMmapData(char** pdata); + + // Discard how much data we have read, so the kernel can reuse this part of mapped area to store + // new data. + void DiscardMmapData(size_t discard_size); + + // Prepare pollfd for poll() to wait on available mmap_data. + void PreparePollForMmapData(pollfd* poll_fd); 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) { + : perf_event_fd_(perf_event_fd), + id_(0), + event_name_(event_name), + pid_(pid), + cpu_(cpu), + mmap_addr_(nullptr), + mmap_len_(0) { } int perf_event_fd_; + mutable uint64_t id_; const std::string event_name_; pid_t pid_; int cpu_; + void* mmap_addr_; + size_t mmap_len_; + perf_event_mmap_page* mmap_metadata_page_; // The first page of mmap_area. + char* mmap_data_buffer_; // Starts from the second page of mmap_area, containing records written + // by then kernel. + size_t mmap_data_buffer_size_; + DISALLOW_COPY_AND_ASSIGN(EventFd); }; diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp new file mode 100644 index 00000000..61f17050 --- /dev/null +++ b/simpleperf/event_selection_set.cpp @@ -0,0 +1,208 @@ +/* + * 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 "event_selection_set.h" + +#include + +#include "environment.h" +#include "event_attr.h" +#include "event_type.h" + +void EventSelectionSet::AddEventType(const EventType& event_type) { + EventSelection selection; + selection.event_type = &event_type; + selection.event_attr = CreateDefaultPerfEventAttr(event_type); + selections_.push_back(std::move(selection)); +} + +void EventSelectionSet::EnableOnExec() { + for (auto& selection : selections_) { + selection.event_attr.enable_on_exec = 1; + } +} + +void EventSelectionSet::SampleIdAll() { + for (auto& selection : selections_) { + selection.event_attr.sample_id_all = 1; + } +} + +void EventSelectionSet::SetSampleFreq(uint64_t sample_freq) { + for (auto& selection : selections_) { + perf_event_attr& attr = selection.event_attr; + attr.freq = 1; + attr.sample_freq = sample_freq; + } +} + +void EventSelectionSet::SetSamplePeriod(uint64_t sample_period) { + for (auto& selection : selections_) { + perf_event_attr& attr = selection.event_attr; + attr.freq = 0; + attr.sample_period = sample_period; + } +} + +bool EventSelectionSet::OpenEventFilesForAllCpus() { + std::vector cpus = GetOnlineCpus(); + if (cpus.empty()) { + return false; + } + for (auto& selection : selections_) { + for (auto& cpu : cpus) { + auto event_fd = EventFd::OpenEventFileForCpu(selection.event_attr, cpu); + if (event_fd != nullptr) { + selection.event_fds.push_back(std::move(event_fd)); + } + } + // As the online cpus can be enabled or disabled at runtime, we may not open event file for + // all cpus successfully. But we should open at least one cpu successfully. + if (selection.event_fds.empty()) { + LOG(ERROR) << "failed to open perf event file for event_type " << selection.event_type->name + << " on all cpus"; + return false; + } + } + return true; +} + +bool EventSelectionSet::OpenEventFilesForProcess(pid_t pid) { + for (auto& selection : selections_) { + auto event_fd = EventFd::OpenEventFileForProcess(selection.event_attr, pid); + if (event_fd == nullptr) { + PLOG(ERROR) << "failed to open perf event file for event type " << selection.event_type->name + << " on pid " << pid; + return false; + } + selection.event_fds.push_back(std::move(event_fd)); + } + return true; +} + +bool EventSelectionSet::EnableEvents() { + for (auto& selection : selections_) { + for (auto& event_fd : selection.event_fds) { + if (!event_fd->EnableEvent()) { + return false; + } + } + } + return true; +} + +bool EventSelectionSet::ReadCounters( + std::map>* counters_map) { + for (auto& selection : selections_) { + std::vector counters; + for (auto& event_fd : selection.event_fds) { + PerfCounter counter; + if (!event_fd->ReadCounter(&counter)) { + return false; + } + counters.push_back(counter); + } + counters_map->insert(std::make_pair(selection.event_type, counters)); + } + return true; +} + +void EventSelectionSet::PreparePollForEventFiles(std::vector* pollfds) { + for (auto& selection : selections_) { + for (auto& event_fd : selection.event_fds) { + pollfd poll_fd; + event_fd->PreparePollForMmapData(&poll_fd); + pollfds->push_back(poll_fd); + } + } +} + +bool EventSelectionSet::MmapEventFiles(size_t mmap_pages) { + for (auto& selection : selections_) { + for (auto& event_fd : selection.event_fds) { + if (!event_fd->MmapContent(mmap_pages)) { + return false; + } + } + } + return true; +} + +static bool ReadMmapEventDataForFd(std::unique_ptr& event_fd, + std::function callback, + bool* have_data) { + *have_data = false; + while (true) { + char* data; + size_t size = event_fd->GetAvailableMmapData(&data); + if (size == 0) { + break; + } + if (!callback(data, size)) { + return false; + } + *have_data = true; + event_fd->DiscardMmapData(size); + } + return true; +} + +bool EventSelectionSet::ReadMmapEventData(std::function callback) { + for (auto& selection : selections_) { + for (auto& event_fd : selection.event_fds) { + while (true) { + bool have_data; + if (!ReadMmapEventDataForFd(event_fd, callback, &have_data)) { + return false; + } + if (!have_data) { + break; + } + } + } + } + return true; +} + +std::string EventSelectionSet::FindEventFileNameById(uint64_t id) { + for (auto& selection : selections_) { + for (auto& event_fd : selection.event_fds) { + if (event_fd->Id() == id) { + return event_fd->Name(); + } + } + } + return ""; +} + +EventSelectionSet::EventSelection* EventSelectionSet::FindSelectionByType( + const EventType& event_type) { + for (auto& selection : selections_) { + if (strcmp(selection.event_type->name, event_type.name) == 0) { + return &selection; + } + } + return nullptr; +} + +const perf_event_attr& EventSelectionSet::FindEventAttrByType(const EventType& event_type) { + return FindSelectionByType(event_type)->event_attr; +} + +const std::vector>& EventSelectionSet::FindEventFdsByType( + const EventType& event_type) { + return FindSelectionByType(event_type)->event_fds; +} diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h new file mode 100644 index 00000000..78be069d --- /dev/null +++ b/simpleperf/event_selection_set.h @@ -0,0 +1,82 @@ +/* + * 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_EVENT_SELECTION_SET_H_ +#define SIMPLE_PERF_EVENT_SELECTION_SET_H_ + +#include +#include +#include +#include + +#include + +#include "event_fd.h" +#include "perf_event.h" + +struct EventType; + +// EventSelectionSet helps to monitor events. +// Firstly, the user creates an EventSelectionSet, and adds the specific event types to monitor. +// Secondly, the user defines how to monitor the events (by setting enable_on_exec flag, +// sample frequency, etc). +// Then, the user can start monitoring by ordering the EventSelectionSet to open perf event files +// and enable events (if enable_on_exec flag isn't used). +// After that, the user can read counters or read mapped event records. +// At last, the EventSelectionSet will clean up resources at destruction automatically. + +class EventSelectionSet { + public: + EventSelectionSet() { + } + + bool Empty() const { + return selections_.empty(); + } + + void AddEventType(const EventType& event_type); + + void EnableOnExec(); + void SampleIdAll(); + void SetSampleFreq(uint64_t sample_freq); + void SetSamplePeriod(uint64_t sample_period); + + bool OpenEventFilesForAllCpus(); + bool OpenEventFilesForProcess(pid_t pid); + bool EnableEvents(); + bool ReadCounters(std::map>* counters_map); + void PreparePollForEventFiles(std::vector* pollfds); + bool MmapEventFiles(size_t mmap_pages); + bool ReadMmapEventData(std::function callback); + + std::string FindEventFileNameById(uint64_t id); + const perf_event_attr& FindEventAttrByType(const EventType& event_type); + const std::vector>& FindEventFdsByType(const EventType& event_type); + + private: + struct EventSelection { + const EventType* event_type; + perf_event_attr event_attr; + std::vector> event_fds; + }; + EventSelection* FindSelectionByType(const EventType& event_type); + + std::vector selections_; + + DISALLOW_COPY_AND_ASSIGN(EventSelectionSet); +}; + +#endif // SIMPLE_PERF_EVENT_SELECTION_SET_H_ diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp index 01d74573..15e3cf17 100644 --- a/simpleperf/event_type.cpp +++ b/simpleperf/event_type.cpp @@ -19,6 +19,9 @@ #include #include #include + +#include + #include "event_attr.h" #include "event_fd.h" @@ -31,8 +34,7 @@ static std::vector event_type_array = { }; static bool IsEventTypeSupportedByKernel(const EventType& event_type) { - auto event_fd = EventFd::OpenEventFileForProcess( - EventAttr::CreateDefaultAttrToMonitorEvent(event_type), getpid()); + auto event_fd = EventFd::OpenEventFileForProcess(CreateDefaultPerfEventAttr(event_type), getpid()); return event_fd != nullptr; } @@ -44,13 +46,26 @@ const std::vector& EventTypeFactory::GetAllEventTypes() { return event_type_array; } -const EventType* EventTypeFactory::FindEventTypeByName(const std::string& name) { +const EventType* EventTypeFactory::FindEventTypeByName(const std::string& name, + bool report_unsupported_type) { + const EventType* result = nullptr; for (auto& event_type : event_type_array) { if (event_type.name == name) { - return &event_type; + result = &event_type; + break; } } - return nullptr; + if (result == nullptr) { + LOG(ERROR) << "Unknown event_type '" << name + << "', try `simpleperf list` to list all possible event type names"; + return nullptr; + } + if (!result->IsSupportedByKernel()) { + (report_unsupported_type ? LOG(ERROR) : LOG(DEBUG)) << "Event type '" << result->name + << "' is not supported by the kernel"; + return nullptr; + } + return result; } const EventType* EventTypeFactory::FindEventTypeByConfig(uint32_t type, uint64_t config) { diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h index e2f21d5d..b486a294 100644 --- a/simpleperf/event_type.h +++ b/simpleperf/event_type.h @@ -37,7 +37,8 @@ struct EventType { class EventTypeFactory { public: static const std::vector& GetAllEventTypes(); - static const EventType* FindEventTypeByName(const std::string& name); + static const EventType* FindEventTypeByName(const std::string& name, + bool report_unsupported_type = true); static const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config); }; diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp index 1f7c7daa..173026eb 100644 --- a/simpleperf/main.cpp +++ b/simpleperf/main.cpp @@ -26,11 +26,15 @@ int main(int argc, char** argv) { InitLogging(argv, android::base::StderrLogger); std::vector args; - if (argc == 1 || (argc == 2 && strcmp(argv[1], "--help") == 0)) { + if (argc == 1) { args.push_back("help"); } else { for (int i = 1; i < argc; ++i) { - args.push_back(argv[i]); + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + args.insert(args.begin(), "help"); + } else { + args.push_back(argv[i]); + } } } @@ -40,7 +44,6 @@ int main(int argc, char** argv) { return 1; } std::string command_name = args[0]; - args.erase(args.begin()); LOG(DEBUG) << "command '" << command_name << "' starts running"; bool result = command->Run(args); diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp new file mode 100644 index 00000000..8e88867a --- /dev/null +++ b/simpleperf/record.cpp @@ -0,0 +1,321 @@ +/* + * 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 "record.h" + +#include +#include + +#include +#include + +#include "environment.h" +#include "utils.h" + +static std::string RecordTypeToString(int record_type) { + static std::unordered_map record_type_names = { + {PERF_RECORD_MMAP, "mmap"}, + {PERF_RECORD_LOST, "lost"}, + {PERF_RECORD_COMM, "comm"}, + {PERF_RECORD_EXIT, "exit"}, + {PERF_RECORD_THROTTLE, "throttle"}, + {PERF_RECORD_UNTHROTTLE, "unthrottle"}, + {PERF_RECORD_FORK, "fork"}, + {PERF_RECORD_READ, "read"}, + {PERF_RECORD_SAMPLE, "sample"}, + {PERF_RECORD_BUILD_ID, "build_id"}, + }; + + auto it = record_type_names.find(record_type); + if (it != record_type_names.end()) { + return it->second; + } + return android::base::StringPrintf("unknown(%d)", record_type); +} + +template +void MoveFromBinaryFormat(T& data, const char*& p) { + data = *reinterpret_cast(p); + p += sizeof(T); +} + +template +void MoveToBinaryFormat(const T& data, char*& p) { + *reinterpret_cast(p) = data; + p += sizeof(T); +} + +SampleId::SampleId() { + sample_id_all = false; + sample_type = 0; +} + +// Return sample_id size in binary format. +size_t SampleId::CreateContent(const perf_event_attr& attr) { + sample_id_all = attr.sample_id_all; + sample_type = attr.sample_type; + // Other data are not necessary. TODO: Set missing SampleId data. + size_t size = 0; + if (sample_id_all) { + if (sample_type & PERF_SAMPLE_TID) { + size += sizeof(PerfSampleTidType); + } + if (sample_type & PERF_SAMPLE_TIME) { + size += sizeof(PerfSampleTimeType); + } + if (sample_type & PERF_SAMPLE_ID) { + size += sizeof(PerfSampleIdType); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + size += sizeof(PerfSampleStreamIdType); + } + if (sample_type & PERF_SAMPLE_CPU) { + size += sizeof(PerfSampleCpuType); + } + } + return size; +} + +void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end) { + sample_id_all = attr.sample_id_all; + sample_type = attr.sample_type; + if (sample_id_all) { + if (sample_type & PERF_SAMPLE_TID) { + MoveFromBinaryFormat(tid_data, p); + } + if (sample_type & PERF_SAMPLE_TIME) { + MoveFromBinaryFormat(time_data, p); + } + if (sample_type & PERF_SAMPLE_ID) { + MoveFromBinaryFormat(id_data, p); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + MoveFromBinaryFormat(stream_id_data, p); + } + if (sample_type & PERF_SAMPLE_CPU) { + MoveFromBinaryFormat(cpu_data, p); + } + // TODO: Add parsing of PERF_SAMPLE_IDENTIFIER. + } + CHECK_LE(p, end); + if (p < end) { + LOG(DEBUG) << "Record SampleId part has " << end - p << " bytes left\n"; + } +} + +void SampleId::WriteToBinaryFormat(char*& p) const { + if (sample_id_all) { + if (sample_type & PERF_SAMPLE_TID) { + MoveToBinaryFormat(tid_data, p); + } + if (sample_type & PERF_SAMPLE_TIME) { + MoveToBinaryFormat(time_data, p); + } + if (sample_type & PERF_SAMPLE_ID) { + MoveToBinaryFormat(id_data, p); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + MoveToBinaryFormat(stream_id_data, p); + } + if (sample_type & PERF_SAMPLE_CPU) { + MoveToBinaryFormat(cpu_data, p); + } + } +} + +void SampleId::Dump(size_t indent) const { + if (sample_id_all) { + if (sample_type & PERF_SAMPLE_TID) { + PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid, tid_data.tid); + } + if (sample_type & PERF_SAMPLE_TIME) { + PrintIndented(indent, "sample_id: time %" PRId64 "\n", time_data.time); + } + if (sample_type & PERF_SAMPLE_ID) { + PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", id_data.id); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", stream_id_data.stream_id); + } + if (sample_type & PERF_SAMPLE_CPU) { + PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu, cpu_data.res); + } + } +} + +Record::Record() { + memset(&header, 0, sizeof(header)); +} + +Record::Record(const perf_event_header* pheader) { + header = *pheader; +} + +void Record::Dump(size_t indent) const { + PrintIndented(indent, "record %s: type %u, misc %u, size %u\n", + RecordTypeToString(header.type).c_str(), header.type, header.misc, header.size); + DumpData(indent + 1); + sample_id.Dump(indent + 1); +} + +MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader) + : Record(pheader) { + const char* p = reinterpret_cast(pheader + 1); + const char* end = reinterpret_cast(pheader) + pheader->size; + MoveFromBinaryFormat(data, p); + filename = p; + p += ALIGN(filename.size() + 1, 8); + CHECK_LE(p, end); + sample_id.ReadFromBinaryFormat(attr, p, end); +} + +void MmapRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u, tid %u, addr %p, len 0x%" PRIx64 "\n", data.pid, data.tid, + reinterpret_cast(data.addr), data.len); + PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data.pgoff, filename.c_str()); +} + +std::vector MmapRecord::BinaryFormat() const { + std::vector buf(header.size); + char* p = buf.data(); + MoveToBinaryFormat(header, p); + MoveToBinaryFormat(data, p); + strcpy(p, filename.c_str()); + p += ALIGN(filename.size() + 1, 8); + sample_id.WriteToBinaryFormat(p); + return buf; +} + +CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* pheader) + : Record(pheader) { + const char* p = reinterpret_cast(pheader + 1); + const char* end = reinterpret_cast(pheader) + pheader->size; + MoveFromBinaryFormat(data, p); + comm = p; + p += ALIGN(strlen(p) + 1, 8); + CHECK_LE(p, end); + sample_id.ReadFromBinaryFormat(attr, p, end); +} + +void CommRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u, tid %u, comm %s\n", data.pid, data.tid, comm.c_str()); +} + +std::vector CommRecord::BinaryFormat() const { + std::vector buf(header.size); + char* p = buf.data(); + MoveToBinaryFormat(header, p); + MoveToBinaryFormat(data, p); + strcpy(p, comm.c_str()); + p += ALIGN(comm.size() + 1, 8); + sample_id.WriteToBinaryFormat(p); + return buf; +} + +ExitRecord::ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader) + : Record(pheader) { + const char* p = reinterpret_cast(pheader + 1); + const char* end = reinterpret_cast(pheader) + pheader->size; + MoveFromBinaryFormat(data, p); + CHECK_LE(p, end); + sample_id.ReadFromBinaryFormat(attr, p, end); +} + +void ExitRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data.pid, data.ppid, data.tid, + data.ptid); +} + +SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader) + : Record(pheader) { + const char* p = reinterpret_cast(pheader + 1); + const char* end = reinterpret_cast(pheader) + pheader->size; + sample_type = attr.sample_type; + + if (sample_type & PERF_SAMPLE_IP) { + MoveFromBinaryFormat(ip_data, p); + } + if (sample_type & PERF_SAMPLE_TID) { + MoveFromBinaryFormat(tid_data, p); + } + if (sample_type & PERF_SAMPLE_TIME) { + MoveFromBinaryFormat(time_data, p); + } + if (sample_type & PERF_SAMPLE_ADDR) { + MoveFromBinaryFormat(addr_data, p); + } + if (sample_type & PERF_SAMPLE_ID) { + MoveFromBinaryFormat(id_data, p); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + MoveFromBinaryFormat(stream_id_data, p); + } + if (sample_type & PERF_SAMPLE_CPU) { + MoveFromBinaryFormat(cpu_data, p); + } + if (sample_type & PERF_SAMPLE_PERIOD) { + MoveFromBinaryFormat(period_data, p); + } + // TODO: Add parsing of other PERF_SAMPLE_*. + CHECK_LE(p, end); + if (p < end) { + LOG(DEBUG) << "Record has " << end - p << " bytes left\n"; + } +} + +void SampleRecord::DumpData(size_t indent) const { + PrintIndented(indent, "sample_type: 0x%" PRIx64 "\n", sample_type); + if (sample_type & PERF_SAMPLE_IP) { + PrintIndented(indent, "ip %p\n", reinterpret_cast(ip_data.ip)); + } + if (sample_type & PERF_SAMPLE_TID) { + PrintIndented(indent, "pid %u, tid %u\n", tid_data.pid, tid_data.tid); + } + if (sample_type & PERF_SAMPLE_TIME) { + PrintIndented(indent, "time %" PRId64 "\n", time_data.time); + } + if (sample_type & PERF_SAMPLE_ADDR) { + PrintIndented(indent, "addr %p\n", reinterpret_cast(addr_data.addr)); + } + if (sample_type & PERF_SAMPLE_ID) { + PrintIndented(indent, "id %" PRId64 "\n", id_data.id); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + PrintIndented(indent, "stream_id %" PRId64 "\n", stream_id_data.stream_id); + } + if (sample_type & PERF_SAMPLE_CPU) { + PrintIndented(indent, "cpu %u, res %u\n", cpu_data.cpu, cpu_data.res); + } + if (sample_type & PERF_SAMPLE_PERIOD) { + PrintIndented(indent, "period %" PRId64 "\n", period_data.period); + } +} + +std::unique_ptr ReadRecordFromBuffer(const perf_event_attr& attr, + const perf_event_header* pheader) { + switch (pheader->type) { + case PERF_RECORD_MMAP: + return std::unique_ptr(new MmapRecord(attr, pheader)); + case PERF_RECORD_COMM: + return std::unique_ptr(new CommRecord(attr, pheader)); + case PERF_RECORD_EXIT: + return std::unique_ptr(new ExitRecord(attr, pheader)); + case PERF_RECORD_SAMPLE: + return std::unique_ptr(new SampleRecord(attr, pheader)); + default: + return std::unique_ptr(new Record(pheader)); + } +} diff --git a/simpleperf/record.h b/simpleperf/record.h new file mode 100644 index 00000000..fbd523d1 --- /dev/null +++ b/simpleperf/record.h @@ -0,0 +1,180 @@ +/* + * 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_RECORD_H_ +#define SIMPLE_PERF_RECORD_H_ + +#include +#include + +#include "perf_event.h" + +struct KernelMmap; +struct ModuleMmap; +struct ThreadComm; +struct ThreadMmap; + +enum user_record_type { + PERF_RECORD_ATTR = 64, + PERF_RECORD_EVENT_TYPE, + PERF_RECORD_TRACING_DATA, + PERF_RECORD_BUILD_ID, + PERF_RECORD_FINISHED_ROUND, +}; + +struct PerfSampleIpType { + uint64_t ip; +}; + +struct PerfSampleTidType { + uint32_t pid, tid; +}; + +struct PerfSampleTimeType { + uint64_t time; +}; + +struct PerfSampleAddrType { + uint64_t addr; +}; + +struct PerfSampleIdType { + uint64_t id; +}; + +struct PerfSampleStreamIdType { + uint64_t stream_id; +}; + +struct PerfSampleCpuType { + uint32_t cpu, res; +}; + +struct PerfSamplePeriodType { + uint64_t period; +}; + +// SampleId is optional at the end of a record in binary format. Its content is determined by +// sample_id_all and sample_type in perf_event_attr. To avoid the complexity of referring to +// perf_event_attr each time, we copy sample_id_all and sample_type inside the SampleId structure. +struct SampleId { + bool sample_id_all; + uint64_t sample_type; + + PerfSampleTidType tid_data; // Valid if sample_id_all && PERF_SAMPLE_TID. + PerfSampleTimeType time_data; // Valid if sample_id_all && PERF_SAMPLE_TIME. + PerfSampleIdType id_data; // Valid if sample_id_all && PERF_SAMPLE_ID. + PerfSampleStreamIdType stream_id_data; // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID. + PerfSampleCpuType cpu_data; // Valid if sample_id_all && PERF_SAMPLE_CPU. + + SampleId(); + + // Create the content of sample_id. It depends on the attr we use. + size_t CreateContent(const perf_event_attr& attr); + + // Parse sample_id from binary format in the buffer pointed by p. + void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end); + + // Write the binary format of sample_id to the buffer pointed by p. + void WriteToBinaryFormat(char*& p) const; + void Dump(size_t indent) const; +}; + +// Usually one record contains the following three parts in order in binary format: +// perf_event_header (at the head of a record, containing type and size information) +// data depends on the record type +// sample_id (optional part at the end of a record) +// We hold the common parts (perf_event_header and sample_id) in the base class Record, and +// hold the type specific data part in classes derived from Record. +struct Record { + perf_event_header header; + SampleId sample_id; + + Record(); + Record(const perf_event_header* pheader); + + virtual ~Record() { + } + + void Dump(size_t indent = 0) const; + + protected: + virtual void DumpData(size_t) const { + } +}; + +struct MmapRecord : public Record { + struct MmapRecordDataType { + uint32_t pid, tid; + uint64_t addr; + uint64_t len; + uint64_t pgoff; + } data; + std::string filename; + + MmapRecord() { // For storage in std::vector. + } + + MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader); + void DumpData(size_t indent) const override; + std::vector BinaryFormat() const; +}; + +struct CommRecord : public Record { + struct CommRecordDataType { + uint32_t pid, tid; + } data; + std::string comm; + + CommRecord() { + } + + CommRecord(const perf_event_attr& attr, const perf_event_header* pheader); + void DumpData(size_t indent) const override; + std::vector BinaryFormat() const; +}; + +struct ExitRecord : public Record { + struct ExitRecordDataType { + uint32_t pid, ppid; + uint32_t tid, ptid; + uint64_t time; + } data; + + ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader); + void DumpData(size_t indent) const override; +}; + +struct SampleRecord : public Record { + uint64_t sample_type; // sample_type is a bit mask determining which fields below are valid. + + PerfSampleIpType ip_data; // Valid if PERF_SAMPLE_IP. + PerfSampleTidType tid_data; // Valid if PERF_SAMPLE_TID. + PerfSampleTimeType time_data; // Valid if PERF_SAMPLE_TIME. + PerfSampleAddrType addr_data; // Valid if PERF_SAMPLE_ADDR. + PerfSampleIdType id_data; // Valid if PERF_SAMPLE_ID. + PerfSampleStreamIdType stream_id_data; // Valid if PERF_SAMPLE_STREAM_ID. + PerfSampleCpuType cpu_data; // Valid if PERF_SAMPLE_CPU. + PerfSamplePeriodType period_data; // Valid if PERF_SAMPLE_PERIOD. + + SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader); + void DumpData(size_t indent) const override; +}; + +std::unique_ptr ReadRecordFromBuffer(const perf_event_attr& attr, + const perf_event_header* pheader); + +#endif // SIMPLE_PERF_RECORD_H_ diff --git a/simpleperf/record_file.cpp b/simpleperf/record_file.cpp new file mode 100644 index 00000000..784dc4e1 --- /dev/null +++ b/simpleperf/record_file.cpp @@ -0,0 +1,263 @@ +/* + * 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 "record_file.h" + +#include +#include +#include +#include +#include + +#include + +#include "event_fd.h" +#include "perf_event.h" +#include "record.h" +#include "utils.h" + +using namespace PerfFileFormat; + +std::unique_ptr RecordFileWriter::CreateInstance( + const std::string& filename, const perf_event_attr& event_attr, + const std::vector>& event_fds) { + FILE* fp = fopen(filename.c_str(), "web+"); + if (fp == nullptr) { + PLOG(ERROR) << "failed to open record file '" << filename << "'"; + return nullptr; + } + + auto writer = std::unique_ptr(new RecordFileWriter(filename, fp)); + if (!writer->WriteAttrSection(event_attr, event_fds)) { + return nullptr; + } + return writer; +} + +RecordFileWriter::RecordFileWriter(const std::string& filename, FILE* fp) + : filename_(filename), record_fp_(fp), data_section_offset_(0), data_section_size_(0) { +} + +RecordFileWriter::~RecordFileWriter() { + if (record_fp_ != nullptr) { + Close(); + } +} + +bool RecordFileWriter::WriteAttrSection(const perf_event_attr& event_attr, + const std::vector>& event_fds) { + // Skip file header part. + if (fseek(record_fp_, sizeof(FileHeader), SEEK_SET) == -1) { + return false; + } + + // Write id section. + std::vector ids; + for (auto& event_fd : event_fds) { + ids.push_back(event_fd->Id()); + } + long id_section_offset = ftell(record_fp_); + if (id_section_offset == -1) { + return false; + } + if (!Write(ids.data(), ids.size() * sizeof(uint64_t))) { + return false; + } + + // Write attr section. + FileAttr attr; + attr.attr = event_attr; + attr.ids.offset = id_section_offset; + attr.ids.size = ids.size() * sizeof(uint64_t); + + long attr_section_offset = ftell(record_fp_); + if (attr_section_offset == -1) { + return false; + } + if (!Write(&attr, sizeof(attr))) { + return false; + } + + long data_section_offset = ftell(record_fp_); + if (data_section_offset == -1) { + return false; + } + + attr_section_offset_ = attr_section_offset; + attr_section_size_ = sizeof(attr); + data_section_offset_ = data_section_offset; + + // Save event_attr for use when reading records. + event_attr_ = event_attr; + return true; +} + +bool RecordFileWriter::WriteData(const void* buf, size_t len) { + if (!Write(buf, len)) { + return false; + } + data_section_size_ += len; + return true; +} + +bool RecordFileWriter::Write(const void* buf, size_t len) { + if (fwrite(buf, len, 1, record_fp_) != 1) { + PLOG(ERROR) << "failed to write to record file '" << filename_ << "'"; + return false; + } + return true; +} + +bool RecordFileWriter::WriteFileHeader() { + FileHeader header; + memset(&header, 0, sizeof(header)); + memcpy(header.magic, PERF_MAGIC, sizeof(header.magic)); + header.header_size = sizeof(header); + header.attr_size = sizeof(FileAttr); + header.attrs.offset = attr_section_offset_; + header.attrs.size = attr_section_size_; + header.data.offset = data_section_offset_; + header.data.size = data_section_size_; + for (auto& feature : features_) { + int i = feature / 8; + int j = feature % 8; + header.features[i] |= (1 << j); + } + + if (fseek(record_fp_, 0, SEEK_SET) == -1) { + return false; + } + if (!Write(&header, sizeof(header))) { + return false; + } + return true; +} + +bool RecordFileWriter::Close() { + CHECK(record_fp_ != nullptr); + bool result = true; + + // Write file header. We gather enough information to write file header only after + // writing data section and feature section. + if (!WriteFileHeader()) { + result = false; + } + + if (fclose(record_fp_) != 0) { + PLOG(ERROR) << "failed to close record file '" << filename_ << "'"; + result = false; + } + record_fp_ = nullptr; + return result; +} + +std::unique_ptr RecordFileReader::CreateInstance(const std::string& filename) { + int fd = open(filename.c_str(), O_RDONLY | O_CLOEXEC); + if (fd == -1) { + PLOG(ERROR) << "failed to open record file '" << filename << "'"; + return nullptr; + } + auto reader = std::unique_ptr(new RecordFileReader(filename, fd)); + if (!reader->MmapFile()) { + return nullptr; + } + return reader; +} + +RecordFileReader::RecordFileReader(const std::string& filename, int fd) + : filename_(filename), record_fd_(fd), mmap_addr_(nullptr), mmap_len_(0) { +} + +RecordFileReader::~RecordFileReader() { + if (record_fd_ != -1) { + Close(); + } +} + +bool RecordFileReader::Close() { + bool result = true; + if (munmap(const_cast(mmap_addr_), mmap_len_) == -1) { + PLOG(ERROR) << "failed to munmap() record file '" << filename_ << "'"; + result = false; + } + if (close(record_fd_) == -1) { + PLOG(ERROR) << "failed to close record file '" << filename_ << "'"; + result = false; + } + record_fd_ = -1; + return result; +} + +bool RecordFileReader::MmapFile() { + off64_t file_size = lseek64(record_fd_, 0, SEEK_END); + if (file_size == -1) { + return false; + } + size_t mmap_len = file_size; + void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ, MAP_SHARED, record_fd_, 0); + if (mmap_addr == MAP_FAILED) { + PLOG(ERROR) << "failed to mmap() record file '" << filename_ << "'"; + return false; + } + + mmap_addr_ = reinterpret_cast(mmap_addr); + mmap_len_ = mmap_len; + return true; +} + +const FileHeader* RecordFileReader::FileHeader() { + return reinterpret_cast(mmap_addr_); +} + +std::vector RecordFileReader::AttrSection() { + std::vector result; + const struct FileHeader* header = FileHeader(); + size_t attr_count = header->attrs.size / header->attr_size; + const FileAttr* attr = reinterpret_cast(mmap_addr_ + header->attrs.offset); + for (size_t i = 0; i < attr_count; ++i) { + result.push_back(attr++); + } + return result; +} + +std::vector RecordFileReader::IdsForAttr(const FileAttr* attr) { + std::vector result; + size_t id_count = attr->ids.size / sizeof(uint64_t); + const uint64_t* id = reinterpret_cast(mmap_addr_ + attr->ids.offset); + for (size_t i = 0; i < id_count; ++i) { + result.push_back(*id++); + } + return result; +} + +std::vector> RecordFileReader::DataSection() { + std::vector> result; + const struct FileHeader* header = FileHeader(); + auto file_attrs = AttrSection(); + CHECK(file_attrs.size() > 0); + perf_event_attr attr = file_attrs[0]->attr; + + const char* end = mmap_addr_ + header->data.offset + header->data.size; + const char* p = mmap_addr_ + header->data.offset; + while (p < end) { + const perf_event_header* header = reinterpret_cast(p); + if (p + header->size <= end) { + result.push_back(std::move(ReadRecordFromBuffer(attr, header))); + } + p += header->size; + } + return result; +} diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h new file mode 100644 index 00000000..cc213d57 --- /dev/null +++ b/simpleperf/record_file.h @@ -0,0 +1,100 @@ +/* + * 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_RECORD_FILE_H_ +#define SIMPLE_PERF_RECORD_FILE_H_ + +#include +#include +#include +#include + +#include + +#include "perf_event.h" +#include "record_file_format.h" + +class EventFd; +struct Record; + +// RecordFileWriter writes to a perf record file, like perf.data. +class RecordFileWriter { + public: + static std::unique_ptr CreateInstance( + const std::string& filename, const perf_event_attr& event_attr, + const std::vector>& event_fds); + + ~RecordFileWriter(); + + bool WriteData(const void* buf, size_t len); + + bool WriteData(const std::vector& data) { + return WriteData(data.data(), data.size()); + } + + // Normally, Close() should be called after writing. But if something + // wrong happens and we need to finish in advance, the destructor + // will take care of calling Close(). + bool Close(); + + private: + RecordFileWriter(const std::string& filename, FILE* fp); + bool WriteAttrSection(const perf_event_attr& event_attr, + const std::vector>& event_fds); + bool WriteFileHeader(); + bool Write(const void* buf, size_t len); + + const std::string filename_; + FILE* record_fp_; + + perf_event_attr event_attr_; + uint64_t attr_section_offset_; + uint64_t attr_section_size_; + uint64_t data_section_offset_; + uint64_t data_section_size_; + + std::vector features_; + + DISALLOW_COPY_AND_ASSIGN(RecordFileWriter); +}; + +// RecordFileReader read contents from a perf record file, like perf.data. +class RecordFileReader { + public: + static std::unique_ptr CreateInstance(const std::string& filename); + + ~RecordFileReader(); + + const PerfFileFormat::FileHeader* FileHeader(); + std::vector AttrSection(); + std::vector IdsForAttr(const PerfFileFormat::FileAttr* attr); + std::vector> DataSection(); + bool Close(); + + private: + RecordFileReader(const std::string& filename, int fd); + bool MmapFile(); + + const std::string filename_; + int record_fd_; + + const char* mmap_addr_; + size_t mmap_len_; + + DISALLOW_COPY_AND_ASSIGN(RecordFileReader); +}; + +#endif // SIMPLE_PERF_RECORD_FILE_H_ diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h new file mode 100644 index 00000000..9758f11f --- /dev/null +++ b/simpleperf/record_file_format.h @@ -0,0 +1,85 @@ +/* + * 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_RECORD_FILE_FORMAT_H_ +#define SIMPLE_PERF_RECORD_FILE_FORMAT_H_ + +#include "perf_event.h" + +// The file structure of perf.data: +// file_header +// id_section +// attr section +// data section +// feature section +// +// The feature section has the following structure: +// a section descriptor array, each element contains the section information of one add_feature. +// data section of feature 1 +// data section of feature 2 +// .... + +namespace PerfFileFormat { + +enum { + FEAT_RESERVED = 0, + FEAT_FIRST_FEATURE = 1, + FEAT_TRACING_DATA = 1, + FEAT_BUILD_ID, + FEAT_HOSTNAME, + FEAT_OSRELEASE, + FEAT_VERSION, + FEAT_ARCH, + FEAT_NRCPUS, + FEAT_CPUDESC, + FEAT_CPUID, + FEAT_TOTAL_MEM, + FEAT_CMDLINE, + FEAT_EVENT_DESC, + FEAT_CPU_TOPOLOGY, + FEAT_NUMA_TOPOLOGY, + FEAT_BRANCH_STACK, + FEAT_PMU_MAPPINGS, + FEAT_GROUP_DESC, + FEAT_LAST_FEATURE, + FEAT_MAX_NUM = 256, +}; + +struct SectionDesc { + uint64_t offset; + uint64_t size; +}; + +static const char* PERF_MAGIC = "PERFILE2"; + +struct FileHeader { + char magic[8]; + uint64_t header_size; + uint64_t attr_size; + SectionDesc attrs; + SectionDesc data; + SectionDesc event_types; + unsigned char features[FEAT_MAX_NUM / 8]; +}; + +struct FileAttr { + perf_event_attr attr; + SectionDesc ids; +}; + +} // namespace PerfFileFormat + +#endif // SIMPLE_PERF_RECORD_FILE_FORMAT_H_ diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp new file mode 100644 index 00000000..85c0212d --- /dev/null +++ b/simpleperf/record_file_test.cpp @@ -0,0 +1,74 @@ +/* + * 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 + +#include "environment.h" +#include "event_attr.h" +#include "event_fd.h" +#include "event_type.h" +#include "record.h" +#include "record_file.h" + +using namespace PerfFileFormat; + +class RecordFileTest : public ::testing::Test { + protected: + virtual void SetUp() { + filename = "temporary.record_file"; + const EventType* event_type = EventTypeFactory::FindEventTypeByName("cpu-cycles"); + event_attr = CreateDefaultPerfEventAttr(*event_type); + std::unique_ptr event_fd = EventFd::OpenEventFileForProcess(event_attr, getpid()); + ASSERT_TRUE(event_fd != nullptr); + event_fds.push_back(std::move(event_fd)); + } + + std::string filename; + perf_event_attr event_attr; + std::vector> event_fds; +}; + +TEST_F(RecordFileTest, smoke) { + // Write to a record file. + std::unique_ptr writer = + RecordFileWriter::CreateInstance(filename, event_attr, event_fds); + ASSERT_TRUE(writer != nullptr); + + // Write Data section. + MmapRecord mmap_record; + mmap_record.header.type = PERF_RECORD_MMAP; + mmap_record.header.size = sizeof(mmap_record); + ASSERT_TRUE(writer->WriteData(mmap_record.BinaryFormat())); + ASSERT_TRUE(writer->Close()); + + // Read from a record file. + std::unique_ptr reader = RecordFileReader::CreateInstance(filename); + ASSERT_TRUE(reader != nullptr); + const FileHeader* file_header = reader->FileHeader(); + ASSERT_TRUE(file_header != nullptr); + std::vector attrs = reader->AttrSection(); + ASSERT_EQ(1u, attrs.size()); + ASSERT_EQ(0, memcmp(&attrs[0]->attr, &event_attr, sizeof(perf_event_attr))); + std::vector ids = reader->IdsForAttr(attrs[0]); + ASSERT_EQ(1u, ids.size()); + + // Read and check data section. + std::vector> records = reader->DataSection(); + ASSERT_EQ(1u, records.size()); + ASSERT_EQ(mmap_record.header.type, records[0]->header.type); + + ASSERT_TRUE(reader->Close()); +} diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp index f7819cbe..eea8988e 100644 --- a/simpleperf/utils.cpp +++ b/simpleperf/utils.cpp @@ -26,22 +26,21 @@ void PrintIndented(size_t indent, const char* fmt, ...) { va_list ap; va_start(ap, fmt); - printf("%*s", static_cast(indent), ""); + printf("%*s", static_cast(indent * 2), ""); vprintf(fmt, ap); va_end(ap); } -bool ReadNBytesFromFile(int fd, void* buf, size_t nbytes) { - char* p = reinterpret_cast(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; - } +bool IsPowerOfTwo(uint64_t value) { + return (value != 0 && ((value & (value - 1)) == 0)); +} + +bool NextArgumentOrError(const std::vector& args, size_t* pi) { + if (*pi + 1 == args.size()) { + LOG(ERROR) << "No argument following " << args[*pi] << " option. Try `simpleperf help " + << args[0] << "`"; + return false; } + ++*pi; return true; } diff --git a/simpleperf/utils.h b/simpleperf/utils.h index b73dccd6..2ff5d953 100644 --- a/simpleperf/utils.h +++ b/simpleperf/utils.h @@ -21,6 +21,7 @@ #include #include #include +#include void PrintIndented(size_t indent, const char* fmt, ...); @@ -51,6 +52,10 @@ class LineReader { size_t bufsize_; }; -bool ReadNBytesFromFile(int fd, void* buf, size_t nbytes); +bool IsPowerOfTwo(uint64_t value); + +bool NextArgumentOrError(const std::vector& args, size_t* pi); + +#define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1)) #endif // SIMPLE_PERF_UTILS_H_ diff --git a/simpleperf/workload.cpp b/simpleperf/workload.cpp index 46dfc404..f8e4edde 100644 --- a/simpleperf/workload.cpp +++ b/simpleperf/workload.cpp @@ -93,9 +93,9 @@ static void ChildProcessFn(std::vector& args, int start_signal_fd, TEMP_FAILURE_RETRY(write(exec_child_fd, &exec_child_failed, 1)); close(exec_child_fd); errno = saved_errno; - PLOG(FATAL) << "execvp() failed"; + PLOG(FATAL) << "execvp(" << argv[0] << ") failed"; } else { - PLOG(FATAL) << "child process failed to receive start_signal"; + PLOG(FATAL) << "child process failed to receive start_signal, nread = " << nread; } } diff --git a/simpleperf/workload.h b/simpleperf/workload.h index dea8030f..57622c8a 100644 --- a/simpleperf/workload.h +++ b/simpleperf/workload.h @@ -48,7 +48,7 @@ class Workload { bool Start(); bool IsFinished(); void WaitFinish(); - pid_t GetWorkPid() { + pid_t GetPid() { return work_pid_; } diff --git a/simpleperf/workload_test.cpp b/simpleperf/workload_test.cpp index 5f0645f2..0cc67b80 100644 --- a/simpleperf/workload_test.cpp +++ b/simpleperf/workload_test.cpp @@ -26,7 +26,7 @@ TEST(workload, smoke) { auto workload = Workload::CreateWorkload({"sleep", "1"}); ASSERT_TRUE(workload != nullptr); ASSERT_FALSE(workload->IsFinished()); - ASSERT_TRUE(workload->GetWorkPid() != 0); + ASSERT_TRUE(workload->GetPid() != 0); auto start_time = steady_clock::now(); ASSERT_TRUE(workload->Start()); ASSERT_FALSE(workload->IsFinished()); -- 2.11.0