Ignore:
Timestamp:
12/08/12 15:02:57 (11 years ago)
Author:
István Váradi <ivaradi@…>
Branch:
default
hg-Phase:
(<MercurialRepository 1 'hg:/home/ivaradi/mlx/hg' '/'>, 'public')
Message:

Implemented the new, more flexible way of storing the log during flight (#143)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/mlx/logger.py

    r315 r345  
    66import sys
    77import time
     8import bisect
    89
    910#--------------------------------------------------------------------------------------
     
    2829
    2930class Logger(object):
    30     """The class with the interface to log the various events."""
     31    """The class with the interface to log the various events.
     32
     33    It contains a list of entries ordered by their timestamps and their ever
     34    increasing IDs."""
     35
     36    class Entry(object):
     37        """An entry in the log."""
     38
     39        # The ID of the next entry to be created
     40        _nextID = 1
     41
     42        def __init__(self, timestamp, text, showTimestamp = True,
     43                     faultID = None, faultScore = 0, id = None):
     44            """Construct the entry."""
     45            if id is None:
     46                self._id = self._nextID
     47                Logger.Entry._nextID += 1
     48            else:
     49                self._id = id
     50
     51            self._timestamp = timestamp
     52            self._text = text
     53            self._showTimestamp = showTimestamp
     54
     55            self._faultID = faultID
     56            self._faultScore = faultScore
     57
     58        @property
     59        def id(self):
     60            """Get the ID of the entry."""
     61            return self._id
     62
     63        @property
     64        def timestamp(self):
     65            """Get the timestamp of this entry."""
     66            return self._timestamp
     67
     68        @property
     69        def timestampString(self):
     70            """Get the timestamp string of this entry.
     71
     72            It returns None, if the timestamp of the entry is not visible."""
     73            return util.getTimestampString(self._timestamp) \
     74                   if self._showTimestamp else None
     75
     76        @property
     77        def text(self):
     78            """Get the text of this entry."""
     79            return self._text
     80
     81        @property
     82        def isFault(self):
     83            """Determine if this is a log entry about a fault."""
     84            return self._faultID is not None
     85
     86        @property
     87        def faultID(self):
     88            """Get the fault ID of the entry.
     89
     90            It may be None, if the entry is not a fault entry."""
     91            return self._faultID
     92
     93        @property
     94        def faultScore(self):
     95            """Get the fault score of the entry, if it is a fault."""
     96            return self._faultScore
     97
     98        def copy(self, text = None):
     99            """Create a copy of this entry with the given values changed."""
     100            return Logger.Entry(self._timestamp,
     101                                self._text if text is None else text,
     102                                showTimestamp = self._showTimestamp,
     103                                faultID = self._faultID,
     104                                faultScore = self._faultScore,
     105                                id = self._id)
     106
     107        def __cmp__(self, other):
     108            """Compare two entries
     109
     110            First their timestamps are compared, and if those are equal, then
     111            their IDs."""
     112            result = cmp(self._timestamp, other.timestamp)
     113            if result==0:
     114                result = cmp(self._id, other._id)
     115            return result
     116
     117    class Fault(object):
     118        """Information about a fault.
     119
     120        It contains the list of log entries that belong to this fault. The list
     121        is ordered so that the first element contains the entry with the
     122        highest score, so that it should be easy to find the actual score."""
     123        def __init__(self, entry):
     124            """Construct the fault info with the given log entry as its only
     125            one."""
     126            self._entries = [entry]
     127
     128        @property
     129        def score(self):
     130            """Get the score of this fault, i.e. the score of the entry with
     131            the highest score."""
     132            return self._entries[0].faultScore if self._entries else 0
     133
     134        def addEntry(self, entry):
     135            """Add an entry to this fault.
     136
     137            The entries will be sorted."""
     138            entries = self._entries
     139            entries.append(entry)
     140            entries.sort(key = Logger.Entry.faultScore, reverse = True)
     141
     142        def removeEntry(self, entry):
     143            """Remove the given entry.
     144
     145            Returns True if at least one entry remains, False otherwise."""
     146            entries = self._entries
     147            for index in range(0, len(entries)):
     148                if entry is entries[index]:
     149                    del entries[index]
     150                    break
     151
     152            return len(entries)>0
     153
    31154    # FIXME: shall we use const.stage2string() instead?
    32155    _stages = { const.STAGE_BOARDING : "Boarding",
     
    42165                const.STAGE_GOAROUND : "Go-Around",
    43166                const.STAGE_END : "End" }
    44    
     167
    45168    NO_GO_SCORE = 10000
    46169
    47170    def __init__(self, output):
    48171        """Construct the logger."""
     172        self._entries = {}
    49173        self._lines = []
     174
    50175        self._faults = {}
    51         self._faultLineIndexes = []
     176
    52177        self._output = output
    53178
     
    55180    def lines(self):
    56181        """Get the lines of the log."""
    57         return self._lines
     182        return [(entry.timestampString, entry.text) for entry in self._lines]
    58183
    59184    @property
    60185    def faultLineIndexes(self):
    61         """Get the array of the indexes of the log line that contains a
    62         fault."""
    63         return self._faultLineIndexes
     186        """Get the sorted array of the indexes of those log lines that contain
     187        a fault."""
     188        faultLineIndexes = []
     189        lines = self._lines
     190        for index in range(0, len(lines)):
     191            if lines[index].isFault:
     192                faultLineIndexes.append(index)
     193        return faultLineIndexes
    64194
    65195    def reset(self):
     
    67197
    68198        The faults logged so far will be cleared."""
     199        self._entries = {}
    69200        self._lines = []
    70         self._faults.clear()
    71         self._faultLineIndexes = []
    72                
    73     def message(self, timestamp, msg, isFault = False):
    74         """Put a simple textual message into the log with the given timestamp."""
    75         timeStr = util.getTimestampString(timestamp)
    76         return self._logLine(msg, timeStr, isFault = isFault)
    77 
    78     def untimedMessage(self, msg, isFault = False):
     201        self._faults = {}
     202
     203    def message(self, timestamp, msg):
     204        """Put a simple textual message into the log with the given timestamp.
     205
     206        Returns an ID of the message so that it could be referred to later."""
     207        return self._addEntry(Logger.Entry(timestamp, msg))
     208
     209    def untimedMessage(self, msg):
    79210        """Put an untimed message into the log."""
    80         return self._logLine(msg, isFault = isFault)
     211        timestamp = self._lines[-1].timestamp if self._lines else 0
     212        return self._addEntry(Logger.Entry(timestamp, msg,
     213                                           showTimestamp = False))
    81214
    82215    def debug(self, msg):
     
    97230                else const.MESSAGETYPE_INFORMATION
    98231            sendMessage(messageType, "Flight stage: " + s, 3)
    99        
     232
    100233    def fault(self, faultID, timestamp, what, score):
    101234        """Report a fault.
     
    104237        this ID has been reported earlier, it will be reported again only if
    105238        the score is greater than last time. This ID can be, e.g. the checker
    106         the report comes from."""
     239        the report comes from.
     240
     241        Returns an ID of the fault, or -1 if it was not logged."""
    107242        if faultID in self._faults:
    108             if score<=self._faults[faultID]:
    109                 return
    110         self._faults[faultID] = score
     243            if score<=self._faults[faultID].score:
     244                return -1
     245
    111246        text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
    112247               else "%s (%.1f)" % (what, score)
    113         lineIndex = self.message(timestamp, text, isFault = True)
    114         self._faultLineIndexes.append(lineIndex)
     248
     249        id = self._addEntry(Logger.Entry(timestamp, text, faultID = faultID,
     250                                         faultScore = score))
     251
    115252        (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
    116253                                  if score==Logger.NO_GO_SCORE \
    117254                                  else (const.MESSAGETYPE_FAULT, 5)
    118         sendMessage(messageType, text, duration)           
     255        sendMessage(messageType, text, duration)
     256
     257        return id
    119258
    120259    def noGo(self, faultID, timestamp, what):
    121260        """Report a No-Go fault."""
    122         self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
     261        return self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
    123262
    124263    def getRating(self):
    125264        """Get the rating of the flight so far."""
    126265        totalScore = 100
    127         for (id, score) in self._faults.iteritems():
     266        for fault in self._faults.itervalues():
     267            score = fault.score
    128268            if score==Logger.NO_GO_SCORE:
    129269                return -score
     
    132272        return totalScore
    133273
    134     def updateLine(self, index, line):
    135         """Update the line at the given index with the given string."""
    136         (timeStr, _line) = self._lines[index]
    137         self._lines[index] = (timeStr, line)
    138         self._output.updateFlightLogLine(index, timeStr, line)
    139 
    140     def _logLine(self, line, timeStr = None, isFault = False):
    141         """Log the given line."""
    142         index = len(self._lines)
    143         self._lines.append((timeStr, line))
    144         self._output.addFlightLogLine(timeStr, line, isFault)
    145         return index
    146        
     274    def updateLine(self, id, line):
     275        """Update the line with the given ID with the given string.
     276
     277        Note, that it does not change the status of the line as a fault!"""
     278        self._updateEntry(id, self._entries[id].copy(text = line))
     279
     280    def _addEntry(self, entry):
     281        """Add the given entry to the log.
     282
     283        @return the ID of the new entry."""
     284        assert entry.id not in self._entries
     285
     286        self._entries[entry.id] = entry
     287
     288        if not self._lines or entry>self._lines[-1]:
     289            index = len(self._lines)
     290            self._lines.append(entry)
     291        else:
     292            index = bisect.bisect_left(self._lines, entry)
     293            self._lines.insert(index, entry)
     294
     295        if entry.isFault:
     296            self._addFault(entry)
     297
     298        self._output.insertFlightLogLine(index, entry.timestampString,
     299                                         entry.text, entry.isFault)
     300
     301        return entry.id
     302
     303    def _updateEntry(self, id, newEntry):
     304        """Update the entry with the given ID from the given new entry."""
     305        self._removeEntry(id)
     306        self._addEntry(newEntry)
     307
     308    def _removeEntry(self, id):
     309        """Remove the entry with the given ID."""
     310        assert id in self._entries
     311
     312        entry = self._entries[id]
     313        del self._entries[id]
     314
     315        for index in range(len(self._lines)-1, -1, -1):
     316            if self._lines[index] is entry:
     317                break
     318        del self._lines[index]
     319
     320        if entry.isFault:
     321            faultID = entry.faultID
     322            fault = self._faults[faultID]
     323            if not fault.removeEntry(entry):
     324                del self._faults[faultID]
     325
     326        self._output.removeFlightLogLine(index)
     327
     328    def _addFault(self, entry):
     329        """Add the given fault entry to the fault with the given ID."""
     330        faultID = entry.faultID
     331        if faultID in self._faults:
     332            self._faults[faultID].addEntry(entry)
     333        else:
     334            self._faults[faultID] = Logger.Fault(entry)
     335
    147336#--------------------------------------------------------------------------------------
Note: See TracChangeset for help on using the changeset viewer.