artdaq_utilities  v1_05_09a
MetricPlugin.hh
1 // MetricPlugin.hh: Metric Plugin Interface
2 // Author: Eric Flumerfelt
3 // Last Modified: 11/05/2014 (Created)
4 //
5 // Defines the interface that any ARTDAQ metric plugin must implement
6 
7 #ifndef __METRIC_INTERFACE__
8 #define __METRIC_INTERFACE__
9 
10 #ifdef TRACE_NAME
11 #pragma push_macro("TRACE_NAME")
12 #undef TRACE_NAME
13 #define TRACE_NAME "MetricPlugin" /* a simple const char * */
14 #define TRACE_NAME_POP 1
15 #endif
16 #include "TRACE/trace.h" // TLOG(x,name)
17 
18 #include <chrono>
19 #include <string>
20 #include <unordered_map>
21 #include "fhiclcpp/ParameterSet.h"
22 #include "fhiclcpp/types/Atom.h"
23 #include "fhiclcpp/types/ConfigurationTable.h"
24 #include "fhiclcpp/types/Sequence.h"
25 
26 #include "artdaq-utilities/Plugins/MetricData.hh"
27 #include "cetlib/compiler_macros.h"
28 #ifndef FALLTHROUGH
29 #define FALLTHROUGH while (0)
30 #endif
31 
32 namespace artdaq {
38 {
39 public:
43  struct Config
44  {
46  fhicl::Atom<std::string> metricPluginType{fhicl::Name{"metricPluginType"}, fhicl::Comment{"The name of the metric plugin to load (may have additional configuration parameters"}};
48  fhicl::Atom<size_t> level{fhicl::Name{"level"}, fhicl::Comment{"The verbosity level threshold for this plugin. sendMetric calls with verbosity level greater than this will not be sent to the plugin. OPTIONAL"}, 0};
50  fhicl::Sequence<size_t> metric_levels{fhicl::Name{"metric_levels"}, fhicl::Comment{"A list of levels that should be enabled for this plugin. OPTIONAL"}, std::vector<size_t>()};
52  fhicl::Atom<std::string> level_string{fhicl::Name{"level_string"}, fhicl::Comment{"A string containing a comma-separated list of levels to enable. Ranges are supported. Example: \"1,2,4-10,11\" OPTIONAL"}, ""};
54  fhicl::Atom<double> reporting_interval{fhicl::Name{"reporting_interval"}, fhicl::Comment{"How often recorded metrics are sent to the underlying metric storage"}, 15.0};
56  fhicl::Atom<bool> send_zeros{fhicl::Name{"send_zeros"}, fhicl::Comment{"Whether zeros should be sent to the metric back-end when metrics are not reported in an interval and during shutdown"}, true};
57  };
59  using Parameters = fhicl::WrappedTable<Config>;
60 
69  explicit MetricPlugin(fhicl::ParameterSet const& ps, std::string const& app_name)
70  : pset(ps)
71  , app_name_(app_name)
72  , inhibit_(false)
73  , level_mask_(0ULL)
74  , sendZeros_(pset.get<bool>("send_zeros", true))
75  {
76  if (pset.has_key("level"))
77  {
78  for (size_t ii = 0; ii <= pset.get<size_t>("level"); ++ii)
79  {
80  level_mask_[ii] = true;
81  }
82  }
83  if (pset.has_key("metric_levels"))
84  {
85  auto levels = pset.get<std::vector<size_t>>("metric_levels");
86  for (auto& l : levels)
87  {
88  level_mask_[l] = true;
89  }
90  }
91  if (pset.has_key("level_string"))
92  {
93  auto string = pset.get<std::string>("level_string");
94  std::stringstream ss(string);
95  std::string token;
96  while (std::getline(ss, token, ','))
97  {
98  auto it = token.find("-");
99  if (it == 0 || it == token.size() - 1) continue;
100 
101  if (it != std::string::npos)
102  {
103  auto minStr = token.substr(0, it);
104  auto maxStr = token.substr(it + 1);
105  auto min = std::stoi(minStr);
106  auto max = std::stoi(maxStr);
107 
108  if (min > max) std::swap(min, max);
109  if (min > 63) min = 63;
110  if (max > 63) max = 63;
111 
112  for (int ii = min; ii <= max; ++ii)
113  {
114  level_mask_[ii] = true;
115  }
116  }
117  else
118  {
119  auto level = std::stoi(token);
120  if (level >= 0 && level < 63) level_mask_[level] = true;
121  }
122  }
123  }
124  if (level_mask_.to_ullong() == 0)
125  {
126  throw cet::exception("Configuration Error") // NOLINT(cert-err60-cpp)
127  << "No levels were enabled for this plugin! Please specify at least one of the following Parameters: \"level\", \"metric_levels\", or \"level_string\"!";
128  }
129  accumulationTime_ = pset.get<double>("reporting_interval", 15.0);
130  }
131 
135  virtual ~MetricPlugin() = default;
136 
138  //
139  // Interface Functions: These should be reimplemented in plugin classes!
140  //
142 
146  virtual std::string getLibName() const { return "ERROR"; }
147 
148 protected:
157  virtual void sendMetric_(const std::string& name, const std::string& value, const std::string& unit) = 0;
158 
167  virtual void sendMetric_(const std::string& name, const int& value, const std::string& unit) = 0;
168 
177  virtual void sendMetric_(const std::string& name, const double& value, const std::string& unit) = 0;
178 
187  virtual void sendMetric_(const std::string& name, const float& value, const std::string& unit) = 0;
188 
197  virtual void sendMetric_(const std::string& name, const uint64_t& value, const std::string& unit) = 0;
198 
204  virtual void startMetrics_() = 0;
205 
211  virtual void stopMetrics_() = 0;
212 
214  //
215  // Implementation Functions: These should be called from ARTDAQ code!
216  //
218 public:
223  void addMetricData(std::unique_ptr<MetricData> const& data)
224  {
225  TLOG(22) << "Adding metric data for name " << data->Name;
226  if (data->Type == MetricType::StringMetric)
227  {
228  sendMetric_(data->Name, data->StringValue, data->Unit);
229  }
230  else
231  {
232  if (metricRegistry_.count(data->Name) == 0)
233  {
234  metricRegistry_[data->Name] = *data;
235  }
236  metricData_[data->Name].push_back(*data);
237  TLOG(22) << "Current list size: " << metricData_[data->Name].size();
238  //sendMetrics();
239  }
240  }
241 
250  void sendMetrics(bool forceSend = false,
251  std::chrono::steady_clock::time_point interval_end = std::chrono::steady_clock::now())
252  {
253  TLOG(23) << "sendMetrics called" << std::endl;
254  for (auto& metric : metricData_)
255  {
256  if (readyToSend_(metric.first) || forceSend)
257  {
258  TLOG(24) << "Sending metric " << metric.first;
259  if (metric.second.empty() && metricRegistry_.count(metric.first))
260  {
261  TLOG(24) << "Sending zero";
262  sendZero_(metricRegistry_[metric.first]);
263  }
264  else if (!metric.second.empty())
265  {
266  TLOG(24) << "Aggregating " << metric.second.size() << " MetricData points";
267 
268  if ((metric.second.front().Mode & MetricMode::Persist) != MetricMode::None && metric.second.size() > 1)
269  {
270  TLOG(24) << "Metric is in Persist mode and multiple instances are present. Removing the first entry.";
271  metric.second.erase(metric.second.begin());
272  }
273 
274  MetricData& data = metric.second.front();
275 
276  auto it = ++(metric.second.begin());
277  while (it != metric.second.end())
278  {
279  data.Add(*it);
280  it = metric.second.erase(it);
281  }
282 
283  std::bitset<32> modeSet(static_cast<uint32_t>(data.Mode));
284  bool useSuffix = true;
285  if (modeSet.count() <= 1 || (modeSet.count() <= 2 && (data.Mode & MetricMode::Persist) != MetricMode::None)) useSuffix = false;
286 
287  if ((data.Mode & MetricMode::LastPoint) != MetricMode::None)
288  {
289  sendMetric_(data.Name + (useSuffix ? " - Last" : ""), data.Last, data.Unit, data.Type);
290  }
291  if ((data.Mode & MetricMode::Accumulate) != MetricMode::None)
292  {
293  sendMetric_(data.Name + (useSuffix ? " - Total" : ""), data.Value, data.Unit, data.Type);
294  }
295  if ((data.Mode & MetricMode::Average) != MetricMode::None)
296  {
297  double average = 0.0;
298  switch (data.Type)
299  {
301  average = data.Value.d / static_cast<double>(data.DataPointCount);
302  break;
304  average = data.Value.f / static_cast<double>(data.DataPointCount);
305  break;
307  average = data.Value.i / static_cast<double>(data.DataPointCount);
308  break;
310  average = data.Value.u / static_cast<double>(data.DataPointCount);
311  break;
312  default:
313  break;
314  }
315  sendMetric_(data.Name + (useSuffix ? " - Average" : ""), average, data.Unit);
316  }
317  if ((data.Mode & MetricMode::Rate) != MetricMode::None)
318  {
319  double duration = std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1>>>(
320  interval_end - interval_start_[metric.first])
321  .count();
322  double rate = 0.0;
323  switch (data.Type)
324  {
326  rate = data.Value.d / duration;
327  break;
329  rate = data.Value.f / duration;
330  break;
332  rate = data.Value.i / duration;
333  break;
335  rate = data.Value.u / duration;
336  break;
337  default:
338  break;
339  }
340  sendMetric_(data.Name + (useSuffix ? " - Rate" : ""), rate, data.Unit + "/s");
341  }
342  if ((data.Mode & MetricMode::Minimum) != MetricMode::None)
343  {
344  sendMetric_(data.Name + (useSuffix ? " - Min" : ""), data.Min, data.Unit, data.Type);
345  }
346  if ((data.Mode & MetricMode::Maximum) != MetricMode::None)
347  {
348  sendMetric_(data.Name + (useSuffix ? " - Max" : ""), data.Max, data.Unit, data.Type);
349  }
350 
351  if ((data.Mode & MetricMode::Persist) == MetricMode::None)
352  {
353  TLOG(24) << "Clearing metric data list sz=" << metric.second.size();
354  metric.second.clear();
355  TLOG(24) << "Cleared metric data list sz=" << metricData_[metric.first].size();
356  }
357  else
358  {
359  TLOG(24) << "Metric is Persisted, leaving " << metricData_[metric.first].size() << " entries (should be 1)";
360  }
361  }
362  interval_start_[metric.first] = interval_end;
363  }
364  }
365  TLOG(23) << "sendMetrics done" << std::endl;
366  }
367 
372 
377  void stopMetrics()
378  {
379  inhibit_ = true;
380  sendMetrics(true);
381  for (auto const& metric : metricRegistry_)
382  {
383  sendZero_(metric.second);
384  }
385  stopMetrics_();
386  inhibit_ = false;
387  }
388 
395  {
396  if (level > 63) level = 63;
397  if (level < 0) return true;
398  return level_mask_[level];
399  }
400 
406  {
407  for (auto& metric : metricData_)
408  {
409  if (!metric.second.empty())
410  {
411  TLOG(TLVL_TRACE) << "Metric " << metric.first << " has " << metric.second.size() << " pending MetricData instances" << std::endl;
412  return true;
413  }
414  }
415 
416  return false;
417  }
418 
419 protected:
420  fhicl::ParameterSet pset;
422  std::string app_name_;
423  bool inhibit_;
424  std::bitset<64> level_mask_;
425  bool sendZeros_;
426 
427 private:
428  MetricPlugin(const MetricPlugin&) = delete;
429  MetricPlugin(MetricPlugin&&) = delete;
430  MetricPlugin& operator=(const MetricPlugin&) = delete;
431  MetricPlugin& operator=(MetricPlugin&&) = delete;
432 
433  std::unordered_map<std::string, std::list<MetricData>> metricData_;
434  std::unordered_map<std::string, MetricData> metricRegistry_;
435  std::unordered_map<std::string, std::chrono::steady_clock::time_point> lastSendTime_;
436  std::unordered_map<std::string, std::chrono::steady_clock::time_point> interval_start_;
437 
438  bool readyToSend_(std::string const& name)
439  {
440  auto now = std::chrono::steady_clock::now();
441  if (std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1>>>(now - lastSendTime_[name]).count() >= accumulationTime_)
442  {
443  lastSendTime_[name] = now;
444  return true;
445  }
446 
447  return false;
448  }
449 
450  void sendZero_(MetricData data)
451  {
452  if (sendZeros_)
453  {
454  std::bitset<32> modeSet(static_cast<uint32_t>(data.Mode));
455  bool useSuffix = true;
456  if (modeSet.count() <= 1) useSuffix = false;
457 
459  switch (data.Type)
460  {
462  zero.d = 0.0;
463  break;
465  zero.f = 0.0f;
466  break;
468  zero.i = 0;
469  break;
471  zero.u = 0;
472  break;
473  default:
474  break;
475  }
476 
477  if ((data.Mode & MetricMode::LastPoint) != MetricMode::None)
478  {
479  sendMetric_(data.Name + (useSuffix ? " - Last" : ""), zero, data.Unit, data.Type);
480  }
481  if ((data.Mode & MetricMode::Accumulate) != MetricMode::None)
482  {
483  sendMetric_(data.Name + (useSuffix ? " - Total" : ""), zero, data.Unit, data.Type);
484  }
485  if ((data.Mode & MetricMode::Average) != MetricMode::None)
486  {
487  sendMetric_(data.Name + (useSuffix ? " - Average" : ""), 0.0, data.Unit);
488  }
489  if ((data.Mode & MetricMode::Rate) != MetricMode::None)
490  {
491  sendMetric_(data.Name + (useSuffix ? " - Rate" : ""), 0.0, data.Unit + "/s");
492  }
493  if ((data.Mode & MetricMode::Minimum) != MetricMode::None)
494  {
495  sendMetric_(data.Name + (useSuffix ? " - Min" : ""), zero, data.Unit, data.Type);
496  }
497  if ((data.Mode & MetricMode::Maximum) != MetricMode::None)
498  {
499  sendMetric_(data.Name + (useSuffix ? " - Max" : ""), zero, data.Unit, data.Type);
500  }
501  }
502  }
503 
504  void sendMetric_(std::string const& name, MetricData::MetricDataValue data, std::string const& unit, MetricType type)
505  {
506  switch (type)
507  {
509  sendMetric_(name, data.d, unit);
510  break;
512  sendMetric_(name, data.f, unit);
513  break;
515  sendMetric_(name, data.i, unit);
516  break;
518  sendMetric_(name, data.u, unit);
519  break;
520  default:
521  break;
522  }
523  }
524 };
525 } //End namespace artdaq
526 
527 #ifdef TRACE_NAME_POP
528 #pragma pop_macro("TRACE_NAME")
529 #undef TRACE_NAME_POP
530 #endif
531 #endif //End ifndef __METRIC_INTERFACE__
bool IsLevelEnabled(int level)
Determine if the given level is enabled for this MetricPlugin instance.
virtual void startMetrics_()=0
Perform any start-up actions necessary for the metric plugin.
The MetricPlugin class defines the interface that MetricManager uses to send metric data to the vario...
Definition: MetricPlugin.hh:37
void startMetrics()
Perform startup actions. Simply calls the virtual startMetrics_ function.
size_t DataPointCount
Number of data points accumulated in this MetricData
Definition: MetricData.hh:178
Report the sum of all values. Use for counters to report accurate results.
fhicl::ParameterSet pset
The ParameterSet used to configure the MetricPlugin.
fhicl::Atom< std::string > metricPluginType
The name of the metric plugin to load (may have additional configuration parameters.
Definition: MetricPlugin.hh:46
virtual void sendMetric_(const std::string &name, const std::string &value, const std::string &unit)=0
Send a metric to the underlying metric storage (file, Graphite, Ganglia, etc.)
std::string Unit
Units of the metric
Definition: MetricData.hh:158
float f
Value of the metric, if it is a MetricType::FloatMetric.
Definition: MetricData.hh:109
Metric is a std::string (not in union)
std::string Name
Name of the metric
Definition: MetricData.hh:96
fhicl::Atom< double > reporting_interval
&quot;reporting_interval&quot; (Default: 15.0): The interval, in seconds, which the metric plugin will accumula...
Definition: MetricPlugin.hh:54
MetricMode Mode
Accumulation mode of the metric
Definition: MetricData.hh:166
bool Add(MetricData other)
Add two MetricData instances together
Definition: MetricData.hh:261
MetricType
This enumeration is used to identify the type of the metric instance (which value should be extraced ...
Definition: MetricData.hh:16
MetricDataValue Max
Maximum recorded vaule of this MetricData.
Definition: MetricData.hh:149
void stopMetrics()
Perform shutdown actions. Zeroes out all accumulators, and sends zeros for each metric. Calls stopMetrics_() for any plugin-defined shutdown actions.
Reports the minimum value recorded.
std::string app_name_
Name of the application which is sending metrics to this plugin.
Metric is a long unsigned int.
void sendMetrics(bool forceSend=false, std::chrono::steady_clock::time_point interval_end=std::chrono::steady_clock::now())
For each known metric, determine whether the reporting interval has elapsed, and if so...
over. Use to create rates from counters.
MetricPlugin(fhicl::ParameterSet const &ps, std::string const &app_name)
MetricPlugin Constructor.
Definition: MetricPlugin.hh:69
Report only the last value recorded. Useful for event counters, run numbers, etc. ...
Repots the maximum value recorded.
std::bitset< 64 > level_mask_
Bitset indicating for each possible metric level, whether this plugin will receive those metrics...
bool metricsPending()
Determine if metrics are waiting to be sent.
virtual std::string getLibName() const
Return the name of the current MetricPlugin instance.
MetricDataValue Value
Accumulated value of this MetricData
Definition: MetricData.hh:146
virtual void stopMetrics_()=0
Perform any shutdown actions necessary for the metric plugin.
int i
Value of the metric, if it is a MetricType::IntMetric.
Definition: MetricData.hh:107
Keep previous metric value in memory.
MetricType Type
Type of the metric
Definition: MetricData.hh:154
fhicl::Atom< std::string > level_string
&quot;level_string&quot; (OPTIONAL): A string containing a comma-separated list of levels to enable...
Definition: MetricPlugin.hh:52
fhicl::Atom< bool > send_zeros
&quot;send_zeros&quot; (Default: true): Whether zeros should be sent to the metric back-end when metrics are no...
Definition: MetricPlugin.hh:56
fhicl::WrappedTable< Config > Parameters
Used for ParameterSet validation (if desired)
Definition: MetricPlugin.hh:59
This union holds the values for all other metric types
Definition: MetricData.hh:105
MetricDataValue Last
Last value of this MetricData.
Definition: MetricData.hh:147
fhicl::Atom< size_t > level
&quot;level&quot; (OPTIONAL): The verbosity level threshold for this plugin. sendMetric calls with verbosity le...
Definition: MetricPlugin.hh:48
double accumulationTime_
The amount of time to average metric values; except for accumulate=false metrics, will be the interva...
Small structure used to hold a metric data point before sending to the metric plugins ...
Definition: MetricData.hh:65
The Config struct defines the accepted configuration parameters for this class.
Definition: MetricPlugin.hh:43
uint64_t u
Value of the metric, if it is a MetricType::UnsignedMetric.
Definition: MetricData.hh:110
Report the average of all values. Use for rates to report accurate results.
MetricDataValue Min
Minimum recorded value of this MetricData.
Definition: MetricData.hh:148
fhicl::Sequence< size_t > metric_levels
&quot;metric_levels&quot; (OPTIONAL): A list of levels that should be enabled for this plugin.
Definition: MetricPlugin.hh:50
void addMetricData(std::unique_ptr< MetricData > const &data)
Send a metric value to the MetricPlugin.
bool sendZeros_
Whether zeros should be sent to this metric backend when metric instances are missing or at the end o...
virtual ~MetricPlugin()=default
Default virtual Desctructor.
double d
Value of the metric, if it is a MetricType::DoubleMetric.
Definition: MetricData.hh:108
bool inhibit_
Flag to indicate that the MetricPlugin is being stopped, and any metric back-ends which do not have a...