diff options
author | Yabin Cui <yabinc@google.com> | 2017-07-25 15:08:05 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2017-07-25 15:08:05 -0700 |
commit | 33275782e8c0fe3f7116236c5353beb0914b4d30 (patch) | |
tree | 206e8bfa8239b9e26d3898a36ab3bb6e6babf5b1 | |
parent | 60c6d5bf601f0bbcf5235996f2d7e3aa6b3bb2dd (diff) | |
download | extras-33275782e8c0fe3f7116236c5353beb0914b4d30.tar.gz |
simpleperf: support reporting trace offcpu data in simpleperf_report_lib.
Export python interface for reading meta info.
Change sample.period to sample time difference when trace_offcpu is true.
Add unit tests.
Bug: http://b/37572306
Test: run python test.py.
Change-Id: Ic144314184d115cc55630d3c69b66a0d8594721a
-rw-r--r-- | simpleperf/report_lib_interface.cpp | 72 | ||||
-rw-r--r-- | simpleperf/scripts/simpleperf_report_lib.py | 101 | ||||
-rw-r--r-- | simpleperf/scripts/test.py | 76 | ||||
-rw-r--r-- | simpleperf/scripts/testdata/perf_with_symbols.data | bin | 0 -> 54797 bytes | |||
-rw-r--r-- | simpleperf/scripts/testdata/perf_with_trace_offcpu.data | bin | 0 -> 197611 bytes |
5 files changed, 175 insertions, 74 deletions
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp index 0d4380f6..79a2103c 100644 --- a/simpleperf/report_lib_interface.cpp +++ b/simpleperf/report_lib_interface.cpp @@ -22,6 +22,7 @@ #include "dso.h" #include "event_attr.h" +#include "event_type.h" #include "record_file.h" #include "thread_tree.h" #include "utils.h" @@ -71,6 +72,11 @@ struct CallChain { CallChainEntry* entries; }; +struct MetaInfoEntry { + const char* key; + const char* value; +}; + // Create a new instance, // pass the instance to the other functions below. ReportLib* CreateReportLib() EXPORT; @@ -90,6 +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; } struct EventAttrWithName { @@ -111,8 +118,10 @@ class ReportLib { new android::base::ScopedLogSeverity(android::base::INFO)), record_filename_("perf.data"), current_thread_(nullptr), - update_flag_(0) - {} + update_flag_(0), + trace_offcpu_(false) { + current_meta_info_.key = current_meta_info_.value = nullptr; + } bool SetLogSeverity(const char* log_level); @@ -133,6 +142,7 @@ class ReportLib { CallChain* GetCallChainOfCurrentSample(); const char* GetBuildIdForPath(const char* path); + MetaInfoEntry* GetNextMetaInfo(); private: Sample* GetCurrentSample(); @@ -154,6 +164,12 @@ class ReportLib { std::string build_id_string_; int update_flag_; std::vector<EventAttrWithName> event_attrs_; + + std::unordered_map<std::string, std::string> meta_info_map_; + MetaInfoEntry current_meta_info_; + std::unique_ptr<ScopedEventTypes> scoped_event_types_; + bool trace_offcpu_; + std::unordered_map<pid_t, std::unique_ptr<SampleRecord>> next_sample_cache_; }; bool ReportLib::SetLogSeverity(const char* log_level) { @@ -184,6 +200,18 @@ bool ReportLib::OpenRecordFileIfNecessary() { return false; } record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_); + if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_META_INFO) && + !record_file_reader_->ReadMetaInfoFeature(&meta_info_map_)) { + return false; + } + 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()) { + trace_offcpu_ = it->second == "true"; + } } return true; } @@ -202,6 +230,17 @@ Sample* ReportLib::GetNextSample() { } thread_tree_.Update(*record); if (record->type() == PERF_RECORD_SAMPLE) { + if (trace_offcpu_) { + SampleRecord* r = static_cast<SampleRecord*>(record.release()); + auto it = next_sample_cache_.find(r->tid_data.tid); + if (it == next_sample_cache_.end()) { + next_sample_cache_[r->tid_data.tid].reset(r); + continue; + } else { + record.reset(it->second.release()); + it->second.reset(r); + } + } current_record_.reset(static_cast<SampleRecord*>(record.release())); break; } @@ -223,7 +262,13 @@ Sample* ReportLib::GetCurrentSample() { current_sample_.time = r.time_data.time; current_sample_.in_kernel = r.InKernel(); current_sample_.cpu = r.cpu_data.cpu; - current_sample_.period = r.period_data.period; + if (trace_offcpu_) { + uint64_t next_time = std::max(next_sample_cache_[r.tid_data.tid]->time_data.time, + r.time_data.time + 1); + current_sample_.period = next_time - r.time_data.time; + } else { + current_sample_.period = r.period_data.period; + } update_flag_ |= UPDATE_FLAG_OF_SAMPLE; } return ¤t_sample_; @@ -342,6 +387,23 @@ const char* ReportLib::GetBuildIdForPath(const char* path) { return build_id_string_.c_str(); } +MetaInfoEntry* ReportLib::GetNextMetaInfo() { + 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()) { + return nullptr; + } + current_meta_info_.key = it->first.c_str(); + current_meta_info_.value = it->second.c_str(); + return ¤t_meta_info_; +} + // Exported methods working with a client created instance ReportLib* CreateReportLib() { return new ReportLib(); @@ -390,3 +452,7 @@ CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) { const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) { return report_lib->GetBuildIdForPath(path); } + +MetaInfoEntry* GetNextMetaInfo(ReportLib* report_lib) { + return report_lib->GetNextMetaInfo(); +} diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py index c540059e..67fdfa3e 100644 --- a/simpleperf/scripts/simpleperf_report_lib.py +++ b/simpleperf/scripts/simpleperf_report_lib.py @@ -83,6 +83,11 @@ class CallChainStructure(ct.Structure): ('entries', ct.POINTER(CallChainEntryStructure))] +class MetaInfoEntryStructure(ct.Structure): + _fields_ = [('key', ct.c_char_p), + ('value', ct.c_char_p)] + + # convert char_p to str for python3. class SampleStructUsingStr(object): def __init__(self, sample): @@ -155,10 +160,14 @@ 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._instance = self._CreateReportLibFunc() assert(not _is_null(self._instance)) self.convert_to_str = (sys.version_info >= (3, 0)) + self.meta_info = None + self.current_sample = None def _load_dependent_lib(self): # As the windows dll is built with mingw we need to load "libwinpthread-1.dll". @@ -195,12 +204,16 @@ class ReportLib(object): self._check(cond, "Failed to set kallsyms file") def GetNextSample(self): - sample = self._GetNextSampleFunc(self.getInstance()) - if _is_null(sample): - return None - if self.convert_to_str: - return SampleStructUsingStr(sample[0]) - return sample[0] + psample = self._GetNextSampleFunc(self.getInstance()) + if _is_null(psample): + self.current_sample = None + else: + sample = psample[0] + self.current_sample = SampleStructUsingStr(sample) if self.convert_to_str else sample + return self.current_sample + + def GetCurrentSample(self): + return self.current_sample def GetEventOfCurrentSample(self): event = self._GetEventOfCurrentSampleFunc(self.getInstance()) @@ -228,6 +241,17 @@ class ReportLib(object): assert(not _is_null(build_id)) return _char_pt_to_str(build_id) + 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 + return self.meta_info + def getInstance(self): if self._instance is None: raise Exception("Instance is Closed") @@ -236,68 +260,3 @@ class ReportLib(object): def _check(self, cond, failmsg): if not cond: raise Exception(failmsg) - - -class TestReportLib(unittest.TestCase): - def setUp(self): - self.perf_data_path = os.path.join(os.path.dirname(get_script_dir()), - 'testdata', 'perf_with_symbols.data') - if not os.path.isfile(self.perf_data_path): - raise Exception("can't find perf_data at %s" % self.perf_data_path) - self.report_lib = ReportLib() - self.report_lib.SetRecordFile(self.perf_data_path) - - def tearDown(self): - self.report_lib.Close() - - def test_build_id(self): - build_id = self.report_lib.GetBuildIdForPath('/data/t2') - self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') - - def test_symbol_addr(self): - found_func2 = False - while True: - sample = self.report_lib.GetNextSample() - if sample is None: - break - symbol = self.report_lib.GetSymbolOfCurrentSample() - if symbol.symbol_name == 'func2(int, int)': - found_func2 = True - self.assertEqual(symbol.symbol_addr, 0x4004ed) - self.assertTrue(found_func2) - - def test_sample(self): - found_sample = False - while True: - sample = self.report_lib.GetNextSample() - if sample is None: - break - if sample.ip == 0x4004ff and sample.time == 7637889424953: - found_sample = True - self.assertEqual(sample.pid, 15926) - self.assertEqual(sample.tid, 15926) - self.assertEqual(sample.thread_comm, 't2') - self.assertEqual(sample.cpu, 5) - self.assertEqual(sample.period, 694614) - event = self.report_lib.GetEventOfCurrentSample() - self.assertEqual(event.name, 'cpu-cycles') - callchain = self.report_lib.GetCallChainOfCurrentSample() - self.assertEqual(callchain.nr, 0) - self.assertTrue(found_sample) - - -def main(): - test_all = True - if len(sys.argv) > 1 and sys.argv[1] == '--test-one': - test_all = False - del sys.argv[1] - - if test_all: - subprocess.check_call(['python', os.path.realpath(__file__), '--test-one']) - subprocess.check_call(['python3', os.path.realpath(__file__), '--test-one']) - else: - sys.exit(unittest.main()) - - -if __name__ == '__main__': - main()
\ No newline at end of file diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index 27327d96..3bc50358 100644 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -41,6 +41,7 @@ import sys import tempfile import unittest from utils import * +from simpleperf_report_lib import ReportLib has_google_protobuf = True try: @@ -418,6 +419,81 @@ class TestExampleOfKotlinRoot(TestExamplesBase): self.common_test_app_profiler() +class TestReportLib(unittest.TestCase): + def setUp(self): + self.report_lib = ReportLib() + self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data')) + + def tearDown(self): + self.report_lib.Close() + + def test_build_id(self): + build_id = self.report_lib.GetBuildIdForPath('/data/t2') + self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de') + + def test_symbol_addr(self): + found_func2 = False + while self.report_lib.GetNextSample(): + sample = self.report_lib.GetCurrentSample() + symbol = self.report_lib.GetSymbolOfCurrentSample() + if symbol.symbol_name == 'func2(int, int)': + found_func2 = True + self.assertEqual(symbol.symbol_addr, 0x4004ed) + self.assertTrue(found_func2) + + def test_sample(self): + found_sample = False + while self.report_lib.GetNextSample(): + sample = self.report_lib.GetCurrentSample() + if sample.ip == 0x4004ff and sample.time == 7637889424953: + found_sample = True + self.assertEqual(sample.pid, 15926) + self.assertEqual(sample.tid, 15926) + self.assertEqual(sample.thread_comm, 't2') + self.assertEqual(sample.cpu, 5) + self.assertEqual(sample.period, 694614) + event = self.report_lib.GetEventOfCurrentSample() + self.assertEqual(event.name, 'cpu-cycles') + callchain = self.report_lib.GetCallChainOfCurrentSample() + self.assertEqual(callchain.nr, 0) + self.assertTrue(found_sample) + + 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.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") + + def test_event_name_from_meta_info(self): + self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) + event_names = set() + while self.report_lib.GetNextSample(): + event_names.add(self.report_lib.GetEventOfCurrentSample().name) + self.assertTrue('sched:sched_switch' in event_names) + self.assertTrue('cpu-cycles' in event_names) + + def test_offcpu(self): + self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data')) + total_period = 0 + sleep_function_period = 0 + sleep_function_name = "SleepFunction(unsigned long long)" + while self.report_lib.GetNextSample(): + sample = self.report_lib.GetCurrentSample() + total_period += sample.period + if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name: + sleep_function_period += sample.period + continue + callchain = self.report_lib.GetCallChainOfCurrentSample() + for i in range(callchain.nr): + if callchain.entries[i].symbol.symbol_name == sleep_function_name: + sleep_function_period += sample.period + break + sleep_percentage = float(sleep_function_period) / total_period + self.assertAlmostEqual(sleep_percentage, 0.4629, delta=0.0001) + + if __name__ == '__main__': test_program = unittest.main(failfast=True, exit=False) if test_program.result.wasSuccessful(): diff --git a/simpleperf/scripts/testdata/perf_with_symbols.data b/simpleperf/scripts/testdata/perf_with_symbols.data Binary files differnew file mode 100644 index 00000000..ca74d159 --- /dev/null +++ b/simpleperf/scripts/testdata/perf_with_symbols.data diff --git a/simpleperf/scripts/testdata/perf_with_trace_offcpu.data b/simpleperf/scripts/testdata/perf_with_trace_offcpu.data Binary files differnew file mode 100644 index 00000000..6d0c5e0a --- /dev/null +++ b/simpleperf/scripts/testdata/perf_with_trace_offcpu.data |