2 ##########################################################################
4 # Copyright 2008-2009 VMware, Inc.
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 ##########################################################################/
30 import xml.parsers.expat
34 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
39 def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
40 assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
42 self.name_or_data = name_or_data
48 if self.type == ELEMENT_START:
49 return '<' + self.name_or_data + ' ...>'
50 if self.type == ELEMENT_END:
51 return '</' + self.name_or_data + '>'
52 if self.type == CHARACTER_DATA:
53 return self.name_or_data
60 """Expat based XML tokenizer."""
62 def __init__(self, fp, skip_ws = True):
67 self.skip_ws = skip_ws
69 self.character_pos = 0, 0
70 self.character_data = ''
72 self.parser = xml.parsers.expat.ParserCreate()
73 self.parser.StartElementHandler = self.handle_element_start
74 self.parser.EndElementHandler = self.handle_element_end
75 self.parser.CharacterDataHandler = self.handle_character_data
77 def handle_element_start(self, name, attributes):
78 self.finish_character_data()
79 line, column = self.pos()
80 token = XmlToken(ELEMENT_START, name, attributes, line, column)
81 self.tokens.append(token)
83 def handle_element_end(self, name):
84 self.finish_character_data()
85 line, column = self.pos()
86 token = XmlToken(ELEMENT_END, name, None, line, column)
87 self.tokens.append(token)
89 def handle_character_data(self, data):
90 if not self.character_data:
91 self.character_pos = self.pos()
92 self.character_data += data
94 def finish_character_data(self):
95 if self.character_data:
96 if not self.skip_ws or not self.character_data.isspace():
97 line, column = self.character_pos
98 token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column)
99 self.tokens.append(token)
100 self.character_data = ''
104 while self.index >= len(self.tokens) and not self.final:
107 data = self.fp.read(size)
108 self.final = len(data) < size
109 data = data.rstrip('\0')
111 self.parser.Parse(data, self.final)
112 except xml.parsers.expat.ExpatError, e:
113 #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
118 if self.index >= len(self.tokens):
119 line, column = self.pos()
120 token = XmlToken(EOF, None, None, line, column)
122 token = self.tokens[self.index]
127 return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
130 class TokenMismatch(Exception):
132 def __init__(self, expected, found):
133 self.expected = expected
137 return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
142 """Base XML document parser."""
144 def __init__(self, fp):
145 self.tokenizer = XmlTokenizer(fp)
149 self.token = self.tokenizer.next()
151 def match_element_start(self, name):
152 return self.token.type == ELEMENT_START and self.token.name_or_data == name
154 def match_element_end(self, name):
155 return self.token.type == ELEMENT_END and self.token.name_or_data == name
157 def element_start(self, name):
158 while self.token.type == CHARACTER_DATA:
160 if self.token.type != ELEMENT_START:
161 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
162 if self.token.name_or_data != name:
163 raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
164 attrs = self.token.attrs
168 def element_end(self, name):
169 while self.token.type == CHARACTER_DATA:
171 if self.token.type != ELEMENT_END:
172 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
173 if self.token.name_or_data != name:
174 raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
177 def character_data(self, strip = True):
179 while self.token.type == CHARACTER_DATA:
180 data += self.token.name_or_data
187 class GzipFile(gzip.GzipFile):
190 # Ignore incomplete files
192 gzip.GzipFile._read_eof(self)
199 def function(self, name):
202 def variable(self, name):
205 def literal(self, value):
208 def address(self, addr):
212 class AnsiFormatter(Formatter):
213 '''Formatter for plain-text files which outputs ANSI escape codes. See
214 http://en.wikipedia.org/wiki/ANSI_escape_code for more information
215 concerning ANSI escape codes.
227 def _escape(self, code, text):
228 return self._csi + code + text + self._csi + self._normal
230 def function(self, name):
231 text = Formatter.function(self, name)
232 return self._escape(self._bold, text)
234 def variable(self, name):
235 text = Formatter.variable(self, name)
236 return self._escape(self._italic, text)
238 def literal(self, value):
239 text = Formatter.literal(self, value)
240 return self._escape(self._blue, text)
242 def address(self, value):
243 text = Formatter.address(self, value)
244 return self._escape(self._green, text)
247 def DefaultFormatter():
248 if sys.platform in ('linux2', 'cygwin'):
249 return AnsiFormatter()
254 class TraceParser(XmlParser):
256 def __init__(self, stream, formatter):
257 XmlParser.__init__(self, stream)
258 self.formatter = formatter
261 self.element_start('trace')
262 while self.token.type not in (ELEMENT_END, EOF):
264 if self.token.type != EOF:
265 self.element_end('trace')
267 def parse_call(self):
268 attrs = self.element_start('call')
273 while self.token.type == ELEMENT_START:
274 if self.token.name_or_data == 'arg':
275 arg = self.parse_arg()
277 elif self.token.name_or_data == 'ret':
278 ret = self.parse_ret()
279 elif self.token.name_or_data == 'duration':
280 duration = self.parse_duration()
281 elif self.token.name_or_data == 'call':
282 # ignore nested function calls
285 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
286 self.element_end('call')
288 self.handle_call(name, args, ret, duration)
291 attrs = self.element_start('arg')
293 value = self.parse_value()
294 self.element_end('arg')
299 attrs = self.element_start('ret')
300 value = self.parse_value()
301 self.element_end('ret')
305 def parse_duration(self):
306 attrs = self.element_start('duration')
307 value = int(self.character_data())
308 self.element_end('duration')
311 def parse_value(self):
312 if self.token.type == CHARACTER_DATA:
313 return self.formatter.literal(self.character_data())
314 if self.token.type == ELEMENT_START:
315 if self.token.name_or_data == 'elem':
316 return self.parse_elems()
317 if self.token.name_or_data == 'ref':
318 return self.parse_ref()
319 raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
321 def parse_elems(self):
322 elems = [self.parse_elem()]
323 while self.token.type != ELEMENT_END:
324 elems.append(self.parse_elem())
325 return '{' + ', '.join(elems) + '}'
327 def parse_elem(self):
328 attrs = self.element_start('elem')
329 value = self.parse_value()
330 self.element_end('elem')
337 value = name + ' = ' + value
342 attrs = self.element_start('ref')
343 if self.token.type != ELEMENT_END:
344 value = '&' + self.parse_value()
346 value = self.formatter.address(attrs['addr'])
347 self.element_end('ref')
351 def handle_call(self, name, args, ret, duration):
354 #if duration is not None:
355 # s += '%8u ' % (duration)
357 s += self.formatter.function(name)
358 s += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
370 class StatsTraceParser(TraceParser):
372 def __init__(self, stream, formatter):
373 TraceParser.__init__(self, stream, formatter)
377 TraceParser.parse(self)
379 sys.stdout.write('%s\t%s\t%s\n' % ("name", "calls", "duration"))
380 for name, (calls, duration) in self.stats.iteritems():
381 sys.stdout.write('%s\t%u\t%f\n' % (name, calls, duration/1000000.0))
383 def handle_call(self, name, args, ret, duration):
385 nr_calls, total_duration = self.stats[name]
388 total_duration = duration
391 if duration is not None:
392 total_duration += duration
393 self.stats[name] = nr_calls, total_duration
397 parser = optparse.OptionParser(
398 usage="\n\t%prog [options] [file] ...")
402 dest="stats", default=False,
403 help="generate statistics instead")
405 '--color', '--colour',
406 type="choice", choices=('never', 'always', 'auto'), metavar='WHEN',
407 dest="color", default="always",
408 help="coloring: never, always, or auto [default: %default]")
409 (options, args) = parser.parse_args(sys.argv[1:])
411 if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
412 formatter = DefaultFormatter()
414 formatter = Formatter()
417 factory = StatsTraceParser
419 factory = TraceParser
423 if arg.endswith('.gz'):
424 stream = GzipFile(arg, 'rb')
425 elif arg.endswith('.bz2'):
426 from bz2 import BZ2File
427 stream = BZ2File(arg, 'rt')
429 stream = open(arg, 'rt')
430 parser = factory(stream, formatter)
433 parser = factory(sys.stdin, formatter)
437 if __name__ == '__main__':