#!/usr/bin/env python from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import argparse import json import smap import trace_data import urllib SECONDS_TO_NANOSECONDS = (1000*1000) SAMPLE_DELTA_IN_SECONDS = 0.0001 class Marker(object): def __init__(self, _name, _timestamp, _depth, _is_end, _ident, url, line, col): self.name = _name self.timestamp = _timestamp self.depth = _depth self.is_end = _is_end self.ident = _ident self.url = url self.line = line self.col = col # sort markers making sure they are ordered by timestamp then depth of function call # and finally that markers of the same ident are sorted in the order begin then end def __cmp__(self, other): if self.timestamp < other.timestamp: return -1 if self.timestamp > other.timestamp: return 1 if self.depth < other.depth: return -1 if self.depth > other.depth: return 1 if self.ident == other.ident: if self.is_end: return 1 return 0 # calculate marker name based on combination of function name and location def _calcname(entry): funcname = "" if "functionName" in entry: funcname = funcname + entry["functionName"] return funcname def _calcurl(mapcache, entry, map_file): if entry.url not in mapcache: map_url = entry.url.replace('.bundle', '.map') if map_url != entry.url: if map_file: print('Loading sourcemap from:' + map_file) map_url = map_file try: url_file = urllib.urlopen(map_url) if url_file != None: entries = smap.parse(url_file) mapcache[entry.url] = entries except Exception, e: mapcache[entry.url] = [] if entry.url in mapcache: source_entry = smap.find(mapcache[entry.url], entry.line, entry.col) if source_entry: entry.url = 'file://' + source_entry.src entry.line = source_entry.src_line entry.col = source_entry.src_col def _compute_markers(markers, call_point, depth): name = _calcname(call_point) ident = len(markers) url = "" lineNumber = -1 columnNumber = -1 if "url" in call_point: url = call_point["url"] if "lineNumber" in call_point: lineNumber = call_point["lineNumber"] if "columnNumber" in call_point: columnNumber = call_point["columnNumber"] for call in call_point["calls"]: markers.append(Marker(name, call["startTime"], depth, 0, ident, url, lineNumber, columnNumber)) markers.append(Marker(name, call["startTime"] + call["totalTime"], depth, 1, ident, url, lineNumber, columnNumber)) ident = ident + 2 if "children" in call_point: for child in call_point["children"]: _compute_markers(markers, child, depth+1); def _find_child(children, name): for child in children: if child['functionName'] == name: return child return None def _add_entry_cpuprofiler_program(newtime, cpuprofiler): curnode = _find_child(cpuprofiler['head']['children'], '(program)') if cpuprofiler['lastTime'] != None: lastTime = cpuprofiler['lastTime'] while lastTime < newtime: curnode['hitCount'] += 1 cpuprofiler['samples'].append(curnode['callUID']) cpuprofiler['timestamps'].append(int(lastTime*SECONDS_TO_NANOSECONDS)) lastTime += SAMPLE_DELTA_IN_SECONDS cpuprofiler['lastTime'] = lastTime else: cpuprofiler['lastTime'] = newtime def _add_entry_cpuprofiler(stack, newtime, cpuprofiler): index = len(stack) - 1 marker = stack[index] if marker.name not in cpuprofiler['markers']: cpuprofiler['markers'][marker.name] = cpuprofiler['id'] cpuprofiler['callUID'] += 1 callUID = cpuprofiler['markers'][marker.name] curnode = cpuprofiler['head'] index = 0 while index < len(stack): newnode = _find_child(curnode['children'], stack[index].name) if newnode == None: newnode = {} newnode['callUID'] = callUID newnode['url'] = marker.url newnode['functionName'] = stack[index].name newnode['hitCount'] = 0 newnode['lineNumber'] = marker.line newnode['columnNumber'] = marker.col newnode['scriptId'] = callUID newnode['positionTicks'] = [] newnode['id'] = cpuprofiler['id'] cpuprofiler['id'] += 1 newnode['children'] = [] curnode['children'].append(newnode) curnode['deoptReason'] = '' curnode = newnode index += 1 if cpuprofiler['lastTime'] == None: cpuprofiler['lastTime'] = newtime if cpuprofiler['lastTime'] != None: lastTime = cpuprofiler['lastTime'] while lastTime < newtime: curnode['hitCount'] += 1 if len(curnode['positionTicks']) == 0: ticks = {} ticks['line'] = curnode['callUID'] ticks['ticks'] = 0 curnode['positionTicks'].append(ticks) curnode['positionTicks'][0]['ticks'] += 1 cpuprofiler['samples'].append(curnode['callUID']) cpuprofiler['timestamps'].append(int(lastTime*1000*1000)) lastTime += 0.0001 cpuprofiler['lastTime'] = lastTime def _create_default_cpuprofiler_node(name, _id, _uid): return {'functionName': name, 'scriptId':'0', 'url':'', 'lineNumber':0, 'columnNumber':0, 'positionTicks':[], 'id':_id, 'callUID':_uid, 'children': [], 'hitCount': 0, 'deoptReason':''} def main(): parser = argparse.ArgumentParser(description="Converts JSON profile format to fbsystrace text output") parser.add_argument( "-o", dest = "output_file", default = None, help = "Output file for trace data") parser.add_argument( "-cpuprofiler", dest = "output_cpuprofiler", default = None, help = "Output file for cpuprofiler data") parser.add_argument( "-map", dest = "map_file", default = None, help = "Map file for symbolicating") parser.add_argument( "file", help = "JSON trace input_file") args = parser.parse_args() markers = [] with open(args.file, "r") as trace_file: trace = json.load(trace_file) for root_entry in trace["rootNodes"]: _compute_markers(markers, root_entry, 0) mapcache = {} for m in markers: _calcurl(mapcache, m, args.map_file) sorted_markers = list(sorted(markers)); if args.output_cpuprofiler != None: cpuprofiler = {} cpuprofiler['startTime'] = None cpuprofiler['endTime'] = None cpuprofiler['lastTime'] = None cpuprofiler['id'] = 4 cpuprofiler['callUID'] = 4 cpuprofiler['samples'] = [] cpuprofiler['timestamps'] = [] cpuprofiler['markers'] = {} cpuprofiler['head'] = _create_default_cpuprofiler_node('(root)', 1, 1) cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(root)', 2, 2)) cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(program)', 3, 3)) marker_stack = [] with open(args.output_cpuprofiler, 'w') as file_out: for marker in sorted_markers: if len(marker_stack): _add_entry_cpuprofiler(marker_stack, marker.timestamp, cpuprofiler) else: _add_entry_cpuprofiler_program(marker.timestamp, cpuprofiler) if marker.is_end: marker_stack.pop() else: marker_stack.append(marker) cpuprofiler['startTime'] = cpuprofiler['timestamps'][0] / 1000000.0 cpuprofiler['endTime'] = cpuprofiler['timestamps'][len(cpuprofiler['timestamps']) - 1] / 1000000.0 json.dump(cpuprofiler, file_out, sort_keys=False, indent=4, separators=(',', ': ')) if args.output_file != None: with open(args.output_file,"w") as trace_file: for marker in sorted_markers: start_or_end = None if marker.is_end: start_or_end = "E" else: start_or_end = "B" #output with timestamp at high level of precision trace_file.write("json-0 [000] .... {0:.12f}: tracing_mark_write: {1}|0|{2}\n".format( marker.timestamp, start_or_end, marker.name)) main()