OSDN Git Service

Implement simpleperf record/dumprecord subcommands.
authorYabin Cui <yabinc@google.com>
Tue, 28 Apr 2015 22:54:13 +0000 (15:54 -0700)
committerYabin Cui <yabinc@google.com>
Thu, 14 May 2015 01:26:05 +0000 (18:26 -0700)
(cherry picked from commit 9759e1b1ce76185aa539aeea2fb1cbd8382156e7)

Bug: 19483574

Change-Id: Id879713a75c2d3a6289d8847b95ee0bb4a2cc8a0

33 files changed:
simpleperf/Android.mk
simpleperf/cmd_dumprecord.cpp [new file with mode: 0644]
simpleperf/cmd_dumprecord_test.cpp [new file with mode: 0644]
simpleperf/cmd_help.cpp
simpleperf/cmd_list.cpp
simpleperf/cmd_list_test.cpp
simpleperf/cmd_record.cpp [new file with mode: 0644]
simpleperf/cmd_record_test.cpp [new file with mode: 0644]
simpleperf/cmd_stat.cpp
simpleperf/cmd_stat_test.cpp
simpleperf/command.cpp
simpleperf/environment.h
simpleperf/environment_test.cpp
simpleperf/event_attr.cpp
simpleperf/event_attr.h
simpleperf/event_fd.cpp
simpleperf/event_fd.h
simpleperf/event_selection_set.cpp [new file with mode: 0644]
simpleperf/event_selection_set.h [new file with mode: 0644]
simpleperf/event_type.cpp
simpleperf/event_type.h
simpleperf/main.cpp
simpleperf/record.cpp [new file with mode: 0644]
simpleperf/record.h [new file with mode: 0644]
simpleperf/record_file.cpp [new file with mode: 0644]
simpleperf/record_file.h [new file with mode: 0644]
simpleperf/record_file_format.h [new file with mode: 0644]
simpleperf/record_file_test.cpp [new file with mode: 0644]
simpleperf/utils.cpp
simpleperf/utils.h
simpleperf/workload.cpp
simpleperf/workload.h
simpleperf/workload_test.cpp

index df37a22..80da24a 100644 (file)
@@ -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 (file)
index 0000000..4ee9394
--- /dev/null
@@ -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 <inttypes.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#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<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args);
+  void DumpFileHeader();
+  void DumpAttrSection();
+  void DumpDataSection();
+
+  std::string record_filename_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+
+  std::vector<int> features_;
+};
+
+bool DumpRecordCommandImpl::Run(const std::vector<std::string>& 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<std::string>& 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<int, std::string> 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<const FileAttr*> 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<uint64_t> 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<std::unique_ptr<const Record>> 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<std::string>& 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 (file)
index 0000000..90772eb
--- /dev/null
@@ -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 <gtest/gtest.h>
+
+#include <command.h>
+
+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"}));
+}
index bf08dba..0f3839b 100644 (file)
@@ -39,10 +39,10 @@ class HelpCommand : public Command {
 };
 
 bool HelpCommand::Run(const std::vector<std::string>& 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\"";
index 224c795..923a884 100644 (file)
@@ -47,7 +47,7 @@ class ListCommand : public Command {
 };
 
 bool ListCommand::Run(const std::vector<std::string>& 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;
index d7e2afc..2368e0f 100644 (file)
@@ -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 (file)
index 0000000..e835acd
--- /dev/null
@@ -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 <poll.h>
+#include <signal.h>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+
+#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<std::string>& args);
+
+  static bool ReadMmapDataCallback(const char* data, size_t size);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* 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<RecordFileWriter> record_file_writer_;
+
+  sighandler_t saved_sigchild_handler_;
+};
+
+bool RecordCommandImpl::Run(const std::vector<std::string>& args) {
+  // 1. Parse options, and use default measured event type if not given.
+  std::vector<std::string> 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<std::string>({"sleep", "1"});
+  }
+  std::unique_ptr<Workload> 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<pollfd> 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<std::string>& args,
+                                     std::vector<std::string>* 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<std::string>& 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 (file)
index 0000000..14fa061
--- /dev/null
@@ -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 <gtest/gtest.h>
+
+#include <command.h>
+
+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"}));
+}
index 9ba4a56..c8e59d9 100644 (file)
@@ -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<std::string>& args, std::vector<std::string>* 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<int>& 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<const EventType*, std::vector<PerfCounter>>& counters_map,
+                    std::chrono::steady_clock::duration counting_duration);
 
