2 #############################################################################
4 # Copyright 2008 Tungsten Graphics, Inc.
6 # This program is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as published
8 # by the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #############################################################################
24 import xml.parsers.expat
28 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
33 def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
34 assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
36 self.name_or_data = name_or_data
42 if self.type == ELEMENT_START:
43 return '<' + self.name_or_data + ' ...>'
44 if self.type == ELEMENT_END:
45 return '</' + self.name_or_data + '>'
46 if self.type == CHARACTER_DATA:
47 return self.name_or_data
54 """Expat based XML tokenizer."""
56 def __init__(self, fp, skip_ws = True):
61 self.skip_ws = skip_ws
63 self.character_pos = 0, 0
64 self.character_data = ''
66 self.parser = xml.parsers.expat.ParserCreate()
67 self.parser.StartElementHandler = self.handle_element_start
68 self.parser.EndElementHandler = self.handle_element_end
69 self.parser.CharacterDataHandler = self.handle_character_data
71 def handle_element_start(self, name, attributes):
72 self.finish_character_data()
73 line, column = self.pos()
74 token = XmlToken(ELEMENT_START, name, attributes, line, column)
75 self.tokens.append(token)
77 def handle_element_end(self, name):
78 self.finish_character_data()
79 line, column = self.pos()
80 token = XmlToken(ELEMENT_END, name, None, line, column)
81 self.tokens.append(token)
83 def handle_character_data(self, data):
84 if not self.character_data:
85 self.character_pos = self.pos()
86 self.character_data += data
88 def finish_character_data(self):
89 if self.character_data:
90 if not self.skip_ws or not self.character_data.isspace():
91 line, column = self.character_pos
92 token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column)
93 self.tokens.append(token)
94 self.character_data = ''
98 while self.index >= len(self.tokens) and not self.final:
101 data = self.fp.read(size)
102 self.final = len(data) < size
103 data = data.rstrip('\0')
105 self.parser.Parse(data, self.final)
106 except xml.parsers.expat.ExpatError, e:
107 #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
112 if self.index >= len(self.tokens):
113 line, column = self.pos()
114 token = XmlToken(EOF, None, None, line, column)
116 token = self.tokens[self.index]
121 return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
124 class TokenMismatch(Exception):
126 def __init__(self, expected, found):
127 self.expected = expected
131 return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
136 """Base XML document parser."""
138 def __init__(self, fp):
139 self.tokenizer = XmlTokenizer(fp)
143 self.token = self.tokenizer.next()
145 def match_element_start(self, name):
146 return self.token.type == ELEMENT_START and self.token.name_or_data == name
148 def match_element_end(self, name):
149 return self.token.type == ELEMENT_END and self.token.name_or_data == name
151 def element_start(self, name):
152 while self.token.type == CHARACTER_DATA:
154 if self.token.type != ELEMENT_START:
155 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
156 if self.token.name_or_data != name:
157 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
158 attrs = self.token.attrs
162 def element_end(self, name):
163 while self.token.type == CHARACTER_DATA:
165 if self.token.type != ELEMENT_END:
166 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
167 if self.token.name_or_data != name:
168 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
171 def character_data(self, strip = True):
173 while self.token.type == CHARACTER_DATA:
174 data += self.token.name_or_data
181 class GzipFile(gzip.GzipFile):
184 # Ignore incomplete files
186 gzip.GzipFile._read_eof(self)
193 def function(self, name):
196 def variable(self, name):
199 def literal(self, value):
202 def address(self, addr):
206 class AnsiFormatter(Formatter):
207 '''Formatter for plain-text files which outputs ANSI escape codes. See
208 http://en.wikipedia.org/wiki/ANSI_escape_code for more information
209 concerning ANSI escape codes.
221 def _escape(self, code, text):
222 return self._csi + code + text + self._csi + self._normal
224 def function(self, name):
225 text = Formatter.function(self, name)
226 return self._escape(self._bold, text)
228 def variable(self, name):
229 text = Formatter.variable(self, name)
230 return self._escape(self._italic, text)
232 def literal(self, value):
233 text = Formatter.literal(self, value)
234 return self._escape(self._blue, text)
236 def address(self, value):
237 text = Formatter.address(self, value)
238 return self._escape(self._green, text)
241 class TraceParser(XmlParser):
243 def __init__(self, stream, formatter):
244 XmlParser.__init__(self, stream)
245 self.formatter = formatter
248 self.element_start('trace')
249 while self.token.type not in (ELEMENT_END, EOF):
251 if self.token.type != EOF:
252 self.element_end('trace')
254 def parse_call(self):
255 attrs = self.element_start('call')
260 while self.token.type == ELEMENT_START:
261 if self.token.name_or_data == 'arg':
262 arg = self.parse_arg()
264 elif self.token.name_or_data == 'ret':
265 ret = self.parse_ret()
266 elif self.token.name_or_data == 'duration':
267 duration = self.parse_duration()
268 elif self.token.name_or_data == 'call':
269 # ignore nested function calls
272 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
273 self.element_end('call')
275 self.handle_call(name, args, ret, duration)
278 attrs = self.element_start('arg')
280 value = self.parse_value()
281 self.element_end('arg')
286 attrs = self.element_start('ret')
287 value = self.parse_value()
288 self.element_end('ret')
292 def parse_duration(self):
293 attrs = self.element_start('duration')
294 value = int(self.character_data())
295 self.element_end('duration')
298 def parse_value(self):
299 if self.token.type == CHARACTER_DATA:
300 return self.formatter.literal(self.character_data())
301 if self.token.type == ELEMENT_START:
302 if self.token.name_or_data == 'elem':
303 return self.parse_elems()
304 if self.token.name_or_data == 'ref':
305 return self.parse_ref()
306 raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
308 def parse_elems(self):
309 elems = [self.parse_elem()]
310 while self.token.type != ELEMENT_END:
311 elems.append(self.parse_elem())
312 return '{' + ', '.join([name + ' = ' + value for name, value in elems]) + '}'
314 def parse_elem(self):
315 attrs = self.element_start('elem')
317 value = self.parse_value()
318 self.element_end('elem')
323 attrs = self.element_start('ref')
324 if self.token.type != ELEMENT_END:
325 value = '&' + self.parse_value()
327 value = self.formatter.address(attrs['addr'])
328 self.element_end('ref')
332 def handle_call(self, name, args, ret, duration):
335 if duration is not None:
336 s += '%8u ' % (duration)
338 s += self.formatter.function(name)
339 s += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
351 class StatsTraceParser(TraceParser):
353 def __init__(self, stream, formatter):
354 TraceParser.__init__(self, stream, formatter)
358 TraceParser.parse(self)
360 sys.stdout.write('%s\t%s\t%s\n' % ("name", "calls", "duration"))
361 for name, (calls, duration) in self.stats.iteritems():
362 sys.stdout.write('%s\t%u\t%f\n' % (name, calls, duration/1000000.0))
364 def handle_call(self, name, args, ret, duration):
366 nr_calls, total_duration = self.stats[name]
369 total_duration = duration
372 if duration is not None:
373 total_duration += duration
374 self.stats[name] = nr_calls, total_duration
378 parser = optparse.OptionParser(
379 usage="\n\t%prog [options] [file] ...")
383 dest="stats", default=False,
384 help="generate statistics instead")
386 '--color', '--colour',
387 type="choice", choices=('never', 'always', 'auto'), metavar='WHEN',
388 dest="color", default="always",
389 help="coloring: never, always, or auto [default: %default]")
390 (options, args) = parser.parse_args(sys.argv[1:])
392 if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
393 formatter = AnsiFormatter()
395 formatter = Formatter()
398 factory = StatsTraceParser
400 factory = TraceParser
404 if arg.endswith('.gz'):
405 stream = GzipFile(arg, 'rt')
406 elif arg.endswith('.bz2'):
407 from bz2 import BZ2File
408 stream = BZ2File(arg, 'rt')
410 stream = open(arg, 'rt')
411 parser = factory(stream, formatter)
414 parser = factory(sys.stdin, formatter)
418 if __name__ == '__main__':