diff options
Diffstat (limited to 'util')
-rwxr-xr-x | util/minorview.py | 107 | ||||
-rw-r--r-- | util/minorview/__init__.py | 36 | ||||
-rw-r--r-- | util/minorview/blobs.py | 461 | ||||
-rw-r--r-- | util/minorview/colours.py | 68 | ||||
-rw-r--r-- | util/minorview/minor.pic | 154 | ||||
-rw-r--r-- | util/minorview/model.py | 1109 | ||||
-rw-r--r-- | util/minorview/parse.py | 109 | ||||
-rw-r--r-- | util/minorview/point.py | 78 | ||||
-rw-r--r-- | util/minorview/view.py | 524 |
9 files changed, 2646 insertions, 0 deletions
diff --git a/util/minorview.py b/util/minorview.py new file mode 100755 index 000000000..1a6b47b01 --- /dev/null +++ b/util/minorview.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley +# +# minorview.py: Minorview visuliser for MinorCPU model MinorTrace output +# + +import gtk +import os +import sys +import argparse + +# Find MinorView modules even if not called from minorview directory +minorviewDir = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(minorviewDir) + +from minorview.model import BlobModel +from minorview.view import BlobView, BlobController, BlobWindow +from minorview.point import Point + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Minor visualiser') + + parser.add_argument('--picture', metavar='picture-file', + default=minorviewDir + '/minorview/minor.pic', + help='markup file containing blob information ' + + '(default: <minorview-path>/minor.pic)') + parser.add_argument('--prefix', metavar='name', default='system.cpu', + help='name prefix in trace for CPU to be visualised (default: ' + + 'system.cpu)') + parser.add_argument('--start-time', metavar='time', type=int, default=0, + help='time of first event to load from file') + parser.add_argument('--end-time', metavar='time', type=int, default=None, + help='time of last event to load from file') + parser.add_argument('--mini-views', action='store_true', default=False, + help='show tiny views of the next 10 time steps') + parser.add_argument('eventFile', metavar='event-file', default='ev') + + args = parser.parse_args(sys.argv[1:]) + + model = BlobModel(unitNamePrefix=args.prefix) + + if args.picture and os.access(args.picture, os.O_RDONLY): + model.load_picture(args.picture) + else: + parser.error('Can\'t read picture file: ' + args.picture) + + # Make the key objects + view = BlobView(model) + controller = BlobController(model, view, + defaultEventFile=args.eventFile, + defaultPictureFile=args.picture) + window = BlobWindow(model, view, controller) + window.add_control_bar(controller.bar) + + # Miniviews allow future timesteps to appear at the bottom of the + # display. + if args.mini_views: + window.miniViewCount = 10 + + window.show_window() + + if args.eventFile and os.access(args.eventFile, os.O_RDONLY): + controller.startTime = args.start_time + controller.endTime = args.end_time + model.load_events(args.eventFile, startTime=args.start_time, + endTime=args.end_time) + controller.set_time_index(0) + else: + parser.error('Can\'t read event file: ' + args.eventFile) + + gtk.main() diff --git a/util/minorview/__init__.py b/util/minorview/__init__.py new file mode 100644 index 000000000..0b957dcec --- /dev/null +++ b/util/minorview/__init__.py @@ -0,0 +1,36 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley diff --git a/util/minorview/blobs.py b/util/minorview/blobs.py new file mode 100644 index 000000000..32d08dfa2 --- /dev/null +++ b/util/minorview/blobs.py @@ -0,0 +1,461 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley +# +# blobs.py: Blobs are the visual blocks, arrows and other coloured +# objects on the visualiser. This file contains Blob definition and +# their rendering instructions in pygtk/cairo. +# + +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import cairo +import re +import math + +from point import Point +import parse +import colours +from colours import backgroundColour, black +import model + +def centre_size_to_sides(centre, size): + """Returns a 4-tuple of the relevant ordinates of the left, + right, top and bottom sides of the described rectangle""" + (x, y) = centre.to_pair() + (half_width, half_height) = (size.scale(0.5)).to_pair() + left = x - half_width + right = x + half_width + top = y - half_height + bottom = y + half_height + return (left, right, top, bottom) + +def box(cr, centre, size): + """Draw a simple box""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + cr.move_to(left, top) + cr.line_to(right, top) + cr.line_to(right, bottom) + cr.line_to(left, bottom) + cr.close_path() + +def stroke_and_fill(cr, colour): + """Stroke with the current colour then fill the same path with the + given colour""" + join = cr.get_line_join() + cr.set_line_join(gtk.gdk.JOIN_ROUND) + cr.close_path() + cr.set_source_color(backgroundColour) + cr.stroke_preserve() + cr.set_source_color(colour) + cr.fill() + cr.set_line_join(join) + +def striped_box(cr, centre, size, colours): + """Fill a rectangle (without outline) striped with the colours given""" + num_colours = len(colours) + if num_colours == 0: + box(cr, centre, size) + cr.set_source_color(backgroundColour) + cr.fill() + elif num_colours == 1: + box(cr, centre, size) + stroke_and_fill(cr, colours[0]) + else: + (left, right, top, bottom) = centre_size_to_sides(centre, size) + (width, height) = size.to_pair() + x_stripe_width = width / num_colours + half_x_stripe_width = x_stripe_width / 2.0 + # Left triangle + cr.move_to(left, bottom) + cr.line_to(left + half_x_stripe_width, bottom) + cr.line_to(left + x_stripe_width + half_x_stripe_width, top) + cr.line_to(left, top) + stroke_and_fill(cr, colours[0]) + # Stripes + for i in xrange(1, num_colours - 1): + xOffset = x_stripe_width * i + cr.move_to(left + xOffset - half_x_stripe_width, bottom) + cr.line_to(left + xOffset + half_x_stripe_width, bottom) + cr.line_to(left + xOffset + x_stripe_width + + half_x_stripe_width, top) + cr.line_to(left + xOffset + x_stripe_width - + half_x_stripe_width, top) + stroke_and_fill(cr, colours[i]) + # Right triangle + cr.move_to((right - x_stripe_width) - half_x_stripe_width, bottom) + cr.line_to(right, bottom) + cr.line_to(right, top) + cr.line_to((right - x_stripe_width) + half_x_stripe_width, top) + stroke_and_fill(cr, colours[num_colours - 1]) + +def speech_bubble(cr, top_left, size, unit): + """Draw a speech bubble with 'size'-sized internal space with its + top left corner at Point(2.0 * unit, 2.0 * unit)""" + def local_arc(centre, angleFrom, angleTo): + cr.arc(centre.x, centre.y, unit, angleFrom * math.pi, + angleTo * math.pi) + + cr.move_to(*top_left.to_pair()) + cr.rel_line_to(unit * 2.0, unit) + cr.rel_line_to(size.x, 0.0) + local_arc(top_left + Point(size.x + unit * 2.0, unit * 2.0), -0.5, 0.0) + cr.rel_line_to(0.0, size.y) + local_arc(top_left + Point(size.x + unit * 2.0, size.y + unit * 2.0), + 0, 0.5) + cr.rel_line_to(-size.x, 0.0) + local_arc(top_left + Point(unit * 2.0, size.y + unit * 2.0), 0.5, 1.0) + cr.rel_line_to(0, -size.y) + cr.close_path() + +def open_bottom(cr, centre, size): + """Draw a box with left, top and right sides""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + cr.move_to(left, bottom) + cr.line_to(left, top) + cr.line_to(right, top) + cr.line_to(right, bottom) + +def fifo(cr, centre, size): + """Draw just the vertical sides of a box""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + cr.move_to(left, bottom) + cr.line_to(left, top) + cr.move_to(right, bottom) + cr.line_to(right, top) + +def cross(cr, centre, size): + """Draw a cross parallel with the axes""" + (left, right, top, bottom) = centre_size_to_sides(centre, size) + (x, y) = centre.to_pair() + cr.move_to(left, y) + cr.line_to(right, y) + cr.move_to(x, top) + cr.line_to(x, bottom) + +class Blob(object): + """Blob super class""" + def __init__(self, picChar, unit, topLeft, colour, size = Point(1,1)): + self.picChar = picChar + self.unit = unit + self.displayName = unit + self.nameLoc = 'top' + self.topLeft = topLeft + self.colour = colour + self.size = size + self.border = 1.0 + self.dataSelect = model.BlobDataSelect() + self.shorten = 0 + + def render(self, cr, view, event, select, time): + """Render this blob with the given event's data. Returns either + None or a pair of (centre, size) in device coordinates for the drawn + blob. The return value can be used to detect if mouse clicks on + the canvas are within the blob""" + return None + +class Block(Blob): + """Blocks are rectangular blogs colourable with a 2D grid of striped + blocks. visualDecoder specifies how event data becomes this coloured + grid""" + def __init__(self, picChar, unit, topLeft=Point(0,0), + colour=colours.black, + size=Point(1,1)): + super(Block,self).__init__(picChar, unit, topLeft, colour, + size = size) + # {horiz, vert} + self.stripDir = 'horiz' + # {LR, RL}: LR means the first strip will be on the left/top, + # RL means the first strip will be on the right/bottom + self.stripOrd = 'LR' + # Number of blank strips if this is a frame + self.blankStrips = 0 + # {box, fifo, openBottom} + self.shape = 'box' + self.visualDecoder = None + + def render(self, cr, view, event, select, time): + # Find the right event, visuals and sizes for things + if event is None or self.displayName.startswith('_'): + event = model.BlobEvent(self.unit, time) + + if self.picChar in event.visuals: + strips = event.visuals[self.picChar].to_striped_block( + select & self.dataSelect) + else: + strips = [[[colours.unknownColour]]] + + if self.stripOrd == 'RL': + strips.reverse() + + if len(strips) == 0: + strips = [[colours.errorColour]] + print 'Problem with the colour of event:', event + + num_strips = len(strips) + strip_proportion = 1.0 / num_strips + first_strip_offset = (num_strips / 2.0) - 0.5 + + # Adjust blocks with 'shorten' attribute to the length of the data + size = Point(*self.size.to_pair()) + if self.shorten != 0 and self.size.x > (num_strips * self.shorten): + size.x = num_strips * self.shorten + + box_size = size - view.blobIndentFactor.scale(2) + + # Now do cr sensitive things + cr.save() + cr.scale(*view.pitch.to_pair()) + cr.translate(*self.topLeft.to_pair()) + cr.translate(*(size - Point(1,1)).scale(0.5).to_pair()) + + translated_centre = Point(*cr.user_to_device(0.0, 0.0)) + translated_size = \ + Point(*cr.user_to_device_distance(*size.to_pair())) + + # The 2D grid is a grid of strips of blocks. Data [[1,2],[3]] + # is 2 strips of 2 and 1 blocks respectively. + # if stripDir == 'horiz', strips are stacked vertically + # from top to bottom if stripOrd == 'LR' or bottom to top if + # stripOrd == 'RL'. + # if stripDir == 'vert', strips are stacked horizontally + # from left to right if stripOf == 'LR' or right to left if + # stripOrd == 'RL'. + + strip_is_horiz = self.stripDir == 'horiz' + + if strip_is_horiz: + strip_step_base = Point(1.0,0.0) + block_step_base = Point(0.0,1.0) + else: + strip_step_base = Point(0.0,1.0) + block_step_base = Point(1.0,0.0) + + strip_size = (box_size * (strip_step_base.scale(strip_proportion) + + block_step_base)) + strip_step = strip_size * strip_step_base + strip_centre = Point(0,0) - (strip_size * + strip_step_base.scale(first_strip_offset)) + + cr.set_line_width(view.midLineWidth / view.pitch.x) + + # Draw the strips and their blocks + for strip_index in xrange(0, num_strips): + num_blocks = len(strips[strip_index]) + block_proportion = 1.0 / num_blocks + firstBlockOffset = (num_blocks / 2.0) - 0.5 + + block_size = (strip_size * + (block_step_base.scale(block_proportion) + + strip_step_base)) + block_step = block_size * block_step_base + block_centre = (strip_centre + strip_step.scale(strip_index) - + (block_size * block_step_base.scale(firstBlockOffset))) + + for block_index in xrange(0, num_blocks): + striped_box(cr, block_centre + + block_step.scale(block_index), block_size, + strips[strip_index][block_index]) + + cr.set_font_size(0.7) + if self.border > 0.5: + weight = cairo.FONT_WEIGHT_BOLD + else: + weight = cairo.FONT_WEIGHT_NORMAL + cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, + weight) + + xb, yb, width, height, dx, dy = cr.text_extents(self.displayName) + + text_comfort_space = 0.15 + + if self.nameLoc == 'left': + # Position text vertically along left side, top aligned + cr.save() + cr.rotate(- (math.pi / 2.0)) + text_point = Point(size.y, size.x).scale(0.5) * Point(-1, -1) + text_point += Point(max(0, size.y - width), 0) + text_point += Point(-text_comfort_space, -text_comfort_space) + else: # Including top + # Position text above the top left hand corner + text_point = size.scale(0.5) * Point(-1,-1) + text_point += Point(0.00, -text_comfort_space) + + if (self.displayName != '' and + not self.displayName.startswith('_')): + cr.set_source_color(self.colour) + cr.move_to(*text_point.to_pair()) + cr.show_text(self.displayName) + + if self.nameLoc == 'left': + cr.restore() + + # Draw the outline shape + cr.save() + if strip_is_horiz: + cr.rotate(- (math.pi / 2.0)) + box_size = Point(box_size.y, box_size.x) + + if self.stripOrd == "RL": + cr.rotate(math.pi) + + if self.shape == 'box': + box(cr, Point(0,0), box_size) + elif self.shape == 'openBottom': + open_bottom(cr, Point(0,0), box_size) + elif self.shape == 'fifo': + fifo(cr, Point(0,0), box_size) + cr.restore() + + # Restore scale and stroke the outline + cr.restore() + cr.set_source_color(self.colour) + cr.set_line_width(view.thickLineWidth * self.border) + cr.stroke() + + # Return blob size/position + if self.unit == '_': + return None + else: + return (translated_centre, translated_size) + +class Key(Blob): + """Draw a key to the special (and numeric colours) with swatches of the + colours half as wide as the key""" + def __init__(self, picChar, unit, topLeft, colour=colours.black, + size=Point(1,1)): + super(Key,self).__init__(picChar, unit, topLeft, colour, size = size) + self.colours = 'BBBB' + self.displayName = unit + + def render(self, cr, view, event, select, time): + cr.save() + cr.scale(*view.pitch.to_pair()) + cr.translate(*self.topLeft.to_pair()) + # cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) + half_width = self.size.x / 2.0 + cr.translate(*(self.size - Point(1.0 + half_width,1.0)).scale(0.5). + to_pair()) + + num_colours = len(self.colours) + cr.set_line_width(view.midLineWidth / view.pitch.x) + + blob_size = (Point(half_width,0.0) + + (self.size * Point(0.0,1.0 / num_colours))) + blob_step = Point(0.0,1.0) * blob_size + first_blob_centre = (Point(0.0,0.0) - + blob_step.scale((num_colours / 2.0) - 0.5)) + + cr.set_source_color(self.colour) + cr.set_line_width(view.thinLineWidth / view.pitch.x) + + blob_proportion = 0.8 + + real_blob_size = blob_size.scale(blob_proportion) + + cr.set_font_size(0.8 * blob_size.y * blob_proportion) + cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL, + cairo.FONT_WEIGHT_BOLD) + + for i in xrange(0, num_colours): + centre = first_blob_centre + blob_step.scale(i) + box(cr, centre, real_blob_size) + + colour_char = self.colours[i] + if colour_char.isdigit(): + cr.set_source_color(colours.number_to_colour( + int(colour_char))) + label = '...' + colour_char + else: + cr.set_source_color(model.special_state_colours[colour_char]) + label = model.special_state_names[colour_char] + + cr.fill_preserve() + cr.set_source_color(self.colour) + cr.stroke() + + xb, yb, width, height, dx, dy = cr.text_extents(label) + + text_left = (centre + (Point(0.5,0.0) * blob_size) + + Point(0.0, height / 2.0)) + + cr.move_to(*text_left.to_pair()) + cr.show_text(label) + +class Arrow(Blob): + """Draw a left or right facing arrow""" + def __init__(self, unit, topLeft, colour=colours.black, + size=Point(1.0,1.0), direc='right'): + super(Arrow,self).__init__(unit, unit, topLeft, colour, size = size) + self.direc = direc + + def render(self, cr, view, event, select, time): + cr.save() + cr.scale(*view.pitch.to_pair()) + cr.translate(*self.topLeft.to_pair()) + cr.translate(*(self.size - Point(1,1)).scale(0.5).to_pair()) + cr.scale(*self.size.to_pair()) + (blob_indent_x, blob_indent_y) = \ + (view.blobIndentFactor / self.size).to_pair() + left = -0.5 - blob_indent_x + right = 0.5 + blob_indent_x + + thickness = 0.2 + flare = 0.2 + + if self.direc == 'left': + cr.rotate(math.pi) + + cr.move_to(left, -thickness) + cr.line_to(0.0, -thickness) + cr.line_to(0.0, -(thickness + flare)) + cr.line_to(right, 0) + # Break arrow to prevent the point ruining the appearance of boxes + cr.move_to(right, 0) + cr.line_to(0.0, (thickness + flare)) + cr.line_to(0.0, +thickness) + cr.line_to(left, +thickness) + + cr.restore() + + # Draw arrow a bit more lightly than the standard line width + cr.set_line_width(cr.get_line_width() * 0.75) + cr.set_source_color(self.colour) + cr.stroke() + + return None diff --git a/util/minorview/colours.py b/util/minorview/colours.py new file mode 100644 index 000000000..b8394b019 --- /dev/null +++ b/util/minorview/colours.py @@ -0,0 +1,68 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley + +import gtk + +# All the miscellaneous colours used in the interface +unknownColour = gtk.gdk.color_parse('magenta') +blockedColour = gtk.gdk.color_parse('grey') +bubbleColour = gtk.gdk.color_parse('bisque') +emptySlotColour = gtk.gdk.color_parse('grey90') +reservedSlotColour = gtk.gdk.color_parse('cyan') +errorColour = gtk.gdk.color_parse('blue') +backgroundColour = gtk.gdk.color_parse('white') +faultColour = gtk.gdk.color_parse('dark cyan') +readColour = gtk.gdk.color_parse('red') +writeColour = gtk.gdk.color_parse('white') + +black = gtk.gdk.color_parse('black') + +def name_to_colour(name): + """Convert a colour name to a GdkColor""" + try: + ret = gtk.gdk.color_parse(name) + except: + ret = unknownColour + return ret + +number_colour_code = map(name_to_colour, ['black', 'brown', 'red', 'orange', + 'yellow', 'green', 'blue', 'violet', 'grey', 'white']) + +def number_to_colour(num): + """Convert the last decimal digit of an integer into a resistor + stripe colour""" + return number_colour_code[num % 10] diff --git a/util/minorview/minor.pic b/util/minorview/minor.pic new file mode 100644 index 000000000..52731d66f --- /dev/null +++ b/util/minorview/minor.pic @@ -0,0 +1,154 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley +# +# minor.pic: Pipeline appearance of the Minor pipeline for minorview + +# Markup of the pipeline blocks. +# '>' and '<' are connecting arrows. +# '/', ':' and '\' mark multi-line arrows. +# All other (non-space) character rectangles are the shapes of the +# corresponding blocks below the markup. + +<<< + IPIPIPIPIPIPIPIPIP LSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLS KKKKKKKKKK + IP IP LS LS KK KK + IPiririr-\itititIP LSimimim drdrdr-\dtdtdtdt sbsbsbsbsbsbLS KK KK + IPiririr-/itititIP LSimimim drdrdr-/dtdtdtdt sbsbsbsbsbsbLS KK KK + IPIPIPIPIPIPIPIPIP LSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLSLS KK KK + KK KK + KK KK + acacac sasasasasasasasa EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE KK KK + acacac sasasasasasasasa EE EE KK KK + EEscscscscscscscscscscscscscscscscscscscEE KK KK + EEscscscscscscscscscscscscscscscscscscscEE KK KK + EE EE KK KK + F1F1F1F1-\1212-\F2F2F2F2-\2D2D-\DDDD-\DEDE-\EEiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqEE KK KK + F1 F1 :1212 :F2 F2 :2D2D :DDDD :DEDE :EEiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqiqEE KK KK + F1fefeF1 :1212 :F2fifiF2 :2D2D :DDDD :DEDE :EE EE KK KK + F1fefeF1 :1212 :F2fifiF2 :2D2D :DDDD :DEDE :EEEiEiEiEi f0f0f0f0f0f0f0f0f0f0f0 ccccEE KK KK + F1fefeF1 :1212 :F2fifiF2 :2D2D :DDDD :DEDE :EEEiEiEiEi f0f0f0f0f0f0f0f0f0f0f0 ccccEE KK KK + F1fefeF1 :1212 :F2fifiF2-/2D2D-/DDDD-/DEDE-/EEEiEiEiEi f1f1f1f1f1f1f1f1f1f1f1 ccccEE KKKKKKKKKK + F1fefeF1 :1212 :F2fifiF2 DDDD EEEiEiEiEi f1f1f1f1f1f1f1f1f1f1f1 ccccEE + F1fefeF1-/1212-/F2fifiF2 DDDD EEEiEiEiEi f2f2f2f2f2f2f2f2f2f2f2 ccccEE + b2b2-\F1fefeF1 -\F2fifiF2 DDDD EEEiEiEiEi f2f2f2f2f2f2f2f2f2f2f2 ccccEE-\b1b1 + b2b2-/F1fefeF1 -/F2fifiF2 DDDD EEEiEiEiEi f3f3f3f3f3f3f3f3f3f3f3 ccccEE-/b1b1 + F1fefeF1/-2121/-F2fifiF2 DDDD EEEiEiEiEi f3f3f3f3f3f3f3f3f3f3f3 ccccEE + F1F1F1F1\-2121\-F2F2F2F2 DDDD EEEiEiEiEi f4f4f4f4f4f4f4f4f4f4f4 ccccEE + EEEiEiEiEi f4f4f4f4f4f4f4f4f4f4f4 ccccEE + FiFiFiFi DiDiDiDi EE f5f5f5f5f5f5f5f5f5f5f5 ccccEE + Fi Fi Di Di EE f5f5f5f5f5f5f5f5f5f5f5 ccccEE + FiFiFiFi DiDiDiDi EE f6f6f6f6f6f6f6f6f6f6f6 ccccEE + EE f6f6f6f6f6f6f6f6f6f6f6 ccccEE + EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +>>> + +# Macros for block type appearance. Macros are expanded in the 'block' +# lines below and can include other macros. +# +# Attributes are name=value pairs. +# Available names (and valid values); +# +# shape: {fifo, box, openBottom} +# stripDir: {vert, horiz} +# stripOrd: {RL, LR} +# decoder: {insts, lines, branch, dcache, counts} +# border: {thin, mid, thick} +# dataElement: <name> +# hideId: ({T, S, P, L, F, E})* +# name: <string> +# colours: ({U, B, -, E, R, F, r, w, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})* +# type: {key, block} + +# macro fifo: shape=fifo stripDir=vert +macro fifo: shape=openBottom stripDir=horiz stripOrd=RL border=mid +macro fu: decoder=insts border=mid name="" nameLoc=left +# macro back: decoder=back border=mid dataElement=space name="" +macro prediction: decoder=branch border=mid dataElement=prediction name="" +macro forward: border=mid name="" +macro dcache: fifo decoder=dcache dataElement=addr +macro icache: fifo decoder=lines +macro cacheFrame: stripDir=vert decoder=frame blankStrips=5 \ + dataElement=in_tlb_mem +macro activity: decoder=counts shorten=2 border=mid +macro inputBuffer: name="inputBuffer" fifo +macro seqNum: decoder=counts border=mid +macro streamFrame: decoder=frame stripDir=vert dataElement=streamSeqNum +macro predictionFrame: decoder=frame stripDir=vert dataElement=predictionSeqNum + +# Block descriptions: +# description ::= <char>: <unit-name-in-trace> +# ( {<macro-name>, <name>=<value> )* +# name ::= ? alphanumeric name with dots ? +# value ::= "(<char-except-">)*", <char-except-' '>* } + +Fi: fetch2.inputBuffer inputBuffer decoder=lines +Di: decode.inputBuffer inputBuffer decoder=insts hideId=E +Ei: execute.inputBuffer inputBuffer stripDir=horiz decoder=insts border=mid +F1: fetch1 streamFrame blankStrips=11 name="Fetch1" +fe: fetch1 decoder=lines border=thin name="Line" +F2: fetch2 predictionFrame blankStrips=11 name="Fetch2" +fi: fetch2 decoder=insts border=thin name="Insts" +DD: decode decoder=insts name="Decode" +EE: execute streamFrame blankStrips=21 name="Execute" +cc: execute decoder=insts name="Commit" border=mid +12: f1ToF2 forward decoder=lines +21: f2ToF1 prediction +2D: f2ToD forward decoder=insts hideId=E +DE: dToE forward decoder=insts +b1: eToF1 forward decoder=branch +b2: eToF1 forward decoder=branch +IP: fetch1 cacheFrame name="Fetch queues" +LS: execute.lsq cacheFrame name="LSQ" +ir: fetch1.requests icache name="Requests" +it: fetch1.transfers icache name="Transfers" +dr: execute.lsq.requests dcache name="Requests" +dt: execute.lsq.transfers dcache name="Transfers" +sb: execute.lsq.storeBuffer dcache name="Store buffer" +KK: _ type=key colours="UB-ERFrw0123456789" +f0: execute.fu.0 fu shorten=2 name=Int +f1: execute.fu.1 fu shorten=2 name=Int +f2: execute.fu.2 fu shorten=2 name=Mul +f3: execute.fu.3 fu shorten=2 name=Div +f4: execute.fu.4 fu shorten=2 name=Float +f5: execute.fu.5 fu shorten=2 name=Mem +f6: execute.fu.6 fu shorten=2 name=Misc +iq: execute.inFlightInsts fifo decoder=insts name="inFlightInsts" +im: execute.inFUMemInsts fifo decoder=insts name="inFU..." +sc: execute.scoreboard name="scoreboard" decoder=indexedCounts \ + dataElement=busy border=mid name="scoreboard" strips=38 stripelems=3 +sa: activity dataElement=stages activity name="Stage activity" +ac: activity dataElement=activity decoder=counts border=mid name="Activity" diff --git a/util/minorview/model.py b/util/minorview/model.py new file mode 100644 index 000000000..a120f1f7c --- /dev/null +++ b/util/minorview/model.py @@ -0,0 +1,1109 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley + +import parse +import colours +from colours import unknownColour +from point import Point +import re +import blobs +from time import time as wall_time +import os + +id_parts = "TSPLFE" + +all_ids = set(id_parts) +no_ids = set([]) + +class BlobDataSelect(object): + """Represents which data is displayed for Ided object""" + def __init__(self): + # Copy all_ids + self.ids = set(all_ids) + + def __and__(self, rhs): + """And for filtering""" + ret = BlobDataSelect() + ret.ids = self.ids.intersection(rhs.ids) + return ret + +class BlobVisualData(object): + """Super class for block data colouring""" + def to_striped_block(self, select): + """Return an array of colours to use for a striped block""" + return unknownColour + + def get_inst(self): + """Get an instruction Id (if any) from this data""" + return None + + def get_line(self): + """Get a line Id (if any) from this data""" + return None + + def __repr__(self): + return self.__class__.__name__ + '().from_string(' + \ + self.__str__() + ')' + + def __str__(self): + return '' + +class Id(BlobVisualData): + """A line or instruction id""" + def __init__(self): + self.isFault = False + self.threadId = 0 + self.streamSeqNum = 0 + self.predictionSeqNum = 0 + self.lineSeqNum = 0 + self.fetchSeqNum = 0 + self.execSeqNum = 0 + + def as_list(self): + return [self.threadId, self.streamSeqNum, self.predictionSeqNum, + self.lineSeqNum, self.fetchSeqNum, self.execSeqNum] + + def __cmp__(self, right): + return cmp(self.as_list(), right.as_list()) + + def from_string(self, string): + m = re.match('^(F;)?(\d+)/(\d+)\.(\d+)/(\d+)(/(\d+)(\.(\d+))?)?', + string) + + def seqnum_from_string(string): + if string is None: + return 0 + else: + return int(string) + + if m is None: + print 'Invalid Id string', string + else: + elems = m.groups() + + if elems[0] is not None: + self.isFault = True + else: + self.isFault = False + + self.threadId = seqnum_from_string(elems[1]) + self.streamSeqNum = seqnum_from_string(elems[2]) + self.predictionSeqNum = seqnum_from_string(elems[3]) + self.lineSeqNum = seqnum_from_string(elems[4]) + self.fetchSeqNum = seqnum_from_string(elems[6]) + self.execSeqNum = seqnum_from_string(elems[8]) + return self + + def get_inst(self): + if self.fetchSeqNum != 0: + return self + else: + return None + + def get_line(self): + return self + + def __str__(self): + """Returns the usual id T/S.P/L/F.E string""" + return ( + str(self.threadId) + '/' + + str(self.streamSeqNum) + '.' + + str(self.predictionSeqNum) + '/' + + str(self.lineSeqNum) + '/' + + str(self.fetchSeqNum) + '.' + + str(self.execSeqNum)) + + def to_striped_block(self, select): + ret = [] + + if self.isFault: + ret.append(colours.faultColour) + + if 'T' in select.ids: + ret.append(colours.number_to_colour(self.threadId)) + if 'S' in select.ids: + ret.append(colours.number_to_colour(self.streamSeqNum)) + if 'P' in select.ids: + ret.append(colours.number_to_colour(self.predictionSeqNum)) + if 'L' in select.ids: + ret.append(colours.number_to_colour(self.lineSeqNum)) + if self.fetchSeqNum != 0 and 'F' in select.ids: + ret.append(colours.number_to_colour(self.fetchSeqNum)) + if self.execSeqNum != 0 and 'E' in select.ids: + ret.append(colours.number_to_colour(self.execSeqNum)) + + if len(ret) == 0: + ret = [colours.unknownColour] + + if self.isFault: + ret.append(colours.faultColour) + + return ret + +class Branch(BlobVisualData): + """Branch data new stream and prediction sequence numbers, a branch + reason and a new PC""" + def __init__(self): + self.newStreamSeqNum = 0 + self.newPredictionSeqNum = 0 + self.newPC = 0 + self.reason = "NoBranch" + self.id = Id() + + def from_string(self, string): + m = re.match('^(\w+);(\d+)\.(\d+);([0-9a-fA-Fx]+);(.*)$', string) + + if m is not None: + self.reason, newStreamSeqNum, newPredictionSeqNum, \ + newPC, id = m.groups() + + self.newStreamSeqNum = int(newStreamSeqNum) + self.newPredictionSeqNum = int(newPredictionSeqNum) + self.newPC = int(newPC, 0) + self.id = special_view_decoder(Id)(id) + # self.branch = special_view_decoder(Branch)(branch) + else: + print "Bad Branch data:", string + return self + + def to_striped_block(self, select): + return [colours.number_to_colour(self.newStreamSeqNum), + colours.number_to_colour(self.newPredictionSeqNum), + colours.number_to_colour(self.newPC)] + +class Counts(BlobVisualData): + """Treat the input data as just a /-separated list of count values (or + just a single value)""" + def __init__(self): + self.counts = [] + + def from_string(self, string): + self.counts = map(int, re.split('/', string)) + return self + + def to_striped_block(self, select): + return map(colours.number_to_colour, self.counts) + +class Colour(BlobVisualData): + """A fixed colour block, used for special colour decoding""" + def __init__(self, colour): + self.colour = colour + + def to_striped_block(self, select): + return [self.colour] + +class DcacheAccess(BlobVisualData): + """Data cache accesses [RW];id""" + def __init__(self): + self.direc = 'R' + self.id = Id() + + def from_string(self, string): + self.direc, id = re.match('^([RW]);([^;]*);.*$', string).groups() + self.id.from_string(id) + return self + + def get_inst(self): + return self.id + + def to_striped_block(self, select): + if self.direc == 'R': + direc_colour = colours.readColour + elif self.direc == 'R': + direc_colour = colours.writeColour + else: + direc_colour = colours.errorColour + return [direc_colour] + self.id.to_striped_block(select) + +class ColourPattern(object): + """Super class for decoders that make 2D grids rather than just single + striped blocks""" + def elems(self): + return [] + + def to_striped_block(self, select): + return [[[colours.errorColour]]] + +def special_view_decoder(class_): + """Generate a decode function that checks for special character + arguments first (and generates a fixed colour) before building a + BlobVisualData of the given class""" + def decode(symbol): + if symbol in special_state_colours: + return Colour(special_state_colours[symbol]) + else: + return class_().from_string(symbol) + return decode + +class TwoDColours(ColourPattern): + """A 2D grid pattern decoder""" + def __init__(self, blockss): + self.blockss = blockss + + @classmethod + def decoder(class_, elemClass, dataName): + """Factory for making decoders for particular block types""" + def decode(pairs): + if dataName not in pairs: + print 'TwoDColours: no event data called:', \ + dataName, 'in:', pairs + return class_([[Colour(colours.errorColour)]]) + else: + parsed = parse.list_parser(pairs[dataName]) + return class_(parse.map2(special_view_decoder(elemClass), \ + parsed)) + return decode + + @classmethod + def indexed_decoder(class_, elemClass, dataName, picPairs): + """Factory for making decoders for particular block types but + where the list elements are pairs of (index, data) and + strip and stripelems counts are picked up from the pair + data on the decoder's picture file. This gives a 2D layout + of the values with index 0 at strip=0, elem=0 and index 1 + at strip=0, elem=1""" + def decode(pairs): + if dataName not in pairs: + print 'TwoDColours: no event data called:', \ + dataName, 'in:', pairs + return class_([[Colour(colours.errorColour)]]) + else: + strips = int(picPairs['strips']) + strip_elems = int(picPairs['stripelems']) + + raw_iv_pairs = pairs[dataName] + + parsed = parse.parse_indexed_list(raw_iv_pairs) + + array = [[Colour(colours.emptySlotColour) + for i in xrange(0, strip_elems)] + for j in xrange(0, strips)] + + for index, value in parsed: + try: + array[index % strips][index / strips] = \ + special_view_decoder(elemClass)(value) + except: + print "Element out of range strips: %d," \ + " stripelems %d, index: %d" % (strips, + strip_elems, index) + + # return class_(array) + return class_(array) + return decode + + def elems(self): + """Get a flat list of all elements""" + ret = [] + for blocks in self.blockss: + ret += blocks + return ret + + def to_striped_block(self, select): + return parse.map2(lambda d: d.to_striped_block(select), self.blockss) + +class FrameColours(ColourPattern): + """Decode to a 2D grid which has a single occupied row from the event + data and some blank rows forming a frame with the occupied row as a + 'title' coloured stripe""" + def __init__(self, block, numBlankSlots): + self.numBlankSlots = numBlankSlots + self.block = block + + @classmethod + def decoder(class_, elemClass, numBlankSlots, dataName): + """Factory for element type""" + def decode(pairs): + if dataName not in pairs: + print 'FrameColours: no event data called:', dataName, \ + 'in:', pairs + return class_([Colour(colours.errorColour)]) + else: + parsed = parse.list_parser(pairs[dataName]) + return class_(special_view_decoder(elemClass) + (parsed[0][0]), numBlankSlots) + return decode + + def elems(self): + return [self.block] + + def to_striped_block(self, select): + return ([[self.block.to_striped_block(select)]] + + (self.numBlankSlots * [[[colours.backgroundColour]]])) + +special_state_colours = { + 'U': colours.unknownColour, + 'B': colours.blockedColour, + '-': colours.bubbleColour, + '': colours.emptySlotColour, + 'E': colours.emptySlotColour, + 'R': colours.reservedSlotColour, + 'X': colours.errorColour, + 'F': colours.faultColour, + 'r': colours.readColour, + 'w': colours.writeColour + } + +special_state_names = { + 'U': '(U)nknown', + 'B': '(B)locked', + '-': '(-)Bubble', + '': '()Empty', + 'E': '(E)mpty', + 'R': '(R)eserved', + 'X': '(X)Error', + 'F': '(F)ault', + 'r': '(r)ead', + 'w': '(w)rite' + } + +special_state_chars = special_state_colours.keys() + +# The complete set of available block data types +decoder_element_classes = { + 'insts': Id, + 'lines': Id, + 'branch': Branch, + 'dcache': DcacheAccess, + 'counts': Counts + } + +indexed_decoder_element_classes = { + 'indexedCounts' : Counts + } + +def find_colour_decoder(stripSpace, decoderName, dataName, picPairs): + """Make a colour decoder from some picture file blob attributes""" + if decoderName == 'frame': + return FrameColours.decoder(Counts, stripSpace, dataName) + elif decoderName in decoder_element_classes: + return TwoDColours.decoder(decoder_element_classes[decoderName], + dataName) + elif decoderName in indexed_decoder_element_classes: + return TwoDColours.indexed_decoder( + indexed_decoder_element_classes[decoderName], dataName, picPairs) + else: + return None + +class IdedObj(object): + """An object identified by an Id carrying paired data. + The super class for Inst and Line""" + + def __init__(self, id, pairs={}): + self.id = id + self.pairs = pairs + + def __cmp__(self, right): + return cmp(self.id, right.id) + + def table_line(self): + """Represent the object as a list of table row data""" + return [] + + # FIXME, add a table column titles? + + def __repr__(self): + return ' '.join(self.table_line()) + +class Inst(IdedObj): + """A non-fault instruction""" + def __init__(self, id, disassembly, addr, pairs={}): + super(Inst,self).__init__(id, pairs) + if 'nextAddr' in pairs: + self.nextAddr = int(pairs['nextAddr'], 0) + del pairs['nextAddr'] + else: + self.nextAddr = None + self.disassembly = disassembly + self.addr = addr + + def table_line(self): + if self.nextAddr is not None: + addrStr = '0x%x->0x%x' % (self.addr, self.nextAddr) + else: + addrStr = '0x%x' % self.addr + ret = [addrStr, self.disassembly] + for name, value in self.pairs.iteritems(): + ret.append("%s=%s" % (name, str(value))) + return ret + +class InstFault(IdedObj): + """A fault instruction""" + def __init__(self, id, fault, addr, pairs={}): + super(InstFault,self).__init__(id, pairs) + self.fault = fault + self.addr = addr + + def table_line(self): + ret = ["0x%x" % self.addr, self.fault] + for name, value in self.pairs: + ret.append("%s=%s", name, str(value)) + return ret + +class Line(IdedObj): + """A fetched line""" + def __init__(self, id, vaddr, paddr, size, pairs={}): + super(Line,self).__init__(id, pairs) + self.vaddr = vaddr + self.paddr = paddr + self.size = size + + def table_line(self): + ret = ["0x%x/0x%x" % (self.vaddr, self.paddr), "%d" % self.size] + for name, value in self.pairs: + ret.append("%s=%s", name, str(value)) + return ret + +class LineFault(IdedObj): + """A faulting line""" + def __init__(self, id, fault, vaddr, pairs={}): + super(LineFault,self).__init__(id, pairs) + self.vaddr = vaddr + self.fault = fault + + def table_line(self): + ret = ["0x%x" % self.vaddr, self.fault] + for name, value in self.pairs: + ret.append("%s=%s", name, str(value)) + return ret + +class BlobEvent(object): + """Time event for a single blob""" + def __init__(self, unit, time, pairs = {}): + # blob's unit name + self.unit = unit + self.time = time + # dict of picChar (blob name) to visual data + self.visuals = {} + # Miscellaneous unparsed MinorTrace line data + self.pairs = pairs + # Non-MinorTrace debug printout for this unit at this time + self.comments = [] + + def find_ided_objects(self, model, picChar, includeInstLines): + """Find instructions/lines mentioned in the blob's event + data""" + ret = [] + if picChar in self.visuals: + blocks = self.visuals[picChar].elems() + def find_inst(data): + instId = data.get_inst() + lineId = data.get_line() + if instId is not None: + inst = model.find_inst(instId) + line = model.find_line(instId) + if inst is not None: + ret.append(inst) + if includeInstLines and line is not None: + ret.append(line) + elif lineId is not None: + line = model.find_line(lineId) + if line is not None: + ret.append(line) + map(find_inst, blocks) + return sorted(ret) + +class BlobModel(object): + """Model bringing together blob definitions and parsed events""" + def __init__(self, unitNamePrefix=''): + self.blobs = [] + self.unitNameToBlobs = {} + self.unitEvents = {} + self.clear_events() + self.picSize = Point(20,10) + self.lastTime = 0 + self.unitNamePrefix = unitNamePrefix + + def clear_events(self): + """Drop all events and times""" + self.lastTime = 0 + self.times = [] + self.insts = {} + self.lines = {} + self.numEvents = 0 + + for unit, events in self.unitEvents.iteritems(): + self.unitEvents[unit] = [] + + def add_blob(self, blob): + """Add a parsed blob to the model""" + self.blobs.append(blob) + if blob.unit not in self.unitNameToBlobs: + self.unitNameToBlobs[blob.unit] = [] + + self.unitNameToBlobs[blob.unit].append(blob) + + def add_inst(self, inst): + """Add a MinorInst instruction definition to the model""" + # Is this a non micro-op instruction. Microops (usually) get their + # fetchSeqNum == 0 varient stored first + macroop_key = (inst.id.fetchSeqNum, 0) + full_key = (inst.id.fetchSeqNum, inst.id.execSeqNum) + + if inst.id.execSeqNum != 0 and macroop_key not in self.insts: + self.insts[macroop_key] = inst + + self.insts[full_key] = inst + + def find_inst(self, id): + """Find an instruction either as a microop or macroop""" + macroop_key = (id.fetchSeqNum, 0) + full_key = (id.fetchSeqNum, id.execSeqNum) + + if full_key in self.insts: + return self.insts[full_key] + elif macroop_key in self.insts: + return self.insts[macroop_key] + else: + return None + + def add_line(self, line): + """Add a MinorLine line to the model""" + self.lines[line.id.lineSeqNum] = line + + def add_unit_event(self, event): + """Add a single event to the model. This must be an event at a + time >= the current maximum time""" + if event.unit in self.unitEvents: + events = self.unitEvents[event.unit] + if len(events) > 0 and events[len(events)-1].time > event.time: + print "Bad event ordering" + events.append(event) + self.numEvents += 1 + self.lastTime = max(self.lastTime, event.time) + + def extract_times(self): + """Extract a list of all the times from the seen events. Call after + reading events to give a safe index list to use for time indices""" + times = {} + for unitEvents in self.unitEvents.itervalues(): + for event in unitEvents: + times[event.time] = 1 + self.times = times.keys() + self.times.sort() + + def find_line(self, id): + """Find a line by id""" + key = id.lineSeqNum + return self.lines.get(key, None) + + def find_event_bisection(self, unit, time, events, + lower_index, upper_index): + """Find an event by binary search on time indices""" + while lower_index <= upper_index: + pivot = (upper_index + lower_index) / 2 + pivotEvent = events[pivot] + event_equal = (pivotEvent.time == time or + (pivotEvent.time < time and + (pivot == len(events) - 1 or + events[pivot + 1].time > time))) + + if event_equal: + return pivotEvent + elif time > pivotEvent.time: + if pivot == upper_index: + return None + else: + lower_index = pivot + 1 + elif time < pivotEvent.time: + if pivot == lower_index: + return None + else: + upper_index = pivot - 1 + else: + return None + return None + + def find_unit_event_by_time(self, unit, time): + """Find the last event for the given unit at time <= time""" + if unit in self.unitEvents: + events = self.unitEvents[unit] + ret = self.find_event_bisection(unit, time, events, + 0, len(events)-1) + + return ret + else: + return None + + def find_time_index(self, time): + """Find a time index close to the given time (where + times[return] <= time and times[return+1] > time""" + ret = 0 + lastIndex = len(self.times) - 1 + while ret < lastIndex and self.times[ret + 1] <= time: + ret += 1 + return ret + + def add_minor_inst(self, rest): + """Parse and add a MinorInst line to the model""" + pairs = parse.parse_pairs(rest) + other_pairs = dict(pairs) + + id = Id().from_string(pairs['id']) + del other_pairs['id'] + + addr = int(pairs['addr'], 0) + del other_pairs['addr'] + + if 'inst' in other_pairs: + del other_pairs['inst'] + + # Collapse unnecessary spaces in disassembly + disassembly = re.sub(' *', ' ', + re.sub('^ *', '', pairs['inst'])) + + inst = Inst(id, disassembly, addr, other_pairs) + self.add_inst(inst) + elif 'fault' in other_pairs: + del other_pairs['fault'] + + inst = InstFault(id, pairs['fault'], addr, other_pairs) + + self.add_inst(inst) + + def add_minor_line(self, rest): + """Parse and add a MinorLine line to the model""" + pairs = parse.parse_pairs(rest) + other_pairs = dict(pairs) + + id = Id().from_string(pairs['id']) + del other_pairs['id'] + + vaddr = int(pairs['vaddr'], 0) + del other_pairs['vaddr'] + + if 'paddr' in other_pairs: + del other_pairs['paddr'] + del other_pairs['size'] + paddr = int(pairs['paddr'], 0) + size = int(pairs['size'], 0) + + self.add_line(Line(id, + vaddr, paddr, size, other_pairs)) + elif 'fault' in other_pairs: + del other_pairs['fault'] + + self.add_line(LineFault(id, pairs['fault'], vaddr, other_pairs)) + + def load_events(self, file, startTime=0, endTime=None): + """Load an event file and add everything to this model""" + def update_comments(comments, time): + # Add a list of comments to an existing event, if there is one at + # the given time, or create a new, correctly-timed, event from + # the last event and attach the comments to that + for commentUnit, commentRest in comments: + event = self.find_unit_event_by_time(commentUnit, time) + # Find an event to which this comment can be attached + if event is None: + # No older event, make a new empty one + event = BlobEvent(commentUnit, time, {}) + self.add_unit_event(event) + elif event.time != time: + # Copy the old event and make a new one with the right + # time and comment + newEvent = BlobEvent(commentUnit, time, event.pairs) + newEvent.visuals = dict(event.visuals) + event = newEvent + self.add_unit_event(event) + event.comments.append(commentRest) + + self.clear_events() + + # A negative time will *always* be different from an event time + time = -1 + time_events = {} + last_time_lines = {} + minor_trace_line_count = 0 + comments = [] + + default_colour = [[colours.unknownColour]] + next_progress_print_event_count = 1000 + + if not os.access(file, os.R_OK): + print 'Can\'t open file', file + exit(1) + else: + print 'Opening file', file + + f = open(file) + + start_wall_time = wall_time() + + # Skip leading events + still_skipping = True + l = f.readline() + while l and still_skipping: + match = re.match('^\s*(\d+):', l) + if match is not None: + event_time = match.groups() + if int(event_time[0]) >= startTime: + still_skipping = False + else: + l = f.readline() + else: + l = f.readline() + + match_line_re = re.compile( + '^\s*(\d+):\s*([\w\.]+):\s*(Minor\w+:)?\s*(.*)$') + + # Parse each line of the events file, accumulating comments to be + # attached to MinorTrace events when the time changes + reached_end_time = False + while not reached_end_time and l: + match = match_line_re.match(l) + if match is not None: + event_time, unit, line_type, rest = match.groups() + event_time = int(event_time) + + unit = re.sub('^' + self.unitNamePrefix + '\.?(.*)$', + '\\1', unit) + + # When the time changes, resolve comments + if event_time != time: + if self.numEvents > next_progress_print_event_count: + print ('Parsed to time: %d' % event_time) + next_progress_print_event_count = ( + self.numEvents + 1000) + update_comments(comments, time) + comments = [] + time = event_time + + if line_type is None: + # Treat this line as just a 'comment' + comments.append((unit, rest)) + elif line_type == 'MinorTrace:': + minor_trace_line_count += 1 + + # Only insert this event if it's not the same as + # the last event we saw for this unit + if last_time_lines.get(unit, None) != rest: + event = BlobEvent(unit, event_time, {}) + pairs = parse.parse_pairs(rest) + event.pairs = pairs + + # Try to decode the colour data for this event + blobs = self.unitNameToBlobs.get(unit, []) + for blob in blobs: + if blob.visualDecoder is not None: + event.visuals[blob.picChar] = ( + blob.visualDecoder(pairs)) + + self.add_unit_event(event) + last_time_lines[unit] = rest + elif line_type == 'MinorInst:': + self.add_minor_inst(rest) + elif line_type == 'MinorLine:': + self.add_minor_line(rest) + + if endTime is not None and time > endTime: + reached_end_time = True + + l = f.readline() + + update_comments(comments, time) + self.extract_times() + f.close() + + end_wall_time = wall_time() + + print 'Total events:', minor_trace_line_count, 'unique events:', \ + self.numEvents + print 'Time to parse:', end_wall_time - start_wall_time + + def add_blob_picture(self, offset, pic, nameDict): + """Add a parsed ASCII-art pipeline markup to the model""" + pic_width = 0 + for line in pic: + pic_width = max(pic_width, len(line)) + pic_height = len(pic) + + # Number of horizontal characters per 'pixel'. Should be 2 + charsPerPixel = 2 + + # Clean up pic_width to a multiple of charsPerPixel + pic_width = (pic_width + charsPerPixel - 1) // 2 + + self.picSize = Point(pic_width, pic_height) + + def pic_at(point): + """Return the char pair at the given point. + Returns None for characters off the picture""" + x, y = point.to_pair() + x *= 2 + if y >= len(pic) or x >= len(pic[y]): + return None + else: + return pic[y][x:x + charsPerPixel] + + def clear_pic_at(point): + """Clear the chars at point so we don't trip over them again""" + line = pic[point.y] + x = point.x * charsPerPixel + pic[point.y] = line[0:x] + (' ' * charsPerPixel) + \ + line[x + charsPerPixel:] + + def skip_same_char(start, increment): + """Skip characters which match pic_at(start)""" + char = pic_at(start) + hunt = start + while pic_at(hunt) == char: + hunt += increment + return hunt + + def find_size(start): + """Find the size of a rectangle with top left hand corner at + start consisting of (at least) a -. shaped corner describing + the top right corner of a rectangle of the same char""" + char = pic_at(start) + hunt_x = skip_same_char(start, Point(1,0)) + hunt_y = skip_same_char(start, Point(0,1)) + off_bottom_right = (hunt_x * Point(1,0)) + (hunt_y * Point(0,1)) + return off_bottom_right - start + + def point_return(point): + """Carriage return, line feed""" + return Point(0, point.y + 1) + + def find_arrow(start): + """Find a simple 1-char wide arrow""" + + def body(endChar, contChar, direc): + arrow_point = start + arrow_point += Point(0, 1) + clear_pic_at(start) + while pic_at(arrow_point) == contChar: + clear_pic_at(arrow_point) + arrow_point += Point(0, 1) + + if pic_at(arrow_point) == endChar: + clear_pic_at(arrow_point) + self.add_blob(blobs.Arrow('_', start + offset, + direc = direc, + size = (Point(1, 1) + arrow_point - start))) + else: + print 'Bad arrow', start + + char = pic_at(start) + if char == '-\\': + body('-/', ' :', 'right') + elif char == '/-': + body('\\-', ': ', 'left') + + blank_chars = [' ', ' :', ': '] + + # Traverse the picture left to right, top to bottom to find blobs + seen_dict = {} + point = Point(0,0) + while pic_at(point) is not None: + while pic_at(point) is not None: + char = pic_at(point) + if char == '->': + self.add_blob(blobs.Arrow('_', point + offset, + direc = 'right')) + elif char == '<-': + self.add_blob(blobs.Arrow('_', point + offset, + direc = 'left')) + elif char == '-\\' or char == '/-': + find_arrow(point) + elif char in blank_chars: + pass + else: + if char not in seen_dict: + size = find_size(point) + topLeft = point + offset + if char not in nameDict: + # Unnamed blobs + self.add_blob(blobs.Block(char, + nameDict.get(char, '_'), + topLeft, size = size)) + else: + # Named blobs, set visual info. + blob = nameDict[char] + blob.size = size + blob.topLeft = topLeft + self.add_blob(blob) + seen_dict[char] = True + point = skip_same_char(point, Point(1,0)) + point = point_return(point) + + def load_picture(self, filename): + """Load a picture file into the model""" + def parse_blob_description(char, unit, macros, pairsList): + # Parse the name value pairs in a blob-describing line + def expand_macros(pairs, newPairs): + # Recursively expand macros + for name, value in newPairs: + if name in macros: + expand_macros(pairs, macros[name]) + else: + pairs[name] = value + return pairs + + pairs = expand_macros({}, pairsList) + + ret = None + + typ = pairs.get('type', 'block') + colour = colours.name_to_colour(pairs.get('colour', 'black')) + + if typ == 'key': + ret = blobs.Key(char, unit, Point(0,0), colour) + elif typ == 'block': + ret = blobs.Block(char, unit, Point(0,0), colour) + else: + print "Bad picture blog type:", typ + + if 'hideId' in pairs: + hide = pairs['hideId'] + ret.dataSelect.ids -= set(hide) + + if typ == 'block': + ret.displayName = pairs.get('name', unit) + ret.nameLoc = pairs.get('nameLoc', 'top') + ret.shape = pairs.get('shape', 'box') + ret.stripDir = pairs.get('stripDir', 'horiz') + ret.stripOrd = pairs.get('stripOrd', 'LR') + ret.blankStrips = int(pairs.get('blankStrips', '0')) + ret.shorten = int(pairs.get('shorten', '0')) + + if 'decoder' in pairs: + decoderName = pairs['decoder'] + dataElement = pairs.get('dataElement', decoderName) + + decoder = find_colour_decoder(ret.blankStrips, + decoderName, dataElement, pairs) + if decoder is not None: + ret.visualDecoder = decoder + else: + print 'Bad visualDecoder requested:', decoderName + + if 'border' in pairs: + border = pairs['border'] + if border == 'thin': + ret.border = 0.2 + elif border == 'mid': + ret.border = 0.5 + else: + ret.border = 1.0 + elif typ == 'key': + ret.colours = pairs.get('colours', ret.colours) + + return ret + + def line_is_comment(line): + """Returns true if a line starts with #, returns False + for lines which are None""" + return line is not None \ + and re.match('^\s*#', line) is not None + + def get_line(f): + """Get a line from file f extending that line if it ends in + '\' and dropping lines that start with '#'s""" + ret = f.readline() + + # Discard comment lines + while line_is_comment(ret): + ret = f.readline() + + if ret is not None: + extend_match = re.match('^(.*)\\\\$', ret) + + while extend_match is not None: + new_line = f.readline() + + if new_line is not None and not line_is_comment(new_line): + line_wo_backslash, = extend_match.groups() + ret = line_wo_backslash + new_line + extend_match = re.match('^(.*)\\\\$', ret) + else: + extend_match = None + + return ret + + # Macros are recursively expanded into name=value pairs + macros = {} + + if not os.access(filename, os.R_OK): + print 'Can\'t open file', filename + exit(1) + else: + print 'Opening file', filename + + f = open(filename) + l = get_line(f) + picture = [] + blob_char_dict = {} + + self.unitEvents = {} + self.clear_events() + + # Actually parse the file + in_picture = False + while l: + l = parse.remove_trailing_ws(l) + l = re.sub('#.*', '', l) + + if re.match("^\s*$", l) is not None: + pass + elif l == '<<<': + in_picture = True + elif l == '>>>': + in_picture = False + elif in_picture: + picture.append(re.sub('\s*$', '', l)) + else: + line_match = re.match( + '^([a-zA-Z0-9][a-zA-Z0-9]):\s+([\w.]+)\s*(.*)', l) + macro_match = re.match('macro\s+(\w+):(.*)', l) + + if macro_match is not None: + name, defn = macro_match.groups() + macros[name] = parse.parse_pairs_list(defn) + elif line_match is not None: + char, unit, pairs = line_match.groups() + blob = parse_blob_description(char, unit, macros, + parse.parse_pairs_list(pairs)) + blob_char_dict[char] = blob + # Setup the events structure + self.unitEvents[unit] = [] + else: + print 'Problem with Blob line:', l + + l = get_line(f) + + self.blobs = [] + self.add_blob_picture(Point(0,1), picture, blob_char_dict) diff --git a/util/minorview/parse.py b/util/minorview/parse.py new file mode 100644 index 000000000..352371428 --- /dev/null +++ b/util/minorview/parse.py @@ -0,0 +1,109 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley + +import re + +def list_parser(names): + """Parse a list of elements, some of which might be one-level sublists + within parentheses, into a a list of lists of those elements. For + example: list_parser('(a,b),c') -> [['a', 'b'], 'c']""" + elems = re.split(',', names) + ret = [] + accum = [] + for elem in elems: + if re.search('^\((.*)\)$', elem): + accum.append(re.sub('^\((.*)\)', '\\1', elem)) + ret.append(accum) + accum = [] + elif re.search('^\(', elem): + accum.append(re.sub('^\(', '', elem)) + elif re.search('\)$', elem): + accum.append(re.sub('\)$', '', elem)) + ret.append(accum) + accum = [] + elif len(accum) != 0: + accum.append(elem) + else: + ret.append([elem]) + + if len(accum) > 0: + print 'Non matching brackets in', names + + return ret + +def map2(f, ls): + """map to a depth of 2. That is, given a list of lists, apply + f to those innermost elements """ + return map(lambda l: map(f, l), ls) + +def remove_trailing_ws(line): + return re.sub('\s*$', '', line) + +def remove_leading_and_trailing_ws(line): + return re.sub('\s*$', '', re.sub('^\s*', '', line)) + +def parse_pairs_list(pairString): + """parse a string like 'name=value name2=value2' into a + list of pairs of ('name', 'value') ...""" + ret = [] + pairs = re.finditer('(\w+)(=("[^"]*"|[^\s]*))?', pairString) + for pair in pairs: + name, rest, value = pair.groups() + if value is not None: + value = re.sub('^"(.*)"$', '\\1', value) + ret.append((name, value)) + else: + ret.append((name, '')) + return ret + +def parse_indexed_list(string): + """parse a string of the form "(index,value),(index,value)..." + into a list of index, value pairs""" + + ret = [] + pairs = list_parser(string) + for pair in pairs: + if len(pair) == 2: + index, value = pair + ret.append((int(index), value)) + + return ret + +def parse_pairs(pairString): + """parse a string like 'name=value name2=value2' into a + dictionary of {'name': 'value', 'name2': 'value2'} """ + return dict(parse_pairs_list(pairString)) diff --git a/util/minorview/point.py b/util/minorview/point.py new file mode 100644 index 000000000..75097e2c2 --- /dev/null +++ b/util/minorview/point.py @@ -0,0 +1,78 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley + +class Point(object): + """2D point coordinates/size type""" + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self, rhs): + return Point(self.x + rhs.x, self.y + rhs.y) + + def __sub__(self, rhs): + return Point(self.x - rhs.x, self.y - rhs.y) + + def __mul__(self, rhs): + return Point(self.x * rhs.x, self.y * rhs.y) + + def __div__(self, rhs): + return Point(float(self.x) / rhs.x, float(self.y) / rhs.y) + + def scale(self, factor): + return Point(self.x * factor, self.y * factor) + + def to_pair(self): + return (self.x, self.y) + + def __str__(self): + return "Point(%f,%f)" % (self.x, self.y) + + def __repr__(self): + return "Point(%f,%f)" % (self.x, self.y) + + def is_within_box(self, box): + """Is this point inside the (centre, size) box box""" + centre, size = box + half_size = size.scale(0.5) + top_left = centre - half_size + bottom_right = centre + half_size + return (top_left.x < self.x and + top_left.y < self.y and + bottom_right.x > self.x and + bottom_right.y > self.y) + diff --git a/util/minorview/view.py b/util/minorview/view.py new file mode 100644 index 000000000..8a9aaffea --- /dev/null +++ b/util/minorview/view.py @@ -0,0 +1,524 @@ +# Copyright (c) 2013 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Andrew Bardsley + +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import cairo +import re + +from point import Point +import parse +import colours +import model +from model import Id, BlobModel, BlobDataSelect, special_state_chars +import blobs + +class BlobView(object): + """The canvas view of the pipeline""" + def __init__(self, model): + # A unit blob will appear at size blobSize inside a space of + # size pitch. + self.blobSize = Point(45.0, 45.0) + self.pitch = Point(60.0, 60.0) + self.origin = Point(50.0, 50.0) + # Some common line definitions to cut down on arbitrary + # set_line_widths + self.thickLineWidth = 10.0 + self.thinLineWidth = 4.0 + self.midLineWidth = 6.0 + # The scale from the units of pitch to device units (nominally + # pixels for 1.0 to 1.0 + self.masterScale = Point(1.0,1.0) + self.model = model + self.fillColour = colours.emptySlotColour + self.timeIndex = 0 + self.time = 0 + self.positions = [] + self.controlbar = None + # The sequence number selector state + self.dataSelect = BlobDataSelect() + # Offset of this view's time from self.time used for miniviews + # This is actually an offset of the index into the array of times + # seen in the event file) + self.timeOffset = 0 + # Maximum view size for initial window mapping + self.initialHeight = 600.0 + + # Overlays are speech bubbles explaining blob data + self.overlays = [] + + self.da = gtk.DrawingArea() + def draw(arg1, arg2): + self.redraw() + self.da.connect('expose_event', draw) + + # Handy offsets from the blob size + self.blobIndent = (self.pitch - self.blobSize).scale(0.5) + self.blobIndentFactor = self.blobIndent / self.pitch + + def add_control_bar(self, controlbar): + """Add a BlobController to this view""" + self.controlbar = controlbar + + def draw_to_png(self, filename): + """Draw the view to a PNG file""" + surface = cairo.ImageSurface( + cairo.FORMAT_ARGB32, + self.da.get_allocation().width, + self.da.get_allocation().height) + cr = gtk.gdk.CairoContext(cairo.Context(surface)) + self.draw_to_cr(cr) + surface.write_to_png(filename) + + def draw_to_cr(self, cr): + """Draw to a given CairoContext""" + cr.set_source_color(colours.backgroundColour) + cr.set_line_width(self.thickLineWidth) + cr.paint() + cr.save() + cr.scale(*self.masterScale.to_pair()) + cr.translate(*self.origin.to_pair()) + + positions = [] # {} + + # Draw each blob + for blob in self.model.blobs: + blob_event = self.model.find_unit_event_by_time( + blob.unit, self.time) + + cr.save() + pos = blob.render(cr, self, blob_event, self.dataSelect, + self.time) + cr.restore() + if pos is not None: + (centre, size) = pos + positions.append((blob, centre, size)) + + # Draw all the overlays over the top + for overlay in self.overlays: + overlay.show(cr) + + cr.restore() + + return positions + + def redraw(self): + """Redraw the whole view""" + buffer = cairo.ImageSurface( + cairo.FORMAT_ARGB32, + self.da.get_allocation().width, + self.da.get_allocation().height) + + cr = gtk.gdk.CairoContext(cairo.Context(buffer)) + positions = self.draw_to_cr(cr) + + # Assume that blobs are in order for depth so we want to + # hit the frontmost blob first if we search by position + positions.reverse() + self.positions = positions + + # Paint the drawn buffer onto the DrawingArea + dacr = self.da.window.cairo_create() + dacr.set_source_surface(buffer, 0.0, 0.0) + dacr.paint() + + buffer.finish() + + def set_time_index(self, time): + """Set the time index for the view. A time index is an index into + the model's times array of seen event times""" + self.timeIndex = time + self.timeOffset + if len(self.model.times) != 0: + if self.timeIndex >= len(self.model.times): + self.time = self.model.times[len(self.model.times) - 1] + else: + self.time = self.model.times[self.timeIndex] + else: + self.time = 0 + + def get_pic_size(self): + """Return the size of ASCII-art picture of the pipeline scaled by + the blob pitch""" + return (self.origin + self.pitch * + (self.model.picSize + Point(1.0,1.0))) + + def set_da_size(self): + """Set the DrawingArea size after scaling""" + self.da.set_size_request(10 , int(self.initialHeight)) + +class BlobController(object): + """The controller bar for the viewer""" + def __init__(self, model, view, + defaultEventFile="", defaultPictureFile=""): + self.model = model + self.view = view + self.playTimer = None + self.filenameEntry = gtk.Entry() + self.filenameEntry.set_text(defaultEventFile) + self.pictureEntry = gtk.Entry() + self.pictureEntry.set_text(defaultPictureFile) + self.timeEntry = None + self.defaultEventFile = defaultEventFile + self.startTime = None + self.endTime = None + + self.otherViews = [] + + def make_bar(elems): + box = gtk.HBox(homogeneous=False, spacing=2) + box.set_border_width(2) + for widget, signal, handler in elems: + if signal is not None: + widget.connect(signal, handler) + box.pack_start(widget, False, True, 0) + return box + + self.timeEntry = gtk.Entry() + + t = gtk.ToggleButton('T') + t.set_active(False) + s = gtk.ToggleButton('S') + s.set_active(True) + p = gtk.ToggleButton('P') + p.set_active(True) + l = gtk.ToggleButton('L') + l.set_active(True) + f = gtk.ToggleButton('F') + f.set_active(True) + e = gtk.ToggleButton('E') + e.set_active(True) + + # Should really generate this from above + self.view.dataSelect.ids = set("SPLFE") + + self.bar = gtk.VBox() + self.bar.set_homogeneous(False) + + row1 = make_bar([ + (gtk.Button('Start'), 'clicked', self.time_start), + (gtk.Button('End'), 'clicked', self.time_end), + (gtk.Button('Back'), 'clicked', self.time_back), + (gtk.Button('Forward'), 'clicked', self.time_forward), + (gtk.Button('Play'), 'clicked', self.time_play), + (gtk.Button('Stop'), 'clicked', self.time_stop), + (self.timeEntry, 'activate', self.time_set), + (gtk.Label('Visible ids:'), None, None), + (t, 'clicked', self.toggle_id('T')), + (gtk.Label('/'), None, None), + (s, 'clicked', self.toggle_id('S')), + (gtk.Label('.'), None, None), + (p, 'clicked', self.toggle_id('P')), + (gtk.Label('/'), None, None), + (l, 'clicked', self.toggle_id('L')), + (gtk.Label('/'), None, None), + (f, 'clicked', self.toggle_id('F')), + (gtk.Label('.'), None, None), + (e, 'clicked', self.toggle_id('E')), + (self.filenameEntry, 'activate', self.load_events), + (gtk.Button('Reload'), 'clicked', self.load_events) + ]) + + self.bar.pack_start(row1, False, True, 0) + self.set_time_index(0) + + def toggle_id(self, id): + """One of the sequence number selector buttons has been toggled""" + def toggle(button): + if button.get_active(): + self.view.dataSelect.ids.add(id) + else: + self.view.dataSelect.ids.discard(id) + + # Always leave one thing visible + if len(self.view.dataSelect.ids) == 0: + self.view.dataSelect.ids.add(id) + button.set_active(True) + self.view.redraw() + return toggle + + def set_time_index(self, time): + """Set the time index in the view""" + self.view.set_time_index(time) + + for view in self.otherViews: + view.set_time_index(time) + view.redraw() + + self.timeEntry.set_text(str(self.view.time)) + + def time_start(self, button): + """Start pressed""" + self.set_time_index(0) + self.view.redraw() + + def time_end(self, button): + """End pressed""" + self.set_time_index(len(self.model.times) - 1) + self.view.redraw() + + def time_forward(self, button): + """Step forward pressed""" + self.set_time_index(min(self.view.timeIndex + 1, + len(self.model.times) - 1)) + self.view.redraw() + gtk.gdk.flush() + + def time_back(self, button): + """Step back pressed""" + self.set_time_index(max(self.view.timeIndex - 1, 0)) + self.view.redraw() + + def time_set(self, entry): + """Time dialogue changed. Need to find a suitable time + <= the entry's time""" + newTime = self.model.find_time_index(int(entry.get_text())) + self.set_time_index(newTime) + self.view.redraw() + + def time_step(self): + """Time step while playing""" + if not self.playTimer \ + or self.view.timeIndex == len(self.model.times) - 1: + self.time_stop(None) + return False + else: + self.time_forward(None) + return True + + def time_play(self, play): + """Automatically advance time every 100 ms""" + if not self.playTimer: + self.playTimer = gobject.timeout_add(100, self.time_step) + + def time_stop(self, play): + """Stop play pressed""" + if self.playTimer: + gobject.source_remove(self.playTimer) + self.playTimer = None + + def load_events(self, button): + """Reload events file""" + self.model.load_events(self.filenameEntry.get_text(), + startTime=self.startTime, endTime=self.endTime) + self.set_time_index(min(len(self.model.times) - 1, + self.view.timeIndex)) + self.view.redraw() + +class Overlay(object): + """An Overlay is a speech bubble explaining the data in a blob""" + def __init__(self, model, view, point, blob): + self.model = model + self.view = view + self.point = point + self.blob = blob + + def find_event(self): + """Find the event for a changing time and a fixed blob""" + return self.model.find_unit_event_by_time(self.blob.unit, + self.view.time) + + def show(self, cr): + """Draw the overlay""" + event = self.find_event() + + if event is None: + return + + insts = event.find_ided_objects(self.model, self.blob.picChar, + False) + + cr.set_line_width(self.view.thinLineWidth) + cr.translate(*(Point(0.0,0.0) - self.view.origin).to_pair()) + cr.scale(*(Point(1.0,1.0) / self.view.masterScale).to_pair()) + + # Get formatted data from the insts to format into a table + lines = list(inst.table_line() for inst in insts) + + text_size = 10.0 + cr.set_font_size(text_size) + + def text_width(str): + xb, yb, width, height, dx, dy = cr.text_extents(str) + return width + + # Find the maximum number of columns and the widths of each column + num_columns = 0 + for line in lines: + num_columns = max(num_columns, len(line)) + + widths = [0] * num_columns + for line in lines: + for i in xrange(0, len(line)): + widths[i] = max(widths[i], text_width(line[i])) + + # Calculate the size of the speech bubble + column_gap = 1 * text_size + id_width = 6 * text_size + total_width = sum(widths) + id_width + column_gap * (num_columns + 1) + gap_step = Point(1.0, 0.0).scale(column_gap) + + text_point = self.point + text_step = Point(0.0, text_size) + + size = Point(total_width, text_size * len(insts)) + + # Draw the speech bubble + blobs.speech_bubble(cr, self.point, size, text_size) + cr.set_source_color(colours.backgroundColour) + cr.fill_preserve() + cr.set_source_color(colours.black) + cr.stroke() + + text_point += Point(1.0,1.0).scale(2.0 * text_size) + + id_size = Point(id_width, text_size) + + # Draw the rows in the table + for i in xrange(0, len(insts)): + row_point = text_point + inst = insts[i] + line = lines[i] + blobs.striped_box(cr, row_point + id_size.scale(0.5), + id_size, inst.id.to_striped_block(self.view.dataSelect)) + cr.set_source_color(colours.black) + + row_point += Point(1.0, 0.0).scale(id_width) + row_point += text_step + # Draw the columns of each row + for j in xrange(0, len(line)): + row_point += gap_step + cr.move_to(*row_point.to_pair()) + cr.show_text(line[j]) + row_point += Point(1.0, 0.0).scale(widths[j]) + + text_point += text_step + +class BlobWindow(object): + """The top-level window and its mouse control""" + def __init__(self, model, view, controller): + self.model = model + self.view = view + self.controller = controller + self.controlbar = None + self.window = None + self.miniViewCount = 0 + + def add_control_bar(self, controlbar): + self.controlbar = controlbar + + def show_window(self): + self.window = gtk.Window() + + self.vbox = gtk.VBox() + self.vbox.set_homogeneous(False) + if self.controlbar: + self.vbox.pack_start(self.controlbar, False, True, 0) + self.vbox.add(self.view.da) + + if self.miniViewCount > 0: + self.miniViews = [] + self.miniViewHBox = gtk.HBox(homogeneous=True, spacing=2) + + # Draw mini views + for i in xrange(1, self.miniViewCount + 1): + miniView = BlobView(self.model) + miniView.set_time_index(0) + miniView.masterScale = Point(0.1, 0.1) + miniView.set_da_size() + miniView.timeOffset = i + 1 + self.miniViews.append(miniView) + self.miniViewHBox.pack_start(miniView.da, False, True, 0) + + self.controller.otherViews = self.miniViews + self.vbox.add(self.miniViewHBox) + + self.window.add(self.vbox) + + def show_event(picChar, event): + print '**** Comments for', event.unit, \ + 'at time', self.view.time + for name, value in event.pairs.iteritems(): + print name, '=', value + for comment in event.comments: + print comment + if picChar in event.visuals: + # blocks = event.visuals[picChar].elems() + print '**** Colour data' + objs = event.find_ided_objects(self.model, picChar, True) + for obj in objs: + print ' '.join(obj.table_line()) + + def clicked_da(da, b): + point = Point(b.x, b.y) + + overlay = None + for blob, centre, size in self.view.positions: + if point.is_within_box((centre, size)): + event = self.model.find_unit_event_by_time(blob.unit, + self.view.time) + if event is not None: + if overlay is None: + overlay = Overlay(self.model, self.view, point, + blob) + show_event(blob.picChar, event) + if overlay is not None: + self.view.overlays = [overlay] + else: + self.view.overlays = [] + + self.view.redraw() + + # Set initial size and event callbacks + self.view.set_da_size() + self.view.da.add_events(gtk.gdk.BUTTON_PRESS_MASK) + self.view.da.connect('button-press-event', clicked_da) + self.window.connect('destroy', lambda(widget): gtk.main_quit()) + + def resize(window, event): + """Resize DrawingArea to match new window size""" + size = Point(float(event.width), float(event.height)) + proportion = size / self.view.get_pic_size() + # Preserve aspect ratio + daScale = min(proportion.x, proportion.y) + self.view.masterScale = Point(daScale, daScale) + self.view.overlays = [] + + self.view.da.connect('configure-event', resize) + + self.window.show_all() |