1 #!/usr/bin/env python
\r
2 # -*- coding: utf-8 -*-
\r
5 A process wrapper class that maintains the text output and execution status of
\r
6 a process or a list of other process wrappers which carry such data.
\r
13 __author__ = 'ACES Developers'
\r
14 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
\r
16 __maintainer__ = 'ACES Developers'
\r
17 __email__ = 'aces@oscars.org'
\r
18 __status__ = 'Production'
\r
20 __all__ = ['read_text',
\r
27 def read_text(text_file):
\r
34 Parameter description.
\r
39 Return value description.
\r
43 with open(text_file, 'rb') as fp:
\r
48 def write_text(text, text_file):
\r
55 Parameter description.
\r
60 Return value description.
\r
64 with open(text_file, 'wb') as fp:
\r
71 A process with logged output.
\r
80 batch_wrapper=False):
\r
82 Initialize the standard class variables.
\r
87 Parameter description.
\r
92 Return value description.
\r
97 self.description = cmd
\r
99 self.description = description
\r
108 self.batch_wrapper = batch_wrapper
\r
109 self.process_keys = []
\r
111 def get_elapsed_seconds(self):
\r
113 Object description.
\r
118 Parameter description.
\r
123 Return value description.
\r
128 if self.end and self.start:
\r
129 delta = (self.end - self.start)
\r
130 formatted = '%s.%s' % (delta.days * 86400 + delta.seconds,
\r
131 int(math.floor(delta.microseconds / 1e3)))
\r
136 def write_key(self, write_dict, key=None, value=None, start_stop=None):
\r
138 Writes a key / value pair in a supported format.
\r
143 Parameter description.
\r
148 Return value description.
\r
151 if key != None and (value != None or start_stop != None):
\r
152 indent = '\t' * write_dict['indentationLevel']
\r
153 if write_dict['format'] == 'xml':
\r
154 if start_stop == 'start':
\r
155 write_dict['logHandle'].write('%s<%s>\n' % (indent, key))
\r
156 elif start_stop == 'stop':
\r
157 write_dict['logHandle'].write('%s</%s>\n' % (indent, key))
\r
159 write_dict['logHandle'].write(
\r
160 '%s<%s>%s</%s>\n' % (indent, key, value, key))
\r
161 else: # writeDict['format'] == 'txt':
\r
162 write_dict['logHandle'].write(
\r
163 '%s%40s : %s\n' % (indent, key, value))
\r
165 def write_log_header(self, write_dict):
\r
167 Object description.
\r
172 Parameter description.
\r
177 Return value description.
\r
182 # Retrieve operating environment information
\r
185 user = os.getlogin()
\r
188 user = os.getenv('USERNAME')
\r
190 user = os.getenv('USER')
\r
192 user = 'unknown_user'
\r
194 (sysname, nodename, release, version, machine,
\r
195 processor) = platform.uname()
\r
197 (sysname, nodename, release, version, machine, processor) = (
\r
198 'unknown_sysname', 'unknown_nodename', 'unknown_release',
\r
199 'unknown_version', 'unknown_machine', 'unknown_processor')
\r
201 hostname = platform.node()
\r
203 hostname = 'unknown_hostname'
\r
205 self.write_key(write_dict, 'process', None, 'start')
\r
206 write_dict['indentationLevel'] += 1
\r
208 self.write_key(write_dict, 'description', self.description)
\r
209 self.write_key(write_dict, 'cmd', self.cmd)
\r
211 self.write_key(write_dict, 'args', ' '.join(self.args))
\r
212 self.write_key(write_dict, 'start', self.start)
\r
213 self.write_key(write_dict, 'end', self.end)
\r
214 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
216 self.write_key(write_dict, 'user', user)
\r
217 self.write_key(write_dict, 'sysname', sysname)
\r
218 self.write_key(write_dict, 'nodename', nodename)
\r
219 self.write_key(write_dict, 'release', release)
\r
220 self.write_key(write_dict, 'version', version)
\r
221 self.write_key(write_dict, 'machine', machine)
\r
222 self.write_key(write_dict, 'processor', processor)
\r
224 if len(self.process_keys) > 0:
\r
225 self.write_key(write_dict, 'processKeys', None, 'start')
\r
226 for pair in self.process_keys:
\r
227 (key, value) = pair
\r
228 write_dict['indentationLevel'] += 1
\r
229 self.write_key(write_dict, key, value)
\r
230 write_dict['indentationLevel'] -= 1
\r
231 self.write_key(write_dict, 'processKeys', None, 'stop')
\r
233 self.write_key(write_dict, 'status', self.status)
\r
235 def write_log_footer(self, write_dict):
\r
237 Object description.
\r
242 Parameter description.
\r
247 Return value description.
\r
250 write_dict['indentationLevel'] -= 1
\r
251 self.write_key(write_dict, 'process', None, 'stop')
\r
253 def write_log(self,
\r
254 log_handle=sys.stdout,
\r
255 indentation_level=0,
\r
258 Writes logging information to the specified handle.
\r
263 Parameter description.
\r
268 Return value description.
\r
272 write_dict['logHandle'] = log_handle
\r
273 write_dict['indentationLevel'] = indentation_level
\r
274 write_dict['format'] = format
\r
277 self.write_log_header(write_dict)
\r
280 self.write_key(write_dict, 'output', None, 'start')
\r
281 if format == 'xml':
\r
282 log_handle.write('<![CDATA[\n')
\r
283 for line in self.log:
\r
284 log_handle.write('%s%s\n' % ('', line))
\r
285 if format == 'xml':
\r
286 log_handle.write(']]>\n')
\r
287 self.write_key(write_dict, 'output', None, 'stop')
\r
289 self.write_log_footer(write_dict)
\r
291 def write_log_to_disk(self, log_filename=None, format='xml', header=None):
\r
293 Object description.
\r
298 Parameter description.
\r
303 Return value description.
\r
308 # TODO: Review statements.
\r
312 open(log_filename, mode='wt', encoding='utf-8'))
\r
315 log_handle = open(log_filename, mode='wt')
\r
317 print('Couldn\'t open log : %s' % log_filename)
\r
322 if format == 'xml':
\r
323 log_handle.write('<![CDATA[\n')
\r
324 log_handle.write(header)
\r
325 if format == 'xml':
\r
326 log_handle.write(']]>\n')
\r
327 self.write_log(log_handle)
\r
330 def log_line(self, line):
\r
332 Adds a line of text to the log.
\r
337 Parameter description.
\r
342 Return value description.
\r
345 self.log.append(line.rstrip())
\r
347 print('%s' % line.rstrip())
\r
351 Executes the current process.
\r
356 Parameter description.
\r
361 Return value description.
\r
368 import subprocess as sp
\r
372 self.start = datetime.datetime.now()
\r
374 cmdargs = [self.cmd]
\r
375 cmdargs.extend(self.args)
\r
380 '\n%s : %s\n' % (self.__class__, sp.list2cmdline(cmdargs)))
\r
382 print('\n%s : %s\n' % (self.__class__, ' '.join(cmdargs)))
\r
384 # intialize a few variables that may or may not be set later
\r
389 parentenv = os.environ
\r
390 parentcwd = os.getcwd()
\r
395 if self.batch_wrapper:
\r
396 cmd = ' '.join(cmdargs)
\r
397 tmp_wrapper = os.path.join(self.cwd, 'process.bat')
\r
398 write_text(cmd, tmp_wrapper)
\r
399 print('%s : Running process through wrapper %s\n' % (
\r
400 self.__class__, tmp_wrapper))
\r
401 process = sp.Popen([tmp_wrapper], stdout=sp.PIPE,
\r
403 cwd=self.cwd, env=self.env)
\r
405 process = sp.Popen(cmdargs, stdout=sp.PIPE,
\r
407 cwd=self.cwd, env=self.env)
\r
412 os.environ = self.env
\r
416 stdin, stdout = os.popen4(cmdargs, 'r')
\r
418 print('Couldn\'t execute command : %s' % cmdargs[0])
\r
419 traceback.print_exc()
\r
423 if process != None:
\r
424 # pid = process.pid
\r
425 # log.logLine('process id %s\n' % pid)
\r
428 # This is more proper python, and resolves some issues with
\r
429 # a process ending before all of its output has been
\r
430 # processed, but it also seems to stall when the read
\r
431 # buffer is near or over it's limit. this happens
\r
432 # relatively frequently with processes that generate lots
\r
433 # of print statements.
\r
434 for line in process.stdout:
\r
435 self.log_line(line)
\r
437 # So we go with the, um, uglier option below
\r
439 # This is now used to ensure that the process has finished
\r
441 while line != None and process.poll() is None:
\r
443 line = process.stdout.readline()
\r
448 self.log_line(str(line, encoding='utf-8'))
\r
451 self.log_line(line)
\r
453 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
455 self.status = process.returncode
\r
457 if self.batch_wrapper and tmp_wrapper:
\r
459 os.remove(tmp_wrapper)
\r
462 'Couldn\'t remove temp wrapper : %s' % tmp_wrapper)
\r
463 traceback.print_exc()
\r
469 # print('reading stdout lines')
\r
470 stdout_lines = stdout.readlines()
\r
471 exit_code = stdout.close()
\r
477 os.environ = parentenv
\r
479 os.chdir(parentcwd)
\r
481 if len(stdout_lines) > 0:
\r
482 for line in stdout_lines:
\r
483 self.log_line(line)
\r
488 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
490 self.status = exit_code
\r
492 self.end = datetime.datetime.now()
\r
495 class ProcessList(Process):
\r
497 A list of processes with logged output.
\r
500 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
502 Object description.
\r
507 Parameter description.
\r
512 Return value description.
\r
515 Process.__init__(self, description, None, None, cwd, env)
\r
516 'Initialize the standard class variables'
\r
517 self.processes = []
\r
518 self.blocking = blocking
\r
520 def generate_report(self, write_dict):
\r
522 Generates a log based on the success of the child processes.
\r
527 Parameter description.
\r
532 Return value description.
\r
537 indent = '\t' * (write_dict['indentationLevel'] + 1)
\r
541 for child in self.processes:
\r
542 if isinstance(child, ProcessList):
\r
543 child.generate_report(write_dict)
\r
546 key = child.description
\r
547 value = child.status
\r
548 if write_dict['format'] == 'xml':
\r
550 '%s<result description=\'%s\'>%s</result>' % (
\r
551 indent, key, value))
\r
552 else: # writeDict['format'] == 'txt':
\r
553 child_result = ('%s%40s : %s' % (indent, key, value))
\r
554 self.log.append(child_result)
\r
556 if child.status != 0:
\r
563 self.log = ['No child processes available to generate a report']
\r
566 def write_log_header(self, write_dict):
\r
568 Object description.
\r
573 Parameter description.
\r
578 Return value description.
\r
581 self.write_key(write_dict, 'processList', None, 'start')
\r
582 write_dict['indentationLevel'] += 1
\r
584 self.write_key(write_dict, 'description', self.description)
\r
585 self.write_key(write_dict, 'start', self.start)
\r
586 self.write_key(write_dict, 'end', self.end)
\r
587 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
589 self.generate_report(write_dict)
\r
591 self.write_key(write_dict, 'status', self.status)
\r
593 def write_log_footer(self, write_dict):
\r
595 Object description.
\r
600 Parameter description.
\r
605 Return value description.
\r
608 write_dict['indentationLevel'] -= 1
\r
609 self.write_key(write_dict, 'processList', None, 'stop')
\r
611 def write_log(self,
\r
612 log_handle=sys.stdout,
\r
613 indentation_level=0,
\r
616 Writes logging information to the specified handle.
\r
621 Parameter description.
\r
626 Return value description.
\r
630 write_dict['logHandle'] = log_handle
\r
631 write_dict['indentationLevel'] = indentation_level
\r
632 write_dict['format'] = format
\r
635 self.write_log_header(write_dict)
\r
638 self.write_key(write_dict, 'output', None, 'start')
\r
639 for line in self.log:
\r
640 log_handle.write('%s%s\n' % ('', line))
\r
641 self.write_key(write_dict, 'output', None, 'stop')
\r
644 self.write_key(write_dict, 'processes', None, 'start')
\r
645 for child in self.processes:
\r
646 child.write_log(log_handle, indentation_level + 1, format)
\r
647 self.write_key(write_dict, 'processes', None, 'stop')
\r
649 self.write_log_footer(write_dict)
\r
653 Executes the list of processes.
\r
658 Parameter description.
\r
663 Return value description.
\r
668 self.start = datetime.datetime.now()
\r
672 for child in self.processes:
\r
677 print('%s : caught exception in child class %s' % (
\r
678 self.__class__, child.__class__))
\r
679 traceback.print_exc()
\r
682 if self.blocking and child.status != 0:
\r
683 print('%s : child class %s finished with an error' % (
\r
684 self.__class__, child.__class__))
\r
688 self.end = datetime.datetime.now()
\r
693 Object description.
\r
698 Parameter description.
\r
703 Return value description.
\r
708 p = optparse.OptionParser(description='A process logging script',
\r
710 version='process 0.1',
\r
711 usage=('%prog [options] '
\r
712 '[options for the logged process]'))
\r
713 p.add_option('--cmd', '-c', default=None)
\r
714 p.add_option('--log', '-l', default=None)
\r
716 options, arguments = p.parse_args()
\r
722 log_filename = options.log
\r
725 args_start = sys.argv.index('--') + 1
\r
726 args = sys.argv[args_start:]
\r
728 args_start = len(sys.argv) + 1
\r
732 print('process: No command specified')
\r
735 # Test regular logging
\r
737 process = Process(description='a process', cmd=cmd, args=args)
\r
740 # Test report generation and writing a log
\r
742 process_list = ProcessList('a process list')
\r
743 process_list.processes.append(process)
\r
744 process_list.echo = True
\r
745 process_list.execute()
\r
747 process_list.write_log_to_disk(log_filename)
\r
750 if __name__ == '__main__':
\r