summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2015-05-14 02:15:48 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-05-14 02:15:48 +0000
commit9e569fa97bd48b043fb6a6d1af11902443e69d25 (patch)
tree3a777a52d40de10c576cd5326f377f740a6c5861
parent1172163929e26bcd7fdf79ad4c45b836af925119 (diff)
parent5beebc88162e3dc294f84d352b9572a05982f572 (diff)
downloadextras-9e569fa97bd48b043fb6a6d1af11902443e69d25.tar.gz
Merge "Dump kernel/modules/thread mmap information in `simpleperf record`." into mnc-dev
-rw-r--r--simpleperf/Android.mk1
-rw-r--r--simpleperf/cmd_dumprecord_test.cpp2
-rw-r--r--simpleperf/cmd_list_test.cpp2
-rw-r--r--simpleperf/cmd_record.cpp72
-rw-r--r--simpleperf/cmd_record_test.cpp24
-rw-r--r--simpleperf/cmd_stat_test.cpp2
-rw-r--r--simpleperf/command_test.cpp2
-rw-r--r--simpleperf/environment.cpp281
-rw-r--r--simpleperf/environment.h49
-rw-r--r--simpleperf/environment_test.cpp37
-rw-r--r--simpleperf/record.cpp35
-rw-r--r--simpleperf/record.h6
-rw-r--r--simpleperf/record_equal_test.h35
-rw-r--r--simpleperf/record_file_test.cpp9
-rw-r--r--simpleperf/record_test.cpp56
-rw-r--r--simpleperf/utils.cpp32
-rw-r--r--simpleperf/utils.h7
17 files changed, 637 insertions, 15 deletions
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index 80da24a6..2635da0a 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -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)
diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp
index 90772eb4..c4708330 100644
--- a/simpleperf/cmd_dumprecord_test.cpp
+++ b/simpleperf/cmd_dumprecord_test.cpp
@@ -16,7 +16,7 @@
#include <gtest/gtest.h>
-#include <command.h>
+#include "command.h"
class DumpRecordCommandTest : public ::testing::Test {
protected:
diff --git a/simpleperf/cmd_list_test.cpp b/simpleperf/cmd_list_test.cpp
index 2368e0fb..4b873a14 100644
--- a/simpleperf/cmd_list_test.cpp
+++ b/simpleperf/cmd_list_test.cpp
@@ -16,7 +16,7 @@
#include <gtest/gtest.h>
-#include <command.h>
+#include "command.h"
TEST(cmd_list, smoke) {
Command* list_cmd = Command::FindCommandByName("list");
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index e835acd6..e27b6e4b 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -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()
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 14fa0614..be011391 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -16,7 +16,10 @@
#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);
+}
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index bcbb1854..6a7a1cdb 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -16,7 +16,7 @@
#include <gtest/gtest.h>
-#include <command.h>
+#include "command.h"
class StatCommandTest : public ::testing::Test {
protected:
diff --git a/simpleperf/command_test.cpp b/simpleperf/command_test.cpp
index dc2e4a6a..4a0baa6b 100644
--- a/simpleperf/command_test.cpp
+++ b/simpleperf/command_test.cpp
@@ -16,7 +16,7 @@
#include <gtest/gtest.h>
-#include <command.h>
+#include "command.h"
class MockCommand : public Command {
public:
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 14c256a5..a2de935a 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -16,10 +16,16 @@
#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;
+}
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index fbc8cfb1..c4110677 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -17,12 +17,61 @@
#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_
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
index 398554d3..3cf81fa6 100644
--- a/simpleperf/environment_test.cpp
+++ b/simpleperf/environment_test.cpp
@@ -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));
+}
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index 8e88867a..3e34d525 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -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;
+}
diff --git a/simpleperf/record.h b/simpleperf/record.h
index fbd523d1..4d62784c 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -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
index 00000000..45b0752c
--- /dev/null
+++ b/simpleperf/record_equal_test.h
@@ -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));
+ }
+}
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index 85c0212d..df138def 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -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
index 00000000..d9e9a4bd
--- /dev/null
+++ b/simpleperf/record_test.cpp
@@ -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);
+}
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index eea8988e..349cf5d1 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -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);
+}
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index 2ff5d953..fba3558b 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -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_