]> git.cworth.org Git - apitrace/blobdiff - xml2txt.py
More concise data model.
[apitrace] / xml2txt.py
index f9fde2e77b3da8d7492d91cbc16aa2535fd2dbc5..e0c5efde50949eacd1dc524821e65b16dba0448f 100755 (executable)
@@ -1,26 +1,36 @@
 #!/usr/bin/env python
-#############################################################################
+##########################################################################
 #
-# Copyright 2008 Tungsten Graphics, Inc.
+# Copyright 2008-2009 VMware, Inc.
+# All Rights Reserved.
 #
-# This program is free software: you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
 #
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Lesser General Public License for more details.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
 #
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
 #
-#############################################################################
+##########################################################################/
 
 
 import sys
+import optparse
 import xml.parsers.expat
+import gzip
+
+from model import *
 
 
 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
@@ -98,6 +108,7 @@ class XmlTokenizer:
             self.index = 0
             data = self.fp.read(size)
             self.final = len(data) < size
+            data = data.rstrip('\0')
             try:
                 self.parser.Parse(data, self.final)
             except xml.parsers.expat.ExpatError, e:
@@ -175,96 +186,56 @@ class XmlParser:
         return data
 
 
-class Formatter:
-    
-    def function(self, name):
-        return name
-        
-    def variable(self, name):
-        return name
-
-    def literal(self, value):
-        return str(value)
-    
-    def address(self, addr):
-        return addr
-
-
-class AnsiFormatter(Formatter):
-    '''Formatter for plain-text files which outputs ANSI escape codes. See
-    http://en.wikipedia.org/wiki/ANSI_escape_code for more information
-    concerning ANSI escape codes.
-    '''
+class GzipFile(gzip.GzipFile):
 
-    _csi = '\33['
-
-    _normal = '0m'
-    _bold = '1m'
-    _italic = '3m'
-    _red = '31m'
-    _green = '32m'
-    _blue = '34m'
-
-    def _escape(self, code, text):
-        return self._csi + code + text + self._csi + self._normal
-
-    def function(self, name):
-        text = Formatter.function(self, name)
-        return self._escape(self._bold, text)
-        
-    def variable(self, name):
-        text = Formatter.variable(self, name)
-        return self._escape(self._italic, text)
-
-    def literal(self, value):
-        text = Formatter.literal(self, value)
-        return self._escape(self._blue, text)
-    
-    def address(self, value):
-        text = Formatter.address(self, value)
-        return self._escape(self._green, text)
+    def _read_eof(self):
+        # Ignore incomplete files
+        try:
+            gzip.GzipFile._read_eof(self)
+        except IOError:
+            pass
 
 
 class TraceParser(XmlParser):
 
-    def __init__(self, stream, formatter):
+    def __init__(self, stream):
         XmlParser.__init__(self, stream)
-        self.formatter = formatter
+        self.call_no = 0
 
     def parse(self):
         self.element_start('trace')
-        while self.token.type != ELEMENT_END:
+        while self.token.type not in (ELEMENT_END, EOF):
             self.parse_call()
-        self.element_end('trace')
+        if self.token.type != EOF:
+            self.element_end('trace')
 
     def parse_call(self):
         attrs = self.element_start('call')
         name = attrs['name']
         args = []
         ret = None
+        properties = {}
         while self.token.type == ELEMENT_START:
             if self.token.name_or_data == 'arg':
                 arg = self.parse_arg()
                 args.append(arg)
             elif self.token.name_or_data == 'ret':
                 ret = self.parse_ret()
+            elif self.token.name_or_data in ('duration', 'starttsc', 'endtsc'):
+                property = self.token.name_or_data
+                properties[property] = self.parse_hex(self.token.name_or_data)
             elif self.token.name_or_data == 'call':
+                # ignore nested function calls
                 self.parse_call()
             else:
                 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
         self.element_end('call')
         
-        call = self.formatter.function(name)
-        call += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
-        if ret is not None:
-            call += ' = ' + ret
-        call += '\n'
-        
-        try:
-            sys.stdout.write(call)
-        except IOError: 
-            # catch broken pipe
-            sys.exit(0)
+        self.call_no += 1
+
+        call = Call(self.call_no, name, args, ret, properties)
+
+        self.handle_call(call)
 
     def parse_arg(self):
         attrs = self.element_start('arg')
@@ -281,12 +252,28 @@ class TraceParser(XmlParser):
 
         return value
 
+    def parse_hex(self, token_name):
+        attrs = self.element_start(token_name)
+        value = int(self.character_data(), 16)
+        self.element_end(token_name)
+        return value
+
     def parse_value(self):
-        if self.token.type == CHARACTER_DATA:
-            return self.formatter.literal(self.character_data())
         if self.token.type == ELEMENT_START:
-            if self.token.name_or_data == 'elem':
-                return self.parse_elems()
+            if self.token.name_or_data == 'int':
+                return self.parse_int()
+            if self.token.name_or_data == 'uint':
+                return self.parse_uint()
+            if self.token.name_or_data == 'float':
+                return self.parse_float()
+            if self.token.name_or_data == 'string':
+                return self.parse_string()
+            if self.token.name_or_data == 'wstring':
+                return self.parse_wstring()
+            if self.token.name_or_data == 'const':
+                return self.parse_const()
+            if self.token.name_or_data == 'bitmask':
+                return self.parse_bitmask()
             if self.token.name_or_data == 'ref':
                 return self.parse_ref()
         raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
