| # 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) |