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-2020 OpenCFD Ltd.
9-------------------------------------------------------------------------------
10License
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
26Application
27 profilingSummary
28
29Group
30 grpMiscUtilities
31
32Description
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
51using namespace Foam;
52
53// The name of the sub-dictionary entry for profiling fileName:
54static const word profilingFileName("profiling");
55
56// The name of the sub-dictionary entry for profiling:
57static 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
61const HashTable<wordList> processing
62{
63 { "profiling", { "calls", "totalTime", "childTime", "maxMem" } },
64 { "memInfo", { "size", "free" } },
65};
66
67
68// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
69
70int main(int argc, char *argv[])
71{
72 argList::addNote
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)
79 argList::noParallel();
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 (
101 Time::controlDictName,
102 args.rootPath(),
103 args.caseName()/("processor" + Foam::name(proci))
104 )
105 );
106 }
107
108 if (!nProcs)
109 {
111 << "No processor* directories found"
112 << exit(FatalError);
113 }
114
115
116 // Use the times list from the master processor
117 // and select a subset based on the command-line options
118 instantList timeDirs = timeSelector::select
119 (
120 databases[0].times(),
121 args
122 );
123
124 if (timeDirs.empty())
125 {
127 << "No times selected" << nl << endl;
128 return 1;
129 }
130
131 // ----------------------------------------------------------------------
132
133 // Processor local profiling information
134 List<dictionary> profiles(nProcs);
135
136 // Loop over all times
137 forAll(timeDirs, timei)
138 {
139 // Set time for global database
140 runTime.setTime(timeDirs[timei], timei);
141
142 Info<< "Time = " << runTime.timeName() << endl;
143
144 // Name/location for the output summary
145 const fileName outputName
146 {
147 functionObject::outputPrefix,
148 "profiling",
149 runTime.timeName(),
150 profilingFileName
151 };
152
153
154 label nDict = 0;
155
156 // Set time for all databases
157 forAll(databases, proci)
158 {
159 profiles[proci].clear();
160 databases[proci].setTime(timeDirs[timei], timei);
161
162 // Look for "uniform/profiling" in each processor directory
163 IOobjectList objects
164 (
165 databases[proci].time(),
166 databases[proci].timeName(),
167 "uniform"
168 );
169
170 const IOobject* ioptr = objects.findObject(profilingFileName);
171 if (ioptr)
172 {
173 IOdictionary dict(*ioptr);
174
175 // Full copy
176 profiles[proci] = dict;
177
178 // Assumed to be good if it has 'profiling' sub-dict
179
180 const dictionary* ptr = dict.findDict(blockNameProfiling);
181 if (ptr)
182 {
183 ++nDict;
184 }
185 }
186
187 if (nDict < proci)
188 {
189 break;
190 }
191 }
192
193 if (nDict != nProcs)
194 {
195 Info<< "found " << nDict << "/" << nProcs
196 << " profiling files" << nl << endl;
197 continue;
198 }
199
200
201 // Information seems to be there for all processors
202 // can do a summary
203
204 IOdictionary summary
205 (
207 (
208 runTime.path()/outputName,
209 runTime,
210 IOobject::NO_READ,
211 IOobject::NO_WRITE,
212 false, // no register
213 true // global-like
214 )
215 );
216
217 summary.note() =
218 (
219 "summarized (max avg min) values from "
220 + Foam::name(nProcs) + " processors"
221 );
222
223
224 // Accumulator for each tag
226
227 // Use first as 'master' to decide what others have
228 forAllConstIters(profiles.first(), mainIter)
229 {
230 const entry& mainEntry = mainIter();
231
232 // level1: eg, profiling {} or memInfo {}
233 const word& level1Name = mainEntry.keyword();
234
235 if
236 (
237 !processing.found(level1Name)
238 || !mainEntry.isDict()
239 || mainEntry.dict().empty()
240 )
241 {
242 continue; // Only process known types
243 }
244
245 const wordList& tags = processing[level1Name];
246
247 const dictionary& level1Dict = mainEntry.dict();
248
249 // We need to handle sub-dicts with other dicts
250 // Eg, trigger0 { .. } trigger1 { .. }
251 //
252 // and ones with primitives
253 // Eg, size xx; free yy;
254
255 // Decide based on the first entry:
256
257 // level2: eg, profiling { trigger0 { } }
258 // or simply itself it contains primitives only
259
260 wordList level2Names;
261
262 const bool hasDictEntries
263 = mainEntry.dict().first()->isDict();
264
265 if (hasDictEntries)
266 {
267 level2Names =
269 }
270 else
271 {
272 level2Names = {level1Name};
273 }
274
275 summary.set(level1Name, dictionary());
276
277 dictionary& outputDict = summary.subDict(level1Name);
278
279 for (const word& level2Name : level2Names)
280 {
281 // Presize everything
282 stats.clear();
283 for (const word& tag : tags)
284 {
285 stats(tag).reserve(nProcs);
286 }
287
288 label nEntry = 0;
289
290 for (const dictionary& procDict : profiles)
291 {
292 const dictionary* inDictPtr = procDict.findDict(level1Name);
293
294 if (inDictPtr && hasDictEntries)
295 {
296 // Descend to the next level as required
297 inDictPtr = inDictPtr->findDict(level2Name);
298 }
299
300 if (!inDictPtr)
301 {
302 break;
303 }
304
305 ++nEntry;
306
307 for (const word& tag : tags)
308 {
309 scalar val;
310
311 if
312 (
313 inDictPtr->readIfPresent(tag, val, keyType::LITERAL)
314 )
315 {
316 stats(tag).append(val);
317 }
318 }
319 }
320
321 if (nEntry != nProcs)
322 {
323 continue;
324 }
325
326 dictionary* outDictPtr = nullptr;
327
328 // Make a full copy of this entry prior to editing it
329 if (hasDictEntries)
330 {
331 outputDict.add(level2Name, level1Dict.subDict(level2Name));
332 outDictPtr = outputDict.findDict(level2Name);
333 }
334 else
335 {
336 // merge into existing (empty) dictionary
337 summary.add(level1Name, level1Dict, true);
338 outDictPtr = &outputDict;
339 }
340
341 dictionary& outSubDict = *outDictPtr;
342
343 // Remove trailing 'processor0' from any descriptions
344 // (looks nicer)
345 {
346 const word key("description");
347 string val;
348
349 if (outSubDict.readIfPresent(key, val))
350 {
351 if (val.removeEnd("processor0"))
352 {
353 outSubDict.set(key, val);
354 }
355 }
356 }
357
358 // Process each tag (calls, time etc)
359 for (const word& tag : tags)
360 {
361 DynamicList<scalar>& lst = stats(tag);
362
363 if (lst.size() == nProcs)
364 {
365 sort(lst);
366 const scalar avg = sum(lst) / nProcs;
367
368 if (lst.first() != lst.last())
369 {
370 outSubDict.set
371 (
372 tag,
374 {
375 lst.last(), avg, lst.first()
376 }
377 );
378 }
379 }
380 }
381 }
382 }
383
384
385 // Now write the summary
386 {
387 mkDir(summary.path());
388
389 OFstream os(summary.objectPath());
390
391 summary.writeHeader(os);
392 summary.writeData(os);
393 summary.writeEndDivider(os);
394
395 Info<< "Wrote to " << outputName << nl << endl;
396 }
397 }
398
399 Info<< "End\n" << endl;
400
401 return 0;
402}
403
404
405// ************************************************************************* //
Functions used by OpenFOAM that are specific to POSIX compliant operating systems and need to be repl...
A 1D vector of objects of type <T> that resizes itself as necessary to accept the new objects.
Definition: DynamicList.H:72
A 1D vector of objects of type <T> with a fixed length <N>.
Definition: FixedList.H:81
A HashTable similar to std::unordered_map.
Definition: HashTable.H:123
void clear()
Clear all entries from table.
Definition: HashTable.C:678
IOdictionary is derived from dictionary and IOobject to give the dictionary automatic IO functionalit...
Definition: IOdictionary.H:57
List of IOobjects with searching and retrieving facilities.
Definition: IOobjectList.H:59
Defines the attributes of an object for which implicit objectRegistry management is supported,...
Definition: IOobject.H:170
Output to file stream, using an OSstream.
Definition: OFstream.H:57
A list of pointers to objects of type <T>, with allocation/deallocation management of the pointers....
Definition: PtrList.H:73
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:80
T * first()
The first entry in the list.
Definition: UILList.H:124
T & first()
Return the first element of the list.
Definition: UListI.H:202
bool empty() const noexcept
True if the UList is empty (ie, size() is zero)
Definition: UListI.H:427
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
T & last()
Return the last element of the list.
Definition: UListI.H:216
const fileName & rootPath() const noexcept
Return root path.
Definition: argListI.H:63
fileName path() const
Return the full path to the (processor local) case.
Definition: argListI.H:81
const fileName & caseName() const noexcept
Return case name (parallel run) or global case (serial run)
Definition: argListI.H:69
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition: dictionary.H:126
dictionary * findDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX)
Find and return a sub-dictionary pointer if present.
Definition: dictionaryI.H:127
const dictionary & subDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a sub-dictionary.
Definition: dictionary.C:460
bool readIfPresent(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX) const
wordList sortedToc() const
Return the sorted table of contents.
Definition: dictionary.C:616
entry * add(entry *entryPtr, bool mergeEntry=false)
Add a new entry.
Definition: dictionary.C:640
entry * set(entry *entryPtr)
Assign a new entry, overwriting any existing entry.
Definition: dictionary.C:780
A keyword and a list of tokens is an 'entry'.
Definition: entry.H:70
virtual bool isDict() const noexcept
Return true if this entry is a dictionary.
Definition: entry.H:233
const keyType & keyword() const noexcept
Return keyword.
Definition: entry.H:195
virtual const dictionary & dict() const =0
Return dictionary, if entry is a dictionary.
A class for handling file names.
Definition: fileName.H:76
virtual label nProcs(const fileName &dir, const fileName &local="") const
Get number of processor directories/results. Used for e.g.
bool removeEnd(const std::string &text)
Remove the given text from the end of the string.
Definition: string.C:229
A class for handling words, derived from Foam::string.
Definition: word.H:68
engineTime & runTime
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:453
word outputName("finiteArea-edges.obj")
OBJstream os(runTime.globalPath()/outputName)
word timeName
Definition: getTimeIndex.H:3
#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:108
Namespace for OpenFOAM.
const fileOperation & fileHandler()
Get current file handler.
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
messageStream Info
Information stream (stdout output on master, null elsewhere)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:372
void sort(UList< T > &list)
Sort the list.
Definition: UList.C:342
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:130
constexpr char nl
The newline '\n' character (0x0a)
Definition: Ostream.H:53
dictionary dict
Foam::argList args(argc, argv)
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:333
#define forAllConstIters(container, iter)
Iterate across all elements of the container object with const access.
Definition: stdFoam.H:278
Encapsulation of natural order sorting for algorithms.
Definition: stringOpsSort.H:63
mkDir(pdfPath)