Merge "simpleperf: support reporting trace offcpu data in simpleperf_report_lib."
am: be51cd0e9a

Change-Id: I28b4d69e575ca6258bb5da2b40b7309d3c0d844b
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index 0d4380f..79a2103 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 @@
   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 @@
 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 @@
             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 @@
   CallChain* GetCallChainOfCurrentSample();
 
   const char* GetBuildIdForPath(const char* path);
+  MetaInfoEntry* GetNextMetaInfo();
 
  private:
   Sample* GetCurrentSample();
@@ -154,6 +164,12 @@
   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 @@
       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 @@
     }
     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 @@
     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 &current_sample_;
@@ -342,6 +387,23 @@
   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 &current_meta_info_;
+}
+
 // Exported methods working with a client created instance
 ReportLib* CreateReportLib() {
   return new ReportLib();
@@ -390,3 +452,7 @@
 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 c540059..67fdfa3 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -83,6 +83,11 @@
                 ('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 @@
             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 @@
         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 @@
         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 @@
     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 27327d9..3bc5035 100644
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -41,6 +41,7 @@
 import tempfile
 import unittest
 from utils import *
+from simpleperf_report_lib import ReportLib
 
 has_google_protobuf = True
 try:
@@ -418,6 +419,81 @@
         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
new file mode 100644
index 0000000..ca74d15
--- /dev/null
+++ b/simpleperf/scripts/testdata/perf_with_symbols.data
Binary files differ
diff --git a/simpleperf/scripts/testdata/perf_with_trace_offcpu.data b/simpleperf/scripts/testdata/perf_with_trace_offcpu.data
new file mode 100644
index 0000000..6d0c5e0
--- /dev/null
+++ b/simpleperf/scripts/testdata/perf_with_trace_offcpu.data
Binary files differ