summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rwxr-xr-xutil/minorview.py107
-rw-r--r--util/minorview/__init__.py36
-rw-r--r--util/minorview/blobs.py461
-rw-r--r--util/minorview/colours.py68
-rw-r--r--util/minorview/minor.pic154
-rw-r--r--util/minorview/model.py1109
-rw-r--r--util/minorview/parse.py109
-rw-r--r--util/minorview/point.py78
-rw-r--r--util/minorview/view.py524
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()