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