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
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
183 user = os.getlogin()
\r
186 user = os.getenv('USERNAME')
\r
188 user = os.getenv('USER')
\r
190 user = 'unknown_user'
\r
192 (sysname, nodename, release, version, machine,
\r
193 processor) = platform.uname()
\r
195 (sysname, nodename, release, version, machine, processor) = (
\r
196 'unknown_sysname', 'unknown_nodename', 'unknown_release',
\r
197 'unknown_version', 'unknown_machine', 'unknown_processor')
\r
199 self.write_key(write_dict, 'process', None, 'start')
\r
200 write_dict['indentationLevel'] += 1
\r
202 self.write_key(write_dict, 'description', self.description)
\r
203 self.write_key(write_dict, 'cmd', self.cmd)
\r
205 self.write_key(write_dict, 'args', ' '.join(self.args))
\r
206 self.write_key(write_dict, 'start', self.start)
\r
207 self.write_key(write_dict, 'end', self.end)
\r
208 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
210 self.write_key(write_dict, 'user', user)
\r
211 self.write_key(write_dict, 'sysname', sysname)
\r
212 self.write_key(write_dict, 'nodename', nodename)
\r
213 self.write_key(write_dict, 'release', release)
\r
214 self.write_key(write_dict, 'version', version)
\r
215 self.write_key(write_dict, 'machine', machine)
\r
216 self.write_key(write_dict, 'processor', processor)
\r
218 if len(self.process_keys) > 0:
\r
219 self.write_key(write_dict, 'processKeys', None, 'start')
\r
220 for pair in self.process_keys:
\r
221 (key, value) = pair
\r
222 write_dict['indentationLevel'] += 1
\r
223 self.write_key(write_dict, key, value)
\r
224 write_dict['indentationLevel'] -= 1
\r
225 self.write_key(write_dict, 'processKeys', None, 'stop')
\r
227 self.write_key(write_dict, 'status', self.status)
\r
229 def write_log_footer(self, write_dict):
\r
231 Object description.
\r
236 Parameter description.
\r
241 Return value description.
\r
244 write_dict['indentationLevel'] -= 1
\r
245 self.write_key(write_dict, 'process', None, 'stop')
\r
247 def write_log(self,
\r
248 log_handle=sys.stdout,
\r
249 indentation_level=0,
\r
252 Writes logging information to the specified handle.
\r
257 Parameter description.
\r
262 Return value description.
\r
266 write_dict['logHandle'] = log_handle
\r
267 write_dict['indentationLevel'] = indentation_level
\r
268 write_dict['format'] = format
\r
271 self.write_log_header(write_dict)
\r
274 self.write_key(write_dict, 'output', None, 'start')
\r
275 if format == 'xml':
\r
276 log_handle.write('<![CDATA[\n')
\r
277 for line in self.log:
\r
278 log_handle.write('%s%s\n' % ('', line))
\r
279 if format == 'xml':
\r
280 log_handle.write(']]>\n')
\r
281 self.write_key(write_dict, 'output', None, 'stop')
\r
283 self.write_log_footer(write_dict)
\r
285 def write_log_to_disk(self, log_filename=None, format='xml', header=None):
\r
287 Object description.
\r
292 Parameter description.
\r
297 Return value description.
\r
302 # TODO: Review statements.
\r
306 open(log_filename, mode='wt', encoding='utf-8'))
\r
309 log_handle = open(log_filename, mode='wt')
\r
311 print('Couldn\'t open log : %s' % log_filename)
\r
316 if format == 'xml':
\r
317 log_handle.write('<![CDATA[\n')
\r
318 log_handle.write(header)
\r
319 if format == 'xml':
\r
320 log_handle.write(']]>\n')
\r
321 self.write_log(log_handle)
\r
324 def log_line(self, line):
\r
326 Adds a line of text to the log.
\r
331 Parameter description.
\r
336 Return value description.
\r
339 self.log.append(line.rstrip())
\r
341 print('%s' % line.rstrip())
\r
345 Executes the current process.
\r
350 Parameter description.
\r
355 Return value description.
\r
362 import subprocess as sp
\r
366 self.start = datetime.datetime.now()
\r
368 cmdargs = [self.cmd]
\r
369 cmdargs.extend(self.args)
\r
374 '\n%s : %s\n' % (self.__class__, sp.list2cmdline(cmdargs)))
\r
376 print('\n%s : %s\n' % (self.__class__, ' '.join(cmdargs)))
\r
382 parentenv = os.environ
\r
383 parentcwd = os.getcwd()
\r
386 # Using *subprocess*.
\r
388 if self.batch_wrapper:
\r
389 cmd = ' '.join(cmdargs)
\r
390 tmp_wrapper = os.path.join(self.cwd, 'process.bat')
\r
391 write_text(cmd, tmp_wrapper)
\r
392 print('%s : Running process through wrapper %s\n' % (
\r
393 self.__class__, tmp_wrapper))
\r
394 process = sp.Popen([tmp_wrapper], stdout=sp.PIPE,
\r
396 cwd=self.cwd, env=self.env)
\r
398 process = sp.Popen(cmdargs, stdout=sp.PIPE,
\r
400 cwd=self.cwd, env=self.env)
\r
402 # using *os.popen4*.
\r
405 os.environ = self.env
\r
409 stdin, stdout = os.popen4(cmdargs, 'r')
\r
411 print('Couldn\'t execute command : %s' % cmdargs[0])
\r
412 traceback.print_exc()
\r
414 # Using *subprocess*
\r
416 if process != None:
\r
417 # pid = process.pid
\r
418 # log.logLine('process id %s\n' % pid)
\r
421 # This is more proper python, and resolves some issues with
\r
422 # a process ending before all of its output has been
\r
423 # processed, but it also seems to stall when the read
\r
424 # buffer is near or over its limit. This happens
\r
425 # relatively frequently with processes that generate lots
\r
426 # of print statements.
\r
427 for line in process.stdout:
\r
428 self.log_line(line)
\r
430 # So we go with the, um, uglier option below.
\r
432 # This is now used to ensure that the process has finished.
\r
434 while line != None and process.poll() is None:
\r
436 line = process.stdout.readline()
\r
441 self.log_line(str(line, encoding='utf-8'))
\r
444 self.log_line(line)
\r
446 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
448 self.status = process.returncode
\r
450 if self.batch_wrapper and tmp_wrapper:
\r
452 os.remove(tmp_wrapper)
\r
455 'Couldn\'t remove temp wrapper : %s' % tmp_wrapper)
\r
456 traceback.print_exc()
\r
458 # Using *os.popen4*.
\r
462 stdout_lines = stdout.readlines()
\r
463 exit_code = stdout.close()
\r
469 os.environ = parentenv
\r
471 os.chdir(parentcwd)
\r
473 if len(stdout_lines) > 0:
\r
474 for line in stdout_lines:
\r
475 self.log_line(line)
\r
480 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
482 self.status = exit_code
\r
484 self.end = datetime.datetime.now()
\r
487 class ProcessList(Process):
\r
489 A list of processes with logged output.
\r
492 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
494 Object description.
\r
499 Parameter description.
\r
504 Return value description.
\r
507 Process.__init__(self, description, None, None, cwd, env)
\r
508 'Initialize the standard class variables'
\r
509 self.processes = []
\r
510 self.blocking = blocking
\r
512 def generate_report(self, write_dict):
\r
514 Generates a log based on the success of the child processes.
\r
519 Parameter description.
\r
524 Return value description.
\r
529 indent = '\t' * (write_dict['indentationLevel'] + 1)
\r
533 for child in self.processes:
\r
534 if isinstance(child, ProcessList):
\r
535 child.generate_report(write_dict)
\r
537 key = child.description
\r
538 value = child.status
\r
539 if write_dict['format'] == 'xml':
\r
541 '%s<result description=\'%s\'>%s</result>' % (
\r
542 indent, key, value))
\r
544 child_result = ('%s%40s : %s' % (indent, key, value))
\r
545 self.log.append(child_result)
\r
547 if child.status != 0:
\r
554 self.log = ['No child processes available to generate a report']
\r
557 def write_log_header(self, write_dict):
\r
559 Object description.
\r
564 Parameter description.
\r
569 Return value description.
\r
572 self.write_key(write_dict, 'processList', None, 'start')
\r
573 write_dict['indentationLevel'] += 1
\r
575 self.write_key(write_dict, 'description', self.description)
\r
576 self.write_key(write_dict, 'start', self.start)
\r
577 self.write_key(write_dict, 'end', self.end)
\r
578 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
580 self.generate_report(write_dict)
\r
582 self.write_key(write_dict, 'status', self.status)
\r
584 def write_log_footer(self, write_dict):
\r
586 Object description.
\r
591 Parameter description.
\r
596 Return value description.
\r
599 write_dict['indentationLevel'] -= 1
\r
600 self.write_key(write_dict, 'processList', None, 'stop')
\r
602 def write_log(self,
\r
603 log_handle=sys.stdout,
\r
604 indentation_level=0,
\r
607 Writes logging information to the specified handle.
\r
612 Parameter description.
\r
617 Return value description.
\r
621 write_dict['logHandle'] = log_handle
\r
622 write_dict['indentationLevel'] = indentation_level
\r
623 write_dict['format'] = format
\r
626 self.write_log_header(write_dict)
\r
629 self.write_key(write_dict, 'output', None, 'start')
\r
630 for line in self.log:
\r
631 log_handle.write('%s%s\n' % ('', line))
\r
632 self.write_key(write_dict, 'output', None, 'stop')
\r
635 self.write_key(write_dict, 'processes', None, 'start')
\r
636 for child in self.processes:
\r
637 child.write_log(log_handle, indentation_level + 1, format)
\r
638 self.write_key(write_dict, 'processes', None, 'stop')
\r
640 self.write_log_footer(write_dict)
\r
644 Executes the list of processes.
\r
649 Parameter description.
\r
654 Return value description.
\r
659 self.start = datetime.datetime.now()
\r
663 for child in self.processes:
\r
668 print('%s : caught exception in child class %s' % (
\r
669 self.__class__, child.__class__))
\r
670 traceback.print_exc()
\r
673 if self.blocking and child.status != 0:
\r
674 print('%s : child class %s finished with an error' % (
\r
675 self.__class__, child.__class__))
\r
679 self.end = datetime.datetime.now()
\r
684 Object description.
\r
689 Parameter description.
\r
694 Return value description.
\r
699 p = optparse.OptionParser(description='A process logging script',
\r
701 version='process 0.1',
\r
702 usage=('%prog [options] '
\r
703 '[options for the logged process]'))
\r
704 p.add_option('--cmd', '-c', default=None)
\r
705 p.add_option('--log', '-l', default=None)
\r
707 options, arguments = p.parse_args()
\r
710 log_filename = options.log
\r
713 args_start = sys.argv.index('--') + 1
\r
714 args = sys.argv[args_start:]
\r
716 args_start = len(sys.argv) + 1
\r
720 print('process: No command specified')
\r
722 # Testing regular logging.
\r
723 process = Process(description='a process', cmd=cmd, args=args)
\r
725 # Testing report generation and writing a log.
\r
726 process_list = ProcessList('a process list')
\r
727 process_list.processes.append(process)
\r
728 process_list.echo = True
\r
729 process_list.execute()
\r
731 process_list.write_log_to_disk(log_filename)
\r
734 if __name__ == '__main__':
\r