Package PyFoam :: Package Execution :: Module BasicRunner
[hide private]
[frames] | no frames]

Source Code for Module PyFoam.Execution.BasicRunner

  1  #  ICE Revision: $Id$ 
  2  """Run a OpenFOAM command""" 
  3   
  4  import sys 
  5  import string 
  6  import gzip 
  7  from os import path 
  8  from platform import uname 
  9  from threading import Timer 
 10  from time import time,asctime 
 11   
 12  from PyFoam.FoamInformation import oldAppConvention as oldApp 
 13  from PyFoam.ThirdParty.six import print_ 
 14   
 15  if not 'curdir' in dir(path) or not 'sep' in dir(path): 
 16      print_("Warning: Inserting symbols into os.path (Python-Version<2.3)") 
 17      path.curdir='.' 
 18      path.sep   ='/' 
 19   
 20  from PyFoam.Execution.FoamThread import FoamThread 
 21  from PyFoam.Infrastructure.FoamServer import FoamServer 
 22  from PyFoam.Infrastructure.Logging import foamLogger 
 23  from PyFoam.RunDictionary.SolutionDirectory import SolutionDirectory 
 24  from PyFoam.RunDictionary.ParameterFile import ParameterFile 
 25  from PyFoam.Error import warning,error,debug 
 26  from PyFoam import configuration as config 
 27   
