1 #!/usr/bin/env python
\r
2 # -*- coding: utf-8 -*-
\r
4 '''A process wrapper class that maintains the text output and execution status of a process
\r
5 or a list of other process wrappers which carry such data.'''
\r
11 def readText(textFile):
\r
12 if( textFile != "" ):
\r
13 fp = open(textFile, 'rb')
\r
14 # Create a text/plain message
\r
20 def writeText(text, textFile):
\r
21 if( textFile != "" ):
\r
22 fp = open(textFile, 'wb')
\r
23 # Create a text/plain message
\r
30 "A process with logged output"
\r
32 def __init__(self, description=None, cmd=None, args=[], cwd=None, env=None, batchWrapper=False):
\r
33 "Initialize the standard class variables"
\r
36 self.description = cmd
\r
38 self.description = description
\r
47 self.batchWrapper = batchWrapper
\r
48 self.processKeys = []
\r
51 def getElapsedSeconds(self):
\r
53 if self.end and self.start:
\r
54 delta = (self.end - self.start)
\r
55 formatted = "%s.%s" % (delta.days * 86400 + delta.seconds, int(math.floor(delta.microseconds/1e3)))
\r
61 def writeKey(self, writeDict, key=None, value=None, startStop=None):
\r
62 "Write a key, value pair in a supported format"
\r
63 if key != None and (value != None or startStop != None):
\r
64 indent = '\t'*writeDict['indentationLevel']
\r
65 if writeDict['format'] == 'xml':
\r
66 if startStop == 'start':
\r
67 writeDict['logHandle'].write( "%s<%s>\n" % (indent, key) )
\r
68 elif startStop == 'stop':
\r
69 writeDict['logHandle'].write( "%s</%s>\n" % (indent, key) )
\r
71 writeDict['logHandle'].write( "%s<%s>%s</%s>\n" % (indent, key, value, key) )
\r
72 else: # writeDict['format'] == 'txt':
\r
73 writeDict['logHandle'].write( "%s%40s : %s\n" % (indent, key, value) )
\r
75 def writeLogHeader(self, writeDict):
\r
78 # Retrieve operating environment information
\r
81 user = os.getlogin()
\r
84 user = os.getenv("USERNAME")
\r
86 user = os.getenv("USER")
\r
88 user = "unknown_user"
\r
90 (sysname, nodename, release, version, machine, processor) = platform.uname()
\r
92 (sysname, nodename, release, version, machine, processor) = ("unknown_sysname", "unknown_nodename", "unknown_release", "unknown_version", "unknown_machine", "unknown_processor")
\r
94 hostname = platform.node()
\r
96 hostname = "unknown_hostname"
\r
98 self.writeKey(writeDict, 'process', None, 'start' )
\r
99 writeDict['indentationLevel'] += 1
\r
101 self.writeKey(writeDict, 'description', self.description )
\r
102 self.writeKey(writeDict, 'cmd', self.cmd )
\r
103 if self.args: self.writeKey(writeDict, 'args', ' '.join(self.args) )
\r
104 self.writeKey(writeDict, 'start', self.start )
\r
105 self.writeKey(writeDict, 'end', self.end )
\r
106 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )
\r
108 self.writeKey(writeDict, 'user', user )
\r
109 self.writeKey(writeDict, 'sysname', sysname )
\r
110 self.writeKey(writeDict, 'nodename', nodename )
\r
111 self.writeKey(writeDict, 'release', release )
\r
112 self.writeKey(writeDict, 'version', version )
\r
113 self.writeKey(writeDict, 'machine', machine )
\r
114 self.writeKey(writeDict, 'processor', processor )
\r
116 if len(self.processKeys) > 0:
\r
117 self.writeKey(writeDict, 'processKeys', None, 'start' )
\r
118 for pair in self.processKeys:
\r
119 (key, value) = pair
\r
120 writeDict['indentationLevel'] += 1
\r
121 self.writeKey(writeDict, key, value )
\r
122 writeDict['indentationLevel'] -= 1
\r
123 self.writeKey(writeDict, 'processKeys', None, 'stop' )
\r
125 self.writeKey(writeDict, 'status', self.status )
\r
128 def writeLogFooter(self, writeDict):
\r
129 writeDict['indentationLevel'] -= 1
\r
130 self.writeKey(writeDict, 'process', None, 'stop' )
\r
133 def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):
\r
134 "Write logging information to the specified handle"
\r
137 writeDict['logHandle'] = logHandle
\r
138 writeDict['indentationLevel'] = indentationLevel
\r
139 writeDict['format'] = format
\r
142 self.writeLogHeader(writeDict)
\r
145 self.writeKey(writeDict, 'output', None, 'start' )
\r
146 if format == 'xml':
\r
147 logHandle.write( "<![CDATA[\n" )
\r
148 for line in self.log:
\r
149 logHandle.write( '%s%s\n' % ("", line) )
\r
150 if format == 'xml':
\r
151 logHandle.write( "]]>\n" )
\r
152 self.writeKey(writeDict, 'output', None, 'stop' )
\r
154 self.writeLogFooter(writeDict)
\r
157 def writeLogToDisk(self, logFilename=None, format='xml', header=None):
\r
160 # This also doesn't seem like the best structure...
\r
163 logHandle = open( logFilename, mode='wt', encoding="utf-8")
\r
166 logHandle = open( logFilename, mode='wt')
\r
168 print( "Couldn't open log : %s" % logFilename )
\r
173 if format == 'xml':
\r
174 logHandle.write( "<![CDATA[\n" )
\r
175 logHandle.write( header )
\r
176 if format == 'xml':
\r
177 logHandle.write( "]]>\n" )
\r
178 self.writeLog(logHandle)
\r
182 def logLine(self, line):
\r
183 "Add a line of text to the log"
\r
184 self.log.append( line.rstrip() )
\r
186 print( "%s" % line.rstrip() )
\r
190 "Execute this process"
\r
196 import subprocess as sp
\r
200 self.start = datetime.datetime.now()
\r
202 cmdargs = [self.cmd]
\r
203 cmdargs.extend(self.args)
\r
207 print( "\n%s : %s\n" % (self.__class__, sp.list2cmdline(cmdargs)) )
\r
209 print( "\n%s : %s\n" % (self.__class__, " ".join(cmdargs)) )
\r
211 # intialize a few variables that may or may not be set later
\r
216 parentenv = os.environ
\r
217 parentcwd = os.getcwd()
\r
222 if self.batchWrapper:
\r
223 cmd = " ".join(cmdargs)
\r
224 tmpWrapper = os.path.join(self.cwd, "process.bat")
\r
225 writeText(cmd, tmpWrapper)
\r
226 print( "%s : Running process through wrapper %s\n" % (self.__class__, tmpWrapper) )
\r
227 process = sp.Popen([tmpWrapper], stdout=sp.PIPE, stderr=sp.STDOUT,
\r
228 cwd=self.cwd, env=self.env)
\r
230 process = sp.Popen(cmdargs, stdout=sp.PIPE, stderr=sp.STDOUT,
\r
231 cwd=self.cwd, env=self.env)
\r
236 os.environ = self.env
\r
238 os.chdir( self.cwd )
\r
240 stdin, stdout = os.popen4( cmdargs, 'r')
\r
242 print( "Couldn't execute command : %s" % cmdargs[0] )
\r
243 traceback.print_exc()
\r
247 if process != None:
\r
249 #log.logLine( "process id %s\n" % pid )
\r
252 # This is more proper python, and resolves some issues with a process ending before all
\r
253 # of its output has been processed, but it also seems to stall when the read buffer
\r
254 # is near or over it's limit. this happens relatively frequently with processes
\r
255 # that generate lots of print statements.
\r
257 for line in process.stdout:
\r
260 # So we go with the, um, uglier option below
\r
262 # This is now used to ensure that the process has finished
\r
264 while line != None and process.poll() == None:
\r
266 line = process.stdout.readline()
\r
271 self.logLine( str(line, encoding="utf-8") )
\r
274 self.logLine( line )
\r
276 self.logLine( "Logging error : %s" % sys.exc_info()[0] )
\r
278 self.status = process.returncode
\r
280 if self.batchWrapper and tmpWrapper:
\r
282 os.remove(tmpWrapper)
\r
284 print( "Couldn't remove temp wrapper : %s" % tmpWrapper )
\r
285 traceback.print_exc()
\r
291 #print( "reading stdout lines" )
\r
292 stdoutLines = stdout.readlines()
\r
293 exitCode = stdout.close()
\r
299 os.environ = parentenv
\r
301 os.chdir( parentcwd )
\r
303 if len( stdoutLines ) > 0:
\r
304 for line in stdoutLines:
\r
310 self.logLine( "Logging error : %s" % sys.exc_info()[0] )
\r
312 self.status = exitCode
\r
314 self.end = datetime.datetime.now()
\r
318 class ProcessList(Process):
\r
319 "A list of processes with logged output"
\r
321 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
322 Process.__init__(self, description, None, None, cwd, env)
\r
323 "Initialize the standard class variables"
\r
324 self.processes = []
\r
325 self.blocking = blocking
\r
328 def generateReport(self, writeDict):
\r
329 "Generate a log based on the success of the child processes"
\r
332 indent = '\t'*(writeDict['indentationLevel']+1)
\r
336 for child in self.processes:
\r
337 if isinstance(child, ProcessList):
\r
338 child.generateReport(writeDict)
\r
341 key = child.description
\r
342 value = child.status
\r
343 if writeDict['format'] == 'xml':
\r
344 childResult = ( "%s<result description=\"%s\">%s</result>" % (indent, key, value) )
\r
345 else: # writeDict['format'] == 'txt':
\r
346 childResult = ( "%s%40s : %s" % (indent, key, value) )
\r
347 self.log.append( childResult )
\r
349 if child.status != 0:
\r
356 self.log = ["No child processes available to generate a report"]
\r
359 def writeLogHeader(self, writeDict):
\r
360 self.writeKey(writeDict, 'processList', None, 'start' )
\r
361 writeDict['indentationLevel'] += 1
\r
363 self.writeKey(writeDict, 'description', self.description )
\r
364 self.writeKey(writeDict, 'start', self.start )
\r
365 self.writeKey(writeDict, 'end', self.end )
\r
366 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )
\r
368 self.generateReport(writeDict)
\r
370 self.writeKey(writeDict, 'status', self.status )
\r
373 def writeLogFooter(self, writeDict):
\r
374 writeDict['indentationLevel'] -= 1
\r
375 self.writeKey(writeDict, 'processList', None, 'stop' )
\r
378 def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):
\r
379 "Write logging information to the specified handle"
\r
382 writeDict['logHandle'] = logHandle
\r
383 writeDict['indentationLevel'] = indentationLevel
\r
384 writeDict['format'] = format
\r
387 self.writeLogHeader(writeDict)
\r
390 self.writeKey(writeDict, 'output', None, 'start' )
\r
391 for line in self.log:
\r
392 logHandle.write( '%s%s\n' % ("", line) )
\r
393 self.writeKey(writeDict, 'output', None, 'stop' )
\r
396 self.writeKey(writeDict, 'processes', None, 'start' )
\r
397 for child in self.processes:
\r
398 child.writeLog( logHandle, indentationLevel + 1, format )
\r
399 self.writeKey(writeDict, 'processes', None, 'stop' )
\r
401 self.writeLogFooter(writeDict)
\r
405 "Execute this list of processes"
\r
408 self.start = datetime.datetime.now()
\r
412 for child in self.processes:
\r
417 print( "%s : caught exception in child class %s" % (self.__class__, child.__class__) )
\r
418 traceback.print_exc()
\r
421 if self.blocking and child.status != 0:
\r
422 print( "%s : child class %s finished with an error" % (self.__class__, child.__class__) )
\r
426 self.end = datetime.datetime.now()
\r
433 p = optparse.OptionParser(description='A process logging script',
\r
435 version='process 0.1',
\r
436 usage='%prog [options] [options for the logged process]')
\r
437 p.add_option('--cmd', '-c', default=None)
\r
438 p.add_option('--log', '-l', default=None)
\r
440 options, arguments = p.parse_args()
\r
446 logFilename = options.log
\r
449 argsStart = sys.argv.index('--') + 1
\r
450 args = sys.argv[argsStart:]
\r
452 argsStart = len(sys.argv)+1
\r
456 print( "process: No command specified" )
\r
459 # Test regular logging
\r
461 process = Process(description="a process",cmd=cmd, args=args)
\r
464 # Test report generation and writing a log
\r
466 processList = ProcessList("a process list")
\r
467 processList.processes.append( process )
\r
468 processList.echo = True
\r
469 processList.execute()
\r
471 processList.writeLogToDisk(logFilename)
\r
474 if __name__ == '__main__':
\r