@@ -295,39 +282,170 @@ class TraceParser(XmlParser):
         elems = [self.parse_elem()]
         while self.token.type != ELEMENT_END:
             elems.append(self.parse_elem())
-        return '{' + ', '.join([name + ' = ' + value for name, value in elems]) + '}'
+        return Struct("", elems)
 
     def parse_elem(self):
         attrs = self.element_start('elem')
-        name = attrs['name']
         value = self.parse_value()
         self.element_end('elem')
 
+        try:
+            name = attrs['name']
+        except KeyError:
+            name = ""
+
         return name, value
 
     def parse_ref(self):
         attrs = self.element_start('ref')
         if self.token.type != ELEMENT_END:
-            value = '&' + self.parse_value()
+            value = self.parse_value()
         else:
-            value = self.formatter.address(attrs['addr'])
+            value = None
         self.element_end('ref')
 
-        return value
+        return Pointer(attrs['addr'], value)
 
+    def parse_bitmask(self):
+        self.element_start('bitmask')
+        elems = []
+        while self.token.type != ELEMENT_END:
+            elems.append(self.parse_value())
+        self.element_end('bitmask')
+        return Bitmask(elems)
+
+    def parse_int(self):
+        self.element_start('int')
+        value = self.character_data()
+        self.element_end('int')
+        return Literal(int(value))
+
+    def parse_uint(self):
+        self.element_start('uint')
+        value = self.character_data()
+        self.element_end('uint')
+        return Literal(int(value))
+
+    def parse_float(self):
+        self.element_start('float')
+        value = self.character_data()
+        self.element_end('float')
+        return Literal(float(value))
+
+    def parse_string(self):
+        self.element_start('string')
+        value = self.character_data()
+        self.element_end('string')
+        return Literal(value)
+
+    def parse_wstring(self):
+        self.element_start('wstring')
+        value = self.character_data()
+        self.element_end('wstring')
+        return Literal(value)
+
+    def parse_const(self):
+        self.element_start('const')
+        value = self.character_data()
+        self.element_end('const')
+        return NamedConstant(value)
+
+    def handle_call(self, call):
+        pass
+
+
+class DumpTraceParser(TraceParser):
+
+    def __init__(self, stream, formatter):
+        XmlParser.__init__(self, stream)
+        self.formatter = formatter
+        self.pretty_printer = PrettyPrinter(self.formatter)
+        self.call_no = 0
+
+    def handle_call(self, call):
+        call.visit(self.pretty_printer)
+        self.formatter.newline()
+
+
+class StatsTraceParser(TraceParser):
+
+    def __init__(self, stream, formatter):
+        TraceParser.__init__(self, stream, formatter)
+        self.stats = {}
+
+    def parse(self):
+        TraceParser.parse(self)
+
+        sys.stdout.write('%s\t%s\t%s\n' % ("name", "calls", "duration"))
+        for name, (calls, duration) in self.stats.iteritems():
+            sys.stdout.write('%s\t%u\t%f\n' % (name, calls, duration/1000000.0))
 
-def main():
-    formatter = AnsiFormatter()
+    def handle_call(self, name, args, ret, duration):
+        try:
+            nr_calls, total_duration = self.stats[name]
+        except KeyError:
+            nr_calls = 1
+            total_duration = duration
+        else:
+            nr_calls += 1
+            if duration is not None:
+                total_duration += duration
+        self.stats[name] = nr_calls, total_duration
+
+
+class Main:
+
+    def __init__(self):
+        pass
+
+    def main(self):
+        optparser = self.get_optparser()
+        (options, args) = optparser.parse_args(sys.argv[1:])
     
-    args = sys.argv[1:]
-    if args:
-        for arg in args:
-            parser = TraceParser(open(arg, 'rt'), formatter)
-            parser.parse()
-    else:
-            parser = TraceParser(sys.stdin, formatter)
-            parser.parse()
+        if args:
+            for arg in args:
+                if arg.endswith('.gz'):
+                    from gzip import GzipFile
+                    stream = GzipFile(arg, 'rb')
+                elif arg.endswith('.bz2'):
+                    from bz2 import BZ2File
+                    stream = BZ2File(arg, 'rU')
+                else:
+                    stream = open(arg, 'rt')
+                self.process_arg(stream, options)
+        else:
+            self.process_arg(stream, options)
+
+    def get_optparser(self):
+        optparser = optparse.OptionParser(
+            usage="\n\t%prog [options] [traces] ...")
+        optparser.add_option(
+            '-s', '--stats',
+            action="store_true",
+            dest="stats", default=False,
+            help="generate statistics instead")
+        optparser.add_option(
+            '--color', '--colour',
+            type="choice", choices=('never', 'always', 'auto'), metavar='WHEN',
+            dest="color", default="always",
+            help="coloring: never, always, or auto [default: %default]")
+        return optparser
+
+    def process_arg(self, stream, options):
+        if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
+            formatter = format.DefaultFormatter(sys.stdout)
+        else:
+            formatter = format.Formatter(sys.stdout)
+        
+        if options.stats:
+            factory = StatsTraceParser
+        else:
+            factory = DumpTraceParser
+
+        parser = DumpTraceParser(stream, formatter)
+        parser.parse()
 
 
 if __name__ == '__main__':
-    main()
+    Main().main()
+