source: src/mlx/logger.py@ 345:a62373a28d90

Last change on this file since 345:a62373a28d90 was 345:a62373a28d90, checked in by István Váradi <ivaradi@…>, 11 years ago

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

File size: 11.2 KB
Line 
1
2from fs import sendMessage
3import const
4import util
5
6import sys
7import time
8import bisect
9
10#--------------------------------------------------------------------------------------
11
12## @package mlx.logger
13#
14# The module for the logger.
15#
16# While the program itself is "logger", it contains an internal logger, which
17# maintains the textual log containing information on the various events and is
18# the reason why the program is called "logger".
19#
20# The log is made up of lines containing an optional timestamp and the text of
21# the message. A line can be updated after having been put into the log by
22# referring to its index.
23#
24# The logger object also maintains a separate set of faults and ensures that
25# one fault type has only one score, even if that fault has been reported
26# multiple times.
27
28#--------------------------------------------------------------------------------------
29
30class Logger(object):
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
154 # FIXME: shall we use const.stage2string() instead?
155 _stages = { const.STAGE_BOARDING : "Boarding",
156 const.STAGE_PUSHANDTAXI : "Pushback and Taxi",
157 const.STAGE_TAKEOFF : "Takeoff",
158 const.STAGE_RTO : "RTO",
159 const.STAGE_CLIMB : "Climb",
160 const.STAGE_CRUISE : "Cruise",
161 const.STAGE_DESCENT : "Descent",
162 const.STAGE_LANDING : "Landing",
163 const.STAGE_TAXIAFTERLAND : "Taxi",
164 const.STAGE_PARKING : "Parking",
165 const.STAGE_GOAROUND : "Go-Around",
166 const.STAGE_END : "End" }
167
168 NO_GO_SCORE = 10000
169
170 def __init__(self, output):
171 """Construct the logger."""
172 self._entries = {}
173 self._lines = []
174
175 self._faults = {}
176
177 self._output = output
178
179 @property
180 def lines(self):
181 """Get the lines of the log."""
182 return [(entry.timestampString, entry.text) for entry in self._lines]
183
184 @property
185 def faultLineIndexes(self):
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
194
195 def reset(self):
196 """Reset the logger.
197
198 The faults logged so far will be cleared."""
199 self._entries = {}
200 self._lines = []
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):
210 """Put an untimed message into the log."""
211 timestamp = self._lines[-1].timestamp if self._lines else 0
212 return self._addEntry(Logger.Entry(timestamp, msg,
213 showTimestamp = False))
214
215 def debug(self, msg):
216 """Log a debug message."""
217 print "[DEBUG]", msg
218
219 def stage(self, timestamp, stage):
220 """Report a change in the flight stage."""
221 s = Logger._stages[stage] if stage in Logger._stages else "<Unknown>"
222 self.message(timestamp, "--- %s ---" % (s,))
223 if stage==const.STAGE_END:
224 self.untimedMessage("Rating: %.0f" % (self.getRating(),))
225 else:
226 messageType = \
227 const.MESSAGETYPE_INFLIGHT if stage in \
228 [const.STAGE_CLIMB, const.STAGE_CRUISE, \
229 const.STAGE_DESCENT, const.STAGE_LANDING] \
230 else const.MESSAGETYPE_INFORMATION
231 sendMessage(messageType, "Flight stage: " + s, 3)
232
233 def fault(self, faultID, timestamp, what, score):
234 """Report a fault.
235
236 faultID as a unique ID for the given kind of fault. If another fault of
237 this ID has been reported earlier, it will be reported again only if
238 the score is greater than last time. This ID can be, e.g. the checker
239 the report comes from.
240
241 Returns an ID of the fault, or -1 if it was not logged."""
242 if faultID in self._faults:
243 if score<=self._faults[faultID].score:
244 return -1
245
246 text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
247 else "%s (%.1f)" % (what, score)
248
249 id = self._addEntry(Logger.Entry(timestamp, text, faultID = faultID,
250 faultScore = score))
251
252 (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
253 if score==Logger.NO_GO_SCORE \
254 else (const.MESSAGETYPE_FAULT, 5)
255 sendMessage(messageType, text, duration)
256
257 return id
258
259 def noGo(self, faultID, timestamp, what):
260 """Report a No-Go fault."""
261 return self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
262
263 def getRating(self):
264 """Get the rating of the flight so far."""
265 totalScore = 100
266 for fault in self._faults.itervalues():
267 score = fault.score
268 if score==Logger.NO_GO_SCORE:
269 return -score
270 else:
271 totalScore -= score
272 return totalScore
273
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
336#--------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.