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__ = ['readText',
\r
27 def readText(textFile):
\r
34 Parameter description.
\r
39 Return value description.
\r
42 if (textFile != ""):
\r
43 fp = open(textFile, 'rb')
\r
44 # Create a text/plain message
\r
50 def writeText(text, textFile):
\r
57 Parameter description.
\r
62 Return value description.
\r
65 if (textFile != ""):
\r
66 fp = open(textFile, 'wb')
\r
67 # Create a text/plain message
\r
75 A process with logged output.
\r
84 batchWrapper=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.batchWrapper = batchWrapper
\r
113 self.processKeys = []
\r
115 def getElapsedSeconds(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 writeKey(self, writeDict, key=None, value=None, startStop=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 startStop != None):
\r
156 indent = '\t' * writeDict['indentationLevel']
\r
157 if writeDict['format'] == 'xml':
\r
158 if startStop == 'start':
\r
159 writeDict['logHandle'].write("%s<%s>\n" % (indent, key))
\r
160 elif startStop == 'stop':
\r
161 writeDict['logHandle'].write("%s</%s>\n" % (indent, key))
\r
163 writeDict['logHandle'].write(
\r
164 "%s<%s>%s</%s>\n" % (indent, key, value, key))
\r
165 else: # writeDict['format'] == 'txt':
\r
166 writeDict['logHandle'].write(
\r
167 "%s%40s : %s\n" % (indent, key, value))
\r
169 def writeLogHeader(self, writeDict):
\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.writeKey(writeDict, 'process', None, 'start')
\r
210 writeDict['indentationLevel'] += 1
\r
212 self.writeKey(writeDict, 'description', self.description)
\r
213 self.writeKey(writeDict, 'cmd', self.cmd)
\r
215 self.writeKey(writeDict, 'args', ' '.join(self.args))
\r
216 self.writeKey(writeDict, 'start', self.start)
\r
217 self.writeKey(writeDict, 'end', self.end)
\r
218 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())
\r
220 self.writeKey(writeDict, 'user', user)
\r
221 self.writeKey(writeDict, 'sysname', sysname)
\r
222 self.writeKey(writeDict, 'nodename', nodename)
\r
223 self.writeKey(writeDict, 'release', release)
\r
224 self.writeKey(writeDict, 'version', version)
\r
225 self.writeKey(writeDict, 'machine', machine)
\r
226 self.writeKey(writeDict, 'processor', processor)
\r
228 if len(self.processKeys) > 0:
\r
229 self.writeKey(writeDict, 'processKeys', None, 'start')
\r
230 for pair in self.processKeys:
\r
231 (key, value) = pair
\r
232 writeDict['indentationLevel'] += 1
\r
233 self.writeKey(writeDict, key, value)
\r
234 writeDict['indentationLevel'] -= 1
\r
235 self.writeKey(writeDict, 'processKeys', None, 'stop')
\r
237 self.writeKey(writeDict, 'status', self.status)
\r
239 def writeLogFooter(self, writeDict):
\r
241 Object description.
\r
246 Parameter description.
\r
251 Return value description.
\r
254 writeDict['indentationLevel'] -= 1
\r
255 self.writeKey(writeDict, 'process', None, 'stop')
\r
257 def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):
\r
259 Writes logging information to the specified handle.
\r
264 Parameter description.
\r
269 Return value description.
\r
273 writeDict['logHandle'] = logHandle
\r
274 writeDict['indentationLevel'] = indentationLevel
\r
275 writeDict['format'] = format
\r
278 self.writeLogHeader(writeDict)
\r
281 self.writeKey(writeDict, 'output', None, 'start')
\r
282 if format == 'xml':
\r
283 logHandle.write("<![CDATA[\n")
\r
284 for line in self.log:
\r
285 logHandle.write('%s%s\n' % ("", line))
\r
286 if format == 'xml':
\r
287 logHandle.write("]]>\n")
\r
288 self.writeKey(writeDict, 'output', None, 'stop')
\r
290 self.writeLogFooter(writeDict)
\r
292 def writeLogToDisk(self, logFilename=None, format='xml', header=None):
\r
294 Object description.
\r
299 Parameter description.
\r
304 Return value description.
\r
309 # This also doesn't seem like the best structure...
\r
312 logHandle = open(logFilename, mode='wt', encoding="utf-8")
\r
315 logHandle = open(logFilename, mode='wt')
\r
317 print("Couldn't open log : %s" % logFilename)
\r
322 if format == 'xml':
\r
323 logHandle.write("<![CDATA[\n")
\r
324 logHandle.write(header)
\r
325 if format == 'xml':
\r
326 logHandle.write("]]>\n")
\r
327 self.writeLog(logHandle)
\r
330 def logLine(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.batchWrapper:
\r
396 cmd = " ".join(cmdargs)
\r
397 tmpWrapper = os.path.join(self.cwd, "process.bat")
\r
398 writeText(cmd, tmpWrapper)
\r
399 print("%s : Running process through wrapper %s\n" % (
\r
400 self.__class__, tmpWrapper))
\r
401 process = sp.Popen([tmpWrapper], 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
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() == None:
\r
443 line = process.stdout.readline()
\r
448 self.logLine(str(line, encoding="utf-8"))
\r
453 self.logLine("Logging error : %s" % sys.exc_info()[0])
\r
455 self.status = process.returncode
\r
457 if self.batchWrapper and tmpWrapper:
\r
459 os.remove(tmpWrapper)
\r
461 print("Couldn't remove temp wrapper : %s" % tmpWrapper)
\r
462 traceback.print_exc()
\r
468 # print("reading stdout lines")
\r
469 stdoutLines = stdout.readlines()
\r
470 exitCode = stdout.close()
\r
476 os.environ = parentenv
\r
478 os.chdir(parentcwd)
\r
480 if len(stdoutLines) > 0:
\r
481 for line in stdoutLines:
\r
487 self.logLine("Logging error : %s" % sys.exc_info()[0])
\r
489 self.status = exitCode
\r
491 self.end = datetime.datetime.now()
\r
494 class ProcessList(Process):
\r
496 A list of processes with logged output.
\r
499 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
501 Object description.
\r
506 Parameter description.
\r
511 Return value description.
\r
514 Process.__init__(self, description, None, None, cwd, env)
\r
515 "Initialize the standard class variables"
\r
516 self.processes = []
\r
517 self.blocking = blocking
\r
519 def generateReport(self, writeDict):
\r
521 Generates a log based on the success of the child processes.
\r
526 Parameter description.
\r
531 Return value description.
\r
536 indent = '\t' * (writeDict['indentationLevel'] + 1)
\r
540 for child in self.processes:
\r
541 if isinstance(child, ProcessList):
\r
542 child.generateReport(writeDict)
\r
545 key = child.description
\r
546 value = child.status
\r
547 if writeDict['format'] == 'xml':
\r
549 "%s<result description=\"%s\">%s</result>" % (
\r
550 indent, key, value))
\r
551 else: # writeDict['format'] == 'txt':
\r
552 childResult = ("%s%40s : %s" % (indent, key, value))
\r
553 self.log.append(childResult)
\r
555 if child.status != 0:
\r
562 self.log = ["No child processes available to generate a report"]
\r
565 def writeLogHeader(self, writeDict):
\r
567 Object description.
\r
572 Parameter description.
\r
577 Return value description.
\r
580 self.writeKey(writeDict, 'processList', None, 'start')
\r
581 writeDict['indentationLevel'] += 1
\r
583 self.writeKey(writeDict, 'description', self.description)
\r
584 self.writeKey(writeDict, 'start', self.start)
\r
585 self.writeKey(writeDict, 'end', self.end)
\r
586 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())
\r
588 self.generateReport(writeDict)
\r
590 self.writeKey(writeDict, 'status', self.status)
\r
592 def writeLogFooter(self, writeDict):
\r
594 Object description.
\r
599 Parameter description.
\r
604 Return value description.
\r
607 writeDict['indentationLevel'] -= 1
\r
608 self.writeKey(writeDict, 'processList', None, 'stop')
\r
610 def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):
\r
612 Writes logging information to the specified handle.
\r
617 Parameter description.
\r
622 Return value description.
\r
626 writeDict['logHandle'] = logHandle
\r
627 writeDict['indentationLevel'] = indentationLevel
\r
628 writeDict['format'] = format
\r
631 self.writeLogHeader(writeDict)
\r
634 self.writeKey(writeDict, 'output', None, 'start')
\r
635 for line in self.log:
\r
636 logHandle.write('%s%s\n' % ("", line))
\r
637 self.writeKey(writeDict, 'output', None, 'stop')
\r
640 self.writeKey(writeDict, 'processes', None, 'start')
\r
641 for child in self.processes:
\r
642 child.writeLog(logHandle, indentationLevel + 1, format)
\r
643 self.writeKey(writeDict, 'processes', None, 'stop')
\r
645 self.writeLogFooter(writeDict)
\r
649 Executes the list of processes.
\r
654 Parameter description.
\r
659 Return value description.
\r
664 self.start = datetime.datetime.now()
\r
668 for child in self.processes:
\r
673 print("%s : caught exception in child class %s" % (
\r
674 self.__class__, child.__class__))
\r
675 traceback.print_exc()
\r
678 if self.blocking and child.status != 0:
\r
679 print("%s : child class %s finished with an error" % (
\r
680 self.__class__, child.__class__))
\r
684 self.end = datetime.datetime.now()
\r
689 Object description.
\r
694 Parameter description.
\r
699 Return value description.
\r
704 p = optparse.OptionParser(description='A process logging script',
\r
706 version='process 0.1',
\r
707 usage=('%prog [options] '
\r
708 '[options for the logged process]'))
\r
709 p.add_option('--cmd', '-c', default=None)
\r
710 p.add_option('--log', '-l', default=None)
\r
712 options, arguments = p.parse_args()
\r
718 logFilename = options.log
\r
721 argsStart = sys.argv.index('--') + 1
\r
722 args = sys.argv[argsStart:]
\r
724 argsStart = len(sys.argv) + 1
\r
728 print("process: No command specified")
\r
731 # Test regular logging
\r
733 process = Process(description="a process", cmd=cmd, args=args)
\r
736 # Test report generation and writing a log
\r
738 processList = ProcessList("a process list")
\r
739 processList.processes.append(process)
\r
740 processList.echo = True
\r
741 processList.execute()
\r
743 processList.writeLogToDisk(logFilename)
\r
747 if __name__ == '__main__':
\r