text: #!/usr/bin/env python
  #
  # Copyright (C) 2016 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.
  #
  
  """simpleperf_report_lib.py: a python wrapper of libsimpleperf_report.so.
     Used to access samples in perf.data.
  
  """
  
  import ctypes as ct
  import os
  import subprocess
  import sys
  import unittest
  from utils import *
  
  
  def _get_native_lib():
      return get_host_binary_path('libsimpleperf_report.so')
  
  
  def _is_null(p):
      if p:
          return False
      return ct.cast(p, ct.c_void_p).value is None
  
  
  def _char_pt(s):
      return str_to_bytes(s)
  
  
  def _char_pt_to_str(char_pt):
      return bytes_to_str(char_pt)
  
  
  class SampleStruct(ct.Structure):
      """ Instance of a sample in perf.data.
          ip: the program counter of the thread generating the sample.
          pid: process id (or thread group id) of the thread generating the sample.
          tid: thread id.
          thread_comm: thread name.
          time: time at which the sample was generated. The value is in nanoseconds.
                The clock is decided by the --clockid option in `simpleperf record`.
          in_kernel: whether the instruction is in kernel space or user space.
          cpu: the cpu generating the sample.
          period: count of events have happened since last sample. For example, if we use
               -e cpu-cycles, it means how many cpu-cycles have happened.
               If we use -e cpu-clock, it means how many nanoseconds have passed.
      """
      _fields_ = [('ip', ct.c_uint64),
                  ('pid', ct.c_uint32),
                  ('tid', ct.c_uint32),
                  ('thread_comm', ct.c_char_p),
                  ('time', ct.c_uint64),
                  ('in_kernel', ct.c_uint32),
                  ('cpu', ct.c_uint32),
                  ('period', ct.c_uint64)]
  
  
  class EventStruct(ct.Structure):
      """ Name of the event. """
      _fields_ = [('name', ct.c_char_p)]
  
  
  class MappingStruct(ct.Structure):
      """ A mapping area in the monitored threads, like the content in /proc/<pid>/maps.
          start: start addr in memory.
          end: end addr in memory.
          pgoff: offset in the mapped shared library.
      """
      _fields_ = [('start', ct.c_uint64),
                  ('end', ct.c_uint64),
                  ('pgoff', ct.c_uint64)]
  
  
  class SymbolStruct(ct.Structure):
      """ Symbol info of the instruction hit by a sample or a callchain entry of a sample.
          dso_name: path of the shared library containing the instruction.
          vaddr_in_file: virtual address of the instruction in the shared library.
          symbol_name: name of the function containing the instruction.
          symbol_addr: start addr of the function containing the instruction.
          symbol_len: length of the function in the shared library.
          mapping: the mapping area hit by the instruction.
      """
      _fields_ = [('dso_name', ct.c_char_p),
                  ('vaddr_in_file', ct.c_uint64),
                  ('symbol_name', ct.c_char_p),
                  ('symbol_addr', ct.c_uint64),
                  ('symbol_len', ct.c_uint64),
                  ('mapping', ct.POINTER(MappingStruct))]
  
  
  class CallChainEntryStructure(ct.Structure):
      """ A callchain entry of a sample.
          ip: the address of the instruction of the callchain entry.
          symbol: symbol info of the callchain entry.
      """
      _fields_ = [('ip', ct.c_uint64),
                  ('symbol', SymbolStruct)]
  
  
  class CallChainStructure(ct.Structure):
      """ Callchain info of a sample.
          nr: number of entries in the callchain.
          entries: a pointer to an array of CallChainEntryStructure.
  
          For example, if a sample is generated when a thread is running function C
          with callchain function A -> function B -> function C.
          Then nr = 2, and entries = [function B, function A].
      """
      _fields_ = [('nr', ct.c_uint32),
                  ('entries', ct.POINTER(CallChainEntryStructure))]
  
  
  class FeatureSectionStructure(ct.Structure):
      """ A feature section in perf.data to store information like record cmd, device arch, etc.
          data: a pointer to a buffer storing the section data.
          data_size: data size in bytes.
      """
      _fields_ = [('data', ct.POINTER(ct.c_char)),
                  ('data_size', ct.c_uint32)]
  
  
  # convert char_p to str for python3.
  class SampleStructUsingStr(object):
      def __init__(self, sample):
          self.ip = sample.ip
          self.pid = sample.pid
          self.tid = sample.tid
          self.thread_comm = _char_pt_to_str(sample.thread_comm)
          self.time = sample.time
          self.in_kernel = sample.in_kernel
          self.cpu = sample.cpu
          self.period = sample.period
  
  
  class EventStructUsingStr(object):
      def __init__(self, event):
          self.name = _char_pt_to_str(event.name)
  
  
  class SymbolStructUsingStr(object):
      def __init__(self, symbol):
          self.dso_name = _char_pt_to_str(symbol.dso_name)
          self.vaddr_in_file = symbol.vaddr_in_file
          self.symbol_name = _char_pt_to_str(symbol.symbol_name)
          self.symbol_addr = symbol.symbol_addr
          self.mapping = symbol.mapping
  
  
  class CallChainEntryStructureUsingStr(object):
      def __init__(self, entry):
          self.ip = entry.ip
          self.symbol = SymbolStructUsingStr(entry.symbol)
  
  
  class CallChainStructureUsingStr(object):
      def __init__(self, callchain):
          self.nr = callchain.nr
          self.entries = []
          for i in range(self.nr):
              self.entries.append(CallChainEntryStructureUsingStr(callchain.entries[i]))
  
  
  class ReportLibStructure(ct.Structure):
      _fields_ = []
  
  
  class ReportLib(object):
  
      def __init__(self, native_lib_path=None):
          if native_lib_path is None:
              native_lib_path = _get_native_lib()
  
          self._load_dependent_lib()
          self._lib = ct.CDLL(native_lib_path)
          self._CreateReportLibFunc = self._lib.CreateReportLib
          self._CreateReportLibFunc.restype = ct.POINTER(ReportLibStructure)
          self._DestroyReportLibFunc = self._lib.DestroyReportLib
          self._SetLogSeverityFunc = self._lib.SetLogSeverity
          self._SetSymfsFunc = self._lib.SetSymfs
          self._SetRecordFileFunc = self._lib.SetRecordFile
          self._SetKallsymsFileFunc = self._lib.SetKallsymsFile
          self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
          self._ShowArtFramesFunc = self._lib.ShowArtFrames
          self._GetNextSampleFunc = self._lib.GetNextSample
          self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
          self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
          self._GetEventOfCurrentSampleFunc.restype = ct.POINTER(EventStruct)
          self._GetSymbolOfCurrentSampleFunc = self._lib.GetSymbolOfCurrentSample
          self._GetSymbolOfCurrentSampleFunc.restype = ct.POINTER(SymbolStruct)
          self._GetCallChainOfCurrentSampleFunc = self._lib.GetCallChainOfCurrentSample
          self._GetCallChainOfCurrentSampleFunc.restype = ct.POINTER(
              CallChainStructure)
          self._GetBuildIdForPathFunc = self._lib.GetBuildIdForPath
          self._GetBuildIdForPathFunc.restype = ct.c_char_p
          self._GetFeatureSection = self._lib.GetFeatureSection
          self._GetFeatureSection.restype = ct.POINTER(FeatureSectionStructure)
          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
          self.record_cmd = None
  
      def _load_dependent_lib(self):
          # As the windows dll is built with mingw we need to load 'libwinpthread-1.dll'.
          if is_windows():
              self._libwinpthread = ct.CDLL(get_host_binary_path('libwinpthread-1.dll'))
  
      def Close(self):
          if self._instance is None:
              return
          self._DestroyReportLibFunc(self._instance)
          self._instance = None
  
      def SetLogSeverity(self, log_level='info'):
          """ Set log severity of native lib, can be verbose,debug,info,error,fatal."""
          cond = self._SetLogSeverityFunc(self.getInstance(), _char_pt(log_level))
          self._check(cond, 'Failed to set log level')
  
      def SetSymfs(self, symfs_dir):
          """ Set directory used to find symbols."""
          cond = self._SetSymfsFunc(self.getInstance(), _char_pt(symfs_dir))
          self._check(cond, 'Failed to set symbols directory')
  
      def SetRecordFile(self, record_file):
          """ Set the path of record file, like perf.data."""
          cond = self._SetRecordFileFunc(self.getInstance(), _char_pt(record_file))
          self._check(cond, 'Failed to set record file')
  
      def ShowIpForUnknownSymbol(self):
          self._ShowIpForUnknownSymbolFunc(self.getInstance())
  
      def ShowArtFrames(self, show=True):
          """ Show frames of internal methods of the Java interpreter. """
          self._ShowArtFramesFunc(self.getInstance(), show)
  
      def SetKallsymsFile(self, kallsym_file):
          """ Set the file path to a copy of the /proc/kallsyms file (for off device decoding) """
          cond = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file))
          self._check(cond, 'Failed to set kallsyms file')
  
      def GetNextSample(self):
          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())
          assert not _is_null(event)
          if self.convert_to_str:
              return EventStructUsingStr(event[0])
          return event[0]
  
      def GetSymbolOfCurrentSample(self):
          symbol = self._GetSymbolOfCurrentSampleFunc(self.getInstance())
          assert not _is_null(symbol)
          if self.convert_to_str:
              return SymbolStructUsingStr(symbol[0])
          return symbol[0]
  
      def GetCallChainOfCurrentSample(self):
          callchain = self._GetCallChainOfCurrentSampleFunc(self.getInstance())
          assert not _is_null(callchain)
          if self.convert_to_str:
              return CallChainStructureUsingStr(callchain[0])
          return callchain[0]
  
      def GetBuildIdForPath(self, path):
          build_id = self._GetBuildIdForPathFunc(self.getInstance(), _char_pt(path))
          assert not _is_null(build_id)
          return _char_pt_to_str(build_id)
  
      def GetRecordCmd(self):
          if self.record_cmd is not None:
              return self.record_cmd
          self.record_cmd = ''
          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)
              arg_count = ct.cast(void_p, ct.POINTER(ct.c_uint32)).contents.value
              void_p.value += 4
              args = []
              for _ 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 _GetFeatureString(self, feature_name):
          feature_data = self._GetFeatureSection(self.getInstance(), _char_pt(feature_name))
          result = ''
          if not _is_null(feature_data):
              void_p = ct.cast(feature_data[0].data, ct.c_void_p)
              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))
              for i in range(str_len):
                  c = bytes_to_str(char_p[i])
                  if c == '\0':
                      break
                  result += c
          return result
  
      def GetArch(self):
          return self._GetFeatureString('arch')
  
      def MetaInfo(self):
          """ Return a string to string map stored in meta_info section in perf.data.
              It is used to pass some short meta information.
          """
          if self.meta_info is None:
              self.meta_info = {}
              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):
          if self._instance is None:
              raise Exception('Instance is Closed')
          return self._instance
  
      def _check(self, cond, failmsg):
          if not cond:
              raise Exception(failmsg)
  