forked from facebook/react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Automatically save and convert JavaScript profile to chrome format
Summary: @public Migrate scripts to open source and add new route on the packager to directly convert profiler outputs to a devtools compatible format. Reviewed By: @jspahrsummers Differential Revision: D2425740
- Loading branch information
1 parent
360d04e
commit 20cd649
Showing
8 changed files
with
936 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
#!/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() |
Oops, something went wrong.