From 80a1e12868bed96aaef78ce5d4abac42e56625ba Mon Sep 17 00:00:00 2001 From: Yabin Cui Date: Fri, 11 Aug 2017 14:52:51 -0700 Subject: simpleperf: export more info through report_lib_interface. Make below changes to better suppport inferno: 1. Save product properties of device in perf.data. 2. Add a python/c api GetFeatureSection. It is used to support reading record cmd and meta info from perf.data. 3. Remove old api GetNextMetaInfo, because meta info can be exported by GetFeatureSection more easily. 4. When reading perf.data in record_file_reader.cpp, remove callchain at and below ip == 0 to avoid caller's effort. Also move to use android-base/properties.h. Bug: http://b/64035530 Test: run simpleperf_unit_test and test.py. Change-Id: Ib6743a09167e2b7cd65a12f17d991bc1ac628588 --- simpleperf/cmd_dumprecord.cpp | 37 +++------------ simpleperf/cmd_record.cpp | 9 ++++ simpleperf/cmd_record_test.cpp | 3 ++ simpleperf/cmd_report_test.cpp | 2 +- simpleperf/cpu_hotplug_test.cpp | 13 ++---- simpleperf/environment.cpp | 14 +++--- simpleperf/gtest_main.cpp | 14 +++--- simpleperf/record_file_format.h | 5 ++ simpleperf/record_file_reader.cpp | 54 ++++++++++++++++++++++ simpleperf/report_lib_interface.cpp | 47 +++++++++---------- simpleperf/scripts/simpleperf_report_lib.py | 59 +++++++++++++++++++----- simpleperf/scripts/test.py | 10 +++- simpleperf/testdata/perf_with_trace_offcpu.data | Bin 197611 -> 365681 bytes 13 files changed, 174 insertions(+), 93 deletions(-) diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp index dcd7bf47..919b62a4 100644 --- a/simpleperf/cmd_dumprecord.cpp +++ b/simpleperf/cmd_dumprecord.cpp @@ -92,7 +92,10 @@ bool DumpRecordCommand::ParseOptions(const std::vector& args) { return true; } -static const std::string GetFeatureName(int feature); +static const std::string GetFeatureNameOrUnknown(int feature) { + std::string name = GetFeatureName(feature); + return name.empty() ? android::base::StringPrintf("unknown_feature(%d)", feature) : name; +} void DumpRecordCommand::DumpFileHeader() { const FileHeader& header = record_file_reader_->FileHeader(); @@ -127,38 +130,10 @@ void DumpRecordCommand::DumpFileHeader() { } } for (auto& feature : features) { - printf("feature: %s\n", GetFeatureName(feature).c_str()); + printf("feature: %s\n", GetFeatureNameOrUnknown(feature).c_str()); } } -static const std::string GetFeatureName(int feature) { - static std::map feature_name_map = { - {FEAT_TRACING_DATA, "tracing_data"}, - {FEAT_BUILD_ID, "build_id"}, - {FEAT_HOSTNAME, "hostname"}, - {FEAT_OSRELEASE, "osrelease"}, - {FEAT_VERSION, "version"}, - {FEAT_ARCH, "arch"}, - {FEAT_NRCPUS, "nrcpus"}, - {FEAT_CPUDESC, "cpudesc"}, - {FEAT_CPUID, "cpuid"}, - {FEAT_TOTAL_MEM, "total_mem"}, - {FEAT_CMDLINE, "cmdline"}, - {FEAT_EVENT_DESC, "event_desc"}, - {FEAT_CPU_TOPOLOGY, "cpu_topology"}, - {FEAT_NUMA_TOPOLOGY, "numa_topology"}, - {FEAT_BRANCH_STACK, "branch_stack"}, - {FEAT_PMU_MAPPINGS, "pmu_mappings"}, - {FEAT_GROUP_DESC, "group_desc"}, - {FEAT_FILE, "file"}, - {FEAT_META_INFO, "meta_info"}, - }; - 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 DumpRecordCommand::DumpAttrSection() { std::vector attrs = record_file_reader_->AttrSection(); @@ -189,7 +164,7 @@ bool DumpRecordCommand::DumpFeatureSection() { int feature = pair.first; const auto& section = pair.second; printf("feature section for %s: offset %" PRId64 ", size %" PRId64 "\n", - GetFeatureName(feature).c_str(), section.offset, section.size); + GetFeatureNameOrUnknown(feature).c_str(), section.offset, section.size); if (feature == FEAT_BUILD_ID) { std::vector records = record_file_reader_->ReadBuildIdFeature(); for (auto& r : records) { diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index 6b2be764..c22129a3 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -31,6 +31,9 @@ #include #include #include +#if defined(__ANDROID__) +#include +#endif #include "command.h" #include "dwarf_unwind.h" @@ -1182,6 +1185,12 @@ bool RecordCommand::DumpMetaInfoFeature() { // By storing event types information in perf.data, the readers of perf.data have the same // understanding of event types, even if they are on another machine. info_map["event_type_info"] = ScopedEventTypes::BuildString(event_selection_set_.GetEvents()); +#if defined(__ANDROID__) + info_map["product_props"] = android::base::StringPrintf("%s:%s:%s", + android::base::GetProperty("ro.product.manufacturer", "").c_str(), + android::base::GetProperty("ro.product.model", "").c_str(), + android::base::GetProperty("ro.product.name", "").c_str()); +#endif return record_file_writer_->WriteMetaInfoFeature(info_map); } diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index 56e1b225..47186a55 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -454,6 +454,9 @@ TEST(record_cmd, record_meta_info_feature) { std::unordered_map info_map; ASSERT_TRUE(reader->ReadMetaInfoFeature(&info_map)); ASSERT_NE(info_map.find("simpleperf_version"), info_map.end()); +#if defined(__ANDROID__) + ASSERT_NE(info_map.find("product_props"), info_map.end()); +#endif } // See http://b/63135835. diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp index f2741144..05ffc184 100644 --- a/simpleperf/cmd_report_test.cpp +++ b/simpleperf/cmd_report_test.cpp @@ -478,7 +478,7 @@ TEST_F(ReportCommandTest, report_offcpu_time) { bool found = false; for (auto& line : lines) { if (line.find("SleepFunction") != std::string::npos) { - ASSERT_NE(line.find("46.29%"), std::string::npos); + ASSERT_NE(line.find("38.77%"), std::string::npos); found = true; break; } diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp index 16e0e5ce..aca7caa0 100644 --- a/simpleperf/cpu_hotplug_test.cpp +++ b/simpleperf/cpu_hotplug_test.cpp @@ -19,7 +19,7 @@ #include #include #if defined(__BIONIC__) -#include +#include #endif #include @@ -59,25 +59,22 @@ class ScopedMpdecisionKiller { private: bool IsMpdecisionRunning() { - char value[PROP_VALUE_MAX]; - int len = __system_property_get("init.svc.mpdecision", value); - if (len == 0 || (len > 0 && strstr(value, "stopped") != nullptr)) { + std::string value = android::base::GetProperty("init.svc.mpdecision", ""); + if (value.empty() || value.find("stopped") != std::string::npos) { return false; } return true; } void DisableMpdecision() { - int ret = __system_property_set("ctl.stop", "mpdecision"); - CHECK_EQ(0, ret); + CHECK(android::base::SetProperty("ctl.stop", "mpdecision")); // Need to wait until mpdecision is actually stopped. std::this_thread::sleep_for(std::chrono::milliseconds(500)); CHECK(!IsMpdecisionRunning()); } void EnableMpdecision() { - int ret = __system_property_set("ctl.start", "mpdecision"); - CHECK_EQ(0, ret); + CHECK(android::base::SetProperty("ctl.start", "mpdecision")); std::this_thread::sleep_for(std::chrono::milliseconds(500)); CHECK(IsMpdecisionRunning()); } diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index 79280d10..c936ac80 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -35,7 +35,7 @@ #include #if defined(__ANDROID__) -#include +#include #endif #include "event_type.h" @@ -383,22 +383,22 @@ bool CheckPerfEventLimit() { return true; } #if defined(__ANDROID__) - const char* prop_name = "security.perf_harden"; - char prop_value[PROP_VALUE_MAX]; - if (__system_property_get(prop_name, prop_value) <= 0) { + const std::string prop_name = "security.perf_harden"; + std::string prop_value = android::base::GetProperty(prop_name, ""); + if (prop_value.empty()) { // can't do anything if there is no such property. return true; } - if (strcmp(prop_value, "0") == 0) { + if (prop_value == "0") { return true; } // Try to enable perf_event_paranoid by setprop security.perf_harden=0. - if (__system_property_set(prop_name, "0") == 0) { + if (android::base::SetProperty(prop_name, "0")) { sleep(1); if (can_read_paranoid && ReadPerfEventParanoid(&limit_level) && limit_level <= 1) { return true; } - if (__system_property_get(prop_name, prop_value) > 0 && strcmp(prop_value, "0") == 0) { + if (android::base::GetProperty(prop_name, "") == "0") { return true; } } diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp index 42519d52..599bb423 100644 --- a/simpleperf/gtest_main.cpp +++ b/simpleperf/gtest_main.cpp @@ -26,7 +26,7 @@ #include #if defined(__ANDROID__) -#include +#include #endif #include "command.h" @@ -106,26 +106,26 @@ static bool ExtractTestDataFromElfSection() { class ScopedEnablingPerf { public: ScopedEnablingPerf() { - memset(prop_value_, '\0', sizeof(prop_value_)); - __system_property_get("security.perf_harden", prop_value_); + prop_value_ = android::base::GetProperty("security.perf_harden", ""); SetProp("0"); } ~ScopedEnablingPerf() { - if (strlen(prop_value_) != 0) { + if (!prop_value_.empty()) { SetProp(prop_value_); } } private: - void SetProp(const char* value) { - __system_property_set("security.perf_harden", value); + void SetProp(const std::string& value) { + android::base::SetProperty("security.perf_harden", value); + // Sleep one second to wait for security.perf_harden changing // /proc/sys/kernel/perf_event_paranoid. sleep(1); } - char prop_value_[PROP_VALUE_MAX]; + std::string prop_value_; }; class ScopedWorkloadExecutable { diff --git a/simpleperf/record_file_format.h b/simpleperf/record_file_format.h index f9ed6f32..1ddaf002 100644 --- a/simpleperf/record_file_format.h +++ b/simpleperf/record_file_format.h @@ -17,6 +17,8 @@ #ifndef SIMPLE_PERF_RECORD_FILE_FORMAT_H_ #define SIMPLE_PERF_RECORD_FILE_FORMAT_H_ +#include + #include "perf_event.h" /* @@ -91,6 +93,9 @@ enum { FEAT_MAX_NUM = 256, }; +std::string GetFeatureName(int feature_id); +int GetFeatureId(const std::string& feature_name); + struct SectionDesc { uint64_t offset; uint64_t size; diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp index 67f35bea..38a4b2d6 100644 --- a/simpleperf/record_file_reader.cpp +++ b/simpleperf/record_file_reader.cpp @@ -29,6 +29,46 @@ using namespace PerfFileFormat; +namespace PerfFileFormat { + +static const std::map feature_name_map = { + {FEAT_TRACING_DATA, "tracing_data"}, + {FEAT_BUILD_ID, "build_id"}, + {FEAT_HOSTNAME, "hostname"}, + {FEAT_OSRELEASE, "osrelease"}, + {FEAT_VERSION, "version"}, + {FEAT_ARCH, "arch"}, + {FEAT_NRCPUS, "nrcpus"}, + {FEAT_CPUDESC, "cpudesc"}, + {FEAT_CPUID, "cpuid"}, + {FEAT_TOTAL_MEM, "total_mem"}, + {FEAT_CMDLINE, "cmdline"}, + {FEAT_EVENT_DESC, "event_desc"}, + {FEAT_CPU_TOPOLOGY, "cpu_topology"}, + {FEAT_NUMA_TOPOLOGY, "numa_topology"}, + {FEAT_BRANCH_STACK, "branch_stack"}, + {FEAT_PMU_MAPPINGS, "pmu_mappings"}, + {FEAT_GROUP_DESC, "group_desc"}, + {FEAT_FILE, "file"}, + {FEAT_META_INFO, "meta_info"}, +}; + +std::string GetFeatureName(int feature_id) { + auto it = feature_name_map.find(feature_id); + return it == feature_name_map.end() ? "" : it->second; +} + +int GetFeatureId(const std::string& feature_name) { + for (auto& pair : feature_name_map) { + if (pair.second == feature_name) { + return pair.first; + } + } + return -1; +} + +} // namespace PerfFileFormat + std::unique_ptr RecordFileReader::CreateInstance(const std::string& filename) { std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE; FILE* fp = fopen(filename.c_str(), mode.c_str()); @@ -203,6 +243,20 @@ bool RecordFileReader::ReadRecord(std::unique_ptr& record, } if (record->type() == SIMPLE_PERF_RECORD_EVENT_ID) { ProcessEventIdRecord(*static_cast(record.get())); + } else if (record->type() == PERF_RECORD_SAMPLE) { + SampleRecord* r = static_cast(record.get()); + // Although we have removed ip == 0 callchains when recording dwarf based callgraph, + // stack frame based callgraph can also generate ip == 0 callchains. Remove them here + // to avoid caller's effort. + if (r->sample_type & PERF_SAMPLE_CALLCHAIN) { + size_t i; + for (i = 0; i < r->callchain_data.ip_nr; ++i) { + if (r->callchain_data.ips[i] == 0) { + break; + } + } + r->callchain_data.ip_nr = i; + } } if (sorted) { record_cache_->Push(std::move(record)); diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp index 79a2103c..88bb4d7e 100644 --- a/simpleperf/report_lib_interface.cpp +++ b/simpleperf/report_lib_interface.cpp @@ -72,9 +72,9 @@ struct CallChain { CallChainEntry* entries; }; -struct MetaInfoEntry { - const char* key; - const char* value; +struct FeatureSection { + const char* data; + uint32_t data_size; }; // Create a new instance, @@ -96,7 +96,7 @@ SymbolEntry* GetSymbolOfCurrentSample(ReportLib* report_lib) EXPORT; CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) EXPORT; const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) EXPORT; -MetaInfoEntry* GetNextMetaInfo(ReportLib* report_lib) EXPORT; +FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) EXPORT; } struct EventAttrWithName { @@ -120,7 +120,6 @@ class ReportLib { current_thread_(nullptr), update_flag_(0), trace_offcpu_(false) { - current_meta_info_.key = current_meta_info_.value = nullptr; } bool SetLogSeverity(const char* log_level); @@ -142,7 +141,7 @@ class ReportLib { CallChain* GetCallChainOfCurrentSample(); const char* GetBuildIdForPath(const char* path); - MetaInfoEntry* GetNextMetaInfo(); + FeatureSection* GetFeatureSection(const char* feature_name); private: Sample* GetCurrentSample(); @@ -164,12 +163,11 @@ class ReportLib { std::string build_id_string_; int update_flag_; std::vector event_attrs_; - - std::unordered_map meta_info_map_; - MetaInfoEntry current_meta_info_; std::unique_ptr scoped_event_types_; bool trace_offcpu_; std::unordered_map> next_sample_cache_; + FeatureSection feature_section_; + std::vector feature_section_data_; }; bool ReportLib::SetLogSeverity(const char* log_level) { @@ -200,16 +198,17 @@ bool ReportLib::OpenRecordFileIfNecessary() { return false; } record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_); + std::unordered_map meta_info_map; if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO) && - !record_file_reader_->ReadMetaInfoFeature(&meta_info_map_)) { + !record_file_reader_->ReadMetaInfoFeature(&meta_info_map)) { return false; } - auto it = meta_info_map_.find("event_type_info"); - if (it != meta_info_map_.end()) { + auto it = meta_info_map.find("event_type_info"); + if (it != meta_info_map.end()) { scoped_event_types_.reset(new ScopedEventTypes(it->second)); } - it = meta_info_map_.find("trace_offcpu"); - if (it != meta_info_map_.end()) { + it = meta_info_map.find("trace_offcpu"); + if (it != meta_info_map.end()) { trace_offcpu_ = it->second == "true"; } } @@ -387,21 +386,17 @@ const char* ReportLib::GetBuildIdForPath(const char* path) { return build_id_string_.c_str(); } -MetaInfoEntry* ReportLib::GetNextMetaInfo() { +FeatureSection* ReportLib::GetFeatureSection(const char* feature_name) { if (!OpenRecordFileIfNecessary()) { return nullptr; } - auto it = meta_info_map_.begin(); - if (current_meta_info_.key != nullptr) { - it = meta_info_map_.find(current_meta_info_.key); - ++it; - } - if (it == meta_info_map_.end()) { + int feature = PerfFileFormat::GetFeatureId(feature_name); + if (feature == -1 || !record_file_reader_->ReadFeatureSection(feature, &feature_section_data_)) { return nullptr; } - current_meta_info_.key = it->first.c_str(); - current_meta_info_.value = it->second.c_str(); - return ¤t_meta_info_; + feature_section_.data = feature_section_data_.data(); + feature_section_.data_size = feature_section_data_.size(); + return &feature_section_; } // Exported methods working with a client created instance @@ -453,6 +448,6 @@ const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) { return report_lib->GetBuildIdForPath(path); } -MetaInfoEntry* GetNextMetaInfo(ReportLib* report_lib) { - return report_lib->GetNextMetaInfo(); +FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) { + return report_lib->GetFeatureSection(feature_name); } diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py index 67fdfa3e..385df2f5 100644 --- a/simpleperf/scripts/simpleperf_report_lib.py +++ b/simpleperf/scripts/simpleperf_report_lib.py @@ -83,9 +83,9 @@ class CallChainStructure(ct.Structure): ('entries', ct.POINTER(CallChainEntryStructure))] -class MetaInfoEntryStructure(ct.Structure): - _fields_ = [('key', ct.c_char_p), - ('value', ct.c_char_p)] +class FeatureSectionStructure(ct.Structure): + _fields_ = [('data', ct.POINTER(ct.c_char)), + ('data_size', ct.c_uint32)] # convert char_p to str for python3. @@ -160,8 +160,8 @@ class ReportLib(object): CallChainStructure) self._GetBuildIdForPathFunc = self._lib.GetBuildIdForPath self._GetBuildIdForPathFunc.restype = ct.c_char_p - self._GetNextMetaInfoFunc = self._lib.GetNextMetaInfo - self._GetNextMetaInfoFunc.restype = ct.POINTER(MetaInfoEntryStructure) + self._GetFeatureSection = self._lib.GetFeatureSection + self._GetFeatureSection.restype = ct.POINTER(FeatureSectionStructure) self._instance = self._CreateReportLibFunc() assert(not _is_null(self._instance)) @@ -241,15 +241,52 @@ class ReportLib(object): assert(not _is_null(build_id)) return _char_pt_to_str(build_id) + def GetRecordCmd(self): + if hasattr(self, "record_cmd"): + return self.record_cmd + self.record_cmd = None + feature_data = self._GetFeatureSection(self.getInstance(), _char_pt("cmdline")) + if not _is_null(feature_data): + void_p = ct.cast(feature_data[0].data, ct.c_void_p) + data_size = feature_data[0].data_size + arg_count = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value + void_p.value += 4 + args = [] + for i in range(arg_count): + str_len = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value + void_p.value += 4 + char_p = ct.cast(void_p, ct.POINTER(ct.c_char)) + current_str = "" + for j in range(str_len): + c = bytes_to_str(char_p[j]) + if c != '\0': + current_str += c + if ' ' in current_str: + current_str = '"' + current_str + '"' + args.append(current_str) + void_p.value += str_len + self.record_cmd = " ".join(args) + return self.record_cmd + + def MetaInfo(self): if self.meta_info is None: self.meta_info = {} - while True: - entry = self._GetNextMetaInfoFunc(self.getInstance()) - if _is_null(entry): break - key = _char_pt_to_str(entry[0].key) - value = _char_pt_to_str(entry[0].value) - self.meta_info[key] = value + feature_data = self._GetFeatureSection(self.getInstance(), _char_pt("meta_info")) + if not _is_null(feature_data): + str_list = [] + data = feature_data[0].data + data_size = feature_data[0].data_size + current_str = "" + for i in range(data_size): + c = bytes_to_str(data[i]) + if c != '\0': + current_str += c + else: + str_list.append(current_str) + current_str = "" + for i in range(0, len(str_list), 2): + self.meta_info[str_list[i]] = str_list[i + 1] return self.meta_info def getInstance(self): diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index 1a97ab1b..3456af44 100644 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -697,10 +697,11 @@ class TestReportLib(unittest.TestCase): def test_meta_info(self): self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) meta_info = self.report_lib.MetaInfo() - self.assertEqual(meta_info["simpleperf_version"], "1.65f91c7ed862") + self.assertTrue("simpleperf_version" in meta_info) self.assertEqual(meta_info["system_wide_collection"], "false") self.assertEqual(meta_info["trace_offcpu"], "true") self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47") + self.assertTrue("product_props" in meta_info) def test_event_name_from_meta_info(self): self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) @@ -710,6 +711,11 @@ class TestReportLib(unittest.TestCase): self.assertTrue('sched:sched_switch' in event_names) self.assertTrue('cpu-cycles' in event_names) + def test_record_cmd(self): + self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) + self.assertEqual(self.report_lib.GetRecordCmd(), + "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g ./simpleperf_runtest_run_and_sleep64") + def test_offcpu(self): self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) total_period = 0 @@ -727,7 +733,7 @@ class TestReportLib(unittest.TestCase): sleep_function_period += sample.period break sleep_percentage = float(sleep_function_period) / total_period - self.assertAlmostEqual(sleep_percentage, 0.4629, delta=0.0001) + self.assertGreater(sleep_percentage, 0.30) def main(): diff --git a/simpleperf/testdata/perf_with_trace_offcpu.data b/simpleperf/testdata/perf_with_trace_offcpu.data index 6d0c5e0a..7d814145 100644 Binary files a/simpleperf/testdata/perf_with_trace_offcpu.data and b/simpleperf/testdata/perf_with_trace_offcpu.data differ -- cgit v1.2.3