]> git.cworth.org Git - apitrace/blob - xml2txt.py
Make autogenerated dump functions static.
[apitrace] / xml2txt.py
1 #!/usr/bin/env python
2 #############################################################################
3 #
4 # Copyright 2008 Tungsten Graphics, Inc.
5 #
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.
10 #
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.
15 #
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/>.
18 #
19 #############################################################################
20
21
22 import sys
23 import optparse
24 import xml.parsers.expat
25 import gzip
26
27
28 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
29
30
31 class XmlToken:
32
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)
35         self.type = type
36         self.name_or_data = name_or_data
37         self.attrs = attrs
38         self.line = line
39         self.column = column
40
41     def __str__(self):
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
48         if self.type == EOF:
49             return 'end of file'
50         assert 0
51
52
53 class XmlTokenizer:
54     """Expat based XML tokenizer."""
55
56     def __init__(self, fp, skip_ws = True):
57         self.fp = fp
58         self.tokens = []
59         self.index = 0
60         self.final = False
61         self.skip_ws = skip_ws
62         
63         self.character_pos = 0, 0
64         self.character_data = ''
65         
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
70     
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)
76     
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)
82
83     def handle_character_data(self, data):
84         if not self.character_data:
85             self.character_pos = self.pos()
86         self.character_data += data
87     
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 = ''
95     
96     def next(self):
97         size = 16*1024
98         while self.index >= len(self.tokens) and not self.final:
99             self.tokens = []
100             self.index = 0
101             data = self.fp.read(size)
102             self.final = len(data) < size
103             data = data.rstrip('\0')
104             try:
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:
108                 if e.code == 3:
109                     pass
110                 else:
111                     raise e
112         if self.index >= len(self.tokens):
113             line, column = self.pos()
114             token = XmlToken(EOF, None, None, line, column)
115         else:
116             token = self.tokens[self.index]
117             self.index += 1
118         return token
119
120     def pos(self):
121         return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
122
123
124 class TokenMismatch(Exception):
125
126     def __init__(self, expected, found):
127         self.expected = expected
128         self.found = found
129
130     def __str__(self):
131         return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
132
133
134
135 class XmlParser:
136     """Base XML document parser."""
137
138     def __init__(self, fp):
139         self.tokenizer = XmlTokenizer(fp)
140         self.consume()
141     
142     def consume(self):
143         self.token = self.tokenizer.next()
144
145     def match_element_start(self, name):
146         return self.token.type == ELEMENT_START and self.token.name_or_data == name
147     
148     def match_element_end(self, name):
149         return self.token.type == ELEMENT_END and self.token.name_or_data == name
150
151     def element_start(self, name):
152         while self.token.type == CHARACTER_DATA:
153             self.consume()
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
159         self.consume()
160         return attrs
161     
162     def element_end(self, name):
163         while self.token.type == CHARACTER_DATA:
164             self.consume()
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)
169         self.consume()
170
171     def character_data(self, strip = True):
172         data = ''
173         while self.token.type == CHARACTER_DATA:
174             data += self.token.name_or_data
175             self.consume()
176         if strip:
177             data = data.strip()
178         return data
179
180
181 class GzipFile(gzip.GzipFile):
182
183     def _read_eof(self):
184         # Ignore incomplete files
185         try:
186             gzip.GzipFile._read_eof(self)
187         except IOError:
188             pass
189
190
191 class Formatter:
192     
193     def function(self, name):
194         return name
195         
196     def variable(self, name):
197         return name
198
199     def literal(self, value):
200         return str(value)
201     
202     def address(self, addr):
203         return addr
204
205
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.
210     '''
211
212     _csi = '\33['
213
214     _normal = '0m'
215     _bold = '1m'
216     _italic = '3m'
217     _red = '31m'
218     _green = '32m'
219     _blue = '34m'
220
221     def _escape(self, code, text):
222         return self._csi + code + text + self._csi + self._normal
223
224     def function(self, name):
225         text = Formatter.function(self, name)
226         return self._escape(self._bold, text)
227         
228     def variable(self, name):
229         text = Formatter.variable(self, name)
230         return self._escape(self._italic, text)
231
232     def literal(self, value):
233         text = Formatter.literal(self, value)
234         return self._escape(self._blue, text)
235     
236     def address(self, value):
237         text = Formatter.address(self, value)
238         return self._escape(self._green, text)
239
240
241 def DefaultFormatter():
242     if sys.platform in ('linux2', 'cygwin'):
243         return AnsiFormatter()
244     else:
245         return Formatter()
246
247
248 class TraceParser(XmlParser):
249
250     def __init__(self, stream, formatter):
251         XmlParser.__init__(self, stream)
252         self.formatter = formatter
253
254     def parse(self):
255         self.element_start('trace')
256         while self.token.type not in (ELEMENT_END, EOF):
257             self.parse_call()
258         if self.token.type != EOF:
259             self.element_end('trace')
260
261     def parse_call(self):
262         attrs = self.element_start('call')
263         name = attrs['name']
264         args = []
265         ret = None
266         duration = None
267         while self.token.type == ELEMENT_START:
268             if self.token.name_or_data == 'arg':
269                 arg = self.parse_arg()
270                 args.append(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
277                 self.parse_call()
278             else:
279                 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
280         self.element_end('call')
281         
282         self.handle_call(name, args, ret, duration)
283
284     def parse_arg(self):
285         attrs = self.element_start('arg')
286         name = attrs['name']
287         value = self.parse_value()
288         self.element_end('arg')
289
290         return name, value
291
292     def parse_ret(self):
293         attrs = self.element_start('ret')
294         value = self.parse_value()
295         self.element_end('ret')
296
297         return value
298
299     def parse_duration(self):
300         attrs = self.element_start('duration')
301         value = int(self.character_data())
302         self.element_end('duration')
303         return value
304
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)
314
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) + '}'
320
321     def parse_elem(self):
322         attrs = self.element_start('elem')
323         value = self.parse_value()
324         self.element_end('elem')
325
326         try:
327             name = attrs['name']
328         except KeyError:
329             pass
330         else:
331             value = name + ' = ' + value
332
333         return value
334
335     def parse_ref(self):
336         attrs = self.element_start('ref')
337         if self.token.type != ELEMENT_END:
338             value = '&' + self.parse_value()
339         else:
340             value = self.formatter.address(attrs['addr'])
341         self.element_end('ref')
342
343         return value
344
345     def handle_call(self, name, args, ret, duration):
346         s = ''
347
348         #if duration is not None:
349         #    s += '%8u ' % (duration)
350
351         s += self.formatter.function(name)
352         s += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
353         if ret is not None:
354             s += ' = ' + ret
355         s += '\n'
356         
357         try:
358             sys.stdout.write(s)
359         except IOError: 
360             # catch broken pipe
361             sys.exit(0)
362
363
364 class StatsTraceParser(TraceParser):
365
366     def __init__(self, stream, formatter):
367         TraceParser.__init__(self, stream, formatter)
368         self.stats = {}
369
370     def parse(self):
371         TraceParser.parse(self)
372
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))
376
377     def handle_call(self, name, args, ret, duration):
378         try:
379             nr_calls, total_duration = self.stats[name]
380         except KeyError:
381             nr_calls = 1
382             total_duration = duration
383         else:
384             nr_calls += 1
385             if duration is not None:
386                 total_duration += duration
387         self.stats[name] = nr_calls, total_duration
388
389
390 def main():
391     parser = optparse.OptionParser(
392         usage="\n\t%prog [options] [file] ...")
393     parser.add_option(
394         '-s', '--stats',
395         action="store_true",
396         dest="stats", default=False,
397         help="generate statistics instead")
398     parser.add_option(
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:])
404
405     if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
406         formatter = DefaultFormatter()
407     else:
408         formatter = Formatter()
409     
410     if options.stats:
411         factory = StatsTraceParser
412     else:
413         factory = TraceParser
414
415     if args:
416         for arg in args:
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')
422             else:
423                 stream = open(arg, 'rt')
424             parser = factory(stream, formatter)
425             parser.parse()
426     else:
427             parser = factory(sys.stdin, formatter)
428             parser.parse()
429
430
431 if __name__ == '__main__':
432     main()