1 """
2 A basic monitor that provides a more convenient use of the process Groups.
3 """
4 import multiprocessing
5 import time
6
7 import proc
8
9 """
10 Manages a Group by collecting output and displaying feedback. This is an abstract
11 class which provides the basic mechanism.
12 """
14
15 - def __init__( self, max_simul = multiprocessing.cpu_count(), feedback_timeout = 5.0 ):
16 """
17 @param max_simul: the maximum number of processes which can be running
18 at the same time
19 @param feedback_timeout: generate progress feedback at this interval
20 """
21 self.group = proc.Group()
22 self.max_simul = max_simul
23 self.jobs = {}
24 self.feedback_timeout = feedback_timeout
25 self.last_feedback = time.time()
26 self.job_count = 0
27
29 """
30 Converts a variable format input command list to a set of Jobs.
31 """
32 out_cmds = []
33 for cmd in in_cmds:
34 if not isinstance(cmd,Job):
35 cmd = Job(cmd)
36
37 if cmd.id == None:
38 cmd.id = self.job_count
39 self.job_count += 1
40
41 out_cmds.append( cmd )
42 return out_cmds
43
44 - def run( self, cmds, shell = False ):
45 """
46 Run a series of commands and wait for their completion.
47
48 @param cmds: a list of cmds or Job's for Group.run. This will be run in parallel obeying the
49 'max_simul' option provided to the constructor. Using Job's directly allows you to associate
50 additional data for use with each job -- helpful for custom monitors.
51 """
52 cmds = self.convert_cmds( cmds )
53
54 while True:
55
56 run_count = self._check_finished()
57 if run_count < self.max_simul and len(cmds):
58
59 job = cmds[0]
60 job.handle = self.group.run( job.cmd, shell = shell )
61 job.status = Job.STATUS_RUNNING
62 cmds = cmds[1:]
63
64 self.jobs[job.handle] = job
65 self.job_started(job)
66
67 continue
68
69 lines = self.group.readlines()
70 for handle, line in lines:
71 self.job_output( self.jobs[handle], line )
72
73 self._check_feedback()
74
75 if run_count == 0:
76 break
77
78 self.gen_feedback()
79
81 """
82 Process all finished items.
83
84 @return: count of still running jobs
85 """
86 codes = self.group.get_exit_codes()
87 count_run = 0
88 count_done = 0
89 for handle, code in codes:
90
91 if code == None or not handle.group_output_done:
92 count_run += 1
93 continue
94
95 job = self.jobs[handle]
96 job.status = Job.STATUS_FINISHED
97 job.exit_code = code
98 count_done += 1
99 self.job_finished( job )
100
101 if count_done > 0:
102 self.group.clear_finished()
103
104 return count_run
105
107 """
108 Call gen_feedback at regular interval.
109 """
110 elapsed = time.time() - self.last_feedback
111 if elapsed > self.feedback_timeout:
112 self.gen_feedback()
113 self.last_feedback = time.time()
114
116 """
117 (Virtual) Called when a job has completed.
118 """
119 pass
120
122 """
123 (Virtual) Called just after a job is started.
124 """
125 pass
126
128 """
129 (Virtual) Called for each line of output from a job.
130 """
131 pass
132
134 """
135 (Virtual) Called whenever the Monitor things feedback should be generated (in addition to the other
136 events). Generally this is called for each feedback_timeout period specified in the constructor.
137 """
138 pass
139
141 """
142 Get the list of jobs.
143 """
144 return self.jobs.values()
145
147 STATUS_NONE = 0
148 STATUS_RUNNING = 1
149 STATUS_FINISHED = 2
150
151 """
152 Encapsulates information about a job.
153 """
155
156 self.handle = None
157
158 self.id = None
159
160 self.cmd = cmd
161
162 self.status = Job.STATUS_NONE
163
164 self.exit_code = None
165
166 self.name = None
167
169 """
170 A reference name suitable for using in identifiers and files.
171 """
172 if not self.name:
173 return self.id
174
175
176 base = self.name.replace( '/', '_' ).replace( '\\', '_' ).replace( ' ', '_' )
177 keep = ('_','.')
178 return "".join( c for c in base if c.isalnum() or c in keep )
179
180
182 """
183 A monitor which writes output to log files. Simple textual feedback will also be reported
184 to the console.
185
186 @param file_pattern: will be formatted with the job.id to produce filenames. These files
187 are overwritten when a job starts and record the output of the job.
188 @param meta: if True then meta information about the job will also be recorded to the
189 logfile
190 @param kwargs: the remaining arguments are passed to the Monitor constructor
191 """
192 - def __init__(self, file_pattern = '/tmp/job_{}.log', meta = True, **kwargs):
193 super(FileMonitor,self).__init__( **kwargs )
194 self.file_pattern = file_pattern
195 self.meta = meta
196
198 """
199 (Virtual) get the log of the log file to use.
200 """
201 return self.file_pattern.format( job.get_ref_name() )
202
204 """
205 Called when a job has completed.
206 """
207 if self.meta:
208 job.log_file.write( "Exit Code: {}\n".format( job.exit_code ) )
209
210 job.log_file.close()
211
213 """
214 Called just after a job is started.
215 """
216 job.log_name = self.get_log_name(job)
217 job.log_file = open(job.log_name,'w')
218 job.got_output = False
219
220 if self.meta:
221 job.log_file.write( "Job #{}: {}\n".format( job.id, job.cmd ) )
222
224 """
225 Called for each line of output from a job.
226 """
227 job.log_file.write(line)
228 job.got_output = True
229
231 """
232 Called whenever the Monitor things feedback should be generated (in addition to the other
233 events). Generally this is called for each feedback_timeout period specified in the constructor.
234 """
235 good = 0
236 bad = 0
237 output = 0
238 stall = 0
239 for job in self.get_jobs():
240 if job.exit_code != None:
241 if job.exit_code == 0:
242 good += 1
243 else:
244 bad += 1
245 elif job.got_output:
246 job.got_output = False
247 output += 1
248 else:
249 stall += 1
250
251 print( "Done: {} Failed: {} Reading: {} Idle: {}".format( good, bad, output, stall ) )
252