3 '''A process wrapper class that maintains the text output and execution status of a process
\r
4 or a list of other process wrappers which carry such data.'''
\r
10 def readText(textFile):
\r
11 if( textFile != "" ):
\r
12 fp = open(textFile, 'rb')
\r
13 # Create a text/plain message
\r
19 def writeText(text, textFile):
\r
20 if( textFile != "" ):
\r
21 fp = open(textFile, 'wb')
\r
22 # Create a text/plain message
\r
29 "A process with logged output"
\r
31 def __init__(self, description=None, cmd=None, args=[], cwd=None, env=None, batchWrapper=False):
\r
32 "Initialize the standard class variables"
\r
35 self.description = cmd
\r
37 self.description = description
\r
46 self.batchWrapper = batchWrapper
\r
47 self.processKeys = []
\r
50 def getElapsedSeconds(self):
\r
52 if self.end and self.start:
\r
53 delta = (self.end - self.start)
\r
54 formatted = "%s.%s" % (delta.days * 86400 + delta.seconds, int(math.floor(delta.microseconds/1e3)))
\r
60 def writeKey(self, writeDict, key=None, value=None, startStop=None):
\r
61 "Write a key, value pair in a supported format"
\r
62 if key != None and (value != None or startStop != None):
\r
63 indent = '\t'*writeDict['indentationLevel']
\r
64 if writeDict['format'] == 'xml':
\r
65 if startStop == 'start':
\r
66 writeDict['logHandle'].write( "%s<%s>\n" % (indent, key) )
\r
67 elif startStop == 'stop':
\r
68 writeDict['logHandle'].write( "%s</%s>\n" % (indent, key) )
\r
70 writeDict['logHandle'].write( "%s<%s>%s</%s>\n" % (indent, key, value, key) )
\r
71 else: # writeDict['format'] == 'txt':
\r
72 writeDict['logHandle'].write( "%s%40s : %s\n" % (indent, key, value) )
\r
74 def writeLogHeader(self, writeDict):
\r
77 # Retrieve operating environment information
\r
80 user = os.getlogin()
\r
83 user = os.getenv("USERNAME")
\r
85 user = os.getenv("USER")
\r
87 user = "unknown_user"
\r
89 (sysname, nodename, release, version, machine, processor) = platform.uname()
\r
91 (sysname, nodename, release, version, machine, processor) = ("unknown_sysname", "unknown_nodename", "unknown_release", "unknown_version", "unknown_machine", "unknown_processor")
\r
93 hostname = platform.node()
\r
95 hostname = "unknown_hostname"
\r
97 self.writeKey(writeDict, 'process', None, 'start' )
\r
98 writeDict['indentationLevel'] += 1
\r
100 self.writeKey(writeDict, 'description', self.description )
\r
101 self.writeKey(writeDict, 'cmd', self.cmd )
\r
102 if self.args: self.writeKey(writeDict, 'args', ' '.join(self.args) )
\r
103 self.writeKey(writeDict, 'start', self.start )
\r
104 self.writeKey(writeDict, 'end', self.end )
\r
105 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )
\r
107 self.writeKey(writeDict, 'user', user )
\r
108 self.writeKey(writeDict, 'sysname', sysname )
\r
109 self.writeKey(writeDict, 'nodename', nodename )
\r
110 self.writeKey(writeDict, 'release', release )
\r
111 self.writeKey(writeDict, 'version', version )
\r
112 self.writeKey(writeDict, 'machine', machine )
\r
113 self.writeKey(writeDict, 'processor', processor )
\r
115 if len(self.processKeys) > 0:
\r
116 self.writeKey(writeDict, 'processKeys', None, 'start' )
\r
117 for pair in self.processKeys:
\r
118 (key, value) = pair
\r
119 writeDict['indentationLevel'] += 1
\r
120 self.writeKey(writeDict, key, value )
\r
121 writeDict['indentationLevel'] -= 1
\r
122 self.writeKey(writeDict, 'processKeys', None, 'stop' )
\r
124 self.writeKey(writeDict, 'status', self.status )
\r
127 def writeLogFooter(self, writeDict):
\r
128 writeDict['indentationLevel'] -= 1
\r
129 self.writeKey(writeDict, 'process', None, 'stop' )
\r
132 def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):
\r
133 "Write logging information to the specified handle"
\r
136 writeDict['logHandle'] = logHandle
\r
137 writeDict['indentationLevel'] = indentationLevel
\r
138 writeDict['format'] = format
\r
141 self.writeLogHeader(writeDict)
\r
144 self.writeKey(writeDict, 'output', None, 'start' )
\r
145 if format == 'xml':
\r
146 logHandle.write( "<![CDATA[\n" )
\r
147 for line in self.log:
\r
148 logHandle.write( '%s%s\n' % ("", line) )
\r
149 if format == 'xml':
\r
150 logHandle.write( "]]>\n" )
\r
151 self.writeKey(writeDict, 'output', None, 'stop' )
\r
153 self.writeLogFooter(writeDict)
\r
156 def writeLogToDisk(self, logFilename=None, format='xml', header=None):
\r
159 # This also doesn't seem like the best structure...
\r
162 logHandle = open( logFilename, mode='wt', encoding="utf-8")
\r
165 logHandle = open( logFilename, mode='wt')
\r
167 print( "Couldn't open log : %s" % logFilename )
\r
172 if format == 'xml':
\r
173 logHandle.write( "<![CDATA[\n" )
\r
174 logHandle.write( header )
\r
175 if format == 'xml':
\r
176 logHandle.write( "]]>\n" )
\r
177 self.writeLog(logHandle)
\r
181 def logLine(self, line):
\r
182 "Add a line of text to the log"
\r
183 self.log.append( line.rstrip() )
\r
185 print( "%s" % line.rstrip() )
\r
189 "Execute this process"
\r
195 import subprocess as sp
\r
199 self.start = datetime.datetime.now()
\r
201 cmdargs = [self.cmd]
\r
202 cmdargs.extend(self.args)
\r
206 print( "\n%s : %s\n" % (self.__class__, sp.list2cmdline(cmdargs)) )
\r
208 print( "\n%s : %s\n" % (self.__class__, " ".join(cmdargs)) )
\r
210 # intialize a few variables that may or may not be set later
\r
215 parentenv = os.environ
\r
216 parentcwd = os.getcwd()
\r
221 if self.batchWrapper:
\r
222 cmd = " ".join(cmdargs)
\r
223 tmpWrapper = os.path.join(self.cwd, "process.bat")
\r
224 writeText(cmd, tmpWrapper)
\r
225 print( "%s : Running process through wrapper %s\n" % (self.__class__, tmpWrapper) )
\r
226 process = sp.Popen([tmpWrapper], stdout=sp.PIPE, stderr=sp.STDOUT,
\r
227 cwd=self.cwd, env=self.env)
\r
229 process = sp.Popen(cmdargs, stdout=sp.PIPE, stderr=sp.STDOUT,
\r
230 cwd=self.cwd, env=self.env)
\r
235 os.environ = self.env
\r
237 os.chdir( self.cwd )
\r
239 stdin, stdout = os.popen4( cmdargs, 'r')
\r
241 print( "Couldn't execute command : %s" % cmdargs[0] )
\r
242 traceback.print_exc()
\r
246 if process != None:
\r
248 #log.logLine( "process id %s\n" % pid )
\r
251 # This is more proper python, and resolves some issues with a process ending before all
\r
252 # of its output has been processed, but it also seems to stall when the read buffer
\r
253 # is near or over it's limit. this happens relatively frequently with processes
\r
254 # that generate lots of print statements.
\r
256 for line in process.stdout:
\r
259 # So we go with the, um, uglier option below
\r
261 # This is now used to ensure that the process has finished
\r
263 while line != None and process.poll() == None:
\r
265 line = process.stdout.readline()
\r
270 self.logLine( str(line, encoding="utf-8") )
\r
273 self.logLine( line )
\r
275 self.logLine( "Logging error : %s" % sys.exc_info()[0] )
\r
277 self.status = process.returncode
\r
279 if self.batchWrapper and tmpWrapper:
\r
281 os.remove(tmpWrapper)
\r
283 print( "Couldn't remove temp wrapper : %s" % tmpWrapper )
\r
284 traceback.print_exc()
\r
290 #print( "reading stdout lines" )
\r
291 stdoutLines = stdout.readlines()
\r
292 exitCode = stdout.close()
\r
298 os.environ = parentenv
\r
300 os.chdir( parentcwd )
\r
302 if len( stdoutLines ) > 0:
\r
303 for line in stdoutLines:
\r
309 self.logLine( "Logging error : %s" % sys.exc_info()[0] )
\r
311 self.status = exitCode
\r
313 self.end = datetime.datetime.now()
\r
317 class ProcessList(Process):
\r
318 "A list of processes with logged output"
\r
320 def __init__(self, description, blocking=True, cwd=None, env=None):
\r
321 Process.__init__(self, description, None, None, cwd, env)
\r
322 "Initialize the standard class variables"
\r
323 self.processes = []
\r
324 self.blocking = blocking
\r
327 def generateReport(self, writeDict):
\r
328 "Generate a log based on the success of the child processes"
\r
331 indent = '\t'*(writeDict['indentationLevel']+1)
\r
335 for child in self.processes:
\r
336 if isinstance(child, ProcessList):
\r
337 child.generateReport(writeDict)
\r
340 key = child.description
\r
341 value = child.status
\r
342 if writeDict['format'] == 'xml':
\r
343 childResult = ( "%s<result description=\"%s\">%s</result>" % (indent, key, value) )
\r
344 else: # writeDict['format'] == 'txt':
\r
345 childResult = ( "%s%40s : %s" % (indent, key, value) )
\r
346 self.log.append( childResult )
\r
348 if child.status != 0:
\r
355 self.log = ["No child processes available to generate a report"]
\r
358 def writeLogHeader(self, writeDict):
\r
359 self.writeKey(writeDict, 'processList', None, 'start' )
\r
360 writeDict['indentationLevel'] += 1
\r
362 self.writeKey(writeDict, 'description', self.description )
\r
363 self.writeKey(writeDict, 'start', self.start )
\r
364 self.writeKey(writeDict, 'end', self.end )
\r
365 self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )
\r
367 self.generateReport(writeDict)
\r
369 self.writeKey(writeDict, 'status', self.status )
\r
372 def writeLogFooter(self, writeDict):
\r
373 writeDict['indentationLevel'] -= 1
\r
374 self.writeKey(writeDict, 'processList', None, 'stop' )
\r
377 def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):
\r
378 "Write logging information to the specified handle"
\r
381 writeDict['logHandle'] = logHandle
\r
382 writeDict['indentationLevel'] = indentationLevel
\r
383 writeDict['format'] = format
\r
386 self.writeLogHeader(writeDict)
\r
389 self.writeKey(writeDict, 'output', None, 'start' )
\r
390 for line in self.log:
\r
391 logHandle.write( '%s%s\n' % ("", line) )
\r
392 self.writeKey(writeDict, 'output', None, 'stop' )
\r
395 self.writeKey(writeDict, 'processes', None, 'start' )
\r
396 for child in self.processes:
\r
397 child.writeLog( logHandle, indentationLevel + 1, format )
\r
398 self.writeKey(writeDict, 'processes', None, 'stop' )
\r
400 self.writeLogFooter(writeDict)
\r
404 "Execute this list of processes"
\r
407 self.start = datetime.datetime.now()
\r
411 for child in self.processes:
\r
416 print( "%s : caught exception in child class %s" % (self.__class__, child.__class__) )
\r
417 traceback.print_exc()
\r
420 if self.blocking and child.status != 0:
\r
421 print( "%s : child class %s finished with an error" % (self.__class__, child.__class__) )
\r
425 self.end = datetime.datetime.now()
\r
432 p = optparse.OptionParser(description='A process logging script',
\r
434 version='process 0.1',
\r
435 usage='%prog [options] [options for the logged process]')
\r
436 p.add_option('--cmd', '-c', default=None)
\r
437 p.add_option('--log', '-l', default=None)
\r
439 options, arguments = p.parse_args()
\r
445 logFilename = options.log
\r
448 argsStart = sys.argv.index('--') + 1
\r
449 args = sys.argv[argsStart:]
\r
451 argsStart = len(sys.argv)+1
\r
455 print( "process: No command specified" )
\r
458 # Test regular logging
\r
460 process = Process(description="a process",cmd=cmd, args=args)
\r
463 # Test report generation and writing a log
\r
465 processList = ProcessList("a process list")
\r
466 processList.processes.append( process )
\r
467 processList.echo = True
\r
468 processList.execute()
\r
470 processList.writeLogToDisk(logFilename)
\r
473 if __name__ == '__main__':
\r