1# -*- coding: utf-8 -*- 2# 3# Copyright 2011 Google Inc. All Rights Reserved. 4# 5"""Tools for recording and reporting timeline of abstract events. 6 7You can store any events provided that they can be stringified. 8""" 9 10__author__ = 'kbaclawski@google.com (Krystian Baclawski)' 11 12import collections 13import datetime 14import time 15 16 17class _EventRecord(object): 18 """Internal class. Attaches extra information to an event.""" 19 20 def __init__(self, event, time_started=None, time_elapsed=None): 21 self._event = event 22 self._time_started = time_started or time.time() 23 self._time_elapsed = None 24 25 if time_elapsed: 26 self.time_elapsed = time_elapsed 27 28 @property 29 def event(self): 30 return self._event 31 32 @property 33 def time_started(self): 34 return self._time_started 35 36 def _TimeElapsedGet(self): 37 if self.has_finished: 38 time_elapsed = self._time_elapsed 39 else: 40 time_elapsed = time.time() - self._time_started 41 42 return datetime.timedelta(seconds=time_elapsed) 43 44 def _TimeElapsedSet(self, time_elapsed): 45 if isinstance(time_elapsed, datetime.timedelta): 46 self._time_elapsed = time_elapsed.seconds 47 else: 48 self._time_elapsed = time_elapsed 49 50 time_elapsed = property(_TimeElapsedGet, _TimeElapsedSet) 51 52 @property 53 def has_finished(self): 54 return self._time_elapsed is not None 55 56 def GetTimeStartedFormatted(self): 57 return time.strftime('%m/%d/%Y %H:%M:%S', time.gmtime(self._time_started)) 58 59 def GetTimeElapsedRounded(self): 60 return datetime.timedelta(seconds=int(self.time_elapsed.seconds)) 61 62 def Finish(self): 63 if not self.has_finished: 64 self._time_elapsed = time.time() - self._time_started 65 66 67class _Transition(collections.namedtuple('_Transition', ('from_', 'to_'))): 68 """Internal class. Represents transition point between events / states.""" 69 70 def __str__(self): 71 return '%s => %s' % (self.from_, self.to_) 72 73 74class EventHistory(collections.Sequence): 75 """Records events and provides human readable events timeline.""" 76 77 def __init__(self, records=None): 78 self._records = records or [] 79 80 def __len__(self): 81 return len(self._records) 82 83 def __iter__(self): 84 return iter(self._records) 85 86 def __getitem__(self, index): 87 return self._records[index] 88 89 @property 90 def last(self): 91 if self._records: 92 return self._records[-1] 93 94 def AddEvent(self, event): 95 if self.last: 96 self.last.Finish() 97 98 evrec = _EventRecord(event) 99 self._records.append(evrec) 100 return evrec 101 102 def GetTotalTime(self): 103 if self._records: 104 total_time_elapsed = sum(evrec.time_elapsed.seconds 105 for evrec in self._records) 106 107 return datetime.timedelta(seconds=int(total_time_elapsed)) 108 109 def GetTransitionEventHistory(self): 110 records = [] 111 112 if self._records: 113 for num, next_evrec in enumerate(self._records[1:], start=1): 114 evrec = self._records[num - 1] 115 116 records.append(_EventRecord( 117 _Transition(evrec.event, next_evrec.event), evrec.time_started, 118 evrec.time_elapsed)) 119 120 if not self.last.has_finished: 121 records.append(_EventRecord( 122 _Transition(self.last.event, 123 'NOW'), self.last.time_started, self.last.time_elapsed)) 124 125 return EventHistory(records) 126 127 @staticmethod 128 def _GetReport(history, report_name): 129 report = [report_name] 130 131 for num, evrec in enumerate(history, start=1): 132 time_elapsed = str(evrec.GetTimeElapsedRounded()) 133 134 if not evrec.has_finished: 135 time_elapsed.append(' (not finished)') 136 137 report.append('%d) %s: %s: %s' % (num, evrec.GetTimeStartedFormatted(), 138 evrec.event, time_elapsed)) 139 140 report.append('Total Time: %s' % history.GetTotalTime()) 141 142 return '\n'.join(report) 143 144 def GetEventReport(self): 145 return EventHistory._GetReport(self, 'Timeline of events:') 146 147 def GetTransitionEventReport(self): 148 return EventHistory._GetReport(self.GetTransitionEventHistory(), 149 'Timeline of transition events:') 150