profilingSummary.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2017-2022 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 Application
27  profilingSummary
28 
29 Group
30  grpMiscUtilities
31 
32 Description
33  Collects information from profiling files in the processor
34  sub-directories and summarizes the number of calls and time spent as
35  max/avg/min values. If the values are identical for all processes,
36  only a single value is written.
37 
38 \*---------------------------------------------------------------------------*/
39 
40 #include "Time.H"
41 #include "polyMesh.H"
42 #include "OSspecific.H"
43 #include "IFstream.H"
44 #include "OFstream.H"
45 #include "argList.H"
46 #include "stringOps.H"
47 #include "timeSelector.H"
48 #include "IOobjectList.H"
49 #include "functionObject.H"
50 
51 using namespace Foam;
52 
53 // The name of the sub-dictionary entry for profiling fileName:
54 static const word profilingFileName("profiling");
55 
56 // The name of the sub-dictionary entry for profiling:
57 static const word blockNameProfiling("profiling");
58 
59 // The name of the sub-dictionary entry for profiling and tags of entries
60 // that will be processed to determine (max,avg,min) values
61 const HashTable<wordList> processing
62 {
63  { "profiling", { "calls", "totalTime", "childTime", "maxMem" } },
64  { "memInfo", { "size", "free" } },
65 };
66 
67 
68 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
69 
70 int main(int argc, char *argv[])
71 {
73  (
74  "Collect profiling information from processor directories and"
75  " summarize time spent and number of calls as (max avg min) values."
76  );
77 
78  timeSelector::addOptions(true, true); // constant(true), zero(true)
80  argList::noFunctionObjects(); // Never use function objects
81 
82  // Note that this should work without problems when profiling is active,
83  // since we don't trigger it anywhere
84 
85  #include "setRootCase.H"
86  #include "createTime.H"
87 
88  // Determine the processor count
89  const label nProcs = fileHandler().nProcs(args.path());
90 
91  // Create the processor databases
92  PtrList<Time> databases(nProcs);
93 
94  forAll(databases, proci)
95  {
96  databases.set
97  (
98  proci,
99  new Time
100  (
102  args.rootPath(),
103  args.caseName()/("processor" + Foam::name(proci)),
105  args.allowLibs()
106  )
107  );
108  }
109 
110  if (!nProcs)
111  {
113  << "No processor* directories found"
114  << exit(FatalError);
115  }
116 
117 
118  // Use the times list from the master processor
119  // and select a subset based on the command-line options
121  (
122  databases[0].times(),
123  args
124  );
125 
126  if (timeDirs.empty())
127  {
129  << "No times selected" << nl << endl;
130  return 1;
131  }
132 
133  // ----------------------------------------------------------------------
134 
135  // Processor local profiling information
136  List<dictionary> profiles(nProcs);
137 
138  // Loop over all times
139  forAll(timeDirs, timei)
140  {
141  // Set time for global database
142  runTime.setTime(timeDirs[timei], timei);
143 
144  Info<< "Time = " << runTime.timeName() << endl;
145 
146  // Name/location for the output summary
147  const fileName outputName
148  {
150  "profiling",
151  runTime.timeName(),
152  profilingFileName
153  };
154 
155 
156  label nDict = 0;
157 
158  // Set time for all databases
159  forAll(databases, proci)
160  {
161  profiles[proci].clear();
162  databases[proci].setTime(timeDirs[timei], timei);
163 
164  // Look for "uniform/profiling" in each processor directory
165  IOobjectList objects
166  (
167  databases[proci].time(),
168  databases[proci].timeName(),
169  "uniform"
170  );
171 
172  const IOobject* ioptr = objects.findObject(profilingFileName);
173  if (ioptr)
174  {
175  IOdictionary dict(*ioptr);
176 
177  // Full copy
178  profiles[proci] = dict;
179 
180  // Assumed to be good if it has 'profiling' sub-dict
181 
182  const dictionary* ptr = dict.findDict(blockNameProfiling);
183  if (ptr)
184  {
185  ++nDict;
186  }
187  }
188 
189  if (nDict < proci)
190  {
191  break;
192  }
193  }
194 
195  if (nDict != nProcs)
196  {
197  Info<< "found " << nDict << "/" << nProcs
198  << " profiling files" << nl << endl;
199  continue;
200  }
201 
202 
203  // Information seems to be there for all processors
204  // can do a summary
205 
206  IOdictionary summary
207  (
208  IOobject
209  (
211  runTime,
214  false, // no register
215  true // global-like
216  )
217  );
218 
219  summary.note() =
220  (
221  "summarized (max avg min) values from "
222  + Foam::name(nProcs) + " processors"
223  );
224 
225 
226  // Accumulator for each tag
228 
229  // Use first as 'master' to decide what others have
230  forAllConstIters(profiles.first(), mainIter)
231  {
232  const entry& mainEntry = mainIter();
233 
234  // level1: eg, profiling {} or memInfo {}
235  const word& level1Name = mainEntry.keyword();
236 
237  if
238  (
239  !processing.found(level1Name)
240  || !mainEntry.isDict()
241  || mainEntry.dict().empty()
242  )
243  {
244  continue; // Only process known types
245  }
246 
247  const wordList& tags = processing[level1Name];
248 
249  const dictionary& level1Dict = mainEntry.dict();
250 
251  // We need to handle sub-dicts with other dicts
252  // Eg, trigger0 { .. } trigger1 { .. }
253  //
254  // and ones with primitives
255  // Eg, size xx; free yy;
256 
257  // Decide based on the first entry:
258 
259  // level2: eg, profiling { trigger0 { } }
260  // or simply itself it contains primitives only
261 
262  wordList level2Names;
263 
264  const bool hasDictEntries
265  = mainEntry.dict().first()->isDict();
266 
267  if (hasDictEntries)
268  {
269  level2Names =
270  mainEntry.dict().sortedToc(stringOps::natural_sort());
271  }
272  else
273  {
274  level2Names = {level1Name};
275  }
276 
277  summary.set(level1Name, dictionary());
278 
279  dictionary& outputDict = summary.subDict(level1Name);
280 
281  for (const word& level2Name : level2Names)
282  {
283  // Presize everything
284  stats.clear();
285  for (const word& tag : tags)
286  {
287  stats(tag).reserve(nProcs);
288  }
289 
290  label nEntry = 0;
291 
292  for (const dictionary& procDict : profiles)
293  {
294  const dictionary* inDictPtr = procDict.findDict(level1Name);
295 
296  if (inDictPtr && hasDictEntries)
297  {
298  // Descend to the next level as required
299  inDictPtr = inDictPtr->findDict(level2Name);
300  }
301 
302  if (!inDictPtr)
303  {
304  break;
305  }
306 
307  ++nEntry;
308 
309  for (const word& tag : tags)
310  {
311  scalar val;
312 
313  if
314  (
315  inDictPtr->readIfPresent(tag, val, keyType::LITERAL)
316  )
317  {
318  stats(tag).append(val);
319  }
320  }
321  }
322 
323  if (nEntry != nProcs)
324  {
325  continue;
326  }
327 
328  dictionary* outDictPtr = nullptr;
329 
330  // Make a full copy of this entry prior to editing it
331  if (hasDictEntries)
332  {
333  outputDict.add(level2Name, level1Dict.subDict(level2Name));
334  outDictPtr = outputDict.findDict(level2Name);
335  }
336  else
337  {
338  // merge into existing (empty) dictionary
339  summary.add(level1Name, level1Dict, true);
340  outDictPtr = &outputDict;
341  }
342 
343  dictionary& outSubDict = *outDictPtr;
344 
345  // Remove trailing 'processor0' from any descriptions
346  // (looks nicer)
347  {
348  const word key("description");
349  string val;
350 
351  if (outSubDict.readIfPresent(key, val))
352  {
353  if (val.removeEnd("processor0"))
354  {
355  outSubDict.set(key, val);
356  }
357  }
358  }
359 
360  // Process each tag (calls, time etc)
361  for (const word& tag : tags)
362  {
363  DynamicList<scalar>& lst = stats(tag);
364 
365  if (lst.size() == nProcs)
366  {
367  sort(lst);
368  const scalar avg = sum(lst) / nProcs;
369 
370  if (lst.first() != lst.last())
371  {
372  outSubDict.set
373  (
374  tag,
376  {
377  lst.last(), avg, lst.first()
378  }
379  );
380  }
381  }
382  }
383  }
384  }
385 
386 
387  // Now write the summary
388  {
389  mkDir(summary.path());
390 
391  OFstream os(summary.objectPath());
392 
393  summary.writeHeader(os);
394  summary.writeData(os);
396 
397  Info<< "Wrote to " << outputName << nl << endl;
398  }
399  }
400 
401  Info<< "End\n" << endl;
402 
403  return 0;
404 }
405 
406 
407 // ************************************************************************* //
dictionary dict
static void noFunctionObjects(bool addWithOption=false)
Remove &#39;-noFunctionObjects&#39; option and ignore any occurrences.
Definition: argList.C:514
static void addNote(const string &note)
Add extra notes for the usage information.
Definition: argList.C:453
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:118
fileName path() const
Return path.
Definition: Time.H:449
A class for handling file names.
Definition: fileName.H:71
List of IOobjects with searching and retrieving facilities. Implemented as a HashTable, so the various sorted methods should be used if traversing in parallel.
Definition: IOobjectList.H:55
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:120
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:578
Output to file stream, using an OSstream.
Definition: OFstream.H:49
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:49
bool empty() const noexcept
True if the UList is empty (ie, size() is zero)
Definition: UListI.H:420
T & first()
Access first element of the list, position [0].
Definition: UList.H:798
engineTime & runTime
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:487
virtual const dictionary & dict() const =0
Return dictionary, if entry is a dictionary.
static void noParallel()
Remove the parallel options.
Definition: argList.C:551
autoPtr< fileOperation > fileHandler(std::nullptr_t)
Delete current file handler.
entry * add(entry *entryPtr, bool mergeEntry=false)
Add a new entry.
Definition: dictionary.C:637
Ignore writing from objectRegistry::writeObject()
const dictionary & subDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a sub-dictionary.
Definition: dictionary.C:453
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:69
instantList select(const instantList &times) const
Select a list of Time values that are within the ranges.
Definition: timeSelector.C:88
bool allowFunctionObjects() const
The controlDict &#39;functions&#39; entry is allowed to be used.
Definition: argList.C:1817
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
IOdictionary is derived from dictionary and IOobject to give the dictionary automatic IO functionalit...
Definition: IOdictionary.H:50
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:413
Functions used by OpenFOAM that are specific to POSIX compliant operating systems and need to be repl...
word timeName
Definition: getTimeIndex.H:3
bool allowLibs() const
The controlDict &#39;libs&#39; entry is allowed to be used. (eg, has not been disabled by the -no-libs option...
Definition: argList.C:1835
word outputName("finiteArea-edges.obj")
const keyType & keyword() const noexcept
Return keyword.
Definition: entry.H:231
bool mkDir(const fileName &pathName, mode_t mode=0777)
Make a directory and return an error if it could not be created.
Definition: POSIX.C:567
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:52
A class for handling words, derived from Foam::string.
Definition: word.H:63
void sort(UList< T > &list)
Sort the list.
Definition: UList.C:334
void clear()
Clear all entries from table.
Definition: HashTable.C:671
virtual void setTime(const Time &t)
Reset the time and time-index to those of the given time.
Definition: Time.C:977
String literal.
Definition: keyType.H:82
static word controlDictName
The default control dictionary name (normally "controlDict")
Definition: Time.H:275
const fileName & caseName() const noexcept
Return case name (parallel run) or global case (serial run)
Definition: argListI.H:62
static Ostream & writeEndDivider(Ostream &os)
Write the standard end file divider.
static word timeName(const scalar t, const int precision=precision_)
Return time name of given scalar time formatted with the given precision.
Definition: Time.C:760
bool readIfPresent(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX) const
Find an entry if present, and assign to T val. FatalIOError if it is found and the number of tokens i...
const fileName & rootPath() const noexcept
Return root path.
Definition: argListI.H:56
OBJstream os(runTime.globalPath()/outputName)
T & last()
Access last element of the list, position [size()-1].
Definition: UList.H:812
Encapsulation of natural order sorting for algorithms.
Definition: stringOpsSort.H:63
fileName path() const
Return the full path to the (processor local) case.
Definition: argListI.H:74
#define WarningInFunction
Report a warning using Foam::Warning.
auto key(const Type &t) -> typename std::enable_if< std::is_enum< Type >::value, typename std::underlying_type< Type >::type >::type
Definition: foamGltfBase.H:103
A list of pointers to objects of type <T>, with allocation/deallocation management of the pointers...
Definition: List.H:55
Nothing to be read.
static word outputPrefix
Directory prefix.
messageStream Info
Information stream (stdout output on master, null elsewhere)
virtual bool isDict() const noexcept
Return true if this entry is a dictionary.
Definition: entry.H:287
entry * set(entry *entryPtr)
Assign a new entry, overwriting any existing entry.
Definition: dictionary.C:777
T * first()
The first entry in the list.
Definition: UILList.H:544
Foam::argList args(argc, argv)
Defines the attributes of an object for which implicit objectRegistry management is supported...
Definition: IOobject.H:166
bool removeEnd(const std::string &text)
Remove the given text from the end of the string.
Definition: string.C:222
wordList sortedToc() const
Return the sorted table of contents.
Definition: dictionary.C:613
static void addOptions(const bool constant=true, const bool withZero=false)
Add timeSelector options to argList::validOptions.
Definition: timeSelector.C:101
Namespace for OpenFOAM.
forAllConstIters(mixture.phases(), phase)
Definition: pEqn.H:28
A keyword and a list of tokens is an &#39;entry&#39;.
Definition: entry.H:63
const dictionary * findDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a sub-dictionary pointer if present (and a sub-dictionary) otherwise return nullptr...
Definition: dictionaryI.H:120