Belle II Software  release-08-01-10
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 
23 using namespace std;
24 using namespace Belle2;
25 
26 int ProcessStatistics::getIndex(const Module* module)
27 {
28  auto indexIt = m_modulesToStatsIndex.find(module);
29  if (indexIt == m_modulesToStatsIndex.end()) {
30  int index = m_stats.size();
31  m_modulesToStatsIndex[module] = index;
32  m_stats.emplace_back();
33  initModule(module);
34  return index;
35  } else {
36  return indexIt->second;
37  }
38 }
39 void ProcessStatistics::initModule(const Module* module)
40 {
41  int index = getIndex(module);
42  ModuleStatistics& stats = m_stats.at(index);
43  if (module and stats.getName().empty()) {
44  const string& type = module->getType();
45  if (type == "Tx" or type == "Rx")
46  stats.setName(type);
47  else
48  stats.setName(module->getName());
49  }
50  stats.setIndex(index);
51 }
52 
53 string ProcessStatistics::getStatisticsString(ModuleStatistics::EStatisticCounters mode,
54  const std::vector<ModuleStatistics>* modules, bool html) const
55 {
56  const ModuleStatistics& global = getGlobal();
57  if (!modules) modules = &(getAll());
58  int moduleNameLength = 21; //minimum: 80 characters
59  const int lengthOfRest = 80 - moduleNameLength;
60  for (const ModuleStatistics& stats : *modules) {
61  int len = stats.getName().length();
62  if (len > moduleNameLength)
63  moduleNameLength = len;
64  }
65  const std::string numTabsModule = (boost::format("%d") % (moduleNameLength + 1)).str();
66  const std::string numWidth = (boost::format("%d") % (moduleNameLength + 1 + lengthOfRest)).str();
67  boost::format outputheader("%s %|" + numTabsModule + "t|| %10s | %10s | %10s | %17s\n");
68  boost::format output("%s %|" + numTabsModule + "t|| %10.0f | %10.0f | %10.2f | %7.2f +-%7.2f\n");
69  if (html) {
70  outputheader = boost::format("<thead><tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr></thead>");
71  output = boost::format("<tr><td>%s</td><td>%.0f</td><td>%.0f</td><td>%.2f</td><td>%.2f &plusmn; %.2f</td></tr>");
72  }
73 
74  stringstream out;
75  if (!html) {
76  out << boost::format("%|" + numWidth + "T=|\n");
77  out << outputheader % "Name" % "Calls" % "Memory(MB)" % "Time(s)" % "Time(ms)/Call";
78  out << boost::format("%|" + numWidth + "T=|\n");
79  } else {
80  out << "<table border=0>";
81  out << outputheader % "Name" % "Calls" % "Memory(MB)" % "Time(s)" % "Time(ms)/Call";
82  out << "<tbody>";
83  }
84 
85  std::vector<ModuleStatistics> modulesSortedByIndex(*modules);
86  sort(modulesSortedByIndex.begin(), modulesSortedByIndex.end(), [](const ModuleStatistics & a, const ModuleStatistics & b) { return a.getIndex() < b.getIndex(); });
87 
88  for (const ModuleStatistics& stats : modulesSortedByIndex) {
89  out << output
90  % stats.getName()
91  % stats.getCalls(mode)
92  % (stats.getMemorySum(mode) / 1024)
93  % (stats.getTimeSum(mode) / Unit::s)
94  % (stats.getTimeMean(mode) / Unit::ms)
95  % (stats.getTimeStddev(mode) / Unit::ms);
96  }
97 
98  if (!html) {
99  out << boost::format("%|" + numWidth + "T=|\n");
100  } else {
101  out << "</tbody><tfoot>";
102  }
103  out << output
104  % (ProcHandler::isOutputProcess() ? "Total (output proc.)" : "Total")
105  % global.getCalls(mode)
106  % (global.getMemorySum(mode) / 1024)
107  % (global.getTimeSum(mode) / Unit::s)
108  % (global.getTimeMean(mode) / Unit::ms)
109  % (global.getTimeStddev(mode) / Unit::ms);
110  if (!html) {
111  out << boost::format("%|" + numWidth + "T=|\n");
112  } else {
113  out << "</tfoot></table>";
114  }
115  return out.str();
116 }
117 
118 void ProcessStatistics::appendUnmergedModules(const ProcessStatistics* otherObject)
119 {
120  unsigned int minIndexUnmerged = 0;
121  if (otherObject->m_modulesToStatsIndex.empty()) {
122  B2WARNING("ProcessStatistics::appendUnmergedModules(): Module -> index list is empty? This might produce wrong results");
123  } else {
124  minIndexUnmerged = otherObject->m_modulesToStatsIndex.begin()->second;
125  for (auto pair : otherObject->m_modulesToStatsIndex) {
126  if (pair.second < (int)minIndexUnmerged)
127  minIndexUnmerged = pair.second;
128  }
129  }
130  if (minIndexUnmerged > m_stats.size())
131  B2FATAL("(minIndexUnmerged > m_stats.size()) :( ");
132  if (minIndexUnmerged > otherObject->m_stats.size())
133  B2FATAL("(minIndexUnmerged > otherObject->m_stats.size()) :( ");
134 
135 
136  //the first minIndexUnmerged entries in m_stats should just be merged...
137  for (unsigned int i = 0; i < minIndexUnmerged; i++) {
138  ModuleStatistics& myStats = m_stats[i];
139  const ModuleStatistics& otherStats = otherObject->m_stats[i];
140  if (myStats.getName() == otherStats.getName()) {
141  myStats.update(otherStats);
142  } else {
143  B2ERROR("mismatch in module names in statistics (" << myStats.getName() << " vs. " << otherStats.getName() <<
144  "). ProcessStatistics::merge() can only merge statistics that contain exactly the same modules.");
145  }
146  }
147 
148  //append the rest
149  for (unsigned int i = minIndexUnmerged; i < otherObject->m_stats.size(); i++) {
150  const ModuleStatistics& otherStats = otherObject->m_stats[i];
151  m_stats.emplace_back(otherStats);
152  m_stats.back().setIndex(m_stats.size() - 1);
153  }
154  //copy m_modulesToStatsIndex
155  //shift indices by #entries missing in otherObject
156  const int shift = m_stats.size() - otherObject->m_stats.size();
157  if (shift < 0) {
158  B2FATAL("shift negative:" << LogVar("shift", shift));
159  }
160  for (auto pair : otherObject->m_modulesToStatsIndex) {
161  m_modulesToStatsIndex[pair.first] = pair.second + shift;
162  }
163 }
164 
165 
167 {
168  const auto* otherObject = static_cast<const ProcessStatistics*>(other);
169 
170  if (m_stats == otherObject->m_stats) {
171  //fast version for merging between processes
172  for (unsigned int i = 0; i < otherObject->m_stats.size(); i++)
173  m_stats[i].update(otherObject->m_stats[i]);
174  } else {
175  //note: statistics in m_global are not merged for pp, we use the output process instead
176  //for objects read from file we need to add them though
177  m_global.update(otherObject->m_global);
178 
179  appendUnmergedModules(otherObject);
180  }
181 
182  //if the other object has transient data on modules, copy remaining counters
183  if (!otherObject->m_modulesToStatsIndex.empty())
184  setTransientCounters(otherObject);
185 }
186 
187 void ProcessStatistics::setTransientCounters(const ProcessStatistics* otherObject)
188 {
189  m_globalTime = otherObject->m_globalTime;
190  m_globalMemory = otherObject->m_globalMemory;
191  m_moduleTime = otherObject->m_moduleTime;
192  m_moduleMemory = otherObject->m_moduleMemory;
193  m_suspendedTime = otherObject->m_suspendedTime;
194  m_suspendedMemory = otherObject->m_suspendedMemory;
195 }
196 
197 void ProcessStatistics::clear()
198 {
199  m_global.clear();
200  for (auto& stats : m_stats) { stats.clear(); }
201 }
202 
203 void ProcessStatistics::setCounters(double& time, double& memory,
204  double startTime, double startMemory)
205 {
206  time = Utils::getClock() - startTime;
207  memory = Utils::getRssMemoryKB() - startMemory;
208 }
209 
210 TObject* ProcessStatistics::Clone(const char*) const
211 {
212  auto* p = new ProcessStatistics(*this);
213  return p;
214 }
215 
216 std::string ProcessStatistics::getInfoHTML() const
217 {
218  std::string s = getStatisticsString();
219  return "Event Statistics:<br />" + s;
220 }
221 
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.
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 update(const ModuleStatistics &other)
Add statistics for each category.
Base class for Modules.
Definition: Module.h:72
Class to collect call statistics for all modules.
double m_globalTime
store clock counter for global time consumption
double m_suspendedMemory
(transient)
std::map< const Module *, int > m_modulesToStatsIndex
transient, maps Module* to m_stats index.
std::vector< Belle2::ModuleStatistics > m_stats
module statistics
double m_suspendedTime
(transient)
double m_moduleTime
(transient)
double m_globalMemory
(transient)
double m_moduleMemory
(transient)
Class to store variables with their name which were sent to the logging service.
std::vector< std::vector< double > > merge(std::vector< std::vector< std::vector< double >>> toMerge)
merge { vector<double> a, vector<double> b} into {a, b}
Definition: tools.h:41
Abstract base class for different kinds of events.