Package shelljob :: Module job
[hide private]
[frames] | no frames]

Source Code for Module shelljob.job

  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  """ 
13 -class Monitor(object):
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
28 - def convert_cmds( self, in_cmds ):
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 # ensure max_simul are running 56 run_count = self._check_finished() 57 if run_count < self.max_simul and len(cmds): 58 # space to create a new job 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 # this allows a quick spawing of max_simul on first call 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
80 - def _check_finished(self):
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 # it must be done and no output left before we consider it done 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
106 - def _check_feedback(self):
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
115 - def job_finished(self, job):
116 """ 117 (Virtual) Called when a job has completed. 118 """ 119 pass
120
121 - def job_started(self, job):
122 """ 123 (Virtual) Called just after a job is started. 124 """ 125 pass
126
127 - def job_output(self, job, line):
128 """ 129 (Virtual) Called for each line of output from a job. 130 """ 131 pass
132
133 - def gen_feedback(self):
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
140 - def get_jobs(self):
141 """ 142 Get the list of jobs. 143 """ 144 return self.jobs.values()
145
146 -class Job:
147 STATUS_NONE = 0 148 STATUS_RUNNING = 1 149 STATUS_FINISHED = 2 150 151 """ 152 Encapsulates information about a job. 153 """
154 - def __init__(self, cmd):
155 # the Popen object, set by monitor when the job starts 156 self.handle = None 157 # An identifier, if set to None then monitor will assign one (incrementing) 158 self.id = None 159 # command executed by the job 160 self.cmd = cmd 161 # Current status of the job 162 self.status = Job.STATUS_NONE 163 # Value of the exit code from the process, or None if not yet finished 164 self.exit_code = None 165 # An identifying name 166 self.name = None
167
168 - def get_ref_name(self):
169 """ 170 A reference name suitable for using in identifiers and files. 171 """ 172 if not self.name: 173 return self.id 174 175 # generate simple safe filename 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
181 -class FileMonitor(Monitor):
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
197 - def get_log_name(self,job):
198 """ 199 (Virtual) get the log of the log file to use. 200 """ 201 return self.file_pattern.format( job.get_ref_name() )
202
203 - def job_finished(self, job):
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
212 - def job_started(self, job):
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
223 - def job_output(self, job, line):
224 """ 225 Called for each line of output from a job. 226 """ 227 job.log_file.write(line) 228 job.got_output = True
229
230 - def gen_feedback(self):
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