|
| 1 | +#!/usr/bin/env python |
| 2 | +from __future__ import absolute_import |
| 3 | +from __future__ import division |
| 4 | +from __future__ import print_function |
| 5 | +from __future__ import unicode_literals |
| 6 | + |
| 7 | +import argparse |
| 8 | +import json |
| 9 | +import smap |
| 10 | +import trace_data |
| 11 | +import urllib |
| 12 | + |
| 13 | +SECONDS_TO_NANOSECONDS = (1000*1000) |
| 14 | +SAMPLE_DELTA_IN_SECONDS = 0.0001 |
| 15 | + |
| 16 | +class Marker(object): |
| 17 | + def __init__(self, _name, _timestamp, _depth, _is_end, _ident, url, line, col): |
| 18 | + self.name = _name |
| 19 | + self.timestamp = _timestamp |
| 20 | + self.depth = _depth |
| 21 | + self.is_end = _is_end |
| 22 | + self.ident = _ident |
| 23 | + self.url = url |
| 24 | + self.line = line |
| 25 | + self.col = col |
| 26 | + |
| 27 | +# sort markers making sure they are ordered by timestamp then depth of function call |
| 28 | +# and finally that markers of the same ident are sorted in the order begin then end |
| 29 | + def __cmp__(self, other): |
| 30 | + if self.timestamp < other.timestamp: |
| 31 | + return -1 |
| 32 | + if self.timestamp > other.timestamp: |
| 33 | + return 1 |
| 34 | + if self.depth < other.depth: |
| 35 | + return -1 |
| 36 | + if self.depth > other.depth: |
| 37 | + return 1 |
| 38 | + if self.ident == other.ident: |
| 39 | + if self.is_end: |
| 40 | + return 1 |
| 41 | + return 0 |
| 42 | + |
| 43 | +# calculate marker name based on combination of function name and location |
| 44 | +def _calcname(entry): |
| 45 | + funcname = "" |
| 46 | + if "functionName" in entry: |
| 47 | + funcname = funcname + entry["functionName"] |
| 48 | + return funcname |
| 49 | + |
| 50 | +def _calcurl(mapcache, entry, map_file): |
| 51 | + if entry.url not in mapcache: |
| 52 | + map_url = entry.url.replace('.bundle', '.map') |
| 53 | + |
| 54 | + if map_url != entry.url: |
| 55 | + if map_file: |
| 56 | + print('Loading sourcemap from:' + map_file) |
| 57 | + map_url = map_file |
| 58 | + |
| 59 | + try: |
| 60 | + url_file = urllib.urlopen(map_url) |
| 61 | + if url_file != None: |
| 62 | + entries = smap.parse(url_file) |
| 63 | + mapcache[entry.url] = entries |
| 64 | + except Exception, e: |
| 65 | + mapcache[entry.url] = [] |
| 66 | + |
| 67 | + if entry.url in mapcache: |
| 68 | + source_entry = smap.find(mapcache[entry.url], entry.line, entry.col) |
| 69 | + if source_entry: |
| 70 | + entry.url = 'file://' + source_entry.src |
| 71 | + entry.line = source_entry.src_line |
| 72 | + entry.col = source_entry.src_col |
| 73 | + |
| 74 | +def _compute_markers(markers, call_point, depth): |
| 75 | + name = _calcname(call_point) |
| 76 | + ident = len(markers) |
| 77 | + url = "" |
| 78 | + lineNumber = -1 |
| 79 | + columnNumber = -1 |
| 80 | + if "url" in call_point: |
| 81 | + url = call_point["url"] |
| 82 | + if "lineNumber" in call_point: |
| 83 | + lineNumber = call_point["lineNumber"] |
| 84 | + if "columnNumber" in call_point: |
| 85 | + columnNumber = call_point["columnNumber"] |
| 86 | + |
| 87 | + for call in call_point["calls"]: |
| 88 | + markers.append(Marker(name, call["startTime"], depth, 0, ident, url, lineNumber, columnNumber)) |
| 89 | + markers.append(Marker(name, call["startTime"] + call["totalTime"], depth, 1, ident, url, lineNumber, columnNumber)) |
| 90 | + ident = ident + 2 |
| 91 | + if "children" in call_point: |
| 92 | + for child in call_point["children"]: |
| 93 | + _compute_markers(markers, child, depth+1); |
| 94 | + |
| 95 | +def _find_child(children, name): |
| 96 | + for child in children: |
| 97 | + if child['functionName'] == name: |
| 98 | + return child |
| 99 | + return None |
| 100 | + |
| 101 | +def _add_entry_cpuprofiler_program(newtime, cpuprofiler): |
| 102 | + curnode = _find_child(cpuprofiler['head']['children'], '(program)') |
| 103 | + if cpuprofiler['lastTime'] != None: |
| 104 | + lastTime = cpuprofiler['lastTime'] |
| 105 | + while lastTime < newtime: |
| 106 | + curnode['hitCount'] += 1 |
| 107 | + cpuprofiler['samples'].append(curnode['callUID']) |
| 108 | + cpuprofiler['timestamps'].append(int(lastTime*SECONDS_TO_NANOSECONDS)) |
| 109 | + lastTime += SAMPLE_DELTA_IN_SECONDS |
| 110 | + cpuprofiler['lastTime'] = lastTime |
| 111 | + else: |
| 112 | + cpuprofiler['lastTime'] = newtime |
| 113 | + |
| 114 | + |
| 115 | +def _add_entry_cpuprofiler(stack, newtime, cpuprofiler): |
| 116 | + index = len(stack) - 1 |
| 117 | + marker = stack[index] |
| 118 | + |
| 119 | + if marker.name not in cpuprofiler['markers']: |
| 120 | + cpuprofiler['markers'][marker.name] = cpuprofiler['id'] |
| 121 | + cpuprofiler['callUID'] += 1 |
| 122 | + callUID = cpuprofiler['markers'][marker.name] |
| 123 | + |
| 124 | + curnode = cpuprofiler['head'] |
| 125 | + index = 0 |
| 126 | + while index < len(stack): |
| 127 | + newnode = _find_child(curnode['children'], stack[index].name) |
| 128 | + if newnode == None: |
| 129 | + newnode = {} |
| 130 | + newnode['callUID'] = callUID |
| 131 | + newnode['url'] = marker.url |
| 132 | + newnode['functionName'] = stack[index].name |
| 133 | + newnode['hitCount'] = 0 |
| 134 | + newnode['lineNumber'] = marker.line |
| 135 | + newnode['columnNumber'] = marker.col |
| 136 | + newnode['scriptId'] = callUID |
| 137 | + newnode['positionTicks'] = [] |
| 138 | + newnode['id'] = cpuprofiler['id'] |
| 139 | + cpuprofiler['id'] += 1 |
| 140 | + newnode['children'] = [] |
| 141 | + curnode['children'].append(newnode) |
| 142 | + curnode['deoptReason'] = '' |
| 143 | + curnode = newnode |
| 144 | + index += 1 |
| 145 | + |
| 146 | + if cpuprofiler['lastTime'] == None: |
| 147 | + cpuprofiler['lastTime'] = newtime |
| 148 | + |
| 149 | + if cpuprofiler['lastTime'] != None: |
| 150 | + lastTime = cpuprofiler['lastTime'] |
| 151 | + while lastTime < newtime: |
| 152 | + curnode['hitCount'] += 1 |
| 153 | + if len(curnode['positionTicks']) == 0: |
| 154 | + ticks = {} |
| 155 | + ticks['line'] = curnode['callUID'] |
| 156 | + ticks['ticks'] = 0 |
| 157 | + curnode['positionTicks'].append(ticks) |
| 158 | + curnode['positionTicks'][0]['ticks'] += 1 |
| 159 | + cpuprofiler['samples'].append(curnode['callUID']) |
| 160 | + cpuprofiler['timestamps'].append(int(lastTime*1000*1000)) |
| 161 | + lastTime += 0.0001 |
| 162 | + cpuprofiler['lastTime'] = lastTime |
| 163 | + |
| 164 | +def _create_default_cpuprofiler_node(name, _id, _uid): |
| 165 | + return {'functionName': name, |
| 166 | + 'scriptId':'0', |
| 167 | + 'url':'', |
| 168 | + 'lineNumber':0, |
| 169 | + 'columnNumber':0, |
| 170 | + 'positionTicks':[], |
| 171 | + 'id':_id, |
| 172 | + 'callUID':_uid, |
| 173 | + 'children': [], |
| 174 | + 'hitCount': 0, |
| 175 | + 'deoptReason':''} |
| 176 | + |
| 177 | +def main(): |
| 178 | + parser = argparse.ArgumentParser(description="Converts JSON profile format to fbsystrace text output") |
| 179 | + |
| 180 | + parser.add_argument( |
| 181 | + "-o", |
| 182 | + dest = "output_file", |
| 183 | + default = None, |
| 184 | + help = "Output file for trace data") |
| 185 | + parser.add_argument( |
| 186 | + "-cpuprofiler", |
| 187 | + dest = "output_cpuprofiler", |
| 188 | + default = None, |
| 189 | + help = "Output file for cpuprofiler data") |
| 190 | + parser.add_argument( |
| 191 | + "-map", |
| 192 | + dest = "map_file", |
| 193 | + default = None, |
| 194 | + help = "Map file for symbolicating") |
| 195 | + parser.add_argument( "file", help = "JSON trace input_file") |
| 196 | + |
| 197 | + args = parser.parse_args() |
| 198 | + |
| 199 | + markers = [] |
| 200 | + with open(args.file, "r") as trace_file: |
| 201 | + trace = json.load(trace_file) |
| 202 | + for root_entry in trace["rootNodes"]: |
| 203 | + _compute_markers(markers, root_entry, 0) |
| 204 | + |
| 205 | + mapcache = {} |
| 206 | + for m in markers: |
| 207 | + _calcurl(mapcache, m, args.map_file) |
| 208 | + |
| 209 | + sorted_markers = list(sorted(markers)); |
| 210 | + |
| 211 | + if args.output_cpuprofiler != None: |
| 212 | + cpuprofiler = {} |
| 213 | + cpuprofiler['startTime'] = None |
| 214 | + cpuprofiler['endTime'] = None |
| 215 | + cpuprofiler['lastTime'] = None |
| 216 | + cpuprofiler['id'] = 4 |
| 217 | + cpuprofiler['callUID'] = 4 |
| 218 | + cpuprofiler['samples'] = [] |
| 219 | + cpuprofiler['timestamps'] = [] |
| 220 | + cpuprofiler['markers'] = {} |
| 221 | + cpuprofiler['head'] = _create_default_cpuprofiler_node('(root)', 1, 1) |
| 222 | + cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(root)', 2, 2)) |
| 223 | + cpuprofiler['head']['children'].append(_create_default_cpuprofiler_node('(program)', 3, 3)) |
| 224 | + marker_stack = [] |
| 225 | + with open(args.output_cpuprofiler, 'w') as file_out: |
| 226 | + for marker in sorted_markers: |
| 227 | + if len(marker_stack): |
| 228 | + _add_entry_cpuprofiler(marker_stack, marker.timestamp, cpuprofiler) |
| 229 | + else: |
| 230 | + _add_entry_cpuprofiler_program(marker.timestamp, cpuprofiler) |
| 231 | + if marker.is_end: |
| 232 | + marker_stack.pop() |
| 233 | + else: |
| 234 | + marker_stack.append(marker) |
| 235 | + cpuprofiler['startTime'] = cpuprofiler['timestamps'][0] / 1000000.0 |
| 236 | + cpuprofiler['endTime'] = cpuprofiler['timestamps'][len(cpuprofiler['timestamps']) - 1] / 1000000.0 |
| 237 | + json.dump(cpuprofiler, file_out, sort_keys=False, indent=4, separators=(',', ': ')) |
| 238 | + |
| 239 | + |
| 240 | + if args.output_file != None: |
| 241 | + with open(args.output_file,"w") as trace_file: |
| 242 | + for marker in sorted_markers: |
| 243 | + start_or_end = None |
| 244 | + if marker.is_end: |
| 245 | + start_or_end = "E" |
| 246 | + else: |
| 247 | + start_or_end = "B" |
| 248 | + #output with timestamp at high level of precision |
| 249 | + trace_file.write("json-0 [000] .... {0:.12f}: tracing_mark_write: {1}|0|{2}\n".format( |
| 250 | + marker.timestamp, |
| 251 | + start_or_end, |
| 252 | + marker.name)) |
| 253 | + |
| 254 | +main() |
0 commit comments