Source code for PyFoam.Applications.ListProfilingInfo

"""
Application-class that implements pyFoamListProfilingInfo.py
"""
from optparse import OptionGroup

from .PyFoamApplication import PyFoamApplication
from .CommonSelectTimesteps import CommonSelectTimesteps

from PyFoam.ThirdParty.six import print_

from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile
from PyFoam.RunDictionary.SolutionDirectory import SolutionDirectory

from os import path
from glob import glob

[docs]class ListProfilingInfo(PyFoamApplication, CommonSelectTimesteps):
[docs] def __init__(self, args=None, **kwargs): description="""\ List the profiling information in time directories (either created by a patched OpenFOAM-version, Foam-extend or by a OpenFOAM-version with the profiling-patch applied) and prints them in a human-readable form. Either gets files or a case directory from which it tries to get the proper files. If more than one file is specified it is assumed that this is a parallel run and the data is accumulated Results are printed as a table. The first column is the name of the profiling entry. Children entries (entries that are called by that entry) are indented. The first numeric column is the percentage that this entry used of the total time (including time spent in children). The next entry is the percentages of the 'self' time (the time spent in this entry minus the time spent in the children) as the percentage of the total execution time. After that the percentages of the parent time (total and 'self'). After that the number of times this entry was called is printed. Then the total time spent in this entry and the time without the child entries are printed. If the data from multiple processors is used then the totalTime and the calls are the average of all processors. Also are there three more columns: the range of the totalTime (maximum-minimum). How big that range is compared to the average totalTime and the range of the calls """ examples="""\ %prog aCase/100/uniform/profilingInfo Print the profiling info of a case named aCase at time 100 %prog aCase --time=100 Also print the profiling info of a case named aCase at time 100 %prog aCase --latest-time Print the profiling info from the latest timestep in case aCase %prog aCase --latest-time --parallel Print the profiling information from the latest timestep in the case aCase but use the data from all processors and accumulate them %prog aCase --latest-time --parallel --sort-by=totalTime --depth=2 Sort the profiling data by the total time that was used and only print the first two levels """ PyFoamApplication.__init__(self, args=args, description=description, examples=examples, usage="%prog [<caseDirectory>|<profiling file>]", interspersed=True, changeVersion=True, nr=1, exactNr=False, **kwargs)
[docs] def addOptions(self): CommonSelectTimesteps.addOptions(self,False,singleTime=True) output=OptionGroup(self.parser, "Output", "How data should be output") self.parser.add_option_group(output) sortModes=["id","totalTime","selfTime","description","calls"] output.add_option("--sort-by", type="choice", dest="sortBy", default="id", choices=sortModes, help="How the entries should be sorted in their 'subtree'. Possible values are "+", ".join(sortModes)+". Default is 'id' which is more or less the order in which these entries were registered") output.add_option("--depth", type="int", dest="depth", default=0, help="How many levels of the tree should be printed. If 0 or smaller then everything is printed")
[docs] def readProfilingInfo(self,fName): """Read the info from a file and return a tuple with (date,children,root)""" pf=ParsedParameterFile(fName, treatBinaryAsASCII=True) try: # Foam-extend and Patch pi=pf["profilingInfo"] newFormat=False except KeyError: try: pi=pf["profiling"] newFormat=True except KeyError: self.error("No profiling info found in",fName) data={} children={} root=None for p in pi: if newFormat: p=pi[p] if p["id"] in data: print_("Duplicate definition of",p["id"]) sys.exit(-1) if p["description"][0]=='"': p["description"]=p["description"][1:] if p["description"][-1]=='"': p["description"]=p["description"][:-1] data[p["id"]]=p if "parentId" in p: if p["parentId"] in children: children[p["parentId"]].append(p["id"]) else: children[p["parentId"]]=[p["id"]] else: if root!=None: print_("Two root elements") sys-exit(-1) else: root=p["id"] p["selfTime"]=p["totalTime"]-p["childTime"] return data,children,root
[docs] def printProfilingInfo(self,data,children,root,parallel=False): """Prints the profiling info in a pseudo-graphical form""" def depth(i): if i in children: return max([depth(j) for j in children[i]])+1 else: return 0 maxdepth=depth(root) depths={} def nameLen(i,d=0): depths[i]=d maxi=len(data[i]["description"]) if i in children: maxi=max(maxi,max([nameLen(j,d+1) for j in children[i]])) if self.opts.depth>0 and depths[i]>self.opts.depth: return 0 else: return maxi+3 maxLen=nameLen(root) format=" %5.1f%% (%5.1f%%) - %5.1f%% (%5.1f%%) | %8d %9.4gs %9.4gs" if parallel: parallelFormat=" | %9.4gs %5.1f%% %9.4g" totalTime=data[root]["totalTime"] header=" "*(maxLen)+" | total ( self ) - parent ( self ) | calls total self " if parallel: header+="| range(total) / % range(calls) " print_(header) print_("-"*len(header)) def printItem(i): result="" if self.opts.depth>0 and depths[i]>self.opts.depth: return "" if depths[i]>1: result+=" "*(depths[i]-1) if depths[i]>0: result+="|- " result+=data[i]["description"] result+=" "*(maxLen-len(result)+1)+"| " parentTime=data[i]["totalTime"] if "parentId" in data[i]: parentTime=data[data[i]["parentId"]]["totalTime"] tt=data[i]["totalTime"] ct=data[i]["childTime"] st=data[i]["selfTime"] result+=format % (100*tt/totalTime, 100*st/totalTime, 100*tt/parentTime, 100*st/tt, data[i]["calls"], tt, st) if parallel: timeRange=data[i]["totalTimeMax"]-data[i]["totalTimeMin"] result+=parallelFormat % (timeRange, 100*timeRange/tt, data[i]["callsMax"]-data[i]["callsMin"]) print_(result) if i in children: def getKey(k): def keyF(i): return data[i][k] return keyF #make sure that children are printed in the correct order if self.opts.sortBy=="id": children[i].sort() else: children[i].sort( key=getKey(self.opts.sortBy), reverse=self.opts.sortBy in ["totalTime","selfTime","calls"]) for c in children[i]: printItem(c) printItem(root)
[docs] def run(self): files=self.parser.getArgs()[0:] if len(files)==1 and path.isdir(files[0]): sol=SolutionDirectory( self.parser.getArgs()[0], archive=None, parallel=self.opts.parallelTimes, paraviewLink=False) self.processTimestepOptions(sol) if len(self.opts.time)<1: self.error("No time specified") globStr=self.parser.getArgs()[0] if self.opts.parallelTimes: globStr=path.join(globStr,"processor*") usedTime=sol.timeName(self.opts.time[0]) globStr=path.join(globStr, usedTime, "uniform","profiling*") files=glob(globStr) print_("Profiling info from time",usedTime) if len(files)<1: self.error("No profiling data found") elif len(files)>1: lst=[] for f in files: lst.append(self.readProfilingInfo(f)) dataAll,children0,root0=lst[0] for i in dataAll: d=dataAll[i] d["totalTimeMin"]=d["totalTime"] d["totalTimeMax"]=d["totalTime"] d["callsMin"]=d["calls"] d["callsMax"]=d["calls"] for data,children,root in lst[1:]: if root0!=root or children!=children0 or data.keys()!=dataAll.keys(): self.error("Inconsistent profiling data. Probably not from same run/timestep") for i in data: d=data[i] s=dataAll[i] s["totalTime"]+=d["totalTime"] s["totalTimeMin"]=min(s["totalTimeMin"],d["totalTime"]) s["totalTimeMax"]=max(s["totalTimeMax"],d["totalTime"]) s["calls"]+=d["calls"] s["callsMin"]=min(s["callsMin"],d["calls"]) s["callsMax"]=max(s["callsMax"],d["calls"]) s["childTime"]+=d["childTime"] for i in dataAll: d=dataAll[i] d["totalTime"]=d["totalTime"]/len(lst) d["childTime"]=d["childTime"]/len(lst) d["calls"]=d["calls"]/len(lst) self.printProfilingInfo(dataAll,children,root,True) else: data,children,root=self.readProfilingInfo(files[0]) self.printProfilingInfo(data,children,root)