#!/usr/bin/env python # Use os.path.splitext to split foo.xml import math import optparse import os import re import sys import xml.dom.minidom import cairo MARGIN = 25 RIGHT_MARGIN = 50 COLUMNS_TOP_MARGIN = 55 palette = [ (0.98, 0.91, 0.31), # Tango Butter 1 (0.94, 0.16, 0.16), # Scarlet Red 1 (0.45, 0.62, 0.81), # Sky Blue 1 (0.45, 0.82, 0.09), # Chameleon 2 (0.5, 0.5, 0.5)] def flatten(L): if type(L) != type([]): return [L] if L == []: return L return flatten(L[0]) + flatten(L[1:]) def set_source_rgb (ctx, triplet): ctx.set_source_rgb (triplet[0], triplet[1], triplet[2]) class TorturePlot: def __init__(self, dataset): # Constants self.font_face = "Bitstream Vera Sans" self.font_size = 10 self.dataset = dataset.result self.datanames = dataset.fileNames self.background_color = (1.0, 1.0, 1.0) self.text_color = (0, 0, 0) self.column_width = 20 self.common_scale = 0 self.colors = self.__computeTimingColors() self.scaleMarks = self.__computeScaleMarks() def __computeScaleMarks(self): (width, height) = self.compute_size (self.dataset) midpoint = (((height - MARGIN) - COLUMNS_TOP_MARGIN) / 2) + COLUMNS_TOP_MARGIN return [COLUMNS_TOP_MARGIN, ((midpoint - COLUMNS_TOP_MARGIN) / 2) + COLUMNS_TOP_MARGIN, midpoint, (((height - MARGIN) - midpoint)/2) + midpoint, height - MARGIN] def __generateColor(self, n): return palette[n] def __computeTimingColors(self): return [self.__generateColor(i % len(self.datanames)) for i in range(len(self.datanames))] def compute_size(self, dataset): # Make this dynamic, and do it only once on startup!! return (750, 400) def __plotHeader(self, widget, ctx): (width, height) = self.compute_size (self.dataset) self.column_max_height = height - COLUMNS_TOP_MARGIN - MARGIN # Background set_source_rgb (ctx, self.background_color) ctx.rectangle (0, 0, width, height) ctx.fill () # Widget name set_source_rgb (ctx, self.text_color) ctx.move_to (MARGIN, MARGIN); ctx.set_font_size (15) ctx.show_text (widget) # Legend (void, void, widget_width, void, void, void) = ctx.text_extents (widget) x = MARGIN + widget_width + MARGIN; ctx.move_to (x, MARGIN) ctx.set_font_size (10) i = 0 for set in self.datanames: set_source_rgb (ctx, self.text_color) ctx.show_text (set) (void, void, set_width, void, void, void) = ctx.text_extents (set) x += 5 + set_width ctx.rectangle (x, 15, 10, 10) set_source_rgb (ctx, self.colors[i]) ctx.fill () i += 1 x += 20 ctx.move_to (x, MARGIN) set_source_rgb (ctx, self.text_color) ctx.move_to (MARGIN, 35) ctx.line_to (width - MARGIN, 35) ctx.stroke () def __calculateCommonScale (self): biggest = 0 for widget in self.dataset: f = flatten(self.dataset[widget].values()) f.sort() if f[-1] > biggest: biggest = f[-1] return biggest def __plotAxis(self, widget, ctx, options): (width, height) = self.compute_size (self.dataset) ctx.set_line_width(1.0) ctx.move_to(RIGHT_MARGIN, 45) ctx.line_to (RIGHT_MARGIN, height - MARGIN) ctx.line_to (width - MARGIN, height - MARGIN) ctx.stroke() # Scale if options.normalize: if self.common_scale == 0: self.common_scale = self.__calculateCommonScale () self.biggest = self.common_scale else: f = flatten(self.dataset[widget].values()) f.sort() self.biggest = f[-1] # Draw the scale step = self.biggest / (len(self.scaleMarks) - 1) mark = self.biggest ctx.set_font_size(10) for y in self.scaleMarks: ctx.move_to(RIGHT_MARGIN, y) ctx.line_to (RIGHT_MARGIN + 5, y) ctx.stroke() ctx.move_to(5, y + 2) ctx.show_text("%.3fs" % mark) mark -= step if (mark < 0): mark = 0 def plotWidget(self, widget, options): (width, height) = self.compute_size (self.dataset) surface = cairo.ImageSurface (cairo.FORMAT_RGB24, width, height) ctx = cairo.Context (surface) ctx.select_font_face (self.font_face, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) ctx.set_font_size (self.font_size) # Header self.__plotHeader(widget, ctx) # Axis self.__plotAxis(widget, ctx, options) # Columns x = RIGHT_MARGIN + 10; for event in self.dataset[widget].items(): n_events = len(event[1]) (void, void, width, void, void, void) = ctx.text_extents (event[0]) set_source_rgb (ctx, self.text_color) ctx.set_font_size (10) ctx.move_to (x, height - MARGIN/2) ctx.show_text (event[0]) color = 0 p = 0 if self.column_width * n_events > width: column_width = width / n_events else: column_width = self.column_width margin = (width - (column_width * n_events)) / 2 for timing in event[1]: column_height = timing * self.column_max_height / self.biggest ctx.rectangle (x + p + margin, (height - MARGIN - 2) - column_height, column_width - 5, column_height) set_source_rgb (ctx, self.colors[color]) color += 1 ctx.fill() p += column_width if p < width: p = width x += width + 30 # Profit! surface.write_to_png (options.prefix+widget+".png") def plotAllWidgets(self, options): for widget in self.dataset: self.plotWidget(widget, options) class TorturerParser: def __init__(self, fileList): self.fileList = fileList self.fileNames = [os.path.splitext(file)[0] for file in self.fileList] def parseWidgets(self): self.result = {} for file in self.fileList: self.__parseWidget(file) def __parseWidget(self, file): print "Parsing file %s" % file dom = xml.dom.minidom.parse(file) for widget in dom.getElementsByTagName("widget"): name = widget.getAttribute("name") if not self.result.has_key(name): self.result[name] = {} for property in widget.getElementsByTagName("timing"): propertyName = property.getAttribute("name") + "::" + property.getAttribute("subname") if not self.result[name].has_key(propertyName): self.result[name][propertyName] = [] self.result[name][propertyName].append(float(property.childNodes[0].data)) def main(args): option_parser = optparse.OptionParser( usage="usage: %prog -p prefix [TortureXMLFile1, TortureXMLFile2, ... ]") option_parser.add_option("-p", "--prefix", dest="prefix", metavar="FILE", help="Prefix added to all the files generated by the program.") option_parser.add_option("-n", "--normalize", dest="normalize", action="store_true", default=False, help="Draw all the images using the same scale") options, args = option_parser.parse_args() parser = TorturerParser(args) parser.parseWidgets() plot = TorturePlot(parser) plot.plotAllWidgets(options) return 0 if __name__ == "__main__": sys.exit(main(sys.argv))