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
9 from __future__ import division
\r
15 __author__ = 'ACES Developers'
\r
16 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
\r
18 __maintainer__ = 'ACES Developers'
\r
19 __email__ = 'aces@oscars.org'
\r
20 __status__ = 'Production'
\r
22 __all__ = ['read_text',
\r
29 def read_text(text_file):
\r
36 Parameter description.
\r
41 Return value description.
\r
44 # TODO: Investigate if check is needed.
\r
48 with open(text_file, 'rb') as fp:
\r
54 def write_text(text, text_file):
\r
61 Parameter description.
\r
66 Return value description.
\r
69 # TODO: Investigate if check is needed.
\r
73 with open(text_file, 'wb') as fp:
\r
81 A process with logged output.
\r
90 batch_wrapper=False):
\r
92 Initialize the standard class variables.
\r
97 Parameter description.
\r
102 Return value description.
\r
109 if not description:
\r
110 self.description = cmd
\r
112 self.description = description
\r
121 self.batch_wrapper = batch_wrapper
\r
122 self.process_keys = []
\r
124 def get_elapsed_seconds(self):
\r
126 Object description.
\r
131 Parameter description.
\r
136 Return value description.
\r
141 if self.end and self.start:
\r
142 delta = (self.end - self.start)
\r
143 formatted = '%s.%s' % (delta.days * 86400 + delta.seconds,
\r
144 int(math.floor(delta.microseconds / 1e3)))
\r
149 def write_key(self, write_dict, key=None, value=None, start_stop=None):
\r
151 Writes a key / value pair in a supported format.
\r
156 Parameter description.
\r
161 Return value description.
\r
164 if key is not None and (value is not None or start_stop is not None):
\r
165 indent = '\t' * write_dict['indentationLevel']
\r
166 if write_dict['format'] == 'xml':
\r
167 if start_stop == 'start':
\r
168 write_dict['logHandle'].write('%s<%s>\n' % (indent, key))
\r
169 elif start_stop == 'stop':
\r
170 write_dict['logHandle'].write('%s</%s>\n' % (indent, key))
\r
172 write_dict['logHandle'].write(
\r
173 '%s<%s>%s</%s>\n' % (indent, key, value, key))
\r
175 write_dict['logHandle'].write(
\r
176 '%s%40s : %s\n' % (indent, key, value))
\r
178 def write_log_header(self, write_dict):
\r
180 Object description.
\r
185 Parameter description.
\r
190 Return value description.
\r
196 user = os.getlogin()
\r
199 user = os.getenv('USERNAME')
\r
201 user = os.getenv('USER')
\r
203 user = 'unknown_user'
\r
205 (sysname, nodename, release, version, machine,
\r
206 processor) = platform.uname()
\r
208 (sysname, nodename, release, version, machine, processor) = (
\r
209 'unknown_sysname', 'unknown_nodename', 'unknown_release',
\r
210 'unknown_version', 'unknown_machine', 'unknown_processor')
\r
212 self.write_key(write_dict, 'process', None, 'start')
\r
213 write_dict['indentationLevel'] += 1
\r
215 self.write_key(write_dict, 'description', self.description)
\r
216 self.write_key(write_dict, 'cmd', self.cmd)
\r
218 self.write_key(write_dict, 'args', ' '.join(self.args))
\r
219 self.write_key(write_dict, 'start', self.start)
\r
220 self.write_key(write_dict, 'end', self.end)
\r
221 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
223 self.write_key(write_dict, 'user', user)
\r
224 self.write_key(write_dict, 'sysname', sysname)
\r
225 self.write_key(write_dict, 'nodename', nodename)
\r
226 self.write_key(write_dict, 'release', release)
\r
227 self.write_key(write_dict, 'version', version)
\r
228 self.write_key(write_dict, 'machine', machine)
\r
229 self.write_key(write_dict, 'processor', processor)
\r
231 if len(self.process_keys) > 0:
\r
232 self.write_key(write_dict, 'processKeys', None, 'start')
\r
233 for pair in self.process_keys:
\r
234 (key, value) = pair
\r
235 write_dict['indentationLevel'] += 1
\r
236 self.write_key(write_dict, key, value)
\r
237 write_dict['indentationLevel'] -= 1
\r
238 self.write_key(write_dict, 'processKeys', None, 'stop')
\r
240 self.write_key(write_dict, 'status', self.status)
\r
242 def write_log_footer(self, write_dict):
\r
244 Object description.
\r
249 Parameter description.
\r
254 Return value description.
\r
257 write_dict['indentationLevel'] -= 1
\r
258 self.write_key(write_dict, 'process', None, 'stop')
\r
260 def write_log(self,
\r
261 log_handle=sys.stdout,
\r
262 indentation_level=0,
\r
265 Writes logging information to the specified handle.
\r
270 Parameter description.
\r
275 Return value description.
\r
279 write_dict['logHandle'] = log_handle
\r
280 write_dict['indentationLevel'] = indentation_level
\r
281 write_dict['format'] = format
\r
284 self.write_log_header(write_dict)
\r
287 self.write_key(write_dict, 'output', None, 'start')
\r
288 if format == 'xml':
\r
289 log_handle.write('<![CDATA[\n')
\r
290 for line in self.log:
\r
291 log_handle.write('%s%s\n' % ('', line))
\r
292 if format == 'xml':
\r
293 log_handle.write(']]>\n')
\r
294 self.write_key(write_dict, 'output', None, 'stop')
\r
296 self.write_log_footer(write_dict)
\r
298 def write_log_to_disk(self, log_filename=None, format='xml', header=None):
\r
300 Object description.
\r
305 Parameter description.
\r
310 Return value description.
\r
315 # TODO: Review statements.
\r
319 open(log_filename, mode='wt', encoding='utf-8'))
\r
322 log_handle = open(log_filename, mode='wt')
\r
324 print('Couldn\'t open log : %s' % log_filename)
\r
329 if format == 'xml':
\r
330 log_handle.write('<![CDATA[\n')
\r
331 log_handle.write(header)
\r
332 if format == 'xml':
\r
333 log_handle.write(']]>\n')
\r
334 self.write_log(log_handle, format=format)
\r
337 def log_line(self, line):
\r
339 Adds a line of text to the log.
\r
344 Parameter description.
\r
349 Return value description.
\r
352 self.log.append(line.rstrip())
\r
354 print('%s' % line.rstrip())
\r
358 Executes the current process.
\r
363 Parameter description.
\r
368 Return value description.
\r
375 import subprocess as sp
\r
379 self.start = datetime.datetime.now()
\r
381 cmdargs = [self.cmd]
\r
382 cmdargs.extend(self.args)
\r
387 '\n%s : %s\n' % (self.__class__, sp.list2cmdline(cmdargs)))
\r
389 print('\n%s : %s\n' % (self.__class__, ' '.join(cmdargs)))
\r
395 parentenv = os.environ
\r
396 parentcwd = os.getcwd()
\r
399 # Using *subprocess*.
\r
401 if self.batch_wrapper:
\r
402 cmd = ' '.join(cmdargs)
\r
403 tmp_wrapper = os.path.join(self.cwd, 'process.bat')
\r
404 write_text(cmd, tmp_wrapper)
\r
405 print('%s : Running process through wrapper %s\n' % (
\r
406 self.__class__, tmp_wrapper))
\r
407 process = sp.Popen([tmp_wrapper], stdout=sp.PIPE,
\r
409 cwd=self.cwd, env=self.env)
\r
411 process = sp.Popen(cmdargs, stdout=sp.PIPE,
\r
413 cwd=self.cwd, env=self.env)
\r
415 # using *os.popen4*.
\r
418 os.environ = self.env
\r
422 stdin, stdout = os.popen4(cmdargs, 'r')
\r
424 print('Couldn\'t execute command : %s' % cmdargs[0])
\r
425 traceback.print_exc()
\r
427 # Using *subprocess*
\r
429 if process is not None:
\r
430 # pid = process.pid
\r
431 # log.logLine('process id %s\n' % pid)
\r
434 # This is more proper python, and resolves some issues with
\r
435 # a process ending before all of its output has been
\r
436 # processed, but it also seems to stall when the read
\r
437 # buffer is near or over its limit. This happens
\r
438 # relatively frequently with processes that generate lots
\r
439 # of print statements.
\r
440 for line in process.stdout:
\r
441 self.log_line(line)
\r
443 # So we go with the, um, uglier option below.
\r
445 # This is now used to ensure that the process has finished.
\r
447 while line is not None and process.poll() is None:
\r
449 line = process.stdout.readline()
\r
454 # TODO: Investigate previous eroneous statement.
\r
455 # self.log_line(str(line, encoding='utf-8'))
\r
456 self.log_line(str(line))
\r
459 self.log_line(line)
\r
461 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
463 self.status = process.returncode
\r
465 if self.batch_wrapper and tmp_wrapper:
\r
467 os.remove(tmp_wrapper)
\r
470 'Couldn\'t remove temp wrapper : %s' % tmp_wrapper)
\r
471 traceback.print_exc()
\r
473 # Using *os.popen4*.
\r
477 stdout_lines = stdout.readlines()
\r
478 # TODO: Investigate if this is the good behavior, close() does
\r
479 # not return anything / None.
\r
480 exit_code = stdout.close()
\r
486 os.environ = parentenv
\r
488 os.chdir(parentcwd)
\r
490 if len(stdout_lines) > 0:
\r
491 for line in stdout_lines:
\r
492 self.log_line(line)
\r
497 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
499 self.status = exit_code
\r
501 self.end = datetime.datetime.now()
\r
504 class ProcessList(Process):
\r
506 A list of processes with logged output.
\r
509 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
511 Object description.
\r
516 Parameter description.
\r
521 Return value description.
\r
524 Process.__init__(self, description, None, None, cwd, env)
\r
525 'Initialize the standard class variables'
\r
526 self.processes = []
\r
527 self.blocking = blocking
\r
529 def generate_report(self, write_dict):
\r
531 Generates a log based on the success of the child processes.
\r
536 Parameter description.
\r
541 Return value description.
\r
546 indent = '\t' * (write_dict['indentationLevel'] + 1)
\r
550 for child in self.processes:
\r
551 if isinstance(child, ProcessList):
\r
552 child.generate_report(write_dict)
\r
554 key = child.description
\r
555 value = child.status
\r
556 if write_dict['format'] == 'xml':
\r
558 '%s<result description=\'%s\'>%s</result>' % (
\r
559 indent, key, value))
\r
561 child_result = ('%s%40s : %s' % (indent, key, value))
\r
562 self.log.append(child_result)
\r
564 if child.status != 0:
\r
571 self.log = ['No child processes available to generate a report']
\r
574 def write_log_header(self, write_dict):
\r
576 Object description.
\r
581 Parameter description.
\r
586 Return value description.
\r
589 self.write_key(write_dict, 'processList', None, 'start')
\r
590 write_dict['indentationLevel'] += 1
\r
592 self.write_key(write_dict, 'description', self.description)
\r
593 self.write_key(write_dict, 'start', self.start)
\r
594 self.write_key(write_dict, 'end', self.end)
\r
595 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
597 self.generate_report(write_dict)
\r
599 self.write_key(write_dict, 'status', self.status)
\r
601 def write_log_footer(self, write_dict):
\r
603 Object description.
\r
608 Parameter description.
\r
613 Return value description.
\r
616 write_dict['indentationLevel'] -= 1
\r
617 self.write_key(write_dict, 'processList', None, 'stop')
\r
619 def write_log(self,
\r
620 log_handle=sys.stdout,
\r
621 indentation_level=0,
\r
624 Writes logging information to the specified handle.
\r
629 Parameter description.
\r
634 Return value description.
\r
638 write_dict['logHandle'] = log_handle
\r
639 write_dict['indentationLevel'] = indentation_level
\r
640 write_dict['format'] = format
\r
643 self.write_log_header(write_dict)
\r
646 self.write_key(write_dict, 'output', None, 'start')
\r
647 for line in self.log:
\r
648 log_handle.write('%s%s\n' % ('', line))
\r
649 self.write_key(write_dict, 'output', None, 'stop')
\r
652 self.write_key(write_dict, 'processes', None, 'start')
\r
653 for child in self.processes:
\r
654 child.write_log(log_handle, indentation_level + 1, format)
\r
655 self.write_key(write_dict, 'processes', None, 'stop')
\r
657 self.write_log_footer(write_dict)
\r
661 Executes the list of processes.
\r
666 Parameter description.
\r
671 Return value description.
\r
676 self.start = datetime.datetime.now()
\r
680 for child in self.processes:
\r
685 print('%s : caught exception in child class %s' % (
\r
686 self.__class__, child.__class__))
\r
687 traceback.print_exc()
\r
690 if self.blocking and child.status != 0:
\r
691 print('%s : child class %s finished with an error' % (
\r
692 self.__class__, child.__class__))
\r
696 self.end = datetime.datetime.now()
\r
701 Object description.
\r
706 Parameter description.
\r
711 Return value description.
\r
716 p = optparse.OptionParser(description='A process logging script',
\r
718 version='process 0.1',
\r
719 usage=('%prog [options] '
\r
720 '[options for the logged process]'))
\r
721 p.add_option('--cmd', '-c', default=None)
\r
722 p.add_option('--log', '-l', default=None)
\r
724 options, arguments = p.parse_args()
\r
727 log_filename = options.log
\r
730 args_start = sys.argv.index('--') + 1
\r
731 args = sys.argv[args_start:]
\r
736 print('process: No command specified')
\r
738 # Testing regular logging.
\r
739 process = Process(description='a process', cmd=cmd, args=args)
\r
741 # Testing 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