-  struct EventElem {
-    const EventType* const event_type;
-    std::vector<std::unique_ptr<EventFd>> event_fds;
-    std::vector<PerfCounter> event_counters;
-    PerfCounter sum_counter;
-
-    EventElem(const EventType* event_type) : event_type(event_type) {
-    }
-  };
-
-  std::vector<EventElem> measured_events_;
+  EventSelectionSet event_selection_set_;
   bool verbose_mode_;
   bool system_wide_collection_;
 };
@@ -79,7 +63,7 @@ bool StatCommandImpl::Run(const std::vector<std::string>& 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<std::string>& 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<std::string>({"sleep", "1"});
   }
   std::unique_ptr<Workload> workload = Workload::CreateWorkload(workload_args);
@@ -96,53 +81,52 @@ bool StatCommandImpl::Run(const std::vector<std::string>& args) {
 
   // 4. Open perf_event_files.
   if (system_wide_collection_) {
-    std::vector<int> 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<const EventType*, std::vector<PerfCounter>> 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<std::string>& args,
                                    std::vector<std::string>* non_option_args) {
   size_t i;
-  for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
+  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<std::string> 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<std::string>& 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<int>& cpus) {
-  for (auto& elem : measured_events_) {
-    EventAttr attr = EventAttr::CreateDefaultAttrToMonitorEvent(*elem.event_type);
-    std::vector<std::unique_ptr<EventFd>> event_fds;
-    for (auto& cpu : cpus) {
-      auto event_fd = EventFd::OpenEventFileForCpu(attr, cpu);
-      if (event_fd != nullptr) {
-        event_fds.push_back(std::move(event_fd));
-      }
-    }
-    // As the online cpus can be enabled or disabled at runtime, we may not open perf_event_file
-    // for all cpus successfully. But we should open at least one cpu successfully for each event
-    // type.
-    if (event_fds.empty()) {
-      LOG(ERROR) << "failed to open perf_event_files for event_type " << elem.event_type->name
-                 << " on all cpus";
-      return false;
-    }
-    elem.event_fds = std::move(event_fds);
-  }
-  return true;
-}
-
-bool StatCommandImpl::OpenEventFilesForProcess(pid_t pid) {
-  for (auto& elem : measured_events_) {
-    EventAttr attr = EventAttr::CreateDefaultAttrToMonitorEvent(*elem.event_type);
-    std::vector<std::unique_ptr<EventFd>> event_fds;
-    auto event_fd = EventFd::OpenEventFileForProcess(attr, pid);
-    if (event_fd == nullptr) {
-      PLOG(ERROR) << "failed to open perf_event_file for event_type " << elem.event_type->name
-                  << " on pid " << pid;
-      return false;
-    }
-    event_fds.push_back(std::move(event_fd));
-    elem.event_fds = std::move(event_fds);
-  }
-  return true;
-}
-
-bool StatCommandImpl::StartCounting() {
-  for (auto& elem : measured_events_) {
-    for (auto& event_fd : elem.event_fds) {
-      if (!event_fd->EnableEvent()) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-bool StatCommandImpl::StopCounting() {
-  for (auto& elem : measured_events_) {
-    for (auto& event_fd : elem.event_fds) {
-      if (!event_fd->DisableEvent()) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-bool StatCommandImpl::ReadCounters() {
-  for (auto& elem : measured_events_) {
-    std::vector<PerfCounter> event_counters;
-    for (auto& event_fd : elem.event_fds) {
-      PerfCounter counter;
-      if (!event_fd->ReadCounter(&counter)) {
-        return false;
-      }
-      event_counters.push_back(counter);
-    }
-    PerfCounter sum_counter = event_counters.front();
-    for (size_t i = 1; i < event_counters.size(); ++i) {
-      sum_counter.value += event_counters[i].value;
-      sum_counter.time_enabled += event_counters[i].time_enabled;
-      sum_counter.time_running += event_counters[i].time_running;
-    }
-    elem.event_counters = event_counters;
-    elem.sum_counter = sum_counter;
-  }
-  return true;
-}
-
-bool StatCommandImpl::ShowCounters(std::chrono::steady_clock::duration counting_duration) {
+bool StatCommandImpl::ShowCounters(
+    const std::map<const EventType*, std::vector<PerfCounter>>& 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<int64_t>(static_cast<double>(counter.value) *
-                                            counter.time_enabled / counter.time_running);
+        scaled_count = static_cast<int64_t>(static_cast<double>(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<std::chrono::duration<double>>(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<std::string>& 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);
   }
index acf668f..bcbb185 100644 (file)
@@ -16,8 +16,6 @@
 
 #include <gtest/gtest.h>
 
-#include <chrono>
-
 #include <command.h>
 
 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"}));
 }
index 8b911fd..79cbc44 100644 (file)
@@ -28,7 +28,7 @@ static std::vector<Command*>& Commands() {
 }
 
 Command* Command::FindCommandByName(const std::string& cmd_name) {
-  for (auto command : Commands()) {
+  for (auto& command : Commands()) {
     if (command->Name() == cmd_name) {
       return command;
     }
index b03e489..fbc8cfb 100644 (file)
@@ -21,6 +21,8 @@
 #include <vector>
 
 std::vector<int> GetOnlineCpus();
+
+// Expose the following functions for unit tests.
 std::vector<int> GetOnlineCpusFromString(const std::string& s);
 
 #endif  // SIMPLE_PERF_ENVIRONMENT_H_
index a53f635..398554d 100644 (file)
@@ -16,7 +16,7 @@
 
 #include <gtest/gtest.h>
 
-#include <environment.h>
+#include "environment.h"
 
 TEST(environment, GetOnlineCpusFromString) {
   ASSERT_EQ(GetOnlineCpusFromString(""), std::vector<int>());
index 418bf44..2b05931 100644 (file)
 #include "event_type.h"
 #include "utils.h"
 
+static std::string BitsToString(const std::string& name, uint64_t bits,
+                                const std::vector<std::pair<int, std::string>>& 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<int, std::string> map = {
+  static std::vector<std::pair<int, std::string>> 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<std::pair<int, std::string>> 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);
 }
index 30052f1..52f4aca 100644 (file)
 
 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_
index b7c1b4c..386685c 100644 (file)
 #include <fcntl.h>
 #include <stdio.h>
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 #include <memory>
 
+#include <base/file.h>
 #include <base/logging.h>
 #include <base/stringprintf.h>
 
 #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> EventFd::OpenEventFileForProcess(const EventAttr& attr, pid_t pid) {
+std::unique_ptr<EventFd> EventFd::OpenEventFileForProcess(const perf_event_attr& attr, pid_t pid) {
   return OpenEventFile(attr, pid, -1);
 }
 
-std::unique_ptr<EventFd> EventFd::OpenEventFileForCpu(const EventAttr& attr, int cpu) {
+std::unique_ptr<EventFd> EventFd::OpenEventFileForCpu(const perf_event_attr& attr, int cpu) {
   return OpenEventFile(attr, -1, cpu);
 }
 
-std::unique_ptr<EventFd> EventFd::OpenEventFile(const EventAttr& attr, pid_t pid, int cpu) {
-  perf_event_attr perf_attr = attr.Attr();
+std::unique_ptr<EventFd> 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> 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<perf_event_mmap_page*>(mmap_addr_);
+  mmap_data_buffer_ = reinterpret_cast<char*>(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;
+}
index 1fc9713..36ea0cb 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef SIMPLE_PERF_EVENT_FD_H_
 #define SIMPLE_PERF_EVENT_FD_H_
 
+#include <poll.h>
 #include <sys/types.h>
 
 #include <memory>
@@ -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<EventFd> OpenEventFileForProcess(const EventAttr& attr, pid_t pid);
-  static std::unique_ptr<EventFd> OpenEventFileForCpu(const EventAttr& attr, int cpu);
-  static std::unique_ptr<EventFd> OpenEventFile(const EventAttr& attr, pid_t pid, int cpu);
+  static std::unique_ptr<EventFd> OpenEventFileForProcess(const perf_event_attr& attr, pid_t pid);
+  static std::unique_ptr<EventFd> OpenEventFileForCpu(const perf_event_attr& attr, int cpu);
+  static std::unique_ptr<EventFd> 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 (file)
index 0000000..61f1705
--- /dev/null
@@ -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 <base/logging.h>
+
+#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<int> 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<const EventType*, std::vector<PerfCounter>>* counters_map) {
+  for (auto& selection : selections_) {
+    std::vector<PerfCounter> 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<pollfd>* 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<EventFd>& event_fd,
+                                   std::function<bool(const char*, size_t)> 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<bool(const char*, size_t)> 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<std::unique_ptr<EventFd>>& 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 (file)
index 0000000..78be069
--- /dev/null
@@ -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 <poll.h>
+#include <functional>
+#include <map>
+#include <vector>
+
+#include <base/macros.h>
+
+#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<const EventType*, std::vector<PerfCounter>>* counters_map);
+  void PreparePollForEventFiles(std::vector<pollfd>* pollfds);
+  bool MmapEventFiles(size_t mmap_pages);
+  bool ReadMmapEventData(std::function<bool(const char*, size_t)> callback);
+
+  std::string FindEventFileNameById(uint64_t id);
+  const perf_event_attr& FindEventAttrByType(const EventType& event_type);
+  const std::vector<std::unique_ptr<EventFd>>& FindEventFdsByType(const EventType& event_type);
+
+ private:
+  struct EventSelection {
+    const EventType* event_type;
+    perf_event_attr event_attr;
+    std::vector<std::unique_ptr<EventFd>> event_fds;
+  };
+  EventSelection* FindSelectionByType(const EventType& event_type);
+
+  std::vector<EventSelection> selections_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
+};
+
+#endif  // SIMPLE_PERF_EVENT_SELECTION_SET_H_
index 01d7457..15e3cf1 100644 (file)
@@ -19,6 +19,9 @@
 #include <unistd.h>
 #include <string>
 #include <vector>
+
+#include <base/logging.h>
+
 #include "event_attr.h"
 #include "event_fd.h"
 
@@ -31,8 +34,7 @@ static std::vector<const EventType> 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<const EventType>& 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) {
index e2f21d5..b486a29 100644 (file)
@@ -37,7 +37,8 @@ struct EventType {
 class EventTypeFactory {
  public:
   static const std::vector<const EventType>& 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);
 };
 
index 1f7c7da..173026e 100644 (file)
@@ -26,11 +26,15 @@ int main(int argc, char** argv) {
   InitLogging(argv, android::base::StderrLogger);
   std::vector<std::string> 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 (file)
index 0000000..8e88867
--- /dev/null
@@ -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 <inttypes.h>
+#include <unordered_map>
+
+#include <base/logging.h>
+#include <base/stringprintf.h>
+
+#include "environment.h"
+#include "utils.h"
+
+static std::string RecordTypeToString(int record_type) {
+  static std::unordered_map<int, std::string> 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 <class T>
+void MoveFromBinaryFormat(T& data, const char*& p) {
+  data = *reinterpret_cast<const T*>(p);
+  p += sizeof(T);
+}
+
+template <class T>
+void MoveToBinaryFormat(const T& data, char*& p) {
+  *reinterpret_cast<T*>(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<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(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<void*>(data.addr), data.len);
+  PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data.pgoff, filename.c_str());
+}
+
+std::vector<char> MmapRecord::BinaryFormat() const {
+  std::vector<char> 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<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(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<char> CommRecord::BinaryFormat() const {
+  std::vector<char> 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<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(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<const char*>(pheader + 1);
+  const char* end = reinterpret_cast<const char*>(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<void*>(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<void*>(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<const Record> ReadRecordFromBuffer(const perf_event_attr& attr,
+                                                   const perf_event_header* pheader) {
+  switch (pheader->type) {
+    case PERF_RECORD_MMAP:
+      return std::unique_ptr<const Record>(new MmapRecord(attr, pheader));
+    case PERF_RECORD_COMM:
+      return std::unique_ptr<const Record>(new CommRecord(attr, pheader));
+    case PERF_RECORD_EXIT:
+      return std::unique_ptr<const Record>(new ExitRecord(attr, pheader));
+    case PERF_RECORD_SAMPLE:
+      return std::unique_ptr<const Record>(new SampleRecord(attr, pheader));
+    default:
+      return std::unique_ptr<const Record>(new Record(pheader));
+  }
+}
diff --git a/simpleperf/record.h b/simpleperf/record.h
new file mode 100644 (file)
index 0000000..fbd523d
--- /dev/null
@@ -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 <string>
+#include <vector>
+
+#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<char> 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<char> 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<const Record> 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 (file)
index 0000000..784dc4e
--- /dev/null
@@ -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 <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <set>
+
+#include <base/logging.h>
+
+#include "event_fd.h"
+#include "perf_event.h"
+#include "record.h"
+#include "utils.h"
+
+using namespace PerfFileFormat;
+
+std::unique_ptr<RecordFileWriter> RecordFileWriter::CreateInstance(
+    const std::string& filename, const perf_event_attr& event_attr,
+    const std::vector<std::unique_ptr<EventFd>>& 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<RecordFileWriter>(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<std::unique_ptr<EventFd>>& event_fds) {
+  // Skip file header part.
+  if (fseek(record_fp_, sizeof(FileHeader), SEEK_SET) == -1) {
+    return false;
+  }
+
+  // Write id section.
+  std::vector<uint64_t> 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> 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<RecordFileReader>(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<char*>(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<const char*>(mmap_addr);
+  mmap_len_ = mmap_len;
+  return true;
+}
+
+const FileHeader* RecordFileReader::FileHeader() {
+  return reinterpret_cast<const struct FileHeader*>(mmap_addr_);
+}
+
+std::vector<const FileAttr*> RecordFileReader::AttrSection() {
+  std::vector<const FileAttr*> result;
+  const struct FileHeader* header = FileHeader();
+  size_t attr_count = header->attrs.size / header->attr_size;
+  const FileAttr* attr = reinterpret_cast<const FileAttr*>(mmap_addr_ + header->attrs.offset);
+  for (size_t i = 0; i < attr_count; ++i) {
+    result.push_back(attr++);
+  }
+  return result;
+}
+
+std::vector<uint64_t> RecordFileReader::IdsForAttr(const FileAttr* attr) {
+  std::vector<uint64_t> result;
+  size_t id_count = attr->ids.size / sizeof(uint64_t);
+  const uint64_t* id = reinterpret_cast<const uint64_t*>(mmap_addr_ + attr->ids.offset);
+  for (size_t i = 0; i < id_count; ++i) {
+    result.push_back(*id++);
+  }
+  return result;
+}
+
+std::vector<std::unique_ptr<const Record>> RecordFileReader::DataSection() {
+  std::vector<std::unique_ptr<const Record>> 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<const perf_event_header*>(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 (file)
index 0000000..cc213d5
--- /dev/null
@@ -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 <stdio.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#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<RecordFileWriter> CreateInstance(
+      const std::string& filename, const perf_event_attr& event_attr,
+      const std::vector<std::unique_ptr<EventFd>>& event_fds);
+
+  ~RecordFileWriter();
+
+  bool WriteData(const void* buf, size_t len);
+
+  bool WriteData(const std::vector<char>& 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<std::unique_ptr<EventFd>>& 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<int> features_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecordFileWriter);
+};
+
+// RecordFileReader read contents from a perf record file, like perf.data.
+class RecordFileReader {
+ public:
+  static std::unique_ptr<RecordFileReader> CreateInstance(const std::string& filename);
+
+  ~RecordFileReader();
+
+  const PerfFileFormat::FileHeader* FileHeader();
+  std::vector<const PerfFileFormat::FileAttr*> AttrSection();
+  std::vector<uint64_t> IdsForAttr(const PerfFileFormat::FileAttr* attr);
+  std::vector<std::unique_ptr<const Record>> 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 (file)
index 0000000..9758f11
--- /dev/null
@@ -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 (file)
index 0000000..85c0212
--- /dev/null
@@ -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 <gtest/gtest.h>
+
+#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<EventFd> 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<std::unique_ptr<EventFd>> event_fds;
+};
+
+TEST_F(RecordFileTest, smoke) {
+  // Write to a record file.
+  std::unique_ptr<RecordFileWriter> 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<RecordFileReader> reader = RecordFileReader::CreateInstance(filename);
+  ASSERT_TRUE(reader != nullptr);
+  const FileHeader* file_header = reader->FileHeader();
+  ASSERT_TRUE(file_header != nullptr);
+  std::vector<const FileAttr*> attrs = reader->AttrSection();
+  ASSERT_EQ(1u, attrs.size());
+  ASSERT_EQ(0, memcmp(&attrs[0]->attr, &event_attr, sizeof(perf_event_attr)));
+  std::vector<uint64_t> ids = reader->IdsForAttr(attrs[0]);
+  ASSERT_EQ(1u, ids.size());
+
+  // Read and check data section.
+  std::vector<std::unique_ptr<const Record>> records = reader->DataSection();
+  ASSERT_EQ(1u, records.size());
+  ASSERT_EQ(mmap_record.header.type, records[0]->header.type);
+
+  ASSERT_TRUE(reader->Close());
+}
index f7819cb..eea8988 100644 (file)
 void PrintIndented(size_t indent, const char* fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
-  printf("%*s", static_cast<int>(indent), "");
+  printf("%*s", static_cast<int>(indent * 2), "");
   vprintf(fmt, ap);
   va_end(ap);
 }
 
-bool ReadNBytesFromFile(int fd, void* buf, size_t nbytes) {
-  char* p = reinterpret_cast<char*>(buf);
-  size_t bytes_left = nbytes;
-  while (bytes_left > 0) {
-    ssize_t nread = TEMP_FAILURE_RETRY(read(fd, p, bytes_left));
-    if (nread <= 0) {
-      return false;
-    } else {
-      p += nread;
-      bytes_left -= nread;
-    }
+bool IsPowerOfTwo(uint64_t value) {
+  return (value != 0 && ((value & (value - 1)) == 0));
+}
+
+bool NextArgumentOrError(const std::vector<std::string>& 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;
 }
index b73dccd..2ff5d95 100644 (file)
@@ -21,6 +21,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string>
+#include <vector>
 
 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<std::string>& args, size_t* pi);
+
+#define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1))
 
 #endif  // SIMPLE_PERF_UTILS_H_
index 46dfc40..f8e4edd 100644 (file)
@@ -93,9 +93,9 @@ static void ChildProcessFn(std::vector<std::string>& 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;
   }
 }
 
index dea8030..57622c8 100644 (file)
@@ -48,7 +48,7 @@ class Workload {
   bool Start();
   bool IsFinished();
   void WaitFinish();
-  pid_t GetWorkPid() {
+  pid_t GetPid() {
     return work_pid_;
   }
 
index 5f0645f..0cc67b8 100644 (file)
@@ -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());