Merge remote branch 'origin/master' into HEAD
[mesa.git] / bin / win32kprof.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
5 # All Rights Reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a
8 # copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sub license, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice (including the
16 # next paragraph) shall be included in all copies or substantial portions
17 # of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22 # IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
23 # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 #
27 ##########################################################################
28
29
30 import sys
31 import optparse
32 import re
33 import struct
34
35 from gprof2dot import Call, Function, Profile
36 from gprof2dot import CALLS, SAMPLES, TIME, TIME_RATIO, TOTAL_TIME, TOTAL_TIME_RATIO
37 from gprof2dot import DotWriter, TEMPERATURE_COLORMAP
38
39
40 __version__ = '0.1'
41
42
43 class ParseError(Exception):
44 pass
45
46
47 class MsvcDemangler:
48 # http://www.kegel.com/mangle.html
49
50 def __init__(self, symbol):
51 self._symbol = symbol
52 self._pos = 0
53
54 def lookahead(self):
55 return self._symbol[self._pos]
56
57 def consume(self):
58 ret = self.lookahead()
59 self._pos += 1
60 return ret
61
62 def match(self, c):
63 if self.lookahead() != c:
64 raise ParseError
65 self.consume()
66
67 def parse(self):
68 self.match('?')
69 name = self.parse_name()
70 qualifications = self.parse_qualifications()
71 return '::'.join(qualifications + [name])
72
73 def parse_name(self):
74 if self.lookahead() == '?':
75 return self.consume() + self.consume()
76 else:
77 name = self.parse_id()
78 self.match('@')
79 return name
80
81 def parse_qualifications(self):
82 qualifications = []
83 while self.lookahead() != '@':
84 name = self.parse_id()
85 qualifications.append(name)
86 self.match('@')
87 return qualifications
88
89 def parse_id(self):
90 s = ''
91 while True:
92 c = self.lookahead()
93 if c.isalnum() or c in '_':
94 s += c
95 self.consume()
96 else:
97 break
98 return s
99
100
101 def demangle(name):
102 if name.startswith('_'):
103 name = name[1:]
104 idx = name.rfind('@')
105 if idx != -1 and name[idx+1:].isdigit():
106 name = name[:idx]
107 return name
108 if name.startswith('?'):
109 demangler = MsvcDemangler(name)
110 return demangler.parse()
111 return name
112
113
114 class Reader:
115
116 def __init__(self):
117 self.symbols = []
118 self.symbol_cache = {}
119 self.base_addr = None
120
121 def read_map(self, mapfile):
122 # See http://msdn.microsoft.com/en-us/library/k7xkk3e2.aspx
123 last_addr = 0
124 last_name = 0
125 for line in file(mapfile, "rt"):
126 fields = line.split()
127 try:
128 section_offset, name, addr, type, lib_object = fields
129 except ValueError:
130 continue
131 if type != 'f':
132 continue
133 section, offset = section_offset.split(':')
134 addr = int(offset, 16)
135 self.symbols.append((addr, name))
136 last_addr = addr
137 last_name = name
138
139 # sort symbols
140 self.symbols.sort(key = lambda (addr, name): addr)
141
142 def lookup_addr(self, addr):
143 try:
144 return self.symbol_cache[addr]
145 except KeyError:
146 pass
147
148 tolerance = 4196
149 s, e = 0, len(self.symbols)
150 while s != e:
151 i = (s + e)//2
152 start_addr, name = self.symbols[i]
153 try:
154 end_addr, next_name = self.symbols[i + 1]
155 except IndexError:
156 end_addr = start_addr + tolerance
157 if addr < start_addr:
158 e = i
159 continue
160 if addr == end_addr:
161 return next_name, addr - start_addr
162 if addr > end_addr:
163 s = i
164 continue
165 return name, addr - start_addr
166 raise ValueError
167
168 def lookup_symbol(self, name):
169 for symbol_addr, symbol_name in self.symbols:
170 if name == symbol_name:
171 return symbol_addr
172 return 0
173
174 def read_data(self, data):
175 profile = Profile()
176
177 fp = file(data, "rb")
178 entry_format = "IIII"
179 entry_size = struct.calcsize(entry_format)
180 caller = None
181 caller_stack = []
182 while True:
183 entry = fp.read(entry_size)
184 if len(entry) < entry_size:
185 break
186 caller_addr, callee_addr, samples_lo, samples_hi = struct.unpack(entry_format, entry)
187 if caller_addr == 0 and callee_addr == 0:
188 continue
189
190 if self.base_addr is None:
191 ref_addr = self.lookup_symbol('___debug_profile_reference@0')
192 if ref_addr:
193 self.base_addr = (caller_addr - ref_addr) & ~(options.align - 1)
194 else:
195 self.base_addr = 0
196 sys.stderr.write('Base addr: %08x\n' % self.base_addr)
197
198 samples = (samples_hi << 32) | samples_lo
199
200 try:
201 caller_raddr = caller_addr - self.base_addr
202 caller_sym, caller_ofs = self.lookup_addr(caller_raddr)
203
204 try:
205 caller = profile.functions[caller_sym]
206 except KeyError:
207 caller_name = demangle(caller_sym)
208 caller = Function(caller_sym, caller_name)
209 profile.add_function(caller)
210 caller[CALLS] = 0
211 caller[SAMPLES] = 0
212 except ValueError:
213 caller = None
214
215 if not callee_addr:
216 if caller:
217 caller[SAMPLES] += samples
218 else:
219 callee_raddr = callee_addr - self.base_addr
220 callee_sym, callee_ofs = self.lookup_addr(callee_raddr)
221
222 try:
223 callee = profile.functions[callee_sym]
224 except KeyError:
225 callee_name = demangle(callee_sym)
226 callee = Function(callee_sym, callee_name)
227 profile.add_function(callee)
228 callee[CALLS] = samples
229 callee[SAMPLES] = 0
230 else:
231 callee[CALLS] += samples
232
233 if caller is not None:
234 try:
235 call = caller.calls[callee.id]
236 except KeyError:
237 call = Call(callee.id)
238 call[CALLS] = samples
239 caller.add_call(call)
240 else:
241 call[CALLS] += samples
242
243 if options.verbose:
244 if not callee_addr:
245 sys.stderr.write('%s+%u: %u\n' % (caller_sym, caller_ofs, samples))
246 else:
247 sys.stderr.write('%s+%u -> %s+%u: %u\n' % (caller_sym, caller_ofs, callee_sym, callee_ofs, samples))
248
249 # compute derived data
250 profile.validate()
251 profile.find_cycles()
252 profile.aggregate(SAMPLES)
253 profile.ratio(TIME_RATIO, SAMPLES)
254 profile.call_ratios(CALLS)
255 profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
256
257 return profile
258
259
260 def main():
261 parser = optparse.OptionParser(
262 usage="\n\t%prog [options] [file] ...",
263 version="%%prog %s" % __version__)
264 parser.add_option(
265 '-a', '--align', metavar='NUMBER',
266 type="int", dest="align", default=16,
267 help="section alignment")
268 parser.add_option(
269 '-m', '--map', metavar='FILE',
270 type="string", dest="map",
271 help="map file")
272 parser.add_option(
273 '-b', '--base', metavar='FILE',
274 type="string", dest="base",
275 help="base addr")
276 parser.add_option(
277 '-n', '--node-thres', metavar='PERCENTAGE',
278 type="float", dest="node_thres", default=0.5,
279 help="eliminate nodes below this threshold [default: %default]")
280 parser.add_option(
281 '-e', '--edge-thres', metavar='PERCENTAGE',
282 type="float", dest="edge_thres", default=0.1,
283 help="eliminate edges below this threshold [default: %default]")
284 parser.add_option(
285 '-v', '--verbose',
286 action="count",
287 dest="verbose", default=0,
288 help="verbose output")
289
290 global options
291 (options, args) = parser.parse_args(sys.argv[1:])
292
293 reader = Reader()
294 if options.base is not None:
295 reader.base_addr = int(options.base, 16)
296 if options.map is not None:
297 reader.read_map(options.map)
298 for arg in args:
299 profile = reader.read_data(arg)
300 profile.prune(options.node_thres/100.0, options.edge_thres/100.0)
301 output = sys.stdout
302 dot = DotWriter(output)
303 colormap = TEMPERATURE_COLORMAP
304 dot.graph(profile, colormap)
305
306
307 if __name__ == '__main__':
308 main()
309