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 def DefaultFormatter():
242 if sys.platform in ('linux2', 'cygwin'):
243 return AnsiFormatter()
248 class TraceParser(XmlParser):
250 def __init__(self, stream, formatter):
251 XmlParser.__init__(self, stream)
252 self.formatter = formatter
255 self.element_start('trace')
256 while self.token.type not in (ELEMENT_END, EOF):
258 if self.token.type != EOF:
259 self.element_end('trace')
261 def parse_call(self):
262 attrs = self.element_start('call')
267 while self.token.type == ELEMENT_START:
268 if self.token.name_or_data == 'arg':
269 arg = self.parse_arg()
271 elif self.token.name_or_data == 'ret':
272 ret = self.parse_ret()
273 elif self.token.name_or_data == 'duration':
274 duration = self.parse_duration()
275 elif self.token.name_or_data == 'call':
276 # ignore nested function calls
279 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
280 self.element_end('call')
282 self.handle_call(name, args, ret, duration)
285 attrs = self.element_start('arg')
287 value = self.parse_value()
288 self.element_end('arg')
293 attrs = self.element_start('ret')
294 value = self.parse_value()
295 self.element_end('ret')
299 def parse_duration(self):
300 attrs = self.element_start('duration')
301 value = int(self.character_data())
302 self.element_end('duration')
305 def parse_value(self):
306 if self.token.type == CHARACTER_DATA:
307 return self.formatter.literal(self.character_data())
308 if self.token.type == ELEMENT_START:
309 if self.token.name_or_data == 'elem':
310 return self.parse_elems()
311 if self.token.name_or_data == 'ref':
312 return self.parse_ref()
313 raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
315 def parse_elems(self):
316 elems = [self.parse_elem()]
317 while self.token.type != ELEMENT_END:
318 elems.append(self.parse_elem())
319 return '{' + ', '.join(elems) + '}'
321 def parse_elem(self):
322 attrs = self.element_start('elem')
323 value = self.parse_value()
324 self.element_end('elem')
331 value = name + ' = ' + value
336 attrs = self.element_start('ref')
337 if self.token.type != ELEMENT_END:
338 value = '&' + self.parse_value()
340 value = self.formatter.address(attrs['addr'])
341 self.element_end('ref')
345 def handle_call(self, name, args, ret, duration):
348 #if duration is not None:
349 # s += '%8u ' % (duration)
351 s += self.formatter.function(name)
352 s += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
364 class StatsTraceParser(TraceParser):
366 def __init__(self, stream, formatter):
367 TraceParser.__init__(self, stream, formatter)
371 TraceParser.parse(self)
373 sys.stdout.write('%s\t%s\t%s\n' % ("name", "calls", "duration"))
374 for name, (calls, duration) in self.stats.iteritems():
375 sys.stdout.write('%s\t%u\t%f\n' % (name, calls, duration/1000000.0))
377 def handle_call(self, name, args, ret, duration):
379 nr_calls, total_duration = self.stats[name]
382 total_duration = duration
385 if duration is not None:
386 total_duration += duration
387 self.stats[name] = nr_calls, total_duration
391 parser = optparse.OptionParser(
392 usage="\n\t%prog [options] [file] ...")
396 dest="stats", default=False,
397 help="generate statistics instead")
399 '--color', '--colour',
400 type="choice", choices=('never', 'always', 'auto'), metavar='WHEN',
401 dest="color", default="always",
402 help="coloring: never, always, or auto [default: %default]")
403 (options, args) = parser.parse_args(sys.argv[1:])
405 if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
406 formatter = DefaultFormatter()
408 formatter = Formatter()
411 factory = StatsTraceParser
413 factory = TraceParser
417 if arg.endswith('.gz'):
418 stream = GzipFile(arg, 'rb')
419 elif arg.endswith('.bz2'):
420 from bz2 import BZ2File
421 stream = BZ2File(arg, 'rt')
423 stream = open(arg, 'rt')
424 parser = factory(stream, formatter)
427 parser = factory(sys.stdin, formatter)
431 if __name__ == '__main__':