]> git.cworth.org Git - apitrace/blob - xml2txt.py
Recognize more array arguments.
[apitrace] / xml2txt.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2008-2009 VMware, Inc.
5 # All Rights Reserved.
6 #
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:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
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
23 # THE SOFTWARE.
24 #
25 ##########################################################################/
26
27
28 import sys
29 import optparse
30 import xml.parsers.expat
31 import gzip
32
33 from model import *
34
35
36 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
37
38
39 class XmlToken:
40
41     def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
42         assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
43         self.type = type
44         self.name_or_data = name_or_data
45         self.attrs = attrs
46         self.line = line
47         self.column = column
48
49     def __str__(self):
50         if self.type == ELEMENT_START:
51             return '<' + self.name_or_data + ' ...>'
52         if self.type == ELEMENT_END:
53             return '</' + self.name_or_data + '>'
54         if self.type == CHARACTER_DATA:
55             return self.name_or_data
56         if self.type == EOF:
57             return 'end of file'
58         assert 0
59
60
61 class XmlTokenizer:
62     """Expat based XML tokenizer."""
63
64     def __init__(self, fp, skip_ws = True):
65         self.fp = fp
66         self.tokens = []
67         self.index = 0
68         self.final = False
69         self.skip_ws = skip_ws
70         
71         self.character_pos = 0, 0
72         self.character_data = ''
73         
74         self.parser = xml.parsers.expat.ParserCreate()
75         self.parser.StartElementHandler  = self.handle_element_start
76         self.parser.EndElementHandler    = self.handle_element_end
77         self.parser.CharacterDataHandler = self.handle_character_data
78     
79     def handle_element_start(self, name, attributes):
80         self.finish_character_data()
81         line, column = self.pos()
82         token = XmlToken(ELEMENT_START, name, attributes, line, column)
83         self.tokens.append(token)
84     
85     def handle_element_end(self, name):
86         self.finish_character_data()
87         line, column = self.pos()
88         token = XmlToken(ELEMENT_END, name, None, line, column)
89         self.tokens.append(token)
90
91     def handle_character_data(self, data):
92         if not self.character_data:
93             self.character_pos = self.pos()
94         self.character_data += data
95     
96     def finish_character_data(self):
97         if self.character_data:
98             if not self.skip_ws or not self.character_data.isspace(): 
99                 line, column = self.character_pos
100                 token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column)
101                 self.tokens.append(token)
102             self.character_data = ''
103     
104     def next(self):
105         size = 16*1024
106         while self.index >= len(self.tokens) and not self.final:
107             self.tokens = []
108             self.index = 0
109             data = self.fp.read(size)
110             self.final = len(data) < size
111             data = data.rstrip('\0')
112             try:
113                 self.parser.Parse(data, self.final)
114             except xml.parsers.expat.ExpatError, e:
115                 #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
116                 if e.code == 3:
117                     pass
118                 else:
119                     raise e
120         if self.index >= len(self.tokens):
121             line, column = self.pos()
122             token = XmlToken(EOF, None, None, line, column)
123         else:
124             token = self.tokens[self.index]
125             self.index += 1
126         return token
127
128     def pos(self):
129         return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
130
131
132 class TokenMismatch(Exception):
133
134     def __init__(self, expected, found):
135         self.expected = expected
136         self.found = found
137
138     def __str__(self):
139         return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
140
141
142
143 class XmlParser:
144     """Base XML document parser."""
145
146     def __init__(self, fp):
147         self.tokenizer = XmlTokenizer(fp)
148         self.consume()
149     
150     def consume(self):
151         self.token = self.tokenizer.next()
152
153     def match_element_start(self, name):
154         return self.token.type == ELEMENT_START and self.token.name_or_data == name
155     
156     def match_element_end(self, name):
157         return self.token.type == ELEMENT_END and self.token.name_or_data == name
158
159     def element_start(self, name):
160         while self.token.type == CHARACTER_DATA:
161             self.consume()
162         if self.token.type != ELEMENT_START:
163             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
164         if self.token.name_or_data != name:
165             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
166         attrs = self.token.attrs
167         self.consume()
168         return attrs
169     
170     def element_end(self, name):
171         while self.token.type == CHARACTER_DATA:
172             self.consume()
173         if self.token.type != ELEMENT_END:
174             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
175         if self.token.name_or_data != name:
176             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
177         self.consume()
178
179     def character_data(self, strip = True):
180         data = ''
181         while self.token.type == CHARACTER_DATA:
182             data += self.token.name_or_data
183             self.consume()
184         if strip:
185             data = data.strip()
186         return data
187
188
189 class GzipFile(gzip.GzipFile):
190
191     def _read_eof(self):
192         # Ignore incomplete files
193         try:
194             gzip.GzipFile._read_eof(self)
195         except IOError:
196             pass
197
198
199 class TraceParser(XmlParser):
200
201     def __init__(self, stream):
202         XmlParser.__init__(self, stream)
203         self.call_no = 0
204
205     def parse(self):
206         self.element_start('trace')
207         while self.token.type not in (ELEMENT_END, EOF):
208             self.parse_call()
209         if self.token.type != EOF:
210             self.element_end('trace')
211
212     def parse_call(self):
213         attrs = self.element_start('call')
214         name = attrs['name']
215         args = []
216         ret = None
217         properties = {}
218         while self.token.type == ELEMENT_START:
219             if self.token.name_or_data == 'arg':
220                 arg = self.parse_arg()
221                 args.append(arg)
222             elif self.token.name_or_data == 'ret':
223                 ret = self.parse_ret()
224             elif self.token.name_or_data in ('duration', 'starttsc', 'endtsc'):
225                 property = self.token.name_or_data
226                 properties[property] = self.parse_hex(self.token.name_or_data)
227             elif self.token.name_or_data == 'call':
228                 # ignore nested function calls
229                 self.parse_call()
230             else:
231                 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
232         self.element_end('call')
233         
234         self.call_no += 1
235
236         call = Call(self.call_no, name, args, ret, properties)
237
238         self.handle_call(call)
239
240     def parse_arg(self):
241         attrs = self.element_start('arg')
242         name = attrs['name']
243         value = self.parse_value()
244         self.element_end('arg')
245
246         return name, value
247
248     def parse_ret(self):
249         attrs = self.element_start('ret')
250         value = self.parse_value()
251         self.element_end('ret')
252
253         return value
254
255     def parse_hex(self, token_name):
256         attrs = self.element_start(token_name)
257         value = int(self.character_data(), 16)
258         self.element_end(token_name)
259         return value
260
261     def parse_value(self):
262         if self.token.type == ELEMENT_START:
263             if self.token.name_or_data == 'int':
264                 return self.parse_int()
265             if self.token.name_or_data == 'uint':
266                 return self.parse_uint()
267             if self.token.name_or_data == 'float':
268                 return self.parse_float()
269             if self.token.name_or_data == 'string':
270                 return self.parse_string()
271             if self.token.name_or_data == 'wstring':
272                 return self.parse_wstring()
273             if self.token.name_or_data == 'const':
274                 return self.parse_const()
275             if self.token.name_or_data == 'bitmask':
276                 return self.parse_bitmask()
277             if self.token.name_or_data == 'ref':
278                 return self.parse_ref()
279         raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
280
281     def parse_elems(self):
282         elems = [self.parse_elem()]
283         while self.token.type != ELEMENT_END:
284             elems.append(self.parse_elem())
285         return Struct("", elems)
286
287     def parse_elem(self):
288         attrs = self.element_start('elem')
289         value = self.parse_value()
290         self.element_end('elem')
291
292         try:
293             name = attrs['name']
294         except KeyError:
295             name = ""
296
297         return name, value
298
299     def parse_ref(self):
300         attrs = self.element_start('ref')
301         if self.token.type != ELEMENT_END:
302             value = self.parse_value()
303         else:
304             value = None
305         self.element_end('ref')
306
307         return Pointer(attrs['addr'], value)
308
309     def parse_bitmask(self):
310         self.element_start('bitmask')
311         elems = []
312         while self.token.type != ELEMENT_END:
313             elems.append(self.parse_value())
314         self.element_end('bitmask')
315         return Bitmask(elems)
316
317     def parse_int(self):
318         self.element_start('int')
319         value = self.character_data()
320         self.element_end('int')
321         return Literal(int(value))
322
323     def parse_uint(self):
324         self.element_start('uint')
325         value = self.character_data()
326         self.element_end('uint')
327         return Literal(int(value))
328
329     def parse_float(self):
330         self.element_start('float')
331         value = self.character_data()
332         self.element_end('float')
333         return Literal(float(value))
334
335     def parse_string(self):
336         self.element_start('string')
337         value = self.character_data()
338         self.element_end('string')
339         return Literal(value)
340
341     def parse_wstring(self):
342         self.element_start('wstring')
343         value = self.character_data()
344         self.element_end('wstring')
345         return Literal(value)
346
347     def parse_const(self):
348         self.element_start('const')
349         value = self.character_data()
350         self.element_end('const')
351         return NamedConstant(value)
352
353     def handle_call(self, call):
354         pass
355
356
357 class DumpTraceParser(TraceParser):
358
359     def __init__(self, stream, formatter):
360         XmlParser.__init__(self, stream)
361         self.formatter = formatter
362         self.pretty_printer = PrettyPrinter(self.formatter)
363         self.call_no = 0
364
365     def handle_call(self, call):
366         call.visit(self.pretty_printer)
367         self.formatter.newline()
368
369
370 class StatsTraceParser(TraceParser):
371
372     def __init__(self, stream, formatter):
373         TraceParser.__init__(self, stream, formatter)
374         self.stats = {}
375
376     def parse(self):
377         TraceParser.parse(self)
378
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))
382
383     def handle_call(self, name, args, ret, duration):
384         try:
385             nr_calls, total_duration = self.stats[name]
386         except KeyError:
387             nr_calls = 1
388             total_duration = duration
389         else:
390             nr_calls += 1
391             if duration is not None:
392                 total_duration += duration
393         self.stats[name] = nr_calls, total_duration
394
395
396 class Main:
397
398     def __init__(self):
399         pass
400
401     def main(self):
402         optparser = self.get_optparser()
403         (options, args) = optparser.parse_args(sys.argv[1:])
404     
405         if args:
406             for arg in args:
407                 if arg.endswith('.gz'):
408                     from gzip import GzipFile
409                     stream = GzipFile(arg, 'rb')
410                 elif arg.endswith('.bz2'):
411                     from bz2 import BZ2File
412                     stream = BZ2File(arg, 'rU')
413                 else:
414                     stream = open(arg, 'rt')
415                 self.process_arg(stream, options)
416         else:
417             self.process_arg(stream, options)
418
419     def get_optparser(self):
420         optparser = optparse.OptionParser(
421             usage="\n\t%prog [options] [traces] ...")
422         optparser.add_option(
423             '-s', '--stats',
424             action="store_true",
425             dest="stats", default=False,
426             help="generate statistics instead")
427         optparser.add_option(
428             '--color', '--colour',
429             type="choice", choices=('never', 'always', 'auto'), metavar='WHEN',
430             dest="color", default="always",
431             help="coloring: never, always, or auto [default: %default]")
432         return optparser
433
434     def process_arg(self, stream, options):
435         if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
436             formatter = format.DefaultFormatter(sys.stdout)
437         else:
438             formatter = format.Formatter(sys.stdout)
439         
440         if options.stats:
441             factory = StatsTraceParser
442         else:
443             factory = DumpTraceParser
444
445         parser = DumpTraceParser(stream, formatter)
446         parser.parse()
447
448
449 if __name__ == '__main__':
450     Main().main()
451