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