• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2##########################################################################
3#
4# Copyright 2008 VMware, Inc.
5# All Rights Reserved.
6#
7# Permission is hereby granted, free of charge, to any person obtaining a
8# copy of this software and associated documentation files (the
9# "Software"), to deal in the Software without restriction, including
10# without limitation the rights to use, copy, modify, merge, publish,
11# distribute, sub license, and/or sell copies of the Software, and to
12# permit persons to whom the Software is furnished to do so, subject to
13# the following conditions:
14#
15# The above copyright notice and this permission notice (including the
16# next paragraph) shall be included in all copies or substantial portions
17# of the Software.
18#
19# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
23# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26#
27##########################################################################
28
29
30import io
31import sys
32import xml.parsers.expat as xpat
33import argparse
34
35import format
36from model import *
37
38
39trace_ignore_calls = set((
40    ("pipe_screen", "is_format_supported"),
41    ("pipe_screen", "get_name"),
42    ("pipe_screen", "get_vendor"),
43    ("pipe_screen", "get_param"),
44    ("pipe_screen", "get_paramf"),
45    ("pipe_screen", "get_shader_param"),
46    ("pipe_screen", "get_compute_param"),
47    ("pipe_screen", "get_disk_shader_cache"),
48))
49
50
51def trace_call_ignore(call):
52    return (call.klass, call.method) in trace_ignore_calls
53
54
55ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
56
57
58class XmlToken:
59
60    def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
61        assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
62        self.type = type
63        self.name_or_data = name_or_data
64        self.attrs = attrs
65        self.line = line
66        self.column = column
67
68    def __str__(self):
69        if self.type == ELEMENT_START:
70            return '<' + self.name_or_data + ' ...>'
71        if self.type == ELEMENT_END:
72            return '</' + self.name_or_data + '>'
73        if self.type == CHARACTER_DATA:
74            return self.name_or_data
75        if self.type == EOF:
76            return 'end of file'
77        assert 0
78
79
80class XmlTokenizer:
81    """Expat based XML tokenizer."""
82
83    def __init__(self, fp, skip_ws = True):
84        self.fp = fp
85        self.tokens = []
86        self.index = 0
87        self.final = False
88        self.skip_ws = skip_ws
89
90        self.character_pos = 0, 0
91        self.character_data = []
92
93        self.parser = xpat.ParserCreate()
94        self.parser.StartElementHandler  = self.handle_element_start
95        self.parser.EndElementHandler    = self.handle_element_end
96        self.parser.CharacterDataHandler = self.handle_character_data
97
98    def handle_element_start(self, name, attributes):
99        self.finish_character_data()
100        line, column = self.pos()
101        token = XmlToken(ELEMENT_START, name, attributes, line, column)
102        self.tokens.append(token)
103
104    def handle_element_end(self, name):
105        self.finish_character_data()
106        line, column = self.pos()
107        token = XmlToken(ELEMENT_END, name, None, line, column)
108        self.tokens.append(token)
109
110    def handle_character_data(self, data):
111        if not self.character_data:
112            self.character_pos = self.pos()
113        self.character_data.append(data)
114
115    def finish_character_data(self):
116        if self.character_data:
117            character_data = ''.join(self.character_data)
118            if not self.skip_ws or not character_data.isspace():
119                line, column = self.character_pos
120                token = XmlToken(CHARACTER_DATA, character_data, None, line, column)
121                self.tokens.append(token)
122            self.character_data = []
123
124    def next(self):
125        size = 16*1024
126        while self.index >= len(self.tokens) and not self.final:
127            self.tokens = []
128            self.index = 0
129            data = self.fp.read(size)
130            self.final = len(data) < size
131            data = data.rstrip('\0')
132            try:
133                self.parser.Parse(data, self.final)
134            except xpat.ExpatError as e:
135                #if e.code == xpat.errors.XML_ERROR_NO_ELEMENTS:
136                if e.code == 3:
137                    pass
138                else:
139                    raise e
140        if self.index >= len(self.tokens):
141            line, column = self.pos()
142            token = XmlToken(EOF, None, None, line, column)
143        else:
144            token = self.tokens[self.index]
145            self.index += 1
146        return token
147
148    def pos(self):
149        return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
150
151
152class TokenMismatch(Exception):
153
154    def __init__(self, expected, found):
155        self.expected = expected
156        self.found = found
157
158    def __str__(self):
159        return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
160
161
162
163class XmlParser:
164    """Base XML document parser."""
165
166    def __init__(self, fp):
167        self.tokenizer = XmlTokenizer(fp)
168        self.consume()
169
170    def consume(self):
171        self.token = self.tokenizer.next()
172
173    def match_element_start(self, name):
174        return self.token.type == ELEMENT_START and self.token.name_or_data == name
175
176    def match_element_end(self, name):
177        return self.token.type == ELEMENT_END and self.token.name_or_data == name
178
179    def element_start(self, name):
180        while self.token.type == CHARACTER_DATA:
181            self.consume()
182        if self.token.type != ELEMENT_START:
183            raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
184        if self.token.name_or_data != name:
185            raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
186        attrs = self.token.attrs
187        self.consume()
188        return attrs
189
190    def element_end(self, name):
191        while self.token.type == CHARACTER_DATA:
192            self.consume()
193        if self.token.type != ELEMENT_END:
194            raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
195        if self.token.name_or_data != name:
196            raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
197        self.consume()
198
199    def character_data(self, strip = True):
200        data = ''
201        while self.token.type == CHARACTER_DATA:
202            data += self.token.name_or_data
203            self.consume()
204        if strip:
205            data = data.strip()
206        return data
207
208
209class TraceParser(XmlParser):
210
211    def __init__(self, fp, options, state):
212        XmlParser.__init__(self, fp)
213        self.last_call_no = 0
214        self.state = state
215        self.options = options
216
217    def parse(self):
218        self.element_start('trace')
219        while self.token.type not in (ELEMENT_END, EOF):
220            call = self.parse_call()
221            call.is_junk = trace_call_ignore(call)
222            self.handle_call(call)
223        if self.token.type != EOF:
224            self.element_end('trace')
225
226    def parse_call(self):
227        attrs = self.element_start('call')
228        try:
229            no = int(attrs['no'])
230        except KeyError as e:
231            self.last_call_no += 1
232            no = self.last_call_no
233        else:
234            self.last_call_no = no
235        klass = attrs['class']
236        method = attrs['method']
237        args = []
238        ret = None
239        time = None
240        while self.token.type == ELEMENT_START:
241            if self.token.name_or_data == 'arg':
242                arg = self.parse_arg()
243                args.append(arg)
244            elif self.token.name_or_data == 'ret':
245                ret = self.parse_ret()
246            elif self.token.name_or_data == 'call':
247                # ignore nested function calls
248                self.parse_call()
249            elif self.token.name_or_data == 'time':
250                time = self.parse_time()
251            else:
252                raise TokenMismatch("<arg ...> or <ret ...>", self.token)
253        self.element_end('call')
254
255        return Call(no, klass, method, args, ret, time)
256
257    def parse_arg(self):
258        attrs = self.element_start('arg')
259        name = attrs['name']
260        value = self.parse_value(name)
261        self.element_end('arg')
262
263        return name, value
264
265    def parse_ret(self):
266        attrs = self.element_start('ret')
267        value = self.parse_value('ret')
268        self.element_end('ret')
269
270        return value
271
272    def parse_time(self):
273        attrs = self.element_start('time')
274        time = self.parse_value('time');
275        self.element_end('time')
276        return time
277
278    def parse_value(self, name):
279        expected_tokens = ('null', 'bool', 'int', 'uint', 'float', 'string', 'enum', 'array', 'struct', 'ptr', 'bytes')
280        if self.token.type == ELEMENT_START:
281            if self.token.name_or_data in expected_tokens:
282                method = getattr(self, 'parse_' +  self.token.name_or_data)
283                return method(name)
284        raise TokenMismatch(" or " .join(expected_tokens), self.token)
285
286    def parse_null(self, pname):
287        self.element_start('null')
288        self.element_end('null')
289        return Literal(None)
290
291    def parse_bool(self, pname):
292        self.element_start('bool')
293        value = int(self.character_data())
294        self.element_end('bool')
295        return Literal(value)
296
297    def parse_int(self, pname):
298        self.element_start('int')
299        value = int(self.character_data())
300        self.element_end('int')
301        return Literal(value)
302
303    def parse_uint(self, pname):
304        self.element_start('uint')
305        value = int(self.character_data())
306        self.element_end('uint')
307        return Literal(value)
308
309    def parse_float(self, pname):
310        self.element_start('float')
311        value = float(self.character_data())
312        self.element_end('float')
313        return Literal(value)
314
315    def parse_enum(self, pname):
316        self.element_start('enum')
317        name = self.character_data()
318        self.element_end('enum')
319        return NamedConstant(name)
320
321    def parse_string(self, pname):
322        self.element_start('string')
323        value = self.character_data()
324        self.element_end('string')
325        return Literal(value)
326
327    def parse_bytes(self, pname):
328        self.element_start('bytes')
329        value = self.character_data()
330        self.element_end('bytes')
331        return Blob(value)
332
333    def parse_array(self, pname):
334        self.element_start('array')
335        elems = []
336        while self.token.type != ELEMENT_END:
337            elems.append(self.parse_elem('array'))
338        self.element_end('array')
339        return Array(elems)
340
341    def parse_elem(self, pname):
342        self.element_start('elem')
343        value = self.parse_value('elem')
344        self.element_end('elem')
345        return value
346
347    def parse_struct(self, pname):
348        attrs = self.element_start('struct')
349        name = attrs['name']
350        members = []
351        while self.token.type != ELEMENT_END:
352            members.append(self.parse_member(name))
353        self.element_end('struct')
354        return Struct(name, members)
355
356    def parse_member(self, pname):
357        attrs = self.element_start('member')
358        name = attrs['name']
359        value = self.parse_value(name)
360        self.element_end('member')
361
362        return name, value
363
364    def parse_ptr(self, pname):
365        self.element_start('ptr')
366        address = self.character_data()
367        self.element_end('ptr')
368
369        return Pointer(self.state, address, pname)
370
371    def handle_call(self, call):
372        pass
373
374
375class SimpleTraceDumper(TraceParser):
376
377    def __init__(self, fp, options, formatter, state):
378        TraceParser.__init__(self, fp, options, state)
379        self.options = options
380        self.formatter = formatter
381        self.pretty_printer = PrettyPrinter(self.formatter, options)
382
383    def handle_call(self, call):
384        if self.options.ignore_junk and call.is_junk:
385            return
386
387        call.visit(self.pretty_printer)
388
389
390class TraceDumper(SimpleTraceDumper):
391
392    def __init__(self, fp, options, formatter, state):
393        SimpleTraceDumper.__init__(self, fp, options, formatter, state)
394        self.call_stack = []
395
396    def handle_call(self, call):
397        if self.options.ignore_junk and call.is_junk:
398            return
399
400        if self.options.named_ptrs:
401            self.call_stack.append(call)
402        else:
403            call.visit(self.pretty_printer)
404
405
406class ParseOptions(ModelOptions):
407
408    def __init__(self, args=None):
409        # Initialize options local to this module
410        self.plain = False
411        self.ignore_junk = False
412
413        ModelOptions.__init__(self, args)
414
415
416class Main:
417    '''Common main class for all retrace command line utilities.'''
418
419    def __init__(self):
420        pass
421
422    def main(self):
423        optparser = self.get_optparser()
424        args = optparser.parse_args()
425        options = self.make_options(args)
426
427        for fname in args.filename:
428            try:
429                if fname.endswith('.gz'):
430                    from gzip import GzipFile
431                    stream = io.TextIOWrapper(GzipFile(fname, 'rb'))
432                elif fname.endswith('.bz2'):
433                    from bz2 import BZ2File
434                    stream = io.TextIOWrapper(BZ2File(fname, 'rb'))
435                else:
436                    stream = open(fname, 'rt')
437            except Exception as e:
438                print("ERROR: {}".format(str(e)))
439                sys.exit(1)
440
441            self.process_arg(stream, options)
442
443    def make_options(self, args):
444        return ParseOptions(args)
445
446    def get_optparser(self):
447        estr = "\nList of junk calls:\n"
448        for klass, call in sorted(trace_ignore_calls):
449            estr += f"  {klass}::{call}\n"
450
451        optparser = argparse.ArgumentParser(
452            description="Parse and dump Gallium trace(s)",
453            formatter_class=argparse.RawDescriptionHelpFormatter,
454            epilog=estr)
455
456        optparser.add_argument("filename", action="extend", nargs="+",
457            type=str, metavar="filename", help="Gallium trace filename (plain or .gz, .bz2)")
458
459        optparser.add_argument("-p", "--plain",
460            action="store_const", const=True, default=False,
461            dest="plain", help="disable ANSI color etc. formatting")
462
463        optparser.add_argument("-S", "--suppress",
464            action="store_const", const=True, default=False,
465            dest="suppress_variants", help="suppress some variants in output for better diffability")
466
467        optparser.add_argument("-N", "--named",
468            action="store_const", const=True, default=False,
469            dest="named_ptrs", help="generate symbolic names for raw pointer values")
470
471        optparser.add_argument("-M", "--method-only",
472            action="store_const", const=True, default=False,
473            dest="method_only", help="output only call names without arguments")
474
475        optparser.add_argument("-I", "--ignore-junk",
476            action="store_const", const=True, default=False,
477            dest="ignore_junk", help="filter out/ignore junk calls (see below)")
478
479        return optparser
480
481    def process_arg(self, stream, options):
482        if options.plain:
483            formatter = format.Formatter(sys.stdout)
484        else:
485            formatter = format.DefaultFormatter(sys.stdout)
486
487        dump = TraceDumper(stream, options, formatter, TraceStateData())
488        dump.parse()
489
490        if options.named_ptrs:
491            for call in dump.call_stack:
492                call.visit(dump.pretty_printer)
493
494
495if __name__ == '__main__':
496    Main().main()
497