1 | #!/usr/bin/env python
|
---|
2 | #
|
---|
3 | # Copyright 2012 VMware Inc
|
---|
4 | # Copyright 2008-2009 Jose Fonseca
|
---|
5 | #
|
---|
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
|
---|
7 | # of this software and associated documentation files (the "Software"), to deal
|
---|
8 | # in the Software without restriction, including without limitation the rights
|
---|
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
---|
10 | # copies of the Software, and to permit persons to whom the Software is
|
---|
11 | # furnished to do so, subject to the following conditions:
|
---|
12 | #
|
---|
13 | # The above copyright notice and this permission notice shall be included in
|
---|
14 | # all copies or substantial portions of the Software.
|
---|
15 | #
|
---|
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
---|
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
---|
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
---|
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
---|
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
---|
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
---|
22 | # THE SOFTWARE.
|
---|
23 | #
|
---|
24 |
|
---|
25 | """Perf annotate for JIT code.
|
---|
26 |
|
---|
27 | Linux `perf annotate` does not work with JIT code. This script takes the data
|
---|
28 | produced by `perf script` command, plus the diassemblies outputed by gallivm
|
---|
29 | into /tmp/perf-XXXXX.map.asm and produces output similar to `perf annotate`.
|
---|
30 |
|
---|
31 | See docs/llvmpipe.rst for usage instructions.
|
---|
32 |
|
---|
33 | The `perf script` output parser was derived from the gprof2dot.py script.
|
---|
34 | """
|
---|
35 |
|
---|
36 |
|
---|
37 | import sys
|
---|
38 | import os.path
|
---|
39 | import re
|
---|
40 | import optparse
|
---|
41 | import subprocess
|
---|
42 |
|
---|
43 |
|
---|
44 | class Parser:
|
---|
45 | """Parser interface."""
|
---|
46 |
|
---|
47 | def __init__(self):
|
---|
48 | pass
|
---|
49 |
|
---|
50 | def parse(self):
|
---|
51 | raise NotImplementedError
|
---|
52 |
|
---|
53 |
|
---|
54 | class LineParser(Parser):
|
---|
55 | """Base class for parsers that read line-based formats."""
|
---|
56 |
|
---|
57 | def __init__(self, file):
|
---|
58 | Parser.__init__(self)
|
---|
59 | self._file = file
|
---|
60 | self.__line = None
|
---|
61 | self.__eof = False
|
---|
62 | self.line_no = 0
|
---|
63 |
|
---|
64 | def readline(self):
|
---|
65 | line = self._file.readline()
|
---|
66 | if not line:
|
---|
67 | self.__line = ''
|
---|
68 | self.__eof = True
|
---|
69 | else:
|
---|
70 | self.line_no += 1
|
---|
71 | self.__line = line.rstrip('\r\n')
|
---|
72 |
|
---|
73 | def lookahead(self):
|
---|
74 | assert self.__line is not None
|
---|
75 | return self.__line
|
---|
76 |
|
---|
77 | def consume(self):
|
---|
78 | assert self.__line is not None
|
---|
79 | line = self.__line
|
---|
80 | self.readline()
|
---|
81 | return line
|
---|
82 |
|
---|
83 | def eof(self):
|
---|
84 | assert self.__line is not None
|
---|
85 | return self.__eof
|
---|
86 |
|
---|
87 |
|
---|
88 | mapFile = None
|
---|
89 |
|
---|
90 | def lookupMap(filename, matchSymbol):
|
---|
91 | global mapFile
|
---|
92 | mapFile = filename
|
---|
93 | stream = open(filename, 'rt')
|
---|
94 | for line in stream:
|
---|
95 | start, length, symbol = line.split()
|
---|
96 |
|
---|
97 | start = int(start, 16)
|
---|
98 | length = int(length,16)
|
---|
99 |
|
---|
100 | if symbol == matchSymbol:
|
---|
101 | return start
|
---|
102 |
|
---|
103 | return None
|
---|
104 |
|
---|
105 | def lookupAsm(filename, desiredFunction):
|
---|
106 | stream = open(filename + '.asm', 'rt')
|
---|
107 | while stream.readline() != desiredFunction + ':\n':
|
---|
108 | pass
|
---|
109 |
|
---|
110 | asm = []
|
---|
111 | line = stream.readline().strip()
|
---|
112 | while line:
|
---|
113 | addr, instr = line.split(':', 1)
|
---|
114 | addr = int(addr)
|
---|
115 | asm.append((addr, instr))
|
---|
116 | line = stream.readline().strip()
|
---|
117 |
|
---|
118 | return asm
|
---|
119 |
|
---|
120 |
|
---|
121 |
|
---|
122 | samples = {}
|
---|
123 |
|
---|
124 |
|
---|
125 | class PerfParser(LineParser):
|
---|
126 | """Parser for linux perf callgraph output.
|
---|
127 |
|
---|
128 | It expects output generated with
|
---|
129 |
|
---|
130 | perf record -g
|
---|
131 | perf script
|
---|
132 | """
|
---|
133 |
|
---|
134 | def __init__(self, infile, symbol):
|
---|
135 | LineParser.__init__(self, infile)
|
---|
136 | self.symbol = symbol
|
---|
137 |
|
---|
138 | def readline(self):
|
---|
139 | # Override LineParser.readline to ignore comment lines
|
---|
140 | while True:
|
---|
141 | LineParser.readline(self)
|
---|
142 | if self.eof() or not self.lookahead().startswith('#'):
|
---|
143 | break
|
---|
144 |
|
---|
145 | def parse(self):
|
---|
146 | # read lookahead
|
---|
147 | self.readline()
|
---|
148 |
|
---|
149 | while not self.eof():
|
---|
150 | self.parse_event()
|
---|
151 |
|
---|
152 | asm = lookupAsm(mapFile, self.symbol)
|
---|
153 |
|
---|
154 | addresses = samples.keys()
|
---|
155 | addresses.sort()
|
---|
156 | total_samples = 0
|
---|
157 |
|
---|
158 | sys.stdout.write('%s:\n' % self.symbol)
|
---|
159 | for address, instr in asm:
|
---|
160 | try:
|
---|
161 | sample = samples.pop(address)
|
---|
162 | except KeyError:
|
---|
163 | sys.stdout.write(6*' ')
|
---|
164 | else:
|
---|
165 | sys.stdout.write('%6u' % (sample))
|
---|
166 | total_samples += sample
|
---|
167 | sys.stdout.write('%6u: %s\n' % (address, instr))
|
---|
168 | print 'total:', total_samples
|
---|
169 | assert len(samples) == 0
|
---|
170 |
|
---|
171 | sys.exit(0)
|
---|
172 |
|
---|
173 | def parse_event(self):
|
---|
174 | if self.eof():
|
---|
175 | return
|
---|
176 |
|
---|
177 | line = self.consume()
|
---|
178 | assert line
|
---|
179 |
|
---|
180 | callchain = self.parse_callchain()
|
---|
181 | if not callchain:
|
---|
182 | return
|
---|
183 |
|
---|
184 | def parse_callchain(self):
|
---|
185 | callchain = []
|
---|
186 | while self.lookahead():
|
---|
187 | function = self.parse_call(len(callchain) == 0)
|
---|
188 | if function is None:
|
---|
189 | break
|
---|
190 | callchain.append(function)
|
---|
191 | if self.lookahead() == '':
|
---|
192 | self.consume()
|
---|
193 | return callchain
|
---|
194 |
|
---|
195 | call_re = re.compile(r'^\s+(?P<address>[0-9a-fA-F]+)\s+(?P<symbol>.*)\s+\((?P<module>[^)]*)\)$')
|
---|
196 |
|
---|
197 | def parse_call(self, first):
|
---|
198 | line = self.consume()
|
---|
199 | mo = self.call_re.match(line)
|
---|
200 | assert mo
|
---|
201 | if not mo:
|
---|
202 | return None
|
---|
203 |
|
---|
204 | if not first:
|
---|
205 | return None
|
---|
206 |
|
---|
207 | function_name = mo.group('symbol')
|
---|
208 | if not function_name:
|
---|
209 | function_name = mo.group('address')
|
---|
210 |
|
---|
211 | module = mo.group('module')
|
---|
212 |
|
---|
213 | function_id = function_name + ':' + module
|
---|
214 |
|
---|
215 | address = mo.group('address')
|
---|
216 | address = int(address, 16)
|
---|
217 |
|
---|
218 | if function_name != self.symbol:
|
---|
219 | return None
|
---|
220 |
|
---|
221 | start_address = lookupMap(module, function_name)
|
---|
222 | address -= start_address
|
---|
223 |
|
---|
224 | #print function_name, module, address
|
---|
225 |
|
---|
226 | samples[address] = samples.get(address, 0) + 1
|
---|
227 |
|
---|
228 | return True
|
---|
229 |
|
---|
230 |
|
---|
231 | def main():
|
---|
232 | """Main program."""
|
---|
233 |
|
---|
234 | optparser = optparse.OptionParser(
|
---|
235 | usage="\n\t%prog [options] symbol_name")
|
---|
236 | (options, args) = optparser.parse_args(sys.argv[1:])
|
---|
237 | if len(args) != 1:
|
---|
238 | optparser.error('wrong number of arguments')
|
---|
239 |
|
---|
240 | symbol = args[0]
|
---|
241 |
|
---|
242 | p = subprocess.Popen(['perf', 'script'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
---|
243 | parser = PerfParser(p.stdout, symbol)
|
---|
244 | parser.parse()
|
---|
245 |
|
---|
246 |
|
---|
247 | if __name__ == '__main__':
|
---|
248 | main()
|
---|
249 |
|
---|
250 |
|
---|
251 | # vim: set sw=4 et:
|
---|