Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tools/perfgraph.py
2723 views
1
#!/usr/bin/python3
2
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
3
4
# Given a profile dump, this tool generates a flame graph based on the stacks listed in the profile
5
# The result of analysis is a .svg file which can be viewed in a browser
6
7
import svg
8
import argparse
9
import json
10
11
argumentParser = argparse.ArgumentParser(description='Generate flamegraph SVG from Luau sampling profiler dumps')
12
argumentParser.add_argument('source_file', type=open)
13
argumentParser.add_argument('--json', dest='useJson',action='store_const',const=1,default=0,help='Parse source_file as JSON')
14
15
class Node(svg.Node):
16
def __init__(self):
17
svg.Node.__init__(self)
18
self.function = ""
19
self.source = ""
20
self.line = 0
21
self.ticks = 0
22
23
def text(self):
24
return self.function
25
26
def title(self):
27
if self.line > 0:
28
return "{}\n{}:{}".format(self.function, self.source, self.line)
29
else:
30
return self.function
31
32
def details(self, root):
33
return "Function: {} [{}:{}] ({:,} usec, {:.1%}); self: {:,} usec".format(self.function, self.source, self.line, self.width, self.width / root.width, self.ticks)
34
35
36
def nodeFromCallstackListFile(source_file):
37
dump = source_file.readlines()
38
root = Node()
39
40
for l in dump:
41
ticks, stack = l.strip().split(" ", 1)
42
node = root
43
44
for f in reversed(stack.split(";")):
45
source, function, line = f.split(",")
46
47
child = node.child(f)
48
child.function = function
49
child.source = source
50
child.line = int(line) if len(line) > 0 else 0
51
52
node = child
53
54
node.ticks += int(ticks)
55
56
return root
57
58
59
def getDuration(nodes, nid):
60
node = nodes[nid - 1]
61
total = node['TotalDuration']
62
63
if 'NodeIds' in node:
64
for cid in node['NodeIds']:
65
total -= nodes[cid - 1]['TotalDuration']
66
67
return total
68
69
def getFunctionKey(fn):
70
source = fn['Source'] if 'Source' in fn else ''
71
name = fn['Name'] if 'Name' in fn else ''
72
line = str(fn['Line']) if 'Line' in fn else '-1'
73
74
return source + "," + name + "," + line
75
76
def recursivelyBuildNodeTree(nodes, functions, parent, fid, nid):
77
ninfo = nodes[nid - 1]
78
finfo = functions[fid - 1]
79
80
child = parent.child(getFunctionKey(finfo))
81
child.source = finfo['Source'] if 'Source' in finfo else ''
82
child.function = finfo['Name'] if 'Name' in finfo else ''
83
child.line = int(finfo['Line']) if 'Line' in finfo and finfo['Line'] > 0 else 0
84
85
child.ticks = getDuration(nodes, nid)
86
87
if 'FunctionIds' in ninfo:
88
assert(len(ninfo['FunctionIds']) == len(ninfo['NodeIds']))
89
90
for i in range(0, len(ninfo['FunctionIds'])):
91
recursivelyBuildNodeTree(nodes, functions, child, ninfo['FunctionIds'][i], ninfo['NodeIds'][i])
92
93
return
94
95
def nodeFromJSONV2(dump):
96
assert(dump['Version'] == 2)
97
98
nodes = dump['Nodes']
99
functions = dump['Functions']
100
categories = dump['Categories']
101
102
root = Node()
103
104
for category in categories:
105
nid = category['NodeId']
106
node = nodes[nid - 1]
107
name = category['Name']
108
109
child = root.child(name)
110
child.function = name
111
child.ticks = getDuration(nodes, nid)
112
113
if 'FunctionIds' in node:
114
assert(len(node['FunctionIds']) == len(node['NodeIds']))
115
116
for i in range(0, len(node['FunctionIds'])):
117
recursivelyBuildNodeTree(nodes, functions, child, node['FunctionIds'][i], node['NodeIds'][i])
118
119
return root
120
121
def getDurationV1(obj):
122
total = obj['TotalDuration']
123
124
if 'Children' in obj:
125
for key, obj in obj['Children'].items():
126
total -= obj['TotalDuration']
127
128
return total
129
130
131
def nodeFromJSONObject(node, key, obj):
132
source, function, line = key.split(",")
133
134
node.function = function
135
node.source = source
136
node.line = int(line) if len(line) > 0 else 0
137
138
node.ticks = getDurationV1(obj)
139
140
if 'Children' in obj:
141
for key, obj in obj['Children'].items():
142
nodeFromJSONObject(node.child(key), key, obj)
143
144
return node
145
146
def nodeFromJSONV1(dump):
147
assert(dump['Version'] == 1)
148
root = Node()
149
150
if 'Children' in dump:
151
for key, obj in dump['Children'].items():
152
nodeFromJSONObject(root.child(key), key, obj)
153
154
return root
155
156
def nodeFromJSONFile(source_file):
157
dump = json.load(source_file)
158
159
if dump['Version'] == 2:
160
return nodeFromJSONV2(dump)
161
elif dump['Version'] == 1:
162
return nodeFromJSONV1(dump)
163
164
return Node()
165
166
167
arguments = argumentParser.parse_args()
168
169
if arguments.useJson:
170
root = nodeFromJSONFile(arguments.source_file)
171
else:
172
root = nodeFromCallstackListFile(arguments.source_file)
173
174
175
176
svg.layout(root, lambda n: n.ticks)
177
svg.display(root, "Flame Graph", "hot", flip = True)
178
179