diff options
Diffstat (limited to 'util/minorview/model.py')
-rw-r--r-- | util/minorview/model.py | 1109 |
1 files changed, 1109 insertions, 0 deletions
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) |