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
42 if (text_file != ''):
\r
43 fp = open(text_file, 'rb')
\r
44 # Create a text/plain message
\r
50 def write_text(text, text_file):
\r
57 Parameter description.
\r
62 Return value description.
\r
65 if (text_file != ''):
\r
66 fp = open(text_file, 'wb')
\r
67 # Create a text/plain message
\r
75 A process with logged output.
\r
84 batch_wrapper=False):
\r
86 Initialize the standard class variables.
\r
91 Parameter description.
\r
96 Return value description.
\r
100 if not description:
\r
101 self.description = cmd
\r
103 self.description = description
\r
112 self.batch_wrapper = batch_wrapper
\r
113 self.process_keys = []
\r
115 def get_elapsed_seconds(self):
\r
117 Object description.
\r
122 Parameter description.
\r
127 Return value description.
\r
132 if self.end and self.start:
\r
133 delta = (self.end - self.start)
\r
134 formatted = '%s.%s' % (delta.days * 86400 + delta.seconds,
\r
135 int(math.floor(delta.microseconds / 1e3)))
\r
140 def write_key(self, write_dict, key=None, value=None, start_stop=None):
\r
142 Writes a key / value pair in a supported format.
\r
147 Parameter description.
\r
152 Return value description.
\r
155 if key != None and (value != None or start_stop != None):
\r
156 indent = '\t' * write_dict['indentationLevel']
\r
157 if write_dict['format'] == 'xml':
\r
158 if start_stop == 'start':
\r
159 write_dict['logHandle'].write('%s<%s>\n' % (indent, key))
\r
160 elif start_stop == 'stop':
\r
161 write_dict['logHandle'].write('%s</%s>\n' % (indent, key))
\r
163 write_dict['logHandle'].write(
\r
164 '%s<%s>%s</%s>\n' % (indent, key, value, key))
\r
165 else: # writeDict['format'] == 'txt':
\r
166 write_dict['logHandle'].write(
\r
167 '%s%40s : %s\n' % (indent, key, value))
\r
169 def write_log_header(self, write_dict):
\r
171 Object description.
\r
176 Parameter description.
\r
181 Return value description.
\r
186 # Retrieve operating environment information
\r
189 user = os.getlogin()
\r
192 user = os.getenv('USERNAME')
\r
194 user = os.getenv('USER')
\r
196 user = 'unknown_user'
\r
198 (sysname, nodename, release, version, machine,
\r
199 processor) = platform.uname()
\r
201 (sysname, nodename, release, version, machine, processor) = (
\r
202 'unknown_sysname', 'unknown_nodename', 'unknown_release',
\r
203 'unknown_version', 'unknown_machine', 'unknown_processor')
\r
205 hostname = platform.node()
\r
207 hostname = 'unknown_hostname'
\r
209 self.write_key(write_dict, 'process', None, 'start')
\r
210 write_dict['indentationLevel'] += 1
\r
212 self.write_key(write_dict, 'description', self.description)
\r
213 self.write_key(write_dict, 'cmd', self.cmd)
\r
215 self.write_key(write_dict, 'args', ' '.join(self.args))
\r
216 self.write_key(write_dict, 'start', self.start)
\r
217 self.write_key(write_dict, 'end', self.end)
\r
218 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
220 self.write_key(write_dict, 'user', user)
\r
221 self.write_key(write_dict, 'sysname', sysname)
\r
222 self.write_key(write_dict, 'nodename', nodename)
\r
223 self.write_key(write_dict, 'release', release)
\r
224 self.write_key(write_dict, 'version', version)
\r
225 self.write_key(write_dict, 'machine', machine)
\r
226 self.write_key(write_dict, 'processor', processor)
\r
228 if len(self.process_keys) > 0:
\r
229 self.write_key(write_dict, 'processKeys', None, 'start')
\r
230 for pair in self.process_keys:
\r
231 (key, value) = pair
\r
232 write_dict['indentationLevel'] += 1
\r
233 self.write_key(write_dict, key, value)
\r
234 write_dict['indentationLevel'] -= 1
\r
235 self.write_key(write_dict, 'processKeys', None, 'stop')
\r
237 self.write_key(write_dict, 'status', self.status)
\r
239 def write_log_footer(self, write_dict):
\r
241 Object description.
\r
246 Parameter description.
\r
251 Return value description.
\r
254 write_dict['indentationLevel'] -= 1
\r
255 self.write_key(write_dict, 'process', None, 'stop')
\r
257 def write_log(self,
\r
258 log_handle=sys.stdout,
\r
259 indentation_level=0,
\r
262 Writes logging information to the specified handle.
\r
267 Parameter description.
\r
272 Return value description.
\r
276 write_dict['logHandle'] = log_handle
\r
277 write_dict['indentationLevel'] = indentation_level
\r
278 write_dict['format'] = format
\r
281 self.write_log_header(write_dict)
\r
284 self.write_key(write_dict, 'output', None, 'start')
\r
285 if format == 'xml':
\r
286 log_handle.write('<![CDATA[\n')
\r
287 for line in self.log:
\r
288 log_handle.write('%s%s\n' % ('', line))
\r
289 if format == 'xml':
\r
290 log_handle.write(']]>\n')
\r
291 self.write_key(write_dict, 'output', None, 'stop')
\r
293 self.write_log_footer(write_dict)
\r
295 def write_log_to_disk(self, log_filename=None, format='xml', header=None):
\r
297 Object description.
\r
302 Parameter description.
\r
307 Return value description.
\r
312 # This also doesn't seem like the best structure...
\r
315 log_handle = open(log_filename,
\r
320 log_handle = open(log_filename,
\r
323 print('Couldn\'t open log : %s' % log_filename)
\r
328 if format == 'xml':
\r
329 log_handle.write('<![CDATA[\n')
\r
330 log_handle.write(header)
\r
331 if format == 'xml':
\r
332 log_handle.write(']]>\n')
\r
333 self.write_log(log_handle)
\r
336 def log_line(self, line):
\r
338 Adds a line of text to the log.
\r
343 Parameter description.
\r
348 Return value description.
\r
351 self.log.append(line.rstrip())
\r
353 print('%s' % line.rstrip())
\r
357 Executes the current process.
\r
362 Parameter description.
\r
367 Return value description.
\r
374 import subprocess as sp
\r
378 self.start = datetime.datetime.now()
\r
380 cmdargs = [self.cmd]
\r
381 cmdargs.extend(self.args)
\r
386 '\n%s : %s\n' % (self.__class__, sp.list2cmdline(cmdargs)))
\r
388 print('\n%s : %s\n' % (self.__class__, ' '.join(cmdargs)))
\r
390 # intialize a few variables that may or may not be set later
\r
395 parentenv = os.environ
\r
396 parentcwd = os.getcwd()
\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
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
429 if process != 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 it's 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 != None and process.poll() is None:
\r
449 line = process.stdout.readline()
\r
454 self.log_line(str(line, encoding='utf-8'))
\r
457 self.log_line(line)
\r
459 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
461 self.status = process.returncode
\r
463 if self.batch_wrapper and tmp_wrapper:
\r
465 os.remove(tmp_wrapper)
\r
468 'Couldn\'t remove temp wrapper : %s' % tmp_wrapper)
\r
469 traceback.print_exc()
\r
475 # print('reading stdout lines')
\r
476 stdout_lines = stdout.readlines()
\r
477 exit_code = stdout.close()
\r
483 os.environ = parentenv
\r
485 os.chdir(parentcwd)
\r
487 if len(stdout_lines) > 0:
\r
488 for line in stdout_lines:
\r
489 self.log_line(line)
\r
494 self.log_line('Logging error : %s' % sys.exc_info()[0])
\r
496 self.status = exit_code
\r
498 self.end = datetime.datetime.now()
\r
501 class ProcessList(Process):
\r
503 A list of processes with logged output.
\r
506 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
508 Object description.
\r
513 Parameter description.
\r
518 Return value description.
\r
521 Process.__init__(self, description, None, None, cwd, env)
\r
522 'Initialize the standard class variables'
\r
523 self.processes = []
\r
524 self.blocking = blocking
\r
526 def generate_report(self, write_dict):
\r
528 Generates a log based on the success of the child processes.
\r
533 Parameter description.
\r
538 Return value description.
\r
543 indent = '\t' * (write_dict['indentationLevel'] + 1)
\r
547 for child in self.processes:
\r
548 if isinstance(child, ProcessList):
\r
549 child.generate_report(write_dict)
\r
552 key = child.description
\r
553 value = child.status
\r
554 if write_dict['format'] == 'xml':
\r
556 '%s<result description=\'%s\'>%s</result>' % (
\r
557 indent, key, value))
\r
558 else: # writeDict['format'] == 'txt':
\r
559 child_result = ('%s%40s : %s' % (indent, key, value))
\r
560 self.log.append(child_result)
\r
562 if child.status != 0:
\r
569 self.log = ['No child processes available to generate a report']
\r
572 def write_log_header(self, write_dict):
\r
574 Object description.
\r
579 Parameter description.
\r
584 Return value description.
\r
587 self.write_key(write_dict, 'processList', None, 'start')
\r
588 write_dict['indentationLevel'] += 1
\r
590 self.write_key(write_dict, 'description', self.description)
\r
591 self.write_key(write_dict, 'start', self.start)
\r
592 self.write_key(write_dict, 'end', self.end)
\r
593 self.write_key(write_dict, 'elapsed', self.get_elapsed_seconds())
\r
595 self.generate_report(write_dict)
\r
597 self.write_key(write_dict, 'status', self.status)
\r
599 def write_log_footer(self, write_dict):
\r
601 Object description.
\r
606 Parameter description.
\r
611 Return value description.
\r
614 write_dict['indentationLevel'] -= 1
\r
615 self.write_key(write_dict, 'processList', None, 'stop')
\r
617 def write_log(self,
\r
618 log_handle=sys.stdout,
\r
619 indentation_level=0,
\r
622 Writes logging information to the specified handle.
\r
627 Parameter description.
\r
632 Return value description.
\r
636 write_dict['logHandle'] = log_handle
\r
637 write_dict['indentationLevel'] = indentation_level
\r
638 write_dict['format'] = format
\r
641 self.write_log_header(write_dict)
\r
644 self.write_key(write_dict, 'output', None, 'start')
\r
645 for line in self.log:
\r
646 log_handle.write('%s%s\n' % ('', line))
\r
647 self.write_key(write_dict, 'output', None, 'stop')
\r
650 self.write_key(write_dict, 'processes', None, 'start')
\r
651 for child in self.processes:
\r
652 child.write_log(log_handle, indentation_level + 1, format)
\r
653 self.write_key(write_dict, 'processes', None, 'stop')
\r
655 self.write_log_footer(write_dict)
\r
659 Executes the list of processes.
\r
664 Parameter description.
\r
669 Return value description.
\r
674 self.start = datetime.datetime.now()
\r
678 for child in self.processes:
\r
683 print('%s : caught exception in child class %s' % (
\r
684 self.__class__, child.__class__))
\r
685 traceback.print_exc()
\r
688 if self.blocking and child.status != 0:
\r
689 print('%s : child class %s finished with an error' % (
\r
690 self.__class__, child.__class__))
\r
694 self.end = datetime.datetime.now()
\r
699 Object description.
\r
704 Parameter description.
\r
709 Return value description.
\r
714 p = optparse.OptionParser(description='A process logging script',
\r
716 version='process 0.1',
\r
717 usage=('%prog [options] '
\r
718 '[options for the logged process]'))
\r
719 p.add_option('--cmd', '-c', default=None)
\r
720 p.add_option('--log', '-l', default=None)
\r
722 options, arguments = p.parse_args()
\r
728 log_filename = options.log
\r
731 args_start = sys.argv.index('--') + 1
\r
732 args = sys.argv[args_start:]
\r
734 args_start = len(sys.argv) + 1
\r
738 print('process: No command specified')
\r
741 # Test regular logging
\r
743 process = Process(description='a process', cmd=cmd, args=args)
\r
746 # Test report generation and writing a log
\r
748 process_list = ProcessList('a process list')
\r
749 process_list.processes.append(process)
\r
750 process_list.echo = True
\r
751 process_list.execute()
\r
753 process_list.write_log_to_disk(log_filename)
\r
756 if __name__ == '__main__':
\r