1 #!/usr/bin/env python
\r
2 # -*- coding: utf-8 -*-
\r
5 A process wrapper class that maintains the text output and execution status
\r
6 of 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
28 if (textFile != ""):
\r
29 fp = open(textFile, 'rb')
\r
30 # Create a text/plain message
\r
38 def writeText(text, textFile):
\r
39 if (textFile != ""):
\r
40 fp = open(textFile, 'wb')
\r
41 # Create a text/plain message
\r
51 A process with logged output
\r
60 batchWrapper=False):
\r
61 """Initialize the standard class variables"""
\r
64 self.description = cmd
\r
66 self.description = description
\r
75 self.batchWrapper = batchWrapper
\r
76 self.processKeys = []
\r
80 def getElapsedSeconds(self):
\r
83 if self.end and self.start:
\r
84 delta = (self.end - self.start)
\r
85 formatted = "%s.%s" % (delta.days * 86400 + delta.seconds,
\r
86 int(math.floor(delta.microseconds / 1e3)))
\r
93 def writeKey(self, writeDict, key=None, value=None, startStop=None):
\r
94 "Write a key, value pair in a supported format"
\r
95 if key != None and (value != None or startStop != None):
\r
96 indent = '\t' * writeDict['indentationLevel']
\r
97 if writeDict['format'] == 'xml':
\r
98 if startStop == 'start':
\r
99 writeDict['logHandle'].write("%s<%s>\n" % (indent, key))
\r
100 elif startStop == 'stop':
\r
101 writeDict['logHandle'].write("%s</%s>\n" % (indent, key))
\r
103 writeDict['logHandle'].write(
\r
104 "%s<%s>%s</%s>\n" % (indent, key, value, key))
\r
105 else: # writeDict['format'] == 'txt':
\r
106 writeDict['logHandle'].write(
\r
107 "%s%40s : %s\n" % (indent, key, value))
\r
109 def writeLogHeader(self, writeDict):
\r
112 # Retrieve operating environment information
\r
115 user = os.getlogin()
\r
118 user = os.getenv("USERNAME")
\r
120 user = os.getenv("USER")
\r
122 user = "unknown_user"
\r
124 (sysname, nodename, release, version, machine,
\r
125 processor) = platform.uname()
\r
127 (sysname, nodename, release, version, machine, processor) = (
\r
128 "unknown_sysname", "unknown_nodename", "unknown_release",
\r
129 "unknown_version", "unknown_machine", "unknown_processor")
\r
131 hostname = platform.node()
\r
133 hostname = "unknown_hostname"
\r
135 self.writeKey(writeDict, 'process', None, 'start')
\r
136 writeDict['indentationLevel'] += 1
\r
138 self.writeKey(writeDict, 'description', self.description)
\r
139 self.writeKey(writeDict, 'cmd', self.cmd)
\r
140 if self.args: self.writeKey(writeDict, 'args', ' '.join(self.args))
\r
141 self.writeKey(writeDict, 'start', self.start)
\r
142 self.writeKey(writeDict, 'end', self.end)
\r
143 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())
\r
145 self.writeKey(writeDict, 'user', user)
\r
146 self.writeKey(writeDict, 'sysname', sysname)
\r
147 self.writeKey(writeDict, 'nodename', nodename)
\r
148 self.writeKey(writeDict, 'release', release)
\r
149 self.writeKey(writeDict, 'version', version)
\r
150 self.writeKey(writeDict, 'machine', machine)
\r
151 self.writeKey(writeDict, 'processor', processor)
\r
153 if len(self.processKeys) > 0:
\r
154 self.writeKey(writeDict, 'processKeys', None, 'start')
\r
155 for pair in self.processKeys:
\r
156 (key, value) = pair
\r
157 writeDict['indentationLevel'] += 1
\r
158 self.writeKey(writeDict, key, value)
\r
159 writeDict['indentationLevel'] -= 1
\r
160 self.writeKey(writeDict, 'processKeys', None, 'stop')
\r
162 self.writeKey(writeDict, 'status', self.status)
\r
166 def writeLogFooter(self, writeDict):
\r
167 writeDict['indentationLevel'] -= 1
\r
168 self.writeKey(writeDict, 'process', None, 'stop')
\r
172 def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):
\r
174 Write logging information to the specified handle
\r
178 writeDict['logHandle'] = logHandle
\r
179 writeDict['indentationLevel'] = indentationLevel
\r
180 writeDict['format'] = format
\r
183 self.writeLogHeader(writeDict)
\r
186 self.writeKey(writeDict, 'output', None, 'start')
\r
187 if format == 'xml':
\r
188 logHandle.write("<![CDATA[\n")
\r
189 for line in self.log:
\r
190 logHandle.write('%s%s\n' % ("", line))
\r
191 if format == 'xml':
\r
192 logHandle.write("]]>\n")
\r
193 self.writeKey(writeDict, 'output', None, 'stop')
\r
195 self.writeLogFooter(writeDict)
\r
199 def writeLogToDisk(self, logFilename=None, format='xml', header=None):
\r
202 # This also doesn't seem like the best structure...
\r
205 logHandle = open(logFilename, mode='wt', encoding="utf-8")
\r
208 logHandle = open(logFilename, mode='wt')
\r
210 print("Couldn't open log : %s" % logFilename)
\r
215 if format == 'xml':
\r
216 logHandle.write("<![CDATA[\n")
\r
217 logHandle.write(header)
\r
218 if format == 'xml':
\r
219 logHandle.write("]]>\n")
\r
220 self.writeLog(logHandle)
\r
225 def logLine(self, line):
\r
226 "Add a line of text to the log"
\r
227 self.log.append(line.rstrip())
\r
229 print("%s" % line.rstrip())
\r
235 Execute this process
\r
242 import subprocess as sp
\r
246 self.start = datetime.datetime.now()
\r
248 cmdargs = [self.cmd]
\r
249 cmdargs.extend(self.args)
\r
254 "\n%s : %s\n" % (self.__class__, sp.list2cmdline(cmdargs)))
\r
256 print("\n%s : %s\n" % (self.__class__, " ".join(cmdargs)))
\r
258 # intialize a few variables that may or may not be set later
\r
263 parentenv = os.environ
\r
264 parentcwd = os.getcwd()
\r
269 if self.batchWrapper:
\r
270 cmd = " ".join(cmdargs)
\r
271 tmpWrapper = os.path.join(self.cwd, "process.bat")
\r
272 writeText(cmd, tmpWrapper)
\r
273 print("%s : Running process through wrapper %s\n" % (
\r
274 self.__class__, tmpWrapper))
\r
275 process = sp.Popen([tmpWrapper], stdout=sp.PIPE,
\r
277 cwd=self.cwd, env=self.env)
\r
279 process = sp.Popen(cmdargs, stdout=sp.PIPE,
\r
281 cwd=self.cwd, env=self.env)
\r
286 os.environ = self.env
\r
290 stdin, stdout = os.popen4(cmdargs, 'r')
\r
292 print("Couldn't execute command : %s" % cmdargs[0])
\r
293 traceback.print_exc()
\r
297 if process != None:
\r
298 # pid = process.pid
\r
299 # log.logLine("process id %s\n" % pid)
\r
302 # This is more proper python, and resolves some issues with
\r
303 # a process ending before all of its output has been
\r
304 # processed, but it also seems to stall when the read
\r
305 # buffer is near or over it's limit. this happens
\r
306 # relatively frequently with processes that generate lots
\r
307 # of print statements.
\r
308 for line in process.stdout:
\r
311 # So we go with the, um, uglier option below
\r
313 # This is now used to ensure that the process has finished
\r
315 while line != None and process.poll() == None:
\r
317 line = process.stdout.readline()
\r
322 self.logLine(str(line, encoding="utf-8"))
\r
327 self.logLine("Logging error : %s" % sys.exc_info()[0])
\r
329 self.status = process.returncode
\r
331 if self.batchWrapper and tmpWrapper:
\r
333 os.remove(tmpWrapper)
\r
335 print("Couldn't remove temp wrapper : %s" % tmpWrapper)
\r
336 traceback.print_exc()
\r
342 # print("reading stdout lines")
\r
343 stdoutLines = stdout.readlines()
\r
344 exitCode = stdout.close()
\r
350 os.environ = parentenv
\r
352 os.chdir(parentcwd)
\r
354 if len(stdoutLines) > 0:
\r
355 for line in stdoutLines:
\r
361 self.logLine("Logging error : %s" % sys.exc_info()[0])
\r
363 self.status = exitCode
\r
365 self.end = datetime.datetime.now()
\r
371 class ProcessList(Process):
\r
373 A list of processes with logged output
\r
376 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
377 Process.__init__(self, description, None, None, cwd, env)
\r
378 "Initialize the standard class variables"
\r
379 self.processes = []
\r
380 self.blocking = blocking
\r
384 def generateReport(self, writeDict):
\r
386 Generate a log based on the success of the child processes
\r
390 indent = '\t' * (writeDict['indentationLevel'] + 1)
\r
394 for child in self.processes:
\r
395 if isinstance(child, ProcessList):
\r
396 child.generateReport(writeDict)
\r
399 key = child.description
\r
400 value = child.status
\r
401 if writeDict['format'] == 'xml':
\r
403 "%s<result description=\"%s\">%s</result>" % (
\r
404 indent, key, value))
\r
405 else: # writeDict['format'] == 'txt':
\r
406 childResult = ("%s%40s : %s" % (indent, key, value))
\r
407 self.log.append(childResult)
\r
409 if child.status != 0:
\r
416 self.log = ["No child processes available to generate a report"]
\r
419 def writeLogHeader(self, writeDict):
\r
420 self.writeKey(writeDict, 'processList', None, 'start')
\r
421 writeDict['indentationLevel'] += 1
\r
423 self.writeKey(writeDict, 'description', self.description)
\r
424 self.writeKey(writeDict, 'start', self.start)
\r
425 self.writeKey(writeDict, 'end', self.end)
\r
426 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())
\r
428 self.generateReport(writeDict)
\r
430 self.writeKey(writeDict, 'status', self.status)
\r
434 def writeLogFooter(self, writeDict):
\r
435 writeDict['indentationLevel'] -= 1
\r
436 self.writeKey(writeDict, 'processList', None, 'stop')
\r
440 def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):
\r
442 Write logging information to the specified handle
\r
446 writeDict['logHandle'] = logHandle
\r
447 writeDict['indentationLevel'] = indentationLevel
\r
448 writeDict['format'] = format
\r
451 self.writeLogHeader(writeDict)
\r
454 self.writeKey(writeDict, 'output', None, 'start')
\r
455 for line in self.log:
\r
456 logHandle.write('%s%s\n' % ("", line))
\r
457 self.writeKey(writeDict, 'output', None, 'stop')
\r
460 self.writeKey(writeDict, 'processes', None, 'start')
\r
461 for child in self.processes:
\r
462 child.writeLog(logHandle, indentationLevel + 1, format)
\r
463 self.writeKey(writeDict, 'processes', None, 'stop')
\r
465 self.writeLogFooter(writeDict)
\r
471 Execute this list of processes
\r
475 self.start = datetime.datetime.now()
\r
479 for child in self.processes:
\r
484 print("%s : caught exception in child class %s" % (
\r
485 self.__class__, child.__class__))
\r
486 traceback.print_exc()
\r
489 if self.blocking and child.status != 0:
\r
490 print("%s : child class %s finished with an error" % (
\r
491 self.__class__, child.__class__))
\r
495 self.end = datetime.datetime.now()
\r
504 p = optparse.OptionParser(description='A process logging script',
\r
506 version='process 0.1',
\r
507 usage=('%prog [options] '
\r
508 '[options for the logged process]'))
\r
509 p.add_option('--cmd', '-c', default=None)
\r
510 p.add_option('--log', '-l', default=None)
\r
512 options, arguments = p.parse_args()
\r
518 logFilename = options.log
\r
521 argsStart = sys.argv.index('--') + 1
\r
522 args = sys.argv[argsStart:]
\r
524 argsStart = len(sys.argv) + 1
\r
528 print("process: No command specified")
\r
531 # Test regular logging
\r
533 process = Process(description="a process", cmd=cmd, args=args)
\r
536 # Test report generation and writing a log
\r
538 processList = ProcessList("a process list")
\r
539 processList.processes.append(process)
\r
540 processList.echo = True
\r
541 processList.execute()
\r
543 processList.writeLogToDisk(logFilename)
\r
547 if __name__ == '__main__':
\r