Belle II Software development
ProcessStatistics.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <framework/core/ProcessStatistics.h>
10
11#include <framework/logging/Logger.h>
12#include <framework/pcore/ProcHandler.h>
13#include <framework/gearbox/Unit.h>
14#include <framework/utilities/Utils.h>
15
16#include <boost/algorithm/string/replace.hpp>
17#include <regex>
18#include <boost/format.hpp>
19
20#include <algorithm>
21#include <sstream>
22#include <fstream>
23
24using namespace std;
25using namespace Belle2;
26
28{
29 auto indexIt = m_modulesToStatsIndex.find(module);
30 if (indexIt == m_modulesToStatsIndex.end()) {
31 int index = m_stats.size();
32 m_modulesToStatsIndex[module] = index;
33 m_stats.emplace_back();
34 initModule(module);
35 return index;
36 } else {
37 return indexIt->second;
38 }
39}
41{
42 int index = getIndex(module);
43 ModuleStatistics& stats = m_stats.at(index);
44 if (module and stats.getName().empty()) {
45 const string& type = module->getType();
46 if (type == "Tx" or type == "Rx")
47 stats.setName(type);
48 else
49 stats.setName(module->getName());
50 }
51 stats.setIndex(index);
52}
53
55 const std::vector<ModuleStatistics>* modules, bool html) const
56{
57 const ModuleStatistics& global = getGlobal();
58 if (!modules) modules = &(getAll());
59 int moduleNameLength = 21; //minimum: 80 characters
60 const int lengthOfRest = 80 - moduleNameLength;
61 for (const ModuleStatistics& stats : *modules) {
62 int len = stats.getName().length();
63 if (len > moduleNameLength)
64 moduleNameLength = len;
65 }
66 const std::string numTabsModule = (boost::format("%d") % (moduleNameLength + 1)).str();
67 const std::string numWidth = (boost::format("%d") % (moduleNameLength + 1 + lengthOfRest)).str();
68 boost::format outputheader("%s %|" + numTabsModule + "t|| %10s | %10s | %10s | %17s\n");
69 boost::format output("%s %|" + numTabsModule + "t|| %10.0f | %10.0f | %10.2f | %7.2f +-%7.2f\n");
70 if (html) {
71 outputheader = boost::format("<thead><tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr></thead>");
72 output = boost::format("<tr><td>%s</td><td>%.0f</td><td>%.0f</td><td>%.2f</td><td>%.2f &plusmn; %.2f</td></tr>");
73 }
74
75 stringstream out;
76 if (!html) {
77 out << boost::format("%|" + numWidth + "T=|\n");
78 out << outputheader % "Name" % "Calls" % "Memory(MB)" % "Time(s)" % "Time(ms)/Call";
79 out << boost::format("%|" + numWidth + "T=|\n");
80 } else {
81 out << "<table border=0>";
82 out << outputheader % "Name" % "Calls" % "Memory(MB)" % "Time(s)" % "Time(ms)/Call";
83 out << "<tbody>";
84 }
85
86 std::vector<ModuleStatistics> modulesSortedByIndex(*modules);
87 sort(modulesSortedByIndex.begin(), modulesSortedByIndex.end(), [](const ModuleStatistics & a, const ModuleStatistics & b) { return a.getIndex() < b.getIndex(); });
88
89 for (const ModuleStatistics& stats : modulesSortedByIndex) {
90 out << output
91 % stats.getName()
92 % stats.getCalls(mode)
93 % (stats.getMemorySum(mode) / 1024)
94 % (stats.getTimeSum(mode) / Unit::s)
95 % (stats.getTimeMean(mode) / Unit::ms)
96 % (stats.getTimeStddev(mode) / Unit::ms);
97 }
98
99 if (!html) {
100 out << boost::format("%|" + numWidth + "T=|\n");
101 } else {
102 out << "</tbody><tfoot>";
103 }
104 out << output
105 % (ProcHandler::isOutputProcess() ? "Total (output proc.)" : "Total")
106 % global.getCalls(mode)
107 % (global.getMemorySum(mode) / 1024)
108 % (global.getTimeSum(mode) / Unit::s)
109 % (global.getTimeMean(mode) / Unit::ms)
110 % (global.getTimeStddev(mode) / Unit::ms);
111 if (!html) {
112 out << boost::format("%|" + numWidth + "T=|\n");
113 } else {
114 out << "</tfoot></table>";
115 }
116 return out.str();
117}
118
120{
121 unsigned int minIndexUnmerged = 0;
122 if (otherObject->m_modulesToStatsIndex.empty()) {
123 B2WARNING("ProcessStatistics::appendUnmergedModules(): Module -> index list is empty? This might produce wrong results");
124 } else {
125 minIndexUnmerged = otherObject->m_modulesToStatsIndex.begin()->second;
126 for (auto pair : otherObject->m_modulesToStatsIndex) {
127 if (pair.second < (int)minIndexUnmerged)
128 minIndexUnmerged = pair.second;
129 }
130 }
131 if (minIndexUnmerged > m_stats.size())
132 B2FATAL("(minIndexUnmerged > m_stats.size()) :( ");
133 if (minIndexUnmerged > otherObject->m_stats.size())
134 B2FATAL("(minIndexUnmerged > otherObject->m_stats.size()) :( ");
135
136
137 //the first minIndexUnmerged entries in m_stats should just be merged...
138 for (unsigned int i = 0; i < minIndexUnmerged; i++) {
139 ModuleStatistics& myStats = m_stats[i];
140 const ModuleStatistics& otherStats = otherObject->m_stats[i];
141 if (myStats.getName() == otherStats.getName()) {
142 myStats.update(otherStats);
143 } else {
144 B2ERROR("mismatch in module names in statistics (" << myStats.getName() << " vs. " << otherStats.getName() <<
145 "). ProcessStatistics::merge() can only merge statistics that contain exactly the same modules.");
146 }
147 }
148
149 //append the rest
150 for (unsigned int i = minIndexUnmerged; i < otherObject->m_stats.size(); i++) {
151 const ModuleStatistics& otherStats = otherObject->m_stats[i];
152 m_stats.emplace_back(otherStats);
153 m_stats.back().setIndex(m_stats.size() - 1);
154 }
155 //copy m_modulesToStatsIndex
156 //shift indices by #entries missing in otherObject
157 const int shift = m_stats.size() - otherObject->m_stats.size();
158 if (shift < 0) {
159 B2FATAL("shift negative:" << LogVar("shift", shift));
160 }
161 for (auto pair : otherObject->m_modulesToStatsIndex) {
162 m_modulesToStatsIndex[pair.first] = pair.second + shift;
163 }
164}
165
166void ProcessStatistics::write_csv(const char* filename) const
167{
168 std::ofstream output(filename);
169 m_global.csv_header(output);
170 for (auto stats : m_stats) {
171 stats.csv(output);
172 }
173 m_global.csv(output);
174}
175
177{
178 const auto* otherObject = static_cast<const ProcessStatistics*>(other);
179
180 if (m_stats == otherObject->m_stats) {
181 //fast version for merging between processes
182 for (unsigned int i = 0; i < otherObject->m_stats.size(); i++)
183 m_stats[i].update(otherObject->m_stats[i]);
184 } else {
185 //note: statistics in m_global are not merged for pp, we use the output process instead
186 //for objects read from file we need to add them though
187 m_global.update(otherObject->m_global);
188
189 appendUnmergedModules(otherObject);
190 }
191
192 //if the other object has transient data on modules, copy remaining counters
193 if (!otherObject->m_modulesToStatsIndex.empty())
194 setTransientCounters(otherObject);
195}
196
198{
199 m_globalTime = otherObject->m_globalTime;
200 m_globalMemory = otherObject->m_globalMemory;
201 m_moduleTime = otherObject->m_moduleTime;
202 m_moduleMemory = otherObject->m_moduleMemory;
203 m_suspendedTime = otherObject->m_suspendedTime;
205}
206
208{
209 m_global.clear();
210 for (auto& stats : m_stats) { stats.clear(); }
211}
212
213void ProcessStatistics::setCounters(double& time, double& memory,
214 double startTime, double startMemory)
215{
216 time = Utils::getClock() - startTime;
217 memory = Utils::getRssMemoryKB() - startMemory;
218}
219
220TObject* ProcessStatistics::Clone(const char*) const
221{
222 auto* p = new ProcessStatistics(*this);
223 return p;
224}
225
227{
228 std::string s = getStatisticsString();
229 return "Event Statistics:<br />" + s;
230}
231
Abstract base class for objects that can be merged.
Definition: Mergeable.h:31
Keep track of time and memory consumption during processing.
value_type getTimeStddev(EStatisticCounters type=c_Total) const
return the stddev of the execution times for a given counter
value_type getCalls(EStatisticCounters type=c_Total) const
return the number of calls for a given counter type
const std::string & getName() const
Return the previously set name.
void csv(std::ostream &output) const
write data to the given stream in csv format
EStatisticCounters
Enum to define all counter types.
value_type getMemorySum(EStatisticCounters type=c_Total) const
return the total used memory for a given counter
value_type getTimeSum(EStatisticCounters type=c_Total) const
return the sum of all execution times for a given counter
value_type getTimeMean(EStatisticCounters type=c_Total) const
return the mean execution time for a given counter
void clear()
Clear all statistics.
void update(const ModuleStatistics &other)
Add statistics for each category.
void csv_header(std::ostream &output) const
write csv header to the given stream
Base class for Modules.
Definition: Module.h:72
static bool isOutputProcess()
Return true if the process is an output process.
Definition: ProcHandler.cc:232
Class to collect call statistics for all modules.
void setCounters(double &time, double &memory, double startTime=0, double startMemory=0)
Set counters time and memory to contain the current clock value and memory consumption respectively.
ModuleStatistics m_global
Statistics object for global time and memory consumption.
const ModuleStatistics & getGlobal() const
Get global statistics.
const std::vector< Belle2::ModuleStatistics > & getAll() const
Get entire statistics map.
void appendUnmergedModules(const ProcessStatistics *otherObject)
Merge dissimilar objects (mainly loading ProcessStatistics from file).
double m_globalTime
store clock counter for global time consumption
std::string getStatisticsString(ModuleStatistics::EStatisticCounters type=ModuleStatistics::c_Event, const std::vector< Belle2::ModuleStatistics > *modules=nullptr, bool html=false) const
Return string with statistics for all modules.
int getIndex(const Module *module)
get m_stats index for given module, inserting it if not found.
double m_suspendedMemory
(transient)
void initModule(const Module *module)
Init module statistics: Set name from module if still empty and remember initialization index for dis...
virtual void merge(const Mergeable *other) override
Merge other ProcessStatistics object into this one.
std::map< const Module *, int > m_modulesToStatsIndex
transient, maps Module* to m_stats index.
virtual TObject * Clone(const char *newname="") const override
Reimplement TObject::Clone() since we also need m_modulesToStatsIndex.
void write_csv(const char *filename="ProcessStatistics.csv") const
Write process statistics to a csv file.
std::string getInfoHTML() const
Return a short summary of this object's contents in HTML format.
std::vector< Belle2::ModuleStatistics > m_stats
module statistics
double m_suspendedTime
(transient)
double m_moduleTime
(transient)
virtual void clear() override
Clear collected statistics but keep names of modules.
double m_globalMemory
(transient)
double m_moduleMemory
(transient)
void setTransientCounters(const ProcessStatistics *otherObject)
Set transient counters from otherObject.
static const double ms
[millisecond]
Definition: Unit.h:96
static const double s
[second]
Definition: Unit.h:95
Class to store variables with their name which were sent to the logging service.
double getClock()
Return current value of the real-time clock.
Definition: Utils.cc:66
unsigned long getRssMemoryKB()
Returns the amount of memory the process actually occupies in the physical RAM of the machine.
Definition: Utils.cc:84
Abstract base class for different kinds of events.
STL namespace.