diff options
author | Yabin Cui <yabinc@google.com> | 2015-05-14 02:15:48 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-05-14 02:15:48 +0000 |
commit | 9e569fa97bd48b043fb6a6d1af11902443e69d25 (patch) | |
tree | 3a777a52d40de10c576cd5326f377f740a6c5861 | |
parent | 1172163929e26bcd7fdf79ad4c45b836af925119 (diff) | |
parent | 5beebc88162e3dc294f84d352b9572a05982f572 (diff) | |
download | extras-9e569fa97bd48b043fb6a6d1af11902443e69d25.tar.gz |
Merge "Dump kernel/modules/thread mmap information in `simpleperf record`." into mnc-dev
-rw-r--r-- | simpleperf/Android.mk | 1 | ||||
-rw-r--r-- | simpleperf/cmd_dumprecord_test.cpp | 2 | ||||
-rw-r--r-- | simpleperf/cmd_list_test.cpp | 2 | ||||
-rw-r--r-- | simpleperf/cmd_record.cpp | 72 | ||||
-rw-r--r-- | simpleperf/cmd_record_test.cpp | 24 | ||||
-rw-r--r-- | simpleperf/cmd_stat_test.cpp | 2 | ||||
-rw-r--r-- | simpleperf/command_test.cpp | 2 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 281 | ||||
-rw-r--r-- | simpleperf/environment.h | 49 | ||||
-rw-r--r-- | simpleperf/environment_test.cpp | 37 | ||||
-rw-r--r-- | simpleperf/record.cpp | 35 | ||||
-rw-r--r-- | simpleperf/record.h | 6 | ||||
-rw-r--r-- | simpleperf/record_equal_test.h | 35 | ||||
-rw-r--r-- | simpleperf/record_file_test.cpp | 9 | ||||
-rw-r--r-- | simpleperf/record_test.cpp | 56 | ||||
-rw-r--r-- | simpleperf/utils.cpp | 32 | ||||
-rw-r--r-- | simpleperf/utils.h | 7 |
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_ |