OSDN Git Service

Dump kernel/modules/thread mmap information in `simpleperf record`.
authorYabin Cui <yabinc@google.com>
Tue, 5 May 2015 03:27:57 +0000 (20:27 -0700)
committerYabin Cui <yabinc@google.com>
Thu, 14 May 2015 02:13:17 +0000 (19:13 -0700)
Bug: 19483574
Change-Id: Ia65cb12804a6dffa440501736a6229b2f7248958
(cherry picked from commit 7d59bb49fb47fbc82ef5c77d7aebf7174fd996e1)

17 files changed:
simpleperf/Android.mk
simpleperf/cmd_dumprecord_test.cpp
simpleperf/cmd_list_test.cpp
simpleperf/cmd_record.cpp
simpleperf/cmd_record_test.cpp
simpleperf/cmd_stat_test.cpp
simpleperf/command_test.cpp
simpleperf/environment.cpp
simpleperf/environment.h
simpleperf/environment_test.cpp
simpleperf/record.cpp
simpleperf/record.h
simpleperf/record_equal_test.h [new file with mode: 0644]
simpleperf/record_file_test.cpp
simpleperf/record_test.cpp [new file with mode: 0644]
simpleperf/utils.cpp
simpleperf/utils.h

index 80da24a..2635da0 100644 (file)
@@ -99,6 +99,7 @@ simpleperf_unit_test_src_files := \
   environment_test.cpp \
   gtest_main.cpp \
   record_file_test.cpp \
+  record_test.cpp \
   workload_test.cpp \
 
 include $(CLEAR_VARS)
index 90772eb..c470833 100644 (file)
@@ -16,7 +16,7 @@
 
 #include <gtest/gtest.h>
 
