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
14 def readText(textFile):
\r
15 if (textFile != ""):
\r
16 fp = open(textFile, 'rb')
\r
17 # Create a text/plain message
\r
25 def writeText(text, textFile):
\r
26 if (textFile != ""):
\r
27 fp = open(textFile, 'wb')
\r
28 # Create a text/plain message
\r
38 A process with logged output
\r
47 batchWrapper=False):
\r
48 """Initialize the standard class variables"""
\r
51 self.description = cmd
\r
53 self.description = description
\r
62 self.batchWrapper = batchWrapper
\r
63 self.processKeys = []
\r
67 def getElapsedSeconds(self):
\r
70 if self.end and self.start:
\r
71 delta = (self.end - self.start)
\r
72 formatted = "%s.%s" % (delta.days * 86400 + delta.seconds,
\r
73 int(math.floor(delta.microseconds / 1e3)))
\r
80 def writeKey(self, writeDict, key=None, value=None, startStop=None):
\r
81 "Write a key, value pair in a supported format"
\r
82 if key != None and (value != None or startStop != None):
\r
83 indent = '\t' * writeDict['indentationLevel']
\r
84 if writeDict['format'] == 'xml':
\r
85 if startStop == 'start':
\r
86 writeDict['logHandle'].write("%s<%s>\n" % (indent, key))
\r
87 elif startStop == 'stop':
\r
88 writeDict['logHandle'].write("%s</%s>\n" % (indent, key))
\r
90 writeDict['logHandle'].write(
\r
91 "%s<%s>%s</%s>\n" % (indent, key, value, key))
\r
92 else: # writeDict['format'] == 'txt':
\r
93 writeDict['logHandle'].write(
\r
94 "%s%40s : %s\n" % (indent, key, value))
\r
96 def writeLogHeader(self, writeDict):
\r
99 # Retrieve operating environment information
\r
102 user = os.getlogin()
\r
105 user = os.getenv("USERNAME")
\r
107 user = os.getenv("USER")
\r
109 user = "unknown_user"
\r
111 (sysname, nodename, release, version, machine,
\r
112 processor) = platform.uname()
\r
114 (sysname, nodename, release, version, machine, processor) = (
\r
115 "unknown_sysname", "unknown_nodename", "unknown_release",
\r
116 "unknown_version", "unknown_machine", "unknown_processor")
\r
118 hostname = platform.node()
\r
120 hostname = "unknown_hostname"
\r
122 self.writeKey(writeDict, 'process', None, 'start')
\r
123 writeDict['indentationLevel'] += 1
\r
125 self.writeKey(writeDict, 'description', self.description)
\r
126 self.writeKey(writeDict, 'cmd', self.cmd)
\r
127 if self.args: self.writeKey(writeDict, 'args', ' '.join(self.args))
\r
128 self.writeKey(writeDict, 'start', self.start)
\r
129 self.writeKey(writeDict, 'end', self.end)
\r
130 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())
\r
132 self.writeKey(writeDict, 'user', user)
\r
133 self.writeKey(writeDict, 'sysname', sysname)
\r
134 self.writeKey(writeDict, 'nodename', nodename)
\r
135 self.writeKey(writeDict, 'release', release)
\r
136 self.writeKey(writeDict, 'version', version)
\r
137 self.writeKey(writeDict, 'machine', machine)
\r
138 self.writeKey(writeDict, 'processor', processor)
\r
140 if len(self.processKeys) > 0:
\r
141 self.writeKey(writeDict, 'processKeys', None, 'start')
\r
142 for pair in self.processKeys:
\r
143 (key, value) = pair
\r
144 writeDict['indentationLevel'] += 1
\r
145 self.writeKey(writeDict, key, value)
\r
146 writeDict['indentationLevel'] -= 1
\r
147 self.writeKey(writeDict, 'processKeys', None, 'stop')
\r
149 self.writeKey(writeDict, 'status', self.status)
\r
153 def writeLogFooter(self, writeDict):
\r
154 writeDict['indentationLevel'] -= 1
\r
155 self.writeKey(writeDict, 'process', None, 'stop')
\r
159 def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):
\r
161 Write logging information to the specified handle
\r
165 writeDict['logHandle'] = logHandle
\r
166 writeDict['indentationLevel'] = indentationLevel
\r
167 writeDict['format'] = format
\r
170 self.writeLogHeader(writeDict)
\r
173 self.writeKey(writeDict, 'output', None, 'start')
\r
174 if format == 'xml':
\r
175 logHandle.write("<![CDATA[\n")
\r
176 for line in self.log:
\r
177 logHandle.write('%s%s\n' % ("", line))
\r
178 if format == 'xml':
\r
179 logHandle.write("]]>\n")
\r
180 self.writeKey(writeDict, 'output', None, 'stop')
\r
182 self.writeLogFooter(writeDict)
\r
186 def writeLogToDisk(self, logFilename=None, format='xml', header=None):
\r
189 # This also doesn't seem like the best structure...
\r
192 logHandle = open(logFilename, mode='wt', encoding="utf-8")
\r
195 logHandle = open(logFilename, mode='wt')
\r
197 print("Couldn't open log : %s" % logFilename)
\r
202 if format == 'xml':
\r
203 logHandle.write("<![CDATA[\n")
\r
204 logHandle.write(header)
\r
205 if format == 'xml':
\r
206 logHandle.write("]]>\n")
\r
207 self.writeLog(logHandle)
\r
212 def logLine(self, line):
\r
213 "Add a line of text to the log"
\r
214 self.log.append(line.rstrip())
\r
216 print("%s" % line.rstrip())
\r
222 Execute this process
\r
229 import subprocess as sp
\r
233 self.start = datetime.datetime.now()
\r
235 cmdargs = [self.cmd]
\r
236 cmdargs.extend(self.args)
\r
241 "\n%s : %s\n" % (self.__class__, sp.list2cmdline(cmdargs)))
\r
243 print("\n%s : %s\n" % (self.__class__, " ".join(cmdargs)))
\r
245 # intialize a few variables that may or may not be set later
\r
250 parentenv = os.environ
\r
251 parentcwd = os.getcwd()
\r
256 if self.batchWrapper:
\r
257 cmd = " ".join(cmdargs)
\r
258 tmpWrapper = os.path.join(self.cwd, "process.bat")
\r
259 writeText(cmd, tmpWrapper)
\r
260 print("%s : Running process through wrapper %s\n" % (
\r
261 self.__class__, tmpWrapper))
\r
262 process = sp.Popen([tmpWrapper], stdout=sp.PIPE,
\r
264 cwd=self.cwd, env=self.env)
\r
266 process = sp.Popen(cmdargs, stdout=sp.PIPE,
\r
268 cwd=self.cwd, env=self.env)
\r
273 os.environ = self.env
\r
277 stdin, stdout = os.popen4(cmdargs, 'r')
\r
279 print("Couldn't execute command : %s" % cmdargs[0])
\r
280 traceback.print_exc()
\r
284 if process != None:
\r
285 # pid = process.pid
\r
286 # log.logLine("process id %s\n" % pid)
\r
289 # This is more proper python, and resolves some issues with
\r
290 # a process ending before all of its output has been
\r
291 # processed, but it also seems to stall when the read
\r
292 # buffer is near or over it's limit. this happens
\r
293 # relatively frequently with processes that generate lots
\r
294 # of print statements.
\r
295 for line in process.stdout:
\r
298 # So we go with the, um, uglier option below
\r
300 # This is now used to ensure that the process has finished
\r
302 while line != None and process.poll() == None:
\r
304 line = process.stdout.readline()
\r
309 self.logLine(str(line, encoding="utf-8"))
\r
314 self.logLine("Logging error : %s" % sys.exc_info()[0])
\r
316 self.status = process.returncode
\r
318 if self.batchWrapper and tmpWrapper:
\r
320 os.remove(tmpWrapper)
\r
322 print("Couldn't remove temp wrapper : %s" % tmpWrapper)
\r
323 traceback.print_exc()
\r
329 # print("reading stdout lines")
\r
330 stdoutLines = stdout.readlines()
\r
331 exitCode = stdout.close()
\r
337 os.environ = parentenv
\r
339 os.chdir(parentcwd)
\r
341 if len(stdoutLines) > 0:
\r
342 for line in stdoutLines:
\r
348 self.logLine("Logging error : %s" % sys.exc_info()[0])
\r
350 self.status = exitCode
\r
352 self.end = datetime.datetime.now()
\r
358 class ProcessList(Process):
\r
360 A list of processes with logged output
\r
363 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
364 Process.__init__(self, description, None, None, cwd, env)
\r
365 "Initialize the standard class variables"
\r
366 self.processes = []
\r
367 self.blocking = blocking
\r
371 def generateReport(self, writeDict):
\r
373 Generate a log based on the success of the child processes
\r
377 indent = '\t' * (writeDict['indentationLevel'] + 1)
\r
381 for child in self.processes:
\r
382 if isinstance(child, ProcessList):
\r
383 child.generateReport(writeDict)
\r
386 key = child.description
\r
387 value = child.status
\r
388 if writeDict['format'] == 'xml':
\r
390 "%s<result description=\"%s\">%s</result>" % (
\r
391 indent, key, value))
\r
392 else: # writeDict['format'] == 'txt':
\r
393 childResult = ("%s%40s : %s" % (indent, key, value))
\r
394 self.log.append(childResult)
\r
396 if child.status != 0:
\r
403 self.log = ["No child processes available to generate a report"]
\r
406 def writeLogHeader(self, writeDict):
\r
407 self.writeKey(writeDict, 'processList', None, 'start')
\r
408 writeDict['indentationLevel'] += 1
\r
410 self.writeKey(writeDict, 'description', self.description)
\r
411 self.writeKey(writeDict, 'start', self.start)
\r
412 self.writeKey(writeDict, 'end', self.end)
\r
413 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds())
\r
415 self.generateReport(writeDict)
\r
417 self.writeKey(writeDict, 'status', self.status)
\r
421 def writeLogFooter(self, writeDict):
\r
422 writeDict['indentationLevel'] -= 1
\r
423 self.writeKey(writeDict, 'processList', None, 'stop')
\r
427 def writeLog(self, logHandle=sys.stdout, indentationLevel=0, format='xml'):
\r
429 Write logging information to the specified handle
\r
433 writeDict['logHandle'] = logHandle
\r
434 writeDict['indentationLevel'] = indentationLevel
\r
435 writeDict['format'] = format
\r
438 self.writeLogHeader(writeDict)
\r
441 self.writeKey(writeDict, 'output', None, 'start')
\r
442 for line in self.log:
\r
443 logHandle.write('%s%s\n' % ("", line))
\r
444 self.writeKey(writeDict, 'output', None, 'stop')
\r
447 self.writeKey(writeDict, 'processes', None, 'start')
\r
448 for child in self.processes:
\r
449 child.writeLog(logHandle, indentationLevel + 1, format)
\r
450 self.writeKey(writeDict, 'processes', None, 'stop')
\r
452 self.writeLogFooter(writeDict)
\r
458 Execute this list of processes
\r
462 self.start = datetime.datetime.now()
\r
466 for child in self.processes:
\r
471 print("%s : caught exception in child class %s" % (
\r
472 self.__class__, child.__class__))
\r
473 traceback.print_exc()
\r
476 if self.blocking and child.status != 0:
\r
477 print("%s : child class %s finished with an error" % (
\r
478 self.__class__, child.__class__))
\r
482 self.end = datetime.datetime.now()
\r
491 p = optparse.OptionParser(description='A process logging script',
\r
493 version='process 0.1',
\r
494 usage=('%prog [options] '
\r
495 '[options for the logged process]'))
\r
496 p.add_option('--cmd', '-c', default=None)
\r
497 p.add_option('--log', '-l', default=None)
\r
499 options, arguments = p.parse_args()
\r
505 logFilename = options.log
\r
508 argsStart = sys.argv.index('--') + 1
\r
509 args = sys.argv[argsStart:]
\r
511 argsStart = len(sys.argv) + 1
\r
515 print("process: No command specified")
\r
518 # Test regular logging
\r
520 process = Process(description="a process", cmd=cmd, args=args)
\r
523 # Test report generation and writing a log
\r
525 processList = ProcessList("a process list")
\r
526 processList.processes.append(process)
\r
527 processList.echo = True
\r
528 processList.execute()
\r
530 processList.writeLogToDisk(logFilename)
\r
534 if __name__ == '__main__':
\r