[3570545] | 1 | # This program is public domain |
---|
| 2 | """ |
---|
| 3 | Asychronous execution monitoring service. |
---|
| 4 | |
---|
| 5 | Long running computations need to convey status information to the user. |
---|
| 6 | This status can take multiple forms, such as output to the console or |
---|
| 7 | activity on a GUI, or even mail to your inbox. |
---|
| 8 | |
---|
| 9 | park.monitor defines several standard message types:: |
---|
| 10 | |
---|
| 11 | `Start` for job start |
---|
| 12 | `Join` first message when joining an already running job |
---|
| 13 | `Progress` for job activity |
---|
| 14 | `Improvement` for partial results |
---|
| 15 | `Complete` for final result |
---|
| 16 | `Abort` when job is killed |
---|
| 17 | `Error` when job has an error |
---|
| 18 | `Log` for various debugging messages |
---|
| 19 | |
---|
| 20 | Individual services may have specialized message types. |
---|
| 21 | |
---|
| 22 | park.monitor also defines `Monitor` to process the various kinds of messages, |
---|
| 23 | and dispatch them to the various user defined handlers. |
---|
| 24 | |
---|
| 25 | For each message type, the Monitor dispatcher will look for a function |
---|
| 26 | named onMonitorQQQ where QQQ is the message type. For example, |
---|
| 27 | onMonitorStart(self, message) will be called in response to a Start message. |
---|
| 28 | If onMonitorQQQ is not defined, then onMonitorMessage will be called. The |
---|
| 29 | default behaviour of onMonitorMessage is to print the message on the console. |
---|
| 30 | |
---|
| 31 | Log messages are sent to the standard system logger. See logging in the |
---|
| 32 | python standard library for details. |
---|
| 33 | |
---|
| 34 | The Monitor class has methods for onMonitorStart(message), etc. |
---|
| 35 | In panel, be sure to have methods for onMonitorStart(message), |
---|
| 36 | onMonitorProgress(message), etc., for the kinds of monitor messages |
---|
| 37 | the application will send. The catch-all method is onMonitorMessage. |
---|
| 38 | |
---|
| 39 | See `park.monitor` for details on the message types. Individual services |
---|
| 40 | may have additional message types. |
---|
| 41 | |
---|
| 42 | """ |
---|
| 43 | __all__ = ['Monitor'] |
---|
| 44 | |
---|
| 45 | import sys |
---|
| 46 | import logging |
---|
| 47 | import traceback |
---|
| 48 | |
---|
| 49 | class Message(object): |
---|
| 50 | """ |
---|
| 51 | Message type |
---|
| 52 | """ |
---|
| 53 | |
---|
| 54 | class Start(Message): |
---|
| 55 | """ |
---|
| 56 | Start. |
---|
| 57 | |
---|
| 58 | Sent when the job has started processing. |
---|
| 59 | """ |
---|
| 60 | def __str__(self): return "Start" |
---|
| 61 | |
---|
| 62 | class Join(Message): |
---|
| 63 | """ |
---|
| 64 | Join: k units of n with partial result |
---|
| 65 | |
---|
| 66 | Sent when the listener is attached to a running job. This is |
---|
| 67 | a combination of Progress and Improvement. |
---|
| 68 | """ |
---|
| 69 | def __init__(self, k, n, partial): |
---|
| 70 | self.total = n |
---|
| 71 | """Total work to complete""" |
---|
| 72 | self.complete = k |
---|
| 73 | """Amount of work complete""" |
---|
| 74 | self.result = partial |
---|
| 75 | """The partial result completed; this is job specific""" |
---|
| 76 | def __str__(self): return "Join: "+str(self.result) |
---|
| 77 | |
---|
| 78 | class Progress(Message): |
---|
| 79 | """ |
---|
| 80 | Progress: k units of n. |
---|
| 81 | |
---|
| 82 | Sent when a certain amount of progress has happened. |
---|
| 83 | |
---|
| 84 | Use the job controller to specify the reporting |
---|
| 85 | frequency (time and/or percentage). |
---|
| 86 | """ |
---|
| 87 | def __init__(self, k, n, units=None): |
---|
| 88 | self.total = n |
---|
| 89 | """Total work to complete""" |
---|
| 90 | self.complete = k |
---|
| 91 | """Amount of work complete""" |
---|
| 92 | self.units = units |
---|
| 93 | """Units of work, or None""" |
---|
| 94 | def __str__(self): |
---|
| 95 | if self.units is not None: |
---|
| 96 | return "Progress: %s %s of %s"%(self.complete, self.units, self.total) |
---|
| 97 | else: |
---|
| 98 | return "Progress: %s of %s"%(self.complete, self.total) |
---|
| 99 | |
---|
| 100 | class Improvement(Message): |
---|
| 101 | """ |
---|
| 102 | Improvement: partial result. |
---|
| 103 | |
---|
| 104 | Use the job controller to specify the improvement frequency |
---|
| 105 | (time and/or percentage). |
---|
| 106 | """ |
---|
| 107 | def __init__(self, partial): |
---|
| 108 | self.result = partial |
---|
| 109 | """The partial result completed; this is job specific""" |
---|
| 110 | def __str__(self): |
---|
| 111 | return "Improvement: "+str(self.result) |
---|
| 112 | |
---|
| 113 | class Complete(Message): |
---|
| 114 | """ |
---|
| 115 | Complete: final result. |
---|
| 116 | """ |
---|
| 117 | def __init__(self, final): |
---|
| 118 | self.result = final |
---|
| 119 | """The final completed result; this is job specific""" |
---|
| 120 | def __str__(self): |
---|
| 121 | return "Complete: "+str(self.result) |
---|
| 122 | |
---|
| 123 | class Error(Message): |
---|
| 124 | """ |
---|
| 125 | Traceback stack trace. |
---|
| 126 | """ |
---|
| 127 | def __init__(self, trace=None): |
---|
| 128 | if trace == None: trace = sys.exc_info() |
---|
| 129 | self.trace = trace |
---|
| 130 | """The stack trace returned from exc_info()""" |
---|
| 131 | def __str__(self): |
---|
| 132 | #print "traceback",traceback.format_exception(*self.trace) |
---|
| 133 | try: |
---|
| 134 | return "".join(traceback.format_exception(*self.trace)) |
---|
| 135 | except TypeError: |
---|
| 136 | return "Error: "+str(self.trace) |
---|
| 137 | |
---|
| 138 | class Abort(Message): |
---|
| 139 | """ |
---|
| 140 | Abort: partial result |
---|
| 141 | |
---|
| 142 | Use the job controller to signal an abort. |
---|
| 143 | """ |
---|
| 144 | def __init__(self, partial): |
---|
| 145 | self.result = partial |
---|
| 146 | """The partial result completed; this is job specific""" |
---|
| 147 | def __str__(self): |
---|
| 148 | return "Abort: "+str(self.result) |
---|
| 149 | |
---|
| 150 | class Log(Message): |
---|
| 151 | """ |
---|
| 152 | Log module.function: log record |
---|
| 153 | """ |
---|
| 154 | formatter = logging.Formatter("Log %(module)s.%(funcName)s: %(message)s") |
---|
| 155 | def __init__(self, record): |
---|
| 156 | self.record = record |
---|
| 157 | """The partial result completed; this is job specific""" |
---|
| 158 | def __str__(self): |
---|
| 159 | return self.formatter.format(self.record) |
---|
| 160 | |
---|
| 161 | class Monitor(object): |
---|
| 162 | """ |
---|
| 163 | Messages that are received during the processing of the job. |
---|
| 164 | |
---|
| 165 | Standard message types:: |
---|
| 166 | |
---|
| 167 | `Start`, `Progress`, `Improvement`, `Complete`, `Error`, `Abort`, `Log` |
---|
| 168 | |
---|
| 169 | Specific job types may have their own monitor messages. |
---|
| 170 | |
---|
| 171 | The messages themselves should all produce nicely formatted results |
---|
| 172 | in response to str(message). |
---|
| 173 | |
---|
| 174 | The message dispatch calls on<Class>(message) if the on<Class> |
---|
| 175 | method exists for the message type. If not, then dispatch |
---|
| 176 | calls otherwise(message). By default onLog(message) submits the |
---|
| 177 | log record to the logger. |
---|
| 178 | |
---|
| 179 | Subclass Monitor to define your own behaviours. |
---|
| 180 | """ |
---|
| 181 | def put(self, message): |
---|
| 182 | """ |
---|
| 183 | Called from thread when new message has arrived. |
---|
| 184 | """ |
---|
| 185 | fn = getattr(self, |
---|
| 186 | "onMonitor"+message.__class__.__name__, |
---|
| 187 | self.onMonitorMessage) |
---|
| 188 | fn(message) |
---|
| 189 | |
---|
| 190 | def onMonitorMessage(self, message): |
---|
| 191 | """ |
---|
| 192 | What to do if the message handler is not found. |
---|
| 193 | |
---|
| 194 | Default is to ignore the message. |
---|
| 195 | """ |
---|
| 196 | print ">",str(message) |
---|
| 197 | |
---|
| 198 | def onMonitorLog(self, message): |
---|
| 199 | """ |
---|
| 200 | Called when the job sends a logging record. |
---|
| 201 | |
---|
| 202 | The logging record contains a normal python logging record. |
---|
| 203 | |
---|
| 204 | The default behaviour is to tie into the application logging |
---|
| 205 | system using:: |
---|
| 206 | |
---|
| 207 | logger = logging.getLogger(message.record.name) |
---|
| 208 | logger.handle(message.record) |
---|
| 209 | |
---|
| 210 | Logging levels are set in the job controller. |
---|
| 211 | """ |
---|
| 212 | logging.basicConfig() |
---|
| 213 | logger = logging.getLogger(message.record.name) |
---|
| 214 | logger.handle(message.record) |
---|
| 215 | |
---|
| 216 | |
---|
| 217 | def demo(rate=0): |
---|
| 218 | import sys, time, thread, logging |
---|
| 219 | import park.monitor |
---|
| 220 | |
---|
| 221 | monitor = Monitor() |
---|
| 222 | def messagestream(monitor,rate,stream): |
---|
| 223 | for m in stream: |
---|
| 224 | time.sleep(rate) |
---|
| 225 | monitor.put(m) |
---|
| 226 | time.sleep(rate) |
---|
| 227 | R = logging.LogRecord('hi',60,'hello.py',3,'log message',(),None,'here') |
---|
| 228 | try: raise Exception('Test exception') |
---|
| 229 | except: trace = sys.exc_info() |
---|
| 230 | stream=[park.monitor.Start(), |
---|
| 231 | park.monitor.Progress(1,10), |
---|
| 232 | park.monitor.Progress(2,10), |
---|
| 233 | park.monitor.Progress(3,10), |
---|
| 234 | park.monitor.Join('Good'), |
---|
| 235 | park.monitor.Improvement('Better!'), |
---|
| 236 | park.monitor.Abort('Abandoned'), |
---|
| 237 | park.monitor.Start(), |
---|
| 238 | park.monitor.Progress(1,10,'seconds'), |
---|
| 239 | park.monitor.Improvement('Better!'), |
---|
| 240 | park.monitor.Progress(8,10), |
---|
| 241 | park.monitor.Complete('Best!'), |
---|
| 242 | park.monitor.Start(), |
---|
| 243 | park.monitor.Log(R), |
---|
| 244 | park.monitor.Progress(6,10), |
---|
| 245 | park.monitor.Error(trace)] |
---|
| 246 | thread.start_new_thread(messagestream, (monitor,rate,stream)) |
---|
| 247 | |
---|
| 248 | time.sleep(20*(rate+0.01)) |
---|
| 249 | |
---|
| 250 | if __name__ == "__main__": demo(rate=0.1) |
---|