win32kprof: Convert tabs to spaces.
[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
36 __version__ = '0.1'
37
38
39 class ParseError(Exception):
40 pass
41
42
43 class MsvcDemangler:
44 # http://www.kegel.com/mangle.html
45
46 def __init__(self, symbol):
47 self._symbol = symbol
48 self._pos = 0
49
50 def lookahead(self):
51 return self._symbol[self._pos]
52
53 def consume(self):
54 ret = self.lookahead()
55 self._pos += 1
56 return ret
57
58 def match(self, c):
59 if self.lookahead() != c:
60 raise ParseError
61 self.consume()
62
63 def parse(self):
64 self.match('?')
65 name = self.parse_name()
66 qualifications = self.parse_qualifications()
67 return '::'.join(qualifications + [name])
68
69 def parse_name(self):
70 if self.lookahead() == '?':
71 return self.consume() + self.consume()
72 else:
73 name = self.parse_id()
74 self.match('@')
75 return name
76
77 def parse_qualifications(self):
78 qualifications = []
79 while self.lookahead() != '@':
80 name = self.parse_id()
81 qualifications.append(name)
82 self.match('@')
83 return qualifications
84
85 def parse_id(self):
86 s = ''
87 while True:
88 c = self.lookahead()
89 if c.isalnum() or c in '_':
90 s += c
91 self.consume()
92 else:
93 break
94 return s
95
96
97 def demangle(name):
98 if name.startswith('_'):
99 name = name[1:]
100 idx = name.rfind('@')
101 if idx != -1 and name[idx+1:].isdigit():
102 name = name[:idx]
103 return name
104 if name.startswith('?'):
105 demangler = MsvcDemangler(name)
106 return demangler.parse()
107
108 return name
109 return name
110
111
112 class Profile:
113
114 def __init__(self):
115 self.symbols = []
116 self.symbol_cache = {}
117 self.base_addr = None
118 self.functions = {}
119 self.last_stamp = 0
120 self.stamp_base = 0
121
122 def unwrap_stamp(self, stamp):
123 if stamp < self.last_stamp:
124 self.stamp_base += 1 << 32
125 self.last_stamp = stamp
126 return self.stamp_base + stamp
127
128 def read_map(self, mapfile):
129 # See http://msdn.microsoft.com/en-us/library/k7xkk3e2.aspx
130 last_addr = 0
131 last_name = 0
132 for line in file(mapfile, "rt"):
133 fields = line.split()
134 try:
135 section_offset, name, addr, type, lib_object = fields
136 except ValueError:
137 continue
138 if type != 'f':
139 continue
140 section, offset = section_offset.split(':')
141 addr = int(offset, 16)
142 name = demangle(name)
143 if last_addr == addr:
144 # TODO: handle collapsed functions
145 #assert last_name == name
146 continue
147 self.symbols.append((addr, name))
148 last_addr = addr
149 last_name = name
150
151 # sort symbols
152 self.symbols.sort(key = lambda (addr, name): addr)
153
154 def lookup_addr(self, addr):
155 try:
156 return self.symbol_cache[addr]
157 except KeyError:
158 pass
159
160 tolerance = 4196
161 s, e = 0, len(self.symbols)
162 while s != e:
163 i = (s + e)//2
164 start_addr, name = self.symbols[i]
165 try:
166 end_addr, next_name = self.symbols[i + 1]
167 except IndexError:
168 end_addr = start_addr + tolerance
169 if addr < start_addr:
170 e = i
171 continue
172 if addr == end_addr:
173 return next_name
174 if addr > end_addr:
175 s = i
176 continue
177 return name
178 return "0x%08x" % addr
179
180 def lookup_symbol(self, name):
181 for symbol_addr, symbol_name in self.symbols:
182 if name == symbol_name:
183 return symbol_addr
184 return 0
185
186 def read_data(self, data):
187 # TODO: compute these automatically
188 caller_overhead = 672 - 2*144 # __debug_profile_reference2 - 2*__debug_profile_reference1
189 callee_overhead = 144 # __debug_profile_reference1
190 callee_overhead -= 48 # tolerance
191 caller_overhead = callee_overhead
192
193 fp = file(data, "rb")
194 entry_format = "II"
195 entry_size = struct.calcsize(entry_format)
196 stack = []
197 last_stamp = 0
198 delta = 0
199 while True:
200 entry = fp.read(entry_size)
201 if len(entry) < entry_size:
202 break
203 addr_exit, stamp = struct.unpack(entry_format, entry)
204 if addr_exit == 0 and stamp == 0:
205 break
206 addr = addr_exit & 0xfffffffe
207 exit = addr_exit & 0x00000001
208
209 if self.base_addr is None:
210 ref_addr = self.lookup_symbol('__debug_profile_reference2')
211 if ref_addr:
212 self.base_addr = (addr - ref_addr) & ~(options.align - 1)
213 else:
214 self.base_addr = 0
215 #print hex(self.base_addr)
216 rel_addr = addr - self.base_addr
217 #print hex(addr - self.base_addr)
218
219 name = self.lookup_addr(rel_addr)
220 stamp = self.unwrap_stamp(stamp)
221
222 delta += stamp - last_stamp
223
224 if not exit:
225 if options.verbose >= 2:
226 print "%10u >> 0x%08x" % (stamp, addr)
227 if options.verbose:
228 print "%10u >> %s" % (stamp, name)
229 delta -= caller_overhead
230 stack.append((name, stamp, delta))
231 delta = 0
232 else:
233 if options.verbose >= 2:
234 print "%10u << 0x%08x" % (stamp, addr)
235 if len(stack):
236 self_time = delta - callee_overhead
237 entry_name, entry_stamp, delta = stack.pop()
238 if entry_name != name:
239 if options.verbose:
240 print "%10u << %s" % (stamp, name)
241 #assert entry_name == name
242 break
243 total_time = stamp - entry_stamp
244 self.functions[entry_name] = self.functions.get(entry_name, 0) + self_time
245 if options.verbose:
246 print "%10u << %s %+u" % (stamp, name, self_time)
247 else:
248 delta = 0
249
250 last_stamp = stamp
251
252 def write_report(self):
253 total = sum(self.functions.values())
254 results = self.functions.items()
255 results.sort(key = lambda (name, time): -time)
256 for name, time in results:
257 perc = float(time)/float(total)*100.0
258 print "%6.03f %s" % (perc, name)
259
260
261 def main():
262 parser = optparse.OptionParser(
263 usage="\n\t%prog [options] [file] ...",
264 version="%%prog %s" % __version__)
265 parser.add_option(
266 '-a', '--align', metavar='NUMBER',
267 type="int", dest="align", default=16,
268 help="section alignment")
269 parser.add_option(
270 '-m', '--map', metavar='FILE',
271 type="string", dest="map",
272 help="map file")
273 parser.add_option(
274 '-b', '--base', metavar='FILE',
275 type="string", dest="base",
276 help="base addr")
277 parser.add_option(
278 '-v', '--verbose',
279 action="count",
280 dest="verbose", default=0,
281 help="verbose output")
282
283 global options
284 (options, args) = parser.parse_args(sys.argv[1:])
285
286 profile = Profile()
287 if options.base is not None:
288 profile.base_addr = int(options.base, 16)
289 if options.map is not None:
290 profile.read_map(options.map)
291 for arg in args:
292 profile.read_data(arg)
293 profile.write_report()
294
295
296 if __name__ == '__main__':
297 main()
298