# Script for converting perf script events into tracing JSON.
#
# Generated by perf script -g python
# Licensed under the terms of the GNU GPL License version 2

import json
import os
import sys

from collections import deque


# Categorize DSOs by component.
dso_to_comp = {
    'libdvm.so': 'Java',
    'libart.so': 'Java',
    'libjavacore.so': 'Java',
    'libandroid_runtime.so': 'Android',
    'libgui.so': 'Android',
    'libui.so': 'Android',
    'libbinder.so': 'Android',
    'libmemalloc.so': 'Android',
    'libcrypto.so': 'Android',
    'libcutils.so':'Android',
    'libutils.so': 'Android',
    '[kernel.kallsyms]': 'Kernel',
    'libc.so': 'Standard Lib',
    'libstdc++.so': 'Standard Lib',
    'libm.so':'Standard Lib',
    'libGLESv2_adreno.so': 'GPU Driver',
    'libGLESv2_adreno200.so': 'GPU Driver',
    'libq3dtools_adreno200.so': 'GPU Driver',
    'libEGL_adreno.so': 'GPU Driver',
    'libEGL_adreno200.so': 'GPU Driver',
    'libEGL.so': 'GPU Driver',
    'libgsl.so': 'GPU Driver',
    'libGLESv2.so': 'GPU Driver',
    'libsc-a3xx.so': 'GPU Driver',
    'libadreno_utils.so': 'GPU Driver',
    'eglsubAndroid.so': 'GPU Driver',
    'gralloc.msm8960.so': 'GPU Driver',
    'libadreno_utils': 'GPU Driver',
    'libGLES_mali.so': 'GPU Driver',
    'libchromeview.so': 'Chrome',
    '[unknown]': '<unknown>',
    '[UNKNOWN]': '<unknown>',
}


def FilterSymbolModule(module):
  m = dso_to_comp.get(module, None)
  if m:
    return m
  if module.find('libchrome.') == 0:
    return 'Chrome'
  if module.find('dalvik') >= 0 or module.find('@') >= 0:
    return 'Java'
  return module


def FilterSymbolName(module, orign_module, name):
  if module == 'Java':
    return name
  elif module == 'GPU Driver':
    return name
  if name == '':
    return orign_module + ':unknown'
  if name[0].isdigit() or name == '(nil)':
    return orign_module + ':unknown'
  return name


class StackFrameNode:
  def __init__(self, stack_id, name, category):
    self.stack_id = stack_id
    self.parent_id = 0
    self.children = {}
    self.category = category
    self.name = name
    self.samples = []
    self.total_weight = 0.0
    self.have_total_weight = False
    self.parent = None

  def ToDict(self, out_dict):
    if self.stack_id:
      node_dict = {}
      node_dict['name'] = self.name
      node_dict['category'] = self.category
      if self.parent_id:
        node_dict['parent'] = self.parent_id

      out_dict[self.stack_id] = node_dict

    for child in self.children.values():
      child.ToDict(out_dict)
    return out_dict

  def GetTotalWeight(self):
    if self.have_total_weight:
      return self.total_weight
    else:
      # Sum up self samples weight, and children's total weights.
      for s in self.samples:
        self.total_weight += s.weight
      for c in self.children.values():
        self.total_weight += c.GetTotalWeight()
      self.have_total_weight = True
      return self.total_weight


class PerfSample:
  def __init__(self, stack_id, ts, cpu, tid, weight, samp_type, comm):
    self.stack_id = stack_id
    self.ts = ts
    self.cpu = cpu
    self.tid = tid
    self.weight = weight
    self.type = samp_type
    self.comm = comm

  def ToDict(self):
    ret = {}
    ret['ts'] = self.ts / 1000.0  # Timestamp in microseconds
    ret['tid'] = self.tid  # Thread id
    ret['cpu'] = self.cpu  # Sampled CPU
    ret['weight'] = self.weight  # Sample weight
    ret['name'] = self.type  # Sample type
    ret['comm'] = self.comm  # Sample type
    assert self.stack_id != 0
    if self.stack_id:
      ret['sf'] = self.stack_id  # Stack frame id
    return ret


samples = []
root_chain = StackFrameNode(0, 'root', '[unknown]')
next_stack_id = 1
tot_period = 0
saved_period = 0


def process_event(param_dict):
  global next_stack_id
  global saved_period
  global tot_period

  samp_comm = param_dict['comm']
  samp_tid = param_dict['tid']
  samp_cpu = param_dict['cpu']
  samp_ts = param_dict['time']
  samp_period = param_dict['period']
  samp_type = param_dict['ev_name']
  tot_period += samp_period

  # Parse call chain.
  seen_syms = set()
  chain = deque()
  for cs in param_dict['cs']:
    cs_name = cs[0]
    cs_dso = os.path.basename(cs[1])
    cs_category = FilterSymbolModule(cs_dso)
    cs_name = FilterSymbolName(cs_category, cs_dso, cs_name)

    if cs_category != '<unknown>' or len(chain) == 0:
      sym = (cs_name, cs_category)
      if sym in seen_syms:
        while chain[0] != sym:
          seen_syms.remove(chain[0])
          chain.popleft()
      else:
        seen_syms.add(sym)
        chain.appendleft(sym)

      # Discard garbage stacktrace before __pthread_start()
      if cs_name == '__pthread_start(void*)':
        break

  # Done reading call chain.  Add to stack frame tree.
  stack_frame = root_chain
  for call in chain:
    if call in stack_frame.children:
      stack_frame = stack_frame.children[call]
    else:
      new_node = StackFrameNode(next_stack_id, call[0], call[1])
      next_stack_id += 1
      new_node.parent_id = stack_frame.stack_id
      stack_frame.children[call] = new_node
      stack_frame = new_node

  # Save sample.
  sample = PerfSample(stack_frame.stack_id,
                  samp_ts,
                  samp_cpu,
                  samp_tid,
                  samp_period,
                  samp_type,
                  samp_comm)
  samples.append(sample)
  stack_frame.samples.append(sample)
  saved_period += samp_period


def trace_begin():
  pass


def trace_end():
  # Return siblings of a call tree node.
  def GetNodeSiblings(node):
    if not node:
      return []
    if not node.parent:
      return []
    return node.parent.children.values()

  # Try to reduce misplaced stack leaves by moving them up into sibling nodes.
  def FixCallTree(node, parent):
    # Get siblings of node's parent.
    node.parent = parent
    parent_siblings = GetNodeSiblings(parent)

    # If parent's sibling has same node name, has no children and small weight,
    # transplant sibling's samples into the current node.
    for sibling in parent_siblings:
      if sibling.name == node.name and \
          len(sibling.children) == 0 and \
          sibling.GetTotalWeight() <= node.GetTotalWeight() * 0.15:

        # Transplant samples from sibling to current node.
        for samp in sibling.samples:
          samp.stack_id = node.stack_id
          node.samples.append(samp)
        sibling.samples = []
        break

    # Recurse child nodes.
    for c in node.children.values():
      FixCallTree(c, node)

  FixCallTree(root_chain, None)

  trace_dict = {}
  trace_dict['samples'] = [s.ToDict() for s in samples]
  trace_dict['stackFrames'] = root_chain.ToDict({})
  trace_dict['traceEvents'] = []

  json.dump(trace_dict, sys.stdout, indent=1)
