xref: /openbmc/telemetry/src/report.cpp (revision 583ba441654657bb4ba9d051b747144a7258c159)
1  #include "report.hpp"
2  
3  #include "errors.hpp"
4  #include "messages/collect_trigger_id.hpp"
5  #include "messages/trigger_presence_changed_ind.hpp"
6  #include "messages/update_report_ind.hpp"
7  #include "report_manager.hpp"
8  #include "utils/clock.hpp"
9  #include "utils/contains.hpp"
10  #include "utils/dbus_path_utils.hpp"
11  #include "utils/ensure.hpp"
12  #include "utils/transform.hpp"
13  
14  #include <phosphor-logging/log.hpp>
15  #include <sdbusplus/vtable.hpp>
16  
17  #include <limits>
18  #include <numeric>
19  #include <optional>
20  
Report(boost::asio::io_context & ioc,const std::shared_ptr<sdbusplus::asio::object_server> & objServer,const std::string & reportId,const std::string & reportName,const ReportingType reportingTypeIn,std::vector<ReportAction> reportActionsIn,const Milliseconds intervalIn,const uint64_t appendLimitIn,const ReportUpdates reportUpdatesIn,interfaces::ReportManager & reportManager,interfaces::JsonStorage & reportStorageIn,std::vector<std::shared_ptr<interfaces::Metric>> metricsIn,const interfaces::ReportFactory & reportFactory,const bool enabledIn,std::unique_ptr<interfaces::Clock> clock,Readings readingsIn)21  Report::Report(
22      boost::asio::io_context& ioc,
23      const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
24      const std::string& reportId, const std::string& reportName,
25      const ReportingType reportingTypeIn,
26      std::vector<ReportAction> reportActionsIn, const Milliseconds intervalIn,
27      const uint64_t appendLimitIn, const ReportUpdates reportUpdatesIn,
28      interfaces::ReportManager& reportManager,
29      interfaces::JsonStorage& reportStorageIn,
30      std::vector<std::shared_ptr<interfaces::Metric>> metricsIn,
31      const interfaces::ReportFactory& reportFactory, const bool enabledIn,
32      std::unique_ptr<interfaces::Clock> clock, Readings readingsIn) :
33      id(reportId), path(utils::pathAppend(utils::constants::reportDirPath, id)),
34      name(reportName), reportingType(reportingTypeIn), interval(intervalIn),
35      reportActions(reportActionsIn.begin(), reportActionsIn.end()),
36      metricCount(getMetricCount(metricsIn)), appendLimit(appendLimitIn),
37      reportUpdates(reportUpdatesIn), readings(std::move(readingsIn)),
38      readingsBuffer(std::get<1>(readings),
39                     deduceBufferSize(reportUpdates, reportingType)),
40      objServer(objServer), metrics(std::move(metricsIn)), timer(ioc),
41      triggerIds(collectTriggerIds(ioc)), reportStorage(reportStorageIn),
42      clock(std::move(clock)), messanger(ioc)
43  {
44      readingParameters =
45          toReadingParameters(utils::transform(metrics, [](const auto& metric) {
46              return metric->dumpConfiguration();
47          }));
48  
49      reportActions.insert(ReportAction::logToMetricReportsCollection);
50  
51      deleteIface = objServer->add_unique_interface(
52          getPath(), deleteIfaceName,
53          [this, &ioc, &reportManager](auto& dbusIface) {
54              dbusIface.register_method("Delete", [this, &ioc, &reportManager] {
55                  if (persistency)
56                  {
57                      persistency = false;
58  
59                      reportIface->signal_property("Persistency");
60                  }
61  
62                  boost::asio::post(ioc, [this, &reportManager] {
63                      reportManager.removeReport(this);
64                  });
65              });
66          });
67  
68      auto errorMessages = verify(reportingType, interval);
69      state.set<ReportFlags::enabled, ReportFlags::valid>(enabledIn,
70                                                          errorMessages.empty());
71  
72      reportIface = makeReportInterface(reportFactory);
73      persistency = storeConfiguration();
74  
75      messanger.on_receive<messages::TriggerPresenceChangedInd>(
76          [this](const auto& msg) {
77              const auto oldSize = triggerIds.size();
78  
79              if (msg.presence == messages::Presence::Exist)
80              {
81                  if (utils::contains(msg.reportIds, id))
82                  {
83                      triggerIds.insert(msg.triggerId);
84                  }
85                  else if (!utils::contains(msg.reportIds, id))
86                  {
87                      triggerIds.erase(msg.triggerId);
88                  }
89              }
90              else if (msg.presence == messages::Presence::Removed)
91              {
92                  triggerIds.erase(msg.triggerId);
93              }
94  
95              if (triggerIds.size() != oldSize)
96              {
97                  reportIface->signal_property("Triggers");
98              }
99          });
100  
101      messanger.on_receive<messages::UpdateReportInd>([this](const auto& msg) {
102          if (utils::contains(msg.reportIds, id))
103          {
104              updateReadings();
105          }
106      });
107  }
108  
~Report()109  Report::~Report()
110  {
111      if (persistency)
112      {
113          if (shouldStoreMetricValues())
114          {
115              storeConfiguration();
116          }
117      }
118      else
119      {
120          reportStorage.remove(reportFileName());
121      }
122  }
123  
activate()124  void Report::activate()
125  {
126      for (auto& metric : metrics)
127      {
128          metric->initialize();
129      }
130  
131      scheduleTimer();
132  }
133  
deactivate()134  void Report::deactivate()
135  {
136      for (auto& metric : metrics)
137      {
138          metric->deinitialize();
139      }
140  
141      unregisterFromMetrics = nullptr;
142      timer.cancel();
143  }
144  
getMetricCount(const std::vector<std::shared_ptr<interfaces::Metric>> & metrics)145  uint64_t Report::getMetricCount(
146      const std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
147  {
148      uint64_t metricCount = 0;
149      for (auto& metric : metrics)
150      {
151          metricCount += metric->metricCount();
152      }
153      return metricCount;
154  }
155  
deduceBufferSize(const ReportUpdates reportUpdatesIn,const ReportingType reportingTypeIn) const156  uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn,
157                                    const ReportingType reportingTypeIn) const
158  {
159      if (reportUpdatesIn == ReportUpdates::overwrite ||
160          reportingTypeIn == ReportingType::onRequest)
161      {
162          return metricCount;
163      }
164      else
165      {
166          return appendLimit;
167      }
168  }
169  
setReadingBuffer(const ReportUpdates newReportUpdates)170  void Report::setReadingBuffer(const ReportUpdates newReportUpdates)
171  {
172      const auto newBufferSize =
173          deduceBufferSize(newReportUpdates, reportingType);
174      if (readingsBuffer.size() != newBufferSize)
175      {
176          readingsBuffer.clearAndResize(newBufferSize);
177      }
178  }
179  
setReportUpdates(const ReportUpdates newReportUpdates)180  void Report::setReportUpdates(const ReportUpdates newReportUpdates)
181  {
182      if (reportUpdates != newReportUpdates)
183      {
184          setReadingBuffer(newReportUpdates);
185          reportUpdates = newReportUpdates;
186      }
187  }
188  
makeReportInterface(const interfaces::ReportFactory & reportFactory)189  std::unique_ptr<sdbusplus::asio::dbus_interface> Report::makeReportInterface(
190      const interfaces::ReportFactory& reportFactory)
191  {
192      auto dbusIface =
193          objServer->add_unique_interface(getPath(), reportIfaceName);
194      dbusIface->register_property_rw<bool>(
195          "Enabled", sdbusplus::vtable::property_::emits_change,
196          [this](bool newVal, auto& oldValue) {
197              if (newVal != state.get<ReportFlags::enabled>())
198              {
199                  state.set<ReportFlags::enabled>(oldValue = newVal);
200  
201                  persistency = storeConfiguration();
202              }
203              return 1;
204          },
205          [this](const auto&) { return state.get<ReportFlags::enabled>(); });
206      dbusIface->register_method(
207          "SetReportingProperties",
208          [this](std::string newReportingType, uint64_t newInterval) {
209              ReportingType newReportingTypeT = reportingType;
210  
211              if (!newReportingType.empty())
212              {
213                  newReportingTypeT = utils::toReportingType(newReportingType);
214              }
215  
216              Milliseconds newIntervalT = interval;
217  
218              if (newInterval != std::numeric_limits<uint64_t>::max())
219              {
220                  newIntervalT = Milliseconds(newInterval);
221              }
222  
223              auto errorMessages = verify(newReportingTypeT, newIntervalT);
224  
225              if (!errorMessages.empty())
226              {
227                  if (newIntervalT != interval)
228                  {
229                      throw errors::InvalidArgument("Interval");
230                  }
231  
232                  throw errors::InvalidArgument("ReportingType");
233              }
234  
235              if (reportingType != newReportingTypeT)
236              {
237                  reportingType = newReportingTypeT;
238                  reportIface->signal_property("ReportingType");
239              }
240  
241              if (interval != newIntervalT)
242              {
243                  interval = newIntervalT;
244                  reportIface->signal_property("Interval");
245              }
246  
247              if (state.set<ReportFlags::valid>(errorMessages.empty()) ==
248                  StateEvent::active)
249              {
250                  scheduleTimer();
251              }
252  
253              persistency = storeConfiguration();
254  
255              setReadingBuffer(reportUpdates);
256          });
257      dbusIface->register_property_r<uint64_t>(
258          "Interval", sdbusplus::vtable::property_::emits_change,
259          [this](const auto&) { return interval.count(); });
260      dbusIface->register_property_rw<bool>(
261          "Persistency", sdbusplus::vtable::property_::emits_change,
262          [this](bool newVal, auto& oldVal) {
263              if (newVal == persistency)
264              {
265                  return 1;
266              }
267              if (newVal)
268              {
269                  persistency = oldVal = storeConfiguration();
270              }
271              else
272              {
273                  reportStorage.remove(reportFileName());
274                  persistency = oldVal = false;
275              }
276              return 1;
277          },
278          [this](const auto&) { return persistency; });
279  
280      dbusIface->register_property_r("Readings", readings,
281                                     sdbusplus::vtable::property_::emits_change,
282                                     [this](const auto&) { return readings; });
283      dbusIface->register_property_r<std::string>(
284          "ReportingType", sdbusplus::vtable::property_::emits_change,
285          [this](const auto&) { return utils::enumToString(reportingType); });
286      dbusIface->register_property_rw(
287          "ReadingParameters", readingParameters,
288          sdbusplus::vtable::property_::emits_change,
289          [this, &reportFactory](auto newVal, auto& oldVal) {
290              auto labeledMetricParams =
291                  reportFactory.convertMetricParams(newVal);
292              ReportManager::verifyMetricParams(labeledMetricParams);
293              reportFactory.updateMetrics(metrics,
294                                          state.get<ReportFlags::enabled>(),
295                                          labeledMetricParams);
296              readingParameters = toReadingParameters(
297                  utils::transform(metrics, [](const auto& metric) {
298                      return metric->dumpConfiguration();
299                  }));
300              metricCount = getMetricCount(metrics);
301              setReadingBuffer(reportUpdates);
302              persistency = storeConfiguration();
303              oldVal = std::move(newVal);
304              return 1;
305          },
306          [this](const auto&) { return readingParameters; });
307      dbusIface->register_property_r<bool>(
308          "EmitsReadingsUpdate", sdbusplus::vtable::property_::none,
309          [this](const auto&) {
310              return reportActions.contains(ReportAction::emitsReadingsUpdate);
311          });
312      dbusIface->register_property_r<std::string>(
313          "Name", sdbusplus::vtable::property_::const_,
314          [this](const auto&) { return name; });
315      dbusIface->register_property_r<bool>(
316          "LogToMetricReportsCollection", sdbusplus::vtable::property_::const_,
317          [this](const auto&) {
318              return reportActions.contains(
319                  ReportAction::logToMetricReportsCollection);
320          });
321      dbusIface->register_property_rw<std::vector<std::string>>(
322          "ReportActions", sdbusplus::vtable::property_::emits_change,
323          [this](auto newVal, auto& oldVal) {
324              auto tmp = utils::transform<std::unordered_set>(
325                  newVal, [](const auto& reportAction) {
326                      return utils::toReportAction(reportAction);
327                  });
328              tmp.insert(ReportAction::logToMetricReportsCollection);
329  
330              if (tmp != reportActions)
331              {
332                  reportActions = tmp;
333                  persistency = storeConfiguration();
334                  oldVal = std::move(newVal);
335              }
336              return 1;
337          },
338          [this](const auto&) {
339              return utils::transform<std::vector>(
340                  reportActions, [](const auto reportAction) {
341                      return utils::enumToString(reportAction);
342                  });
343          });
344      dbusIface->register_property_r<uint64_t>(
345          "AppendLimit", sdbusplus::vtable::property_::emits_change,
346          [this](const auto&) { return appendLimit; });
347      dbusIface->register_property_rw(
348          "ReportUpdates", std::string(),
349          sdbusplus::vtable::property_::emits_change,
350          [this](auto newVal, auto& oldVal) {
351              setReportUpdates(utils::toReportUpdates(newVal));
352              oldVal = newVal;
353              return 1;
354          },
355          [this](const auto&) { return utils::enumToString(reportUpdates); });
356      dbusIface->register_property_r(
357          "Triggers", std::vector<sdbusplus::message::object_path>{},
358          sdbusplus::vtable::property_::emits_change, [this](const auto&) {
359              return utils::transform<std::vector>(
360                  triggerIds, [](const auto& triggerId) {
361                      return utils::pathAppend(utils::constants::triggerDirPath,
362                                               triggerId);
363                  });
364          });
365      dbusIface->register_method("Update", [this] {
366          if (reportingType == ReportingType::onRequest)
367          {
368              updateReadings();
369          }
370      });
371      constexpr bool skipPropertiesChangedSignal = true;
372      dbusIface->initialize(skipPropertiesChangedSignal);
373      return dbusIface;
374  }
375  
timerProcForPeriodicReport(boost::system::error_code ec,Report & self)376  void Report::timerProcForPeriodicReport(boost::system::error_code ec,
377                                          Report& self)
378  {
379      if (ec)
380      {
381          return;
382      }
383  
384      self.updateReadings();
385      self.scheduleTimerForPeriodicReport(self.interval);
386  }
387  
timerProcForOnChangeReport(boost::system::error_code ec,Report & self)388  void Report::timerProcForOnChangeReport(boost::system::error_code ec,
389                                          Report& self)
390  {
391      if (ec)
392      {
393          return;
394      }
395  
396      const auto ensure =
397          utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }};
398  
399      self.onChangeContext.emplace(self);
400  
401      const auto steadyTimestamp = self.clock->steadyTimestamp();
402  
403      for (auto& metric : self.metrics)
404      {
405          metric->updateReadings(steadyTimestamp);
406      }
407  
408      self.scheduleTimerForOnChangeReport();
409  }
410  
scheduleTimerForPeriodicReport(Milliseconds timerInterval)411  void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval)
412  {
413      try
414      {
415          timer.expires_after(timerInterval);
416          timer.async_wait([this](boost::system::error_code ec) {
417              timerProcForPeriodicReport(ec, *this);
418          });
419      }
420      catch (const boost::system::system_error& exception)
421      {
422          phosphor::logging::log<phosphor::logging::level::ERR>(
423              "Failed to schedule timer for periodic report: ",
424              phosphor::logging::entry("EXCEPTION_MSG=%s", exception.what()));
425      }
426  }
427  
scheduleTimerForOnChangeReport()428  void Report::scheduleTimerForOnChangeReport()
429  {
430      constexpr Milliseconds timerInterval{100};
431  
432      timer.expires_after(timerInterval);
433      timer.async_wait([this](boost::system::error_code ec) {
434          timerProcForOnChangeReport(ec, *this);
435      });
436  }
437  
updateReadings()438  void Report::updateReadings()
439  {
440      if (!state.isActive())
441      {
442          return;
443      }
444  
445      if (reportUpdates == ReportUpdates::overwrite ||
446          reportingType == ReportingType::onRequest)
447      {
448          readingsBuffer.clear();
449      }
450  
451      for (const auto& metric : metrics)
452      {
453          if (!state.isActive())
454          {
455              break;
456          }
457  
458          for (const auto& [metadata, value, timestamp] :
459               metric->getUpdatedReadings())
460          {
461              if (reportUpdates == ReportUpdates::appendStopsWhenFull &&
462                  readingsBuffer.isFull())
463              {
464                  state.set<ReportFlags::enabled>(false);
465                  reportIface->signal_property("Enabled");
466                  break;
467              }
468              readingsBuffer.emplace(metadata, value, timestamp);
469          }
470      }
471  
472      std::get<0>(readings) =
473          std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp())
474              .count();
475  
476      if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
477      {
478          reportIface->signal_property("Readings");
479      }
480  }
481  
shouldStoreMetricValues() const482  bool Report::shouldStoreMetricValues() const
483  {
484      return reportingType != ReportingType::onRequest &&
485             reportUpdates == ReportUpdates::appendStopsWhenFull;
486  }
487  
storeConfiguration() const488  bool Report::storeConfiguration() const
489  {
490      try
491      {
492          nlohmann::json data;
493  
494          data["Enabled"] = state.get<ReportFlags::enabled>();
495          data["Version"] = reportVersion;
496          data["Id"] = id;
497          data["Name"] = name;
498          data["ReportingType"] = utils::toUnderlying(reportingType);
499          data["ReportActions"] =
500              utils::transform(reportActions, [](const auto reportAction) {
501                  return utils::toUnderlying(reportAction);
502              });
503          data["Interval"] = interval.count();
504          data["AppendLimit"] = appendLimit;
505          data["ReportUpdates"] = utils::toUnderlying(reportUpdates);
506          data["ReadingParameters"] =
507              utils::transform(metrics, [](const auto& metric) {
508                  return metric->dumpConfiguration();
509              });
510  
511          if (shouldStoreMetricValues())
512          {
513              data["MetricValues"] = utils::toLabeledReadings(readings);
514          }
515  
516          reportStorage.store(reportFileName(), data);
517      }
518      catch (const std::exception& e)
519      {
520          phosphor::logging::log<phosphor::logging::level::ERR>(
521              "Failed to store a report in storage",
522              phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
523          return false;
524      }
525  
526      return true;
527  }
528  
reportFileName() const529  interfaces::JsonStorage::FilePath Report::reportFileName() const
530  {
531      return interfaces::JsonStorage::FilePath{
532          std::to_string(std::hash<std::string>{}(id))};
533  }
534  
collectTriggerIds(boost::asio::io_context & ioc) const535  std::unordered_set<std::string> Report::collectTriggerIds(
536      boost::asio::io_context& ioc) const
537  {
538      utils::Messanger tmp(ioc);
539  
540      auto result = std::unordered_set<std::string>();
541  
542      tmp.on_receive<messages::CollectTriggerIdResp>([&result](const auto& msg) {
543          result.insert(msg.triggerId);
544      });
545  
546      tmp.send(messages::CollectTriggerIdReq{id});
547  
548      return result;
549  }
550  
metricUpdated()551  void Report::metricUpdated()
552  {
553      if (onChangeContext)
554      {
555          onChangeContext->metricUpdated();
556          return;
557      }
558  
559      updateReadings();
560  }
561  
scheduleTimer()562  void Report::scheduleTimer()
563  {
564      switch (reportingType)
565      {
566          case ReportingType::periodic:
567          {
568              unregisterFromMetrics = nullptr;
569              scheduleTimerForPeriodicReport(interval);
570              break;
571          }
572          case ReportingType::onChange:
573          {
574              if (!unregisterFromMetrics)
575              {
576                  unregisterFromMetrics = [this] {
577                      for (auto& metric : metrics)
578                      {
579                          metric->unregisterFromUpdates(*this);
580                      }
581                  };
582  
583                  for (auto& metric : metrics)
584                  {
585                      metric->registerForUpdates(*this);
586                  }
587              }
588  
589              bool isTimerRequired = false;
590  
591              for (auto& metric : metrics)
592              {
593                  if (metric->isTimerRequired())
594                  {
595                      isTimerRequired = true;
596                  }
597              }
598  
599              if (isTimerRequired)
600              {
601                  scheduleTimerForOnChangeReport();
602              }
603              else
604              {
605                  timer.cancel();
606              }
607              break;
608          }
609          default:
610              unregisterFromMetrics = nullptr;
611              timer.cancel();
612              break;
613      }
614  }
615  
verify(ReportingType reportingType,Milliseconds interval)616  std::vector<ErrorMessage> Report::verify(ReportingType reportingType,
617                                           Milliseconds interval)
618  {
619      if (interval != Milliseconds{0} && interval < ReportManager::minInterval)
620      {
621          throw errors::InvalidArgument("Interval");
622      }
623  
624      std::vector<ErrorMessage> result;
625  
626      if ((reportingType == ReportingType::periodic &&
627           interval == Milliseconds{0}) ||
628          (reportingType != ReportingType::periodic &&
629           interval != Milliseconds{0}))
630      {
631          result.emplace_back(ErrorType::propertyConflict, "Interval");
632          result.emplace_back(ErrorType::propertyConflict, "ReportingType");
633      }
634  
635      return result;
636  }
637