-#include <command.h>
+#include "command.h"
 
 class DumpRecordCommandTest : public ::testing::Test {
  protected:
index 2368e0f..4b873a1 100644 (file)
@@ -16,7 +16,7 @@
 
 #include <gtest/gtest.h>
 
-#include <command.h>
+#include "command.h"
 
 TEST(cmd_list, smoke) {
   Command* list_cmd = Command::FindCommandByName("list");
index e835acd..e27b6e4 100644 (file)
@@ -25,6 +25,7 @@
 #include "environment.h"
 #include "event_selection_set.h"
 #include "event_type.h"
+#include "record.h"
 #include "record_file.h"
 #include "utils.h"
 #include "workload.h"
@@ -57,6 +58,8 @@ class RecordCommandImpl {
   bool SetMeasuredEventType(const std::string& event_type_name);
   void SetEventSelection();
   bool WriteData(const char* data, size_t size);
+  bool DumpKernelAndModuleMmaps();
+  bool DumpThreadCommAndMmaps();
 
   bool use_sample_freq_;    // Use sample_freq_ when true, otherwise using sample_period_.
   uint64_t sample_freq_;    // Sample 'sample_freq_' times per second.
@@ -116,15 +119,21 @@ bool RecordCommandImpl::Run(const std::vector<std::string>& args) {
   std::vector<pollfd> pollfds;
   event_selection_set_.PreparePollForEventFiles(&pollfds);
 
-  // 4. Open record file writer.
+  // 4. Open record file writer, and dump kernel/modules/threads mmap information.
   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;
   }
+  if (!DumpKernelAndModuleMmaps()) {
+    return false;
+  }
+  if (system_wide_collection_ && !DumpThreadCommAndMmaps()) {
+    return false;
+  }
 
-  // 5. Dump records in mmap buffers of perf_event_files to output file while workload is running.
+  // 5. Write 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.
@@ -234,6 +243,65 @@ bool RecordCommandImpl::WriteData(const char* data, size_t size) {
   return record_file_writer_->WriteData(data, size);
 }
 
+bool RecordCommandImpl::DumpKernelAndModuleMmaps() {
+  KernelMmap kernel_mmap;
+  std::vector<ModuleMmap> module_mmaps;
+  if (!GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps)) {
+    return false;
+  }
+  const perf_event_attr& attr = event_selection_set_.FindEventAttrByType(*measured_event_type_);
+  MmapRecord mmap_record = CreateMmapRecord(attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
+                                            kernel_mmap.len, kernel_mmap.pgoff, kernel_mmap.name);
+  if (!record_file_writer_->WriteData(mmap_record.BinaryFormat())) {
+    return false;
+  }
+  for (auto& module_mmap : module_mmaps) {
+    std::string filename = module_mmap.filepath;
+    if (filename.empty()) {
+      filename = "[" + module_mmap.name + "]";
+    }
+    MmapRecord mmap_record = CreateMmapRecord(attr, true, UINT_MAX, 0, module_mmap.start_addr,
+                                              module_mmap.len, 0, filename);
+    if (!record_file_writer_->WriteData(mmap_record.BinaryFormat())) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RecordCommandImpl::DumpThreadCommAndMmaps() {
+  std::vector<ThreadComm> thread_comms;
+  if (!GetThreadComms(&thread_comms)) {
+    return false;
+  }
+  const perf_event_attr& attr = event_selection_set_.FindEventAttrByType(*measured_event_type_);
+  for (auto& thread : thread_comms) {
+    CommRecord record = CreateCommRecord(attr, thread.tgid, thread.tid, thread.comm);
+    if (!record_file_writer_->WriteData(record.BinaryFormat())) {
+      return false;
+    }
+    if (thread.is_process) {
+      std::vector<ThreadMmap> thread_mmaps;
+      if (!GetThreadMmapsInProcess(thread.tgid, &thread_mmaps)) {
+        // The thread may exit before we get its info.
+        continue;
+      }
+      for (auto& thread_mmap : thread_mmaps) {
+        if (thread_mmap.executable == 0) {
+          continue;  // No need to dump non-executable mmap info.
+        }
+        MmapRecord record =
+            CreateMmapRecord(attr, false, thread.tgid, thread.tid, thread_mmap.start_addr,
+                             thread_mmap.len, thread_mmap.pgoff, thread_mmap.name);
+        if (!record_file_writer_->WriteData(record.BinaryFormat())) {
+          return false;
+        }
+      }
+    }
+  }
+  return true;
+}
+
 class RecordCommand : public Command {
  public:
   RecordCommand()
index 14fa061..be01139 100644 (file)
 
 #include <gtest/gtest.h>
 
-#include <command.h>
+#include "command.h"
+#include "environment.h"
+#include "record.h"
+#include "record_file.h"
 
 class RecordCommandTest : public ::testing::Test {
  protected:
@@ -52,3 +55,22 @@ TEST_F(RecordCommandTest, freq_option) {
 TEST_F(RecordCommandTest, output_file_option) {
   ASSERT_TRUE(record_cmd->Run({"record", "-o", "perf2.data", "sleep", "1"}));
 }
+
+TEST_F(RecordCommandTest, dump_kernel_mmap) {
+  ASSERT_TRUE(record_cmd->Run({"record", "sleep", "1"}));
+  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance("perf.data");
+  ASSERT_TRUE(reader != nullptr);
+  std::vector<std::unique_ptr<const Record>> records = reader->DataSection();
+  ASSERT_GT(records.size(), 0U);
+  bool have_kernel_mmap = false;
+  for (auto& record : records) {
+    if (record->header.type == PERF_RECORD_MMAP) {
+      const MmapRecord* mmap_record = static_cast<const MmapRecord*>(record.get());
+      if (mmap_record->filename == DEFAULT_KERNEL_MMAP_NAME) {
+        have_kernel_mmap = true;
+        break;
+      }
+    }
+  }
+  ASSERT_TRUE(have_kernel_mmap);
+}
index bcbb185..6a7a1cd 100644 (file)
@@ -16,7 +16,7 @@
 
 #include <gtest/gtest.h>
 
-#include <command.h>
+#include "command.h"
 
 class StatCommandTest : public ::testing::Test {
  protected:
index dc2e4a6..4a0baa6 100644 (file)
@@ -16,7 +16,7 @@
 
 #include <gtest/gtest.h>
 
-#include <command.h>
+#include "command.h"
 
 class MockCommand : public Command {
  public:
index 14c256a..a2de935 100644 (file)
 
 #include "environment.h"
 
+#include <inttypes.h>
+#include <stdio.h>
 #include <stdlib.h>
+#include <unordered_map>
 #include <vector>
 
+#include <base/file.h>
 #include <base/logging.h>
+#include <base/strings.h>
+#include <base/stringprintf.h>
 
 #include "utils.h"
 
@@ -65,3 +71,278 @@ std::vector<int> GetOnlineCpusFromString(const std::string& s) {
   }
   return result;
 }
+
+bool ProcessKernelSymbols(const std::string& symbol_file,
+                          std::function<bool(const KernelSymbol&)> callback) {
+  FILE* fp = fopen(symbol_file.c_str(), "re");
+  if (fp == nullptr) {
+    PLOG(ERROR) << "failed to open file " << symbol_file;
+    return false;
+  }
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    // Parse line like: ffffffffa005c4e4 d __warned.41698       [libsas]
+    char name[reader.MaxLineSize()];
+    char module[reader.MaxLineSize()];
+    strcpy(module, "");
+
+    KernelSymbol symbol;
+    if (sscanf(line, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module) < 3) {
+      continue;
+    }
+    symbol.name = name;
+    size_t module_len = strlen(module);
+    if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
+      module[module_len - 1] = '\0';
+      symbol.module = &module[1];
+    } else {
+      symbol.module = nullptr;
+    }
+
+    if (callback(symbol)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool FindStartOfKernelSymbolCallback(const KernelSymbol& symbol, uint64_t* start_addr) {
+  if (symbol.module == nullptr) {
+    *start_addr = symbol.addr;
+    return true;
+  }
+  return false;
+}
+
+static bool FindStartOfKernelSymbol(const std::string& symbol_file, uint64_t* start_addr) {
+  return ProcessKernelSymbols(
+      symbol_file, std::bind(&FindStartOfKernelSymbolCallback, std::placeholders::_1, start_addr));
+}
+
+static bool FindKernelFunctionSymbolCallback(const KernelSymbol& symbol, const std::string& name,
+                                             uint64_t* addr) {
+  if ((symbol.type == 'T' || symbol.type == 'W' || symbol.type == 'A') &&
+      symbol.module == nullptr && name == symbol.name) {
+    *addr = symbol.addr;
+    return true;
+  }
+  return false;
+}
+
+static bool FindKernelFunctionSymbol(const std::string& symbol_file, const std::string& name,
+                                     uint64_t* addr) {
+  return ProcessKernelSymbols(
+      symbol_file, std::bind(&FindKernelFunctionSymbolCallback, std::placeholders::_1, name, addr));
+}
+
+std::vector<ModuleMmap> GetLoadedModules() {
+  std::vector<ModuleMmap> result;
+  FILE* fp = fopen("/proc/modules", "re");
+  if (fp == nullptr) {
+    // There is no /proc/modules on Android devices, so we don't print error if failed to open it.
+    PLOG(DEBUG) << "failed to open file /proc/modules";
+    return result;
+  }
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    // Parse line like: nf_defrag_ipv6 34768 1 nf_conntrack_ipv6, Live 0xffffffffa0fe5000
+    char name[reader.MaxLineSize()];
+    uint64_t addr;
+    if (sscanf(line, "%s%*lu%*u%*s%*s 0x%" PRIx64, name, &addr) == 2) {
+      ModuleMmap map;
+      map.name = name;
+      map.start_addr = addr;
+      result.push_back(map);
+    }
+  }
+  return result;
+}
+
+static std::string GetLinuxVersion() {
+  std::string content;
+  if (android::base::ReadFileToString("/proc/version", &content)) {
+    char s[content.size() + 1];
+    if (sscanf(content.c_str(), "Linux version %s", s) == 1) {
+      return s;
+    }
+  }
+  PLOG(FATAL) << "can't read linux version";
+  return "";
+}
+
+static void GetAllModuleFiles(const std::string& path,
+                              std::unordered_map<std::string, std::string>* module_file_map) {
+  std::vector<std::string> files;
+  std::vector<std::string> subdirs;
+  GetEntriesInDir(path, &files, &subdirs);
+  for (auto& name : files) {
+    if (android::base::EndsWith(name, ".ko")) {
+      std::string module_name = name.substr(0, name.size() - 3);
+      std::replace(module_name.begin(), module_name.end(), '-', '_');
+      module_file_map->insert(std::make_pair(module_name, path + name));
+    }
+  }
+  for (auto& name : subdirs) {
+    GetAllModuleFiles(path + "/" + name, module_file_map);
+  }
+}
+
+static std::vector<ModuleMmap> GetModulesInUse() {
+  // TODO: There is no /proc/modules or /lib/modules on Android, find methods work on it.
+  std::vector<ModuleMmap> module_mmaps = GetLoadedModules();
+  std::string linux_version = GetLinuxVersion();
+  std::string module_dirpath = "/lib/modules/" + linux_version + "/kernel";
+  std::unordered_map<std::string, std::string> module_file_map;
+  GetAllModuleFiles(module_dirpath, &module_file_map);
+  for (auto& module : module_mmaps) {
+    auto it = module_file_map.find(module.name);
+    if (it != module_file_map.end()) {
+      module.filepath = it->second;
+    }
+  }
+  return module_mmaps;
+}
+
+bool GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<ModuleMmap>* module_mmaps) {
+  if (!FindStartOfKernelSymbol("/proc/kallsyms", &kernel_mmap->start_addr)) {
+    LOG(DEBUG) << "call FindStartOfKernelSymbol() failed";
+    return false;
+  }
+  if (!FindKernelFunctionSymbol("/proc/kallsyms", "_text", &kernel_mmap->pgoff)) {
+    LOG(DEBUG) << "call FindKernelFunctionSymbol() failed";
+    return false;
+  }
+  kernel_mmap->name = DEFAULT_KERNEL_MMAP_NAME;
+  *module_mmaps = GetModulesInUse();
+  if (module_mmaps->size() == 0) {
+    kernel_mmap->len = ULLONG_MAX - kernel_mmap->start_addr;
+  } else {
+    std::sort(
+        module_mmaps->begin(), module_mmaps->end(),
+        [](const ModuleMmap& m1, const ModuleMmap& m2) { return m1.start_addr < m2.start_addr; });
+    CHECK_LE(kernel_mmap->start_addr, (*module_mmaps)[0].start_addr);
+    // When not having enough privilege, all addresses are read as 0.
+    if (kernel_mmap->start_addr == (*module_mmaps)[0].start_addr) {
+      kernel_mmap->len = 0;
+    } else {
+      kernel_mmap->len = (*module_mmaps)[0].start_addr - kernel_mmap->start_addr - 1;
+    }
+    for (size_t i = 0; i + 1 < module_mmaps->size(); ++i) {
+      if ((*module_mmaps)[i].start_addr == (*module_mmaps)[i + 1].start_addr) {
+        (*module_mmaps)[i].len = 0;
+      } else {
+        (*module_mmaps)[i].len =
+            (*module_mmaps)[i + 1].start_addr - (*module_mmaps)[i].start_addr - 1;
+      }
+    }
+    module_mmaps->back().len = ULLONG_MAX - module_mmaps->back().start_addr;
+  }
+  return true;
+}
+
+static bool StringToPid(const std::string& s, pid_t* pid) {
+  char* endptr;
+  *pid = static_cast<pid_t>(strtol(s.c_str(), &endptr, 10));
+  return *endptr == '\0';
+}
+
+static bool ReadThreadNameAndTgid(const std::string& status_file, std::string* comm, pid_t* tgid) {
+  FILE* fp = fopen(status_file.c_str(), "re");
+  if (fp == nullptr) {
+    return false;
+  }
+  bool read_comm = false;
+  bool read_tgid = false;
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    char s[reader.MaxLineSize()];
+    if (sscanf(line, "Name:%s", s) == 1) {
+      *comm = s;
+      read_comm = true;
+    } else if (sscanf(line, "Tgid:%d", tgid) == 1) {
+      read_tgid = true;
+    }
+    if (read_comm && read_tgid) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool GetThreadComm(pid_t pid, std::vector<ThreadComm>* thread_comms) {
+  std::string task_dirname = android::base::StringPrintf("/proc/%d/task", pid);
+  std::vector<std::string> subdirs;
+  GetEntriesInDir(task_dirname, nullptr, &subdirs);
+  for (auto& name : subdirs) {
+    pid_t tid;
+    if (!StringToPid(name, &tid)) {
+      continue;
+    }
+    std::string status_file = task_dirname + "/" + name + "/status";
+    std::string comm;
+    pid_t tgid;
+    if (!ReadThreadNameAndTgid(status_file, &comm, &tgid)) {
+      return false;
+    }
+    ThreadComm thread;
+    thread.tid = tid;
+    thread.tgid = tgid;
+    thread.comm = comm;
+    thread.is_process = (tid == pid);
+    thread_comms->push_back(thread);
+  }
+  return true;
+}
+
+bool GetThreadComms(std::vector<ThreadComm>* thread_comms) {
+  thread_comms->clear();
+  std::vector<std::string> subdirs;
+  GetEntriesInDir("/proc", nullptr, &subdirs);
+  for (auto& name : subdirs) {
+    pid_t pid;
+    if (!StringToPid(name, &pid)) {
+      continue;
+    }
+    if (!GetThreadComm(pid, thread_comms)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps) {
+  std::string map_file = android::base::StringPrintf("/proc/%d/maps", pid);
+  FILE* fp = fopen(map_file.c_str(), "re");
+  if (fp == nullptr) {
+    PLOG(DEBUG) << "can't open file " << map_file;
+    return false;
+  }
+  thread_mmaps->clear();
+  LineReader reader(fp);
+  char* line;
+  while ((line = reader.ReadLine()) != nullptr) {
+    // Parse line like: 00400000-00409000 r-xp 00000000 fc:00 426998  /usr/lib/gvfs/gvfsd-http
+    uint64_t start_addr, end_addr, pgoff;
+    char type[reader.MaxLineSize()];
+    char execname[reader.MaxLineSize()];
+    strcpy(execname, "");
+    if (sscanf(line, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %*x:%*x %*u %s\n", &start_addr,
+               &end_addr, type, &pgoff, execname) < 4) {
+      continue;
+    }
+    if (strcmp(execname, "") == 0) {
+      strcpy(execname, DEFAULT_EXECNAME_FOR_THREAD_MMAP);
+    }
+    ThreadMmap thread;
+    thread.start_addr = start_addr;
+    thread.len = end_addr - start_addr;
+    thread.pgoff = pgoff;
+    thread.name = execname;
+    thread.executable = (type[2] == 'x');
+    thread_mmaps->push_back(thread);
+  }
+  return true;
+}
index fbc8cfb..c411067 100644 (file)
 #ifndef SIMPLE_PERF_ENVIRONMENT_H_
 #define SIMPLE_PERF_ENVIRONMENT_H_
 
+#include <functional>
 #include <string>
 #include <vector>
 
 std::vector<int> GetOnlineCpus();
 
+static const char* DEFAULT_KERNEL_MMAP_NAME = "[kernel.kallsyms]_text";
+
+struct KernelMmap {
+  std::string name;
+  uint64_t start_addr;
+  uint64_t len;
+  uint64_t pgoff;
+};
+
+struct ModuleMmap {
+  std::string name;
+  uint64_t start_addr;
+  uint64_t len;
+  std::string filepath;
+};
+
+bool GetKernelAndModuleMmaps(KernelMmap* kernel_mmap, std::vector<ModuleMmap>* module_mmaps);
+
+struct ThreadComm {
+  pid_t tgid, tid;
+  std::string comm;
+  bool is_process;
+};
+
+bool GetThreadComms(std::vector<ThreadComm>* thread_comms);
+
+static const char* DEFAULT_EXECNAME_FOR_THREAD_MMAP = "//anon";
+
+struct ThreadMmap {
+  uint64_t start_addr;
+  uint64_t len;
+  uint64_t pgoff;
+  std::string name;
+  bool executable;
+};
+
+bool GetThreadMmapsInProcess(pid_t pid, std::vector<ThreadMmap>* thread_mmaps);
+
 // Expose the following functions for unit tests.
 std::vector<int> GetOnlineCpusFromString(const std::string& s);
 
+struct KernelSymbol {
+  uint64_t addr;
+  char type;
+  const char* name;
+  const char* module;  // If nullptr, the symbol is not in a kernel module.
+};
+
+bool ProcessKernelSymbols(const std::string& symbol_file,
+                          std::function<bool(const KernelSymbol&)> callback);
+
 #endif  // SIMPLE_PERF_ENVIRONMENT_H_
index 398554d..3cf81fa 100644 (file)
@@ -16,6 +16,9 @@
 
 #include <gtest/gtest.h>
 
+#include <functional>
+#include <base/file.h>
+
 #include "environment.h"
 
 TEST(environment, GetOnlineCpusFromString) {
@@ -23,3 +26,37 @@ TEST(environment, GetOnlineCpusFromString) {
   ASSERT_EQ(GetOnlineCpusFromString("0-2"), std::vector<int>({0, 1, 2}));
   ASSERT_EQ(GetOnlineCpusFromString("0,2-3"), std::vector<int>({0, 2, 3}));
 }
+
+static bool FindKernelSymbol(const KernelSymbol& sym1, const KernelSymbol& sym2) {
+  return sym1.addr == sym2.addr && sym1.type == sym2.type && strcmp(sym1.name, sym2.name) == 0 &&
+         ((sym1.module == nullptr && sym2.module == nullptr) ||
+          (strcmp(sym1.module, sym2.module) == 0));
+}
+
+TEST(environment, ProcessKernelSymbols) {
+  std::string data =
+      "ffffffffa005c4e4 d __warned.41698   [libsas]\n"
+      "aaaaaaaaaaaaaaaa T _text\n"
+      "cccccccccccccccc c ccccc\n";
+  const char* tempfile = "tempfile_process_kernel_symbols";
+  ASSERT_TRUE(android::base::WriteStringToFile(data, tempfile));
+  KernelSymbol expected_symbol;
+  expected_symbol.addr = 0xffffffffa005c4e4ULL;
+  expected_symbol.type = 'd';
+  expected_symbol.name = "__warned.41698";
+  expected_symbol.module = "libsas";
+  ASSERT_TRUE(ProcessKernelSymbols(
+      tempfile, std::bind(&FindKernelSymbol, std::placeholders::_1, expected_symbol)));
+
+  expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL;
+  expected_symbol.type = 'T';
+  expected_symbol.name = "_text";
+  expected_symbol.module = nullptr;
+  ASSERT_TRUE(ProcessKernelSymbols(
+      tempfile, std::bind(&FindKernelSymbol, std::placeholders::_1, expected_symbol)));
+
+  expected_symbol.name = "non_existent_symbol";
+  ASSERT_FALSE(ProcessKernelSymbols(
+      tempfile, std::bind(&FindKernelSymbol, std::placeholders::_1, expected_symbol)));
+  ASSERT_EQ(0, unlink(tempfile));
+}
index 8e88867..3e34d52 100644 (file)
@@ -59,8 +59,7 @@ void MoveToBinaryFormat(const T& data, char*& p) {
 }
 
 SampleId::SampleId() {
-  sample_id_all = false;
-  sample_type = 0;
+  memset(this, 0, sizeof(SampleId));
 }
 
 // Return sample_id size in binary format.
@@ -319,3 +318,35 @@ std::unique_ptr<const Record> ReadRecordFromBuffer(const perf_event_attr& attr,
       return std::unique_ptr<const Record>(new Record(pheader));
   }
 }
+
+MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+                            uint64_t addr, uint64_t len, uint64_t pgoff,
+                            const std::string& filename) {
+  MmapRecord record;
+  record.header.type = PERF_RECORD_MMAP;
+  record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+  record.data.pid = pid;
+  record.data.tid = tid;
+  record.data.addr = addr;
+  record.data.len = len;
+  record.data.pgoff = pgoff;
+  record.filename = filename;
+  size_t sample_id_size = record.sample_id.CreateContent(attr);
+  record.header.size = sizeof(record.header) + sizeof(record.data) +
+                       ALIGN(record.filename.size() + 1, 8) + sample_id_size;
+  return record;
+}
+
+CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+                            const std::string& comm) {
+  CommRecord record;
+  record.header.type = PERF_RECORD_COMM;
+  record.header.misc = 0;
+  record.data.pid = pid;
+  record.data.tid = tid;
+  record.comm = comm;
+  size_t sample_id_size = record.sample_id.CreateContent(attr);
+  record.header.size = sizeof(record.header) + sizeof(record.data) +
+                       ALIGN(record.comm.size() + 1, 8) + sample_id_size;
+  return record;
+}
index fbd523d..4d62784 100644 (file)
@@ -176,5 +176,9 @@ struct SampleRecord : public Record {
 
 std::unique_ptr<const Record> ReadRecordFromBuffer(const perf_event_attr& attr,
                                                    const perf_event_header* pheader);
-
+MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+                            uint64_t addr, uint64_t len, uint64_t pgoff,
+                            const std::string& filename);
+CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+                            const std::string& comm);
 #endif  // SIMPLE_PERF_RECORD_H_
diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h
new file mode 100644 (file)
index 0000000..45b0752
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+static void CheckMmapRecordDataEqual(const MmapRecord& r1, const MmapRecord& r2) {
+  ASSERT_EQ(0, memcmp(&r1.data, &r2.data, sizeof(r1.data)));
+  ASSERT_EQ(r1.filename, r2.filename);
+}
+
+static void CheckCommRecordDataEqual(const CommRecord& r1, const CommRecord& r2) {
+  ASSERT_EQ(0, memcmp(&r1.data, &r2.data, sizeof(r1.data)));
+  ASSERT_EQ(r1.comm, r2.comm);
+}
+
+static void CheckRecordEqual(const Record& r1, const Record& r2) {
+  ASSERT_EQ(0, memcmp(&r1.header, &r2.header, sizeof(r1.header)));
+  ASSERT_EQ(0, memcmp(&r1.sample_id, &r2.sample_id, sizeof(r1.sample_id)));
+  if (r1.header.type == PERF_RECORD_MMAP) {
+    CheckMmapRecordDataEqual(static_cast<const MmapRecord&>(r1), static_cast<const MmapRecord&>(r2));
+  } else if (r1.header.type == PERF_RECORD_COMM) {
+    CheckCommRecordDataEqual(static_cast<const CommRecord&>(r1), static_cast<const CommRecord&>(r2));
+  }
+}
index 85c0212..df138de 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 
+#include <string.h>
 #include "environment.h"
 #include "event_attr.h"
 #include "event_fd.h"
@@ -23,6 +24,8 @@
 #include "record.h"
 #include "record_file.h"
 
+#include "record_equal_test.h"
+
 using namespace PerfFileFormat;
 
 class RecordFileTest : public ::testing::Test {
@@ -48,9 +51,8 @@ TEST_F(RecordFileTest, smoke) {
   ASSERT_TRUE(writer != nullptr);
 
   // Write Data section.
-  MmapRecord mmap_record;
-  mmap_record.header.type = PERF_RECORD_MMAP;
-  mmap_record.header.size = sizeof(mmap_record);
+  MmapRecord mmap_record =
+      CreateMmapRecord(event_attr, true, 1, 1, 0x1000, 0x2000, 0x3000, "mmap_record_example");
   ASSERT_TRUE(writer->WriteData(mmap_record.BinaryFormat()));
   ASSERT_TRUE(writer->Close());
 
@@ -69,6 +71,7 @@ TEST_F(RecordFileTest, smoke) {
   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);
+  CheckRecordEqual(mmap_record, *records[0]);
 
   ASSERT_TRUE(reader->Close());
 }
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
new file mode 100644 (file)
index 0000000..d9e9a4b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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 "event_attr.h"
+#include "event_type.h"
+#include "record.h"
+#include "record_equal_test.h"
+
+class RecordTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    const EventType* event_type = EventTypeFactory::FindEventTypeByName("cpu-cycles");
+    ASSERT_TRUE(event_type != nullptr);
+    event_attr = CreateDefaultPerfEventAttr(*event_type);
+  }
+
+  template <class RecordType>
+  void CheckRecordMatchBinary(const RecordType& record);
+
+  perf_event_attr event_attr;
+};
+
+template <class RecordType>
+void RecordTest::CheckRecordMatchBinary(const RecordType& record) {
+  std::vector<char> binary = record.BinaryFormat();
+  std::unique_ptr<const Record> record_p =
+      ReadRecordFromBuffer(event_attr, reinterpret_cast<const perf_event_header*>(binary.data()));
+  ASSERT_TRUE(record_p != nullptr);
+  CheckRecordEqual(record, *record_p);
+}
+
+TEST_F(RecordTest, MmapRecordMatchBinary) {
+  MmapRecord record =
+      CreateMmapRecord(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000, "MmapRecord");
+  CheckRecordMatchBinary(record);
+}
+
+TEST_F(RecordTest, CommRecordMatchBinary) {
+  CommRecord record = CreateCommRecord(event_attr, 1, 2, "CommRecord");
+  CheckRecordMatchBinary(record);
+}
index eea8988..349cf5d 100644 (file)
@@ -16,6 +16,7 @@
 
 #include "utils.h"
 
+#include <dirent.h>
 #include <errno.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -44,3 +45,34 @@ bool NextArgumentOrError(const std::vector<std::string>& args, size_t* pi) {
   ++*pi;
   return true;
 }
+
+void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
+                     std::vector<std::string>* subdirs) {
+  if (files != nullptr) {
+    files->clear();
+  }
+  if (subdirs != nullptr) {
+    subdirs->clear();
+  }
+  DIR* dir = opendir(dirpath.c_str());
+  if (dir == nullptr) {
+    PLOG(DEBUG) << "can't open dir " << dirpath;
+    return;
+  }
+  dirent* entry;
+  while ((entry = readdir(dir)) != nullptr) {
+    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
+      continue;
+    }
+    if (entry->d_type == DT_DIR) {
+      if (subdirs != nullptr) {
+        subdirs->push_back(entry->d_name);
+      }
+    } else {
+      if (files != nullptr) {
+        files->push_back(entry->d_name);
+      }
+    }
+  }
+  closedir(dir);
+}
index 2ff5d95..fba3558 100644 (file)
@@ -23,7 +23,7 @@
 #include <string>
 #include <vector>
 
-void PrintIndented(size_t indent, const char* fmt, ...);
+#define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1))
 
 class LineReader {
  public:
@@ -52,10 +52,13 @@ class LineReader {
   size_t bufsize_;
 };
 
+void PrintIndented(size_t indent, const char* fmt, ...);
+
 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))
+void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
+                     std::vector<std::string>* subdirs);
 
 #endif  // SIMPLE_PERF_UTILS_H_