28 -def restoreControlDict(ctrl,runner):
29 """Timed function to avoid time-stamp-problems""" 30 warning("Restoring the controlDict") 31 ctrl.restore() 32 runner.controlDict=None
33
34 -class BasicRunner(object):
35 """Base class for the running of commands 36 37 When the command is run the output is copied to a LogFile and 38 (optionally) standard-out 39 40 The argument list assumes for the first three elements the 41 OpenFOAM-convention: 42 43 <cmd> <dir> <case> 44 45 The directory name for outputs is therefor created from <dir> and 46 <case> 47 48 Provides some handle-methods that are to be overloaded for 49 additional functionality""" 50
51 - def __init__(self, 52 argv=None, 53 silent=False, 54 logname=None, 55 compressLog=False, 56 lam=None, 57 server=False, 58 restart=False, 59 noLog=False, 60 logTail=None, 61 remark=None, 62 jobId=None, 63 parameters=None, 64 writeState=True):
65 """@param argv: list with the tokens that are the command line 66 if not set the standard command line is used 67 @param silent: if True no output is sent to stdout 68 @param logname: name of the logfile 69 @param compressLog: Compress the logfile into a gzip 70 @param lam: Information about a parallel run 71 @param server: Whether or not to start the network-server 72 @type lam: PyFoam.Execution.ParallelExecution.LAMMachine 73 @param noLog: Don't output a log file 74 @param logTail: only the last lines of the log should be written 75 @param remark: User defined remark about the job 76 @param parameters: User defined dictionary with parameters for 77 documentation purposes 78 @param jobId: Job ID of the controlling system (Queueing system) 79 @param writeState: Write the state to some files in the case""" 80 81 if sys.version_info < (2,3): 82 # Python 2.2 does not have the capabilities for the Server-Thread 83 if server: 84 warning("Can not start server-process because Python-Version is too old") 85 server=False 86 87 if argv==None: 88 self.argv=sys.argv[1:] 89 else: 90 self.argv=argv 91 92 if oldApp(): 93 self.dir=path.join(self.argv[1],self.argv[2]) 94 if self.argv[2][-1]==path.sep: 95 self.argv[2]=self.argv[2][:-1] 96 else: 97 self.dir=path.curdir 98 if "-case" in self.argv: 99 self.dir=self.argv[self.argv.index("-case")+1] 100 101 if logname==None: 102 logname="PyFoam."+path.basename(argv[0]) 103 104 try: 105 sol=self.getSolutionDirectory() 106 except OSError: 107 e = sys.exc_info()[1] # compatible with 2.x and 3.x 108 error("Solution directory",self.dir,"does not exist. No use running. Problem:",e) 109 110 self.silent=silent 111 self.lam=lam 112 self.origArgv=self.argv 113 self.writeState=writeState 114 self.__lastLastSeenWrite=0 115 self.__lastNowTimeWrite=0 116 117 if self.lam!=None: 118 self.argv=lam.buildMPIrun(self.argv) 119 if config().getdebug("ParallelExecution"): 120 debug("Command line:"," ".join(self.argv)) 121 self.cmd=" ".join(self.argv) 122 foamLogger().info("Starting: "+self.cmd+" in "+path.abspath(path.curdir)) 123 self.logFile=path.join(self.dir,logname+".logfile") 124 125 self.noLog=noLog 126 self.logTail=logTail 127 if self.logTail: 128 if self.noLog: 129 warning("Log tail",self.logTail,"and no-log specified. Using logTail") 130 self.noLog=True 131 self.lastLines=[] 132 133 self.compressLog=compressLog 134 if self.compressLog: 135 self.logFile+=".gz" 136 137 self.fatalError=False 138 self.fatalFPE=False 139 self.fatalStackdump=False 140 141 self.warnings=0 142 self.started=False 143 144 self.isRestarted=False 145 if restart: 146 self.controlDict=ParameterFile(path.join(self.dir,"system","controlDict"),backup=True) 147 self.controlDict.replaceParameter("startFrom","latestTime") 148 self.isRestarted=True 149 else: 150 self.controlDict=None 151 152 self.run=FoamThread(self.cmd,self) 153 154 self.server=None 155 if server: 156 self.server=FoamServer(run=self.run,master=self) 157 self.server.setDaemon(True) 158 self.server.start() 159 try: 160 IP,PID,Port=self.server.info() 161 f=open(path.join(self.dir,"PyFoamServer.info"),"w") 162 print_(IP,PID,Port,file=f) 163 f.close() 164 except AttributeError: 165 warning("There seems to be a problem with starting the server:",self.server,"with attributes",dir(self.server)) 166 self.server=None 167 168 self.createTime=None 169 self.nowTime=None 170 self.startTimestamp=time() 171 172 self.stopMe=False 173 self.writeRequested=False 174 175 self.endTriggers=[] 176 177 self.lastLogLineSeen=None 178 self.lastTimeStepSeen=None 179 180 self.remark=remark 181 self.jobId=jobId 182 183 self.data={"lines":0} # self.data={"lines":0L} 184 self.data["logfile"]=self.logFile 185 self.data["casefullname"]=path.abspath(self.dir) 186 self.data["casename"]=path.basename(path.abspath(self.dir)) 187 self.data["solver"]=path.basename(self.argv[0]) 188 self.data["solverFull"]=self.argv[0] 189 self.data["commandLine"]=self.cmd 190 self.data["hostname"]=uname()[1] 191 if remark: 192 self.data["remark"]=remark 193 else: 194 self.data["remark"]="No remark given" 195 if jobId: 196 self.data["jobId"]=jobId 197 if parameters: 198 self.data["parameters"]=parameters 199 self.data["starttime"]=asctime()
200
201 - def appendTailLine(self,line):
202 """Append lines to the tail of the log""" 203 if len(self.lastLines)>10*self.logTail: 204 # truncate the lines, but not too often 205 self.lastLines=self.lastLines[-self.logTail:] 206 self.writeTailLog() 207 208 self.lastLines.append(line+"\n")
209
210 - def writeTailLog(self):
211 """Write the last lines to the log""" 212 fh=open(self.logFile,"w") 213 if len(self.lastLines)<=self.logTail: 214 fh.writelines(self.lastLines) 215 else: 216 fh.writelines(self.lastLines[-self.logTail:]) 217 fh.close()
218
219 - def start(self):
220 """starts the command and stays with it till the end""" 221 222 self.started=True 223 if not self.noLog: 224 if self.compressLog: 225 fh=gzip.open(self.logFile,"w") 226 else: 227 fh=open(self.logFile,"w") 228 229 self.startHandle() 230 231 self.writeStartTime() 232 self.writeTheState("Running") 233 234 check=BasicRunnerCheck() 235 236 self.run.start() 237 interrupted=False 238 239 totalWarningLines=0 240 addLinesToWarning=0 241 collectWarnings=True 242 243 while self.run.check(): 244 try: 245 self.run.read() 246 if not self.run.check(): 247 break 248 249 line=self.run.getLine() 250 251 if "errorText" in self.data: 252 self.data["errorText"]+=line+"\n" 253 254 if addLinesToWarning>0: 255 self.data["warningText"]+=line+"\n" 256 addLinesToWarning-=1 257 totalWarningLines+=1 258 if totalWarningLines>500: 259 collectWarnings=False 260 addLinesToWarning=0 261 self.data["warningText"]+="No more warnings added because limit of 500 lines exceeded" 262 self.data["lines"]+=1 263 self.lastLogLineSeen=time() 264 self.writeLastSeen() 265 266 tmp=check.getTime(line) 267 if check.controlDictRead(line): 268 if self.writeRequested: 269 duration=config().getfloat("Execution","controlDictRestoreWait",default=30.) 270 warning("Preparing to reset controlDict to old glory in",duration,"seconds") 271 Timer(duration, 272 restoreControlDict, 273 args=[self.controlDict,self]).start() 274 self.writeRequested=False 275 276 if tmp!=None: 277 self.data["time"]=tmp 278 self.nowTime=tmp 279 self.writeTheState("Running",always=False) 280 self.writeNowTime() 281 self.lastTimeStepSeen=time() 282 if self.createTime==None: 283 # necessary because interFoam reports no creation time 284 self.createTime=tmp 285 try: 286 self.data["stepNr"]+=1 287 except KeyError: 288 self.data["stepNr"]=1 # =1L 289 290 self.data["lasttimesteptime"]=asctime() 291 292 tmp=check.getCreateTime(line) 293 if tmp!=None: 294 self.createTime=tmp 295 296 if not self.silent: 297 try: 298 print_(line) 299 except IOError: 300 e = sys.exc_info()[1] # compatible with 2.x and 3.x 301 if e.errno!=32: 302 raise e 303 else: 304 # Pipe was broken 305 self.run.interrupt() 306 307 if line.find("FOAM FATAL ERROR")>=0 or line.find("FOAM FATAL IO ERROR")>=0: 308 self.fatalError=True 309 self.data["errorText"]="PyFoam found a Fatal Error " 310 if "time" in self.data: 311 self.data["errorText"]+="at time "+str(self.data["time"])+"\n" 312 else: 313 self.data["errorText"]+="before time started\n" 314 self.data["errorText"]+="\n"+line+"\n" 315 316 if line.find("Foam::sigFpe::sigFpeHandler")>=0: 317 self.fatalFPE=True 318 if line.find("Foam::error::printStack")>=0: 319 self.fatalStackdump=True 320 321 if self.fatalError and line!="": 322 foamLogger().error(line) 323 324 if line.find("FOAM Warning")>=0: 325 self.warnings+=1 326 try: 327 self.data["warnings"]+=1 328 except KeyError: 329 self.data["warnings"]=1 330 if collectWarnings: 331 addLinesToWarning=20 332 if not "warningText" in self.data: 333 self.data["warningText"]="" 334 else: 335 self.data["warningText"]+=("-"*40)+"\n" 336 self.data["warningText"]+="Warning found by PyFoam on line " 337 self.data["warningText"]+=str(self.data["lines"])+" " 338 if "time" in self.data: 339 self.data["warningText"]+="at time "+str(self.data["time"])+"\n" 340 else: 341 self.data["warningText"]+="before time started\n" 342 self.data["warningText"]+="\n"+line+"\n" 343 344 if self.server!=None: 345 self.server._insertLine(line) 346 347 self.lineHandle(line) 348 349 if not self.noLog: 350 fh.write(line+"\n") 351 fh.flush() 352 elif self.logTail: 353 self.appendTailLine(line) 354 355 except KeyboardInterrupt: 356 e = sys.exc_info()[1] # compatible with 2.x and 3.x 357 foamLogger().warning("Keyboard Interrupt") 358 self.run.interrupt() 359 self.writeTheState("Interrupted") 360 interrupted=True 361 362 self.data["interrupted"]=interrupted 363 self.data["OK"]=self.runOK() 364 self.data["cpuTime"]=self.run.cpuTime() 365 self.data["cpuUserTime"]=self.run.cpuUserTime() 366 self.data["cpuSystemTime"]=self.run.cpuSystemTime() 367 self.data["wallTime"]=self.run.wallTime() 368 self.data["usedMemory"]=self.run.usedMemory() 369 self.data["endtime"]=asctime() 370 371 self.data["fatalError"]=self.fatalError 372 self.data["fatalFPE"]=self.fatalFPE 373 self.data["fatalStackdump"]=self.fatalStackdump 374 375 self.writeNowTime(force=True) 376 377 self.stopHandle() 378 379 if not interrupted: 380 self.writeTheState("Finished") 381 382 for t in self.endTriggers: 383 t() 384 385 if not self.noLog: 386 fh.close() 387 elif self.logTail: 388 self.writeTailLog() 389 390 if self.server!=None: 391 self.server.deregister() 392 self.server.kill() 393 394 foamLogger().info("Finished") 395 396 return self.data
397
398 - def writeToStateFile(self,fName,message):
399 """Write a message to a state file""" 400 if self.writeState: 401 open(path.join(self.dir,"PyFoamState."+fName),"w").write(message+"\n")
402
403 - def writeStartTime(self):
404 """Write the real time the run was started at""" 405 self.writeToStateFile("StartedAt",asctime())
406
407 - def writeTheState(self,state,always=True):
408 """Write the current state the run is in""" 409 if always or (time()-self.__lastLastSeenWrite)>9: 410 self.writeToStateFile("TheState",state)
411
412 - def writeLastSeen(self):
413 if (time()-self.__lastLastSeenWrite)>10: 414 self.writeToStateFile("LastOutputSeen",asctime()) 415 self.__lastLastSeenWrite=time()
416
417 - def writeNowTime(self,force=False):
418 if (time()-self.__lastNowTimeWrite)>10 or force: 419 self.writeToStateFile("CurrentTime",str(self.nowTime)) 420 self.__lastNowTimeWrite=time()
421
422 - def runOK(self):
423 """checks whether the run was successful""" 424 if self.started: 425 return not self.fatalError and not self.fatalFPE and not self.fatalStackdump # and self.run.getReturnCode()==0 426 else: 427 return False
428
429 - def startHandle(self):
430 """to be called before the program is started""" 431 pass
432
433 - def stopGracefully(self):
434 """Tells the runner to stop at the next convenient time""" 435 if not self.stopMe: 436 self.stopMe=True 437 if not self.isRestarted: 438 if self.controlDict: 439 warning("The controlDict has already been modified. Restoring will be problementic") 440 self.controlDict=ParameterFile(path.join(self.dir,"system","controlDict"),backup=True) 441 self.controlDict.replaceParameter("stopAt","writeNow") 442 warning("Stopping run at next write")
443
444 - def writeResults(self):
445 """Writes the next possible time-step""" 446 # warning("writeResult is not yet implemented") 447 if not self.writeRequested: 448 if not self.isRestarted: 449 if self.controlDict: 450 warning("The controlDict has already been modified. Restoring will be problementic") 451 self.controlDict=ParameterFile(path.join(self.dir,"system","controlDict"),backup=True) 452 self.controlDict.replaceParameter("writeControl","timeStep") 453 self.controlDict.replaceParameter("writeInterval","1") 454 self.writeRequested=True
455
456 - def stopHandle(self):
457 """called after the program has stopped""" 458 if self.stopMe or self.isRestarted: 459 self.controlDict.restore()
460
461 - def lineHandle(self,line):
462 """called every time a new line is read""" 463 pass
464
465 - def logName(self):
466 """Get the name of the logfiles""" 467 return self.logFile
468
469 - def getSolutionDirectory(self,archive=None):
470 """@return: The directory of the case 471 @rtype: PyFoam.RunDictionary.SolutionDirectory 472 @param archive: Name of the directory for archiving results""" 473 474 return SolutionDirectory(self.dir,archive=archive,parallel=True)
475
476 - def addEndTrigger(self,f):
477 """@param f: A function that is to be executed at the end of the simulation""" 478 self.endTriggers.append(f)
479 480 import re 481
482 -class BasicRunnerCheck(object):
483 """A small class that does primitve checking for BasicRunner 484 Duplicates other efforts, but ....""" 485 486 floatRegExp="[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?" 487
488 - def __init__(self):
489 # self.timeExpr=re.compile("^Time = (%f%)$".replace("%f%",self.floatRegExp)) 490 self.timeExpr=config().getRegexp("SolverOutput","timeregexp") 491 self.createExpr=re.compile("^Create mesh for time = (%f%)$".replace("%f%",self.floatRegExp))
492
493 - def getTime(self,line):
494 """Does this line contain time information?""" 495 m=self.timeExpr.match(line) 496 if m: 497 return float(m.group(2)) 498 else: 499 return None
500
501 - def getCreateTime(self,line):
502 """Does this line contain mesh time information?""" 503 m=self.createExpr.match(line) 504 if m: 505 return float(m.group(1)) 506 else: 507 return None
508
509 - def controlDictRead(self,line):
510 """Was the controlDict reread?""" 511 phrases=["Reading object controlDict from file", 512 "Re-reading object controlDict from file"] 513 514 for p in phrases: 515 if line.find(p)>=0: 516 return True 517 518 return False
519 520 # Should work with Python3 and Python2 521