#!/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)
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:
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')
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)
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()
+