Statistics.cc
Go to the documentation of this file.
1 //
2 // Copyright (c) 2020 Fraunhofer Institute for Applied Information Technology (FIT)
3 // Network Research Group (NET)
4 // Schloss Birlinghoven, 53754 Sankt Augustin, GERMANY
5 // Contact: support@wiback.org
6 //
7 // This file is part of the SENF code tree.
8 // It is licensed under the 3-clause BSD License (aka New BSD License).
9 // See LICENSE.txt in the top level directory for details or visit
10 // https://opensource.org/licenses/BSD-3-Clause
11 //
12 
13 
17 #include "Statistics.hh"
18 //#include "Statistics.ih"
19 
20 // Custom includes
21 #include <cmath>
22 #include <cstdlib>
23 #include <sstream>
24 #include <senf/Utils/Format.hh>
26 #include "StatisticsTargets.hh"
27 #include "String.hh"
28 
29 //#include "Statistics.mpp"
30 #define prefix_
31 //-/////////////////////////////////////////////////////////////////////////////////////////////////
32 
33 //-/////////////////////////////////////////////////////////////////////////////////////////////////
34 // senf::StatisticsBase
35 
36 prefix_ void senf::StatisticsBase::enter(unsigned n, unsigned cnt, float min, float avg, float max, float dev)
37 {
38  cnt_ = cnt;
39  min_ = min;
40  avg_ = avg;
41  max_ = max;
42  dev_ = dev;
43  for (unsigned i (0); i < n; ++i)
44  generateOutput();
45  Children::iterator i (children_.begin());
46  Children::iterator const i_end (children_.end());
47  for (; i != i_end; ++i)
48  i->second.enter(n, cnt_, min_, avg_, max_, dev_);
49 }
50 
52 {
53  Children::iterator i (children_.find(rank));
54  if (i == children_.end())
55  throw InvalidRankException();
56  return i->second;
57 }
58 
60  const
61 {
62  Children::const_iterator i (children_.find(rank));
63  if (i == children_.end())
64  throw InvalidRankException();
65  return i->second;
66 }
67 
69 {
70  std::pair<Children::iterator, bool> state (
71  children_.insert(std::make_pair(rank, Collector(this, rank))) );
72  if (! state.second)
73  throw DuplicateRankException();
74  return state.first->second;
75 }
76 
79 {
80  OutputMap::iterator i (outputs_.find(n));
81  if (i == outputs_.end()) {
82  i = outputs_.insert(std::make_pair(n, OutputEntry(n))).first;
83  std::stringstream nm;
84  nm << "output" << path() << ":" << n;
85  base().dir.node().add(nm.str(), i->second.dir);
86  detail::StatisticsLoggerRegistry::instance().apply(*this, n, i->second.dir);
87  }
88  if (n > maxQueueLen_)
89  maxQueueLen_ = n;
90  return OutputProxy<StatisticsBase>(this, &(i->second));
91 }
92 
93 prefix_ void senf::StatisticsBase::consoleList(unsigned level, std::ostream & os)
94  const
95 {
96  namespace fmt = senf::format;
97 
98  /*
99  os << boost::format("%s%-5d%|15t| %12.5g %19.5g %12.5g\n")
100  % std::string(2*level,' ') % rank()
101  % fmt::eng(min()).setw().showbase() % fmt::eng(avg(),dev()).setw().showbase() % fmt::eng(max()).setw().showbase();
102  {
103  OutputMap::const_iterator i (outputs_.begin());
104  OutputMap::const_iterator i_end (outputs_.end());
105  for (; i != i_end; ++i)
106  os << boost::format(" %3d %12.5g %19.5g %12.5g\n")
107  % i->second.n
108  % fmt::eng(i->second.min).setw().showbase()
109  % fmt::eng(i->second.avg, i->second.dev).setw().showbase()
110  % fmt::eng(i->second.max).setw().showbase();
111  }
112  */
113 
114  os << boost::format("%s%-5d%|15t| %12.5g %19.5g %12.5g %12.5g\n")
115  % std::string(2*level,' ') % rank()
116  % fmt::eng(min()).setw() % fmt::eng(avg(),dev()).setw() % fmt::eng(max()).setw() % fmt::eng(cnt()).setw();
117  {
118  OutputMap::const_iterator i (outputs_.begin());
119  OutputMap::const_iterator i_end (outputs_.end());
120  for (; i != i_end; ++i)
121  os << boost::format(" %3d %12.5g %19.5g %12.5g %12.5g\n")
122  % i->second.n
123  % fmt::eng(i->second.min).setw()
124  % fmt::eng(i->second.avg).setw()
125  % fmt::eng(i->second.max).setw()
126  % fmt::eng(i->second.cnt).setw();
127  }
128  {
129  Children::const_iterator i (children_.begin());
130  Children::const_iterator const i_end (children_.end());
131  for (; i != i_end; ++i)
132  i->second.consoleList(level+1, os);
133  }
134 }
135 
136 prefix_ void senf::StatisticsBase::generateOutput()
137 {
138  queue_.push_front(QueueEntry(cnt_, min_, avg_, max_, dev_));
139  while (queue_.size() > maxQueueLen_)
140  queue_.pop_back();
141 
142  OutputMap::iterator i (outputs_.begin());
143  OutputMap::iterator const i_end (outputs_.end());
144  for (; i != i_end; ++i) {
145  i->second.cnt = 0;
146  i->second.min = FLT_MAX;
147  i->second.max = -FLT_MAX;
148  i->second.avg = i->second.dev = 0.0f;
149  Queue::const_iterator j (queue_.begin());
150  Queue::const_iterator const j_end (queue_.end());
151  unsigned n (0), num (0);
152  for (; n < i->second.n && j != j_end; ++n, ++j) {
153  if (j->cnt > 0) {
154  i->second.cnt += j->cnt;
155  i->second.min = std::min(i->second.min, j->min);
156  i->second.avg += j->avg;
157  i->second.max = std::max( i->second.max, j->max);
158  i->second.dev += j->dev;
159  num++;
160  }
161  }
162  if (num > 0) {
163  i->second.avg /= num;
164  i->second.dev /= num;
165  }
166  i->second.signal(i->second.cnt, i->second.min, i->second.avg, i->second.max, i->second.dev);
167  }
168 }
169 
170 //-/////////////////////////////////////////////////////////////////////////////////////////////////
171 // senf::StatisticsBase::OutputEntry
172 
173 prefix_ void senf::StatisticsBase::OutputEntry::consoleList(std::ostream & os)
174 {
175  for (boost::ptr_vector<TargetBase>::iterator i (targets_.begin());
176  i != targets_.end(); ++i)
177  if (! i->label.empty())
178  os << i->label << "\n";
179 }
180 
181 //-/////////////////////////////////////////////////////////////////////////////////////////////////
182 // senf::Statistics
183 
185 {
186 #ifndef SENF_DISABLE_CONSOLE
187  namespace fty = console::factory;
188 
189  dir.add("list", fty::Command(&Statistics::consoleList, this)
190  .doc("List statistics collection intervals and current values.\n"
191  "\n"
192  "Columns:\n"
193  " RANK Number of values collected. Since the statistics collectors form\n"
194  " a tree, the value is indented according to it's tree location.\n"
195  " WIN Size of output average window.\n"
196  " MIN Last entered minimum value.\n"
197  " AVG Last entered average value.\n"
198  " DEV Standard deviation of average value over the collector rank.\n"
199  " MAX Last entered maximum value."
200  " CNT Number of samples represented by this collector") );
201  dir.add("collect", fty::Command(&Statistics::consoleCollect, this)
202  .doc("Add statistics collection groups. The argument gives a sequence of collector\n"
203  "ranks each building on the preceding collector:\n"
204  "\n"
205  " $ collect (10 60 60)\n"
206  "\n"
207  "Will start by collecting every 10 values together to a new value. 60 of such\n"
208  "combined values will be collected together in the next step again followed by\n"
209  "a collection of 60 values. If the statistics is entered with a frequency of\n"
210  "10 values per second, this will provide combined statistics over the second,\n"
211  "minutes and hours ranges.\n"
212  "\n"
213  "You may call collect multiple times. Any missing collection ranks will be\n"
214  "added.")
215  .arg("ranks","chain of collector ranks") );
216  dir.add("output", fty::Command(&Statistics::consoleOutput, this)
217  .doc("Generate statistics output. This statement will add an additional output\n"
218  "generator. This generator will be attached to the collector specified by\n"
219  "the {rank} parameter. This parameter is a chain of successive rank values\n"
220  "which specifies the exact collector to use. If the collector does not\n"
221  "exist, it will be created (this is like automatically calling 'collect'\n"
222  "with {rank} as argument).\n"
223  "\n"
224  "If the output is to be sent somewhere it must be connected to a statistics\n"
225  "target.\n"
226  "\n"
227  "The output may optionally be built using a sliding average over the last\n"
228  "{window} values.\n"
229  "\n"
230  " $ output ()\n"
231  "\n"
232  "will output the basic statistics value each time a new value is entered.\n"
233  "\n"
234  " $ output (10 60) 5\n"
235  "\n"
236  "Assuming that new data values are entered 10 times per second, this command\n"
237  "will generate output once every minute. The value will be the average over\n"
238  "the last 5 minutes.")
239  .arg("rank","Rank chain selecting the value to generate output for")
240  .arg("window","Optional size of sliding average window",
241  console::kw::default_value = 1u) );
242 #endif
243 }
244 
245 prefix_ void senf::Statistics::consoleList(std::ostream & os)
246  const
247 {
248  os << "RANK WIN MIN AVG MAX CNT\n";
250 }
251 
252 prefix_ void senf::Statistics::consoleCollect(std::vector<unsigned> & ranks)
253 {
254  StatisticsBase * stats (this);
255  std::vector<unsigned>::const_iterator i (ranks.begin());
256  std::vector<unsigned>::const_iterator const i_end (ranks.end());
257 
258  try {
259  for (; i != i_end; ++i)
260  stats = &(*stats)[*i];
261  }
262  catch (InvalidRankException &) {}
263 
264  for (; i != i_end; ++i)
265  stats = & (stats->collect(*i));
266 }
267 
268 prefix_ boost::shared_ptr<senf::console::DirectoryNode>
269 senf::Statistics::consoleOutput(std::vector<unsigned> & ranks, unsigned window)
270 {
271  StatisticsBase * stats (this);
272  std::vector<unsigned>::const_iterator i (ranks.begin());
273  std::vector<unsigned>::const_iterator const i_end (ranks.end());
274 
275  try {
276  for (; i != i_end; ++i)
277  stats = &(*stats)[*i];
278  }
279  catch (InvalidRankException &) {}
280 
281  for (; i != i_end; ++i)
282  stats = & (stats->collect(*i));
283 
284  return stats->output(window).dir().node().thisptr();
285 }
286 
287 prefix_ senf::Statistics & senf::Statistics::v_base()
288 {
289  return *this;
290 }
291 
292 prefix_ std::string senf::Statistics::v_path()
293  const
294 {
295  return "";
296 }
297 
298 //-/////////////////////////////////////////////////////////////////////////////////////////////////
299 // senf::Collector
300 
301 prefix_ void senf::Collector::enter(unsigned n, unsigned cnt, float min, float avg, float max, float dev)
302 {
303  updated_ = false;
304 
305  if (cnt > 0) {
306  if (min < accMin_) accMin_ = min;
307  if (max > accMax_) accMax_ = max;
308  }
309 
310  if ((i_+l_) + n >= rank_) {
311  if (cnt > 0) {
312  accCnt_ += (rank_ - (i_+l_)) * cnt;
313  accSum_ += (rank_ - (i_+l_)) * avg;
314  accSumSq_ += (rank_ - (i_+l_)) * (rank_ - (i_+l_)) * (avg*avg + dev*dev);
315  }
316  float accAvg (accSum_ / (rank_ - l_));
317  float accDev (std::sqrt(std::max(0.0f,accSumSq_ / (rank_ - l_) - accAvg*accAvg)));
318  StatisticsBase::enter(1, accCnt_, accMin_, accAvg, accMax_, accDev);
319  accCnt_ = 0;
320  accMin_ = FLT_MAX;
321  accSum_ = 0.0f;
322  accSumSq_ = 0.0f;
323  accMax_ = -FLT_MAX;
324  n -= (rank_ - (i_+l_));
325  i_ = 0;
326  l_ = 0;
327 
328  if (n >= rank_) {
329  std::div_t d (std::div(int(n), int(rank_)));
330  StatisticsBase::enter(d.quot, cnt, min, avg, max, dev);
331  n = d.rem;
332  }
333  updated_ = true;
334  }
335 
336  if (cnt > 0) {
337  accCnt_ += n * cnt;
338  accSum_ += n * avg;
339  accSumSq_ += n*n * (avg*avg+dev*dev);
340  i_ += n;
341  if (min < accMin_) accMin_ = min;
342  if (max > accMax_) accMax_ = max;
343  } else {
344  // account for 'no valid data'
345  l_ += n;
346  }
347 }
348 
349 prefix_ senf::Statistics & senf::Collector::v_base()
350 {
351  return owner_->base();
352 }
353 
354 prefix_ std::string senf::Collector::v_path()
355  const
356 {
357  return owner_->path() + "-" + senf::str(rank_);
358 }
359 
360 //-/////////////////////////////////////////////////////////////////////////////////////////////////
361 #undef prefix_
362 //#include "Statistics.mpp"
363 
364 
365 // Local Variables:
366 // mode: c++
367 // fill-column: 100
368 // comment-column: 40
369 // c-file-style: "senf"
370 // indent-tabs-mode: nil
371 // ispell-local-dictionary: "american"
372 // compile-command: "scons -u test"
373 // End:
std::string path() const
Get the path to this collector.
std::string str(T const &t)
Get string representation.
OutputProxy< StatisticsBase > output(unsigned n=1u)
Register output.
Definition: Statistics.cc:78
Internal: Generic Statistics collection.
Definition: Statistics.hh:59
Statistics public header.
Format public header.
unsigned cnt() const
Last cnt value entered.
Collector & operator[](unsigned rank)
Get child collector.
Definition: Statistics.cc:51
Collect statistics and generate log messages.
Definition: Statistics.hh:435
streamable_type eng(float v, float d=NAN)
Format value in engineering representation.
NodeType & add(std::string const &name, boost::shared_ptr< NodeType > node)
#define prefix_
Definition: Statistics.cc:30
float avg() const
Last avg value entered.
String public header.
float max() const
Last max value entered.
float dev() const
Last dev value entered.
DirectoryNode & node() const
Statistics const & base() const
Output connection interface.
Definition: Statistics.hh:123
Accumulated statistics collector.
Definition: Statistics.hh:512
console::ScopedDirectory dir
Definition: Statistics.hh:440
void consoleList(unsigned level, std::ostream &os) const
Definition: Statistics.cc:93
void enter(unsigned n, unsigned cnt, float min, float avg, float max, float dev)
Definition: Statistics.cc:36
StatisticsTargets public header.
Collector & collect(unsigned rank)
Register a new collector.
Definition: Statistics.cc:68
virtual unsigned rank() const
Return collectors rank value.
float min() const
Last min value entered.