1
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
29 """Timed function to avoid time-stamp-problems"""
30 warning("Restoring the controlDict")
31 ctrl.restore()
32 runner.controlDict=None
33
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
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]
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}
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
202 """Append lines to the tail of the log"""
203 if len(self.lastLines)>10*self.logTail:
204
205 self.lastLines=self.lastLines[-self.logTail:]
206 self.writeTailLog()
207
208 self.lastLines.append(line+"\n")
209
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
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
284 self.createTime=tmp
285 try:
286 self.data["stepNr"]+=1
287 except KeyError:
288 self.data["stepNr"]=1
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]
301 if e.errno!=32:
302 raise e
303 else:
304
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]
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
402
404 """Write the real time the run was started at"""
405 self.writeToStateFile("StartedAt",asctime())
406
408 """Write the current state the run is in"""
409 if always or (time()-self.__lastLastSeenWrite)>9:
410 self.writeToStateFile("TheState",state)
411
413 if (time()-self.__lastLastSeenWrite)>10:
414 self.writeToStateFile("LastOutputSeen",asctime())
415 self.__lastLastSeenWrite=time()
416
418 if (time()-self.__lastNowTimeWrite)>10 or force:
419 self.writeToStateFile("CurrentTime",str(self.nowTime))
420 self.__lastNowTimeWrite=time()
421
423 """checks whether the run was successful"""
424 if self.started:
425 return not self.fatalError and not self.fatalFPE and not self.fatalStackdump
426 else:
427 return False
428
430 """to be called before the program is started"""
431 pass
432
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
455
457 """called after the program has stopped"""
458 if self.stopMe or self.isRestarted:
459 self.controlDict.restore()
460
462 """called every time a new line is read"""
463 pass
464
466 """Get the name of the logfiles"""
467 return self.logFile
468
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
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
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
489
490 self.timeExpr=config().getRegexp("SolverOutput","timeregexp")
491 self.createExpr=re.compile("^Create mesh for time = (%f%)$".replace("%f%",self.floatRegExp))
492
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
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
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
521