xref: /openbmc/telemetry/src/report.cpp (revision daeddc7c5587933729a7a1e981b054c4c8be8069)
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     const auto newBufferSize =
208         deduceBufferSize(newReportUpdates, reportingType);
209     if (readingsBuffer.size() != newBufferSize)
210     {
211         readingsBuffer.clearAndResize(newBufferSize);
212     }
213 }
214 
215 void Report::setReportUpdates(const ReportUpdates newReportUpdates)
216 {
217     if (reportUpdates != newReportUpdates)
218     {
219         setReadingBuffer(newReportUpdates);
220         reportUpdates = newReportUpdates;
221     }
222 }
223 
224 void Report::updateSensorCount(const uint64_t newSensorCount)
225 {
226     if (sensorCount != newSensorCount)
227     {
228         sensorCount = newSensorCount;
229         if (!appendLimit.has_value())
230         {
231             reportIface->signal_property("AppendLimit");
232         }
233     }
234 }
235 
236 std::unique_ptr<sdbusplus::asio::dbus_interface>
237     Report::makeReportInterface(const interfaces::ReportFactory& reportFactory)
238 {
239     auto dbusIface =
240         objServer->add_unique_interface(getPath(), reportIfaceName);
241     dbusIface->register_property_rw<bool>(
242         "Enabled", sdbusplus::vtable::property_::emits_change,
243         [this](bool newVal, auto& oldValue) {
244             if (newVal != state.get<ReportFlags::enabled>())
245             {
246                 state.set<ReportFlags::enabled>(oldValue = newVal);
247 
248                 persistency = storeConfiguration();
249             }
250             return 1;
251         },
252         [this](const auto&) { return state.get<ReportFlags::enabled>(); });
253     dbusIface->register_property_r<
254         std::vector<std::tuple<std::string, std::string>>>(
255         "ErrorMessages", sdbusplus::vtable::property_::emits_change,
256         [this](const auto&) {
257             return utils::transform(errorMessages, [](const auto& em) {
258                 return std::tuple<std::string, std::string>(
259                     utils::enumToString(em.error), em.arg0);
260             });
261         });
262     dbusIface->register_property_rw<uint64_t>(
263         "Interval", sdbusplus::vtable::property_::emits_change,
264         [this](uint64_t newVal, auto& oldVal) {
265             const Milliseconds newValT{newVal};
266             if (newValT < ReportManager::minInterval &&
267                 newValT != Milliseconds{0})
268             {
269                 throw sdbusplus::exception::SdBusError(
270                     static_cast<int>(std::errc::invalid_argument),
271                     "Invalid interval");
272             }
273 
274             if (newValT != interval)
275             {
276                 oldVal = newVal;
277                 interval = newValT;
278 
279                 errorMessages = verify();
280                 if (state.set<ReportFlags::valid>(errorMessages.empty()) ==
281                     StateEvent::active)
282                 {
283                     scheduleTimer();
284                 }
285 
286                 persistency = storeConfiguration();
287             }
288             return 1;
289         },
290         [this](const auto&) { return interval.count(); });
291     dbusIface->register_property_rw<bool>(
292         "Persistency", sdbusplus::vtable::property_::emits_change,
293         [this](bool newVal, auto& oldVal) {
294             if (newVal == persistency)
295             {
296                 return 1;
297             }
298             if (newVal)
299             {
300                 persistency = oldVal = storeConfiguration();
301             }
302             else
303             {
304                 reportStorage.remove(reportFileName());
305                 persistency = oldVal = false;
306             }
307             return 1;
308         },
309         [this](const auto&) { return persistency; });
310 
311     dbusIface->register_property_r("Readings", readings,
312                                    sdbusplus::vtable::property_::emits_change,
313                                    [this](const auto&) { return readings; });
314     dbusIface->register_property_rw<std::string>(
315         "ReportingType", sdbusplus::vtable::property_::emits_change,
316         [this](auto newVal, auto& oldVal) {
317             ReportingType tmp = utils::toReportingType(newVal);
318             if (tmp != reportingType)
319             {
320                 reportingType = tmp;
321                 oldVal = std::move(newVal);
322 
323                 errorMessages = verify();
324                 if (state.set<ReportFlags::valid>(errorMessages.empty()) ==
325                     StateEvent::active)
326                 {
327                     scheduleTimer();
328                 }
329 
330                 persistency = storeConfiguration();
331 
332                 setReadingBuffer(reportUpdates);
333             }
334             return 1;
335         },
336         [this](const auto&) { return utils::enumToString(reportingType); });
337     dbusIface->register_property_r(
338         "ReadingParameters", readingParametersPastVersion,
339         sdbusplus::vtable::property_::const_,
340         [this](const auto&) { return readingParametersPastVersion; });
341     dbusIface->register_property_rw(
342         "ReadingParametersFutureVersion", readingParameters,
343         sdbusplus::vtable::property_::emits_change,
344         [this, &reportFactory](auto newVal, auto& oldVal) {
345             auto labeledMetricParams =
346                 reportFactory.convertMetricParams(newVal);
347             ReportManager::verifyMetricParameters(labeledMetricParams);
348             reportFactory.updateMetrics(metrics,
349                                         state.get<ReportFlags::enabled>(),
350                                         labeledMetricParams);
351             readingParameters = toReadingParameters(
352                 utils::transform(metrics, [](const auto& metric) {
353                     return metric->dumpConfiguration();
354                 }));
355             updateSensorCount(getSensorCount(metrics));
356             setReadingBuffer(reportUpdates);
357             persistency = storeConfiguration();
358             oldVal = std::move(newVal);
359             return 1;
360         },
361         [this](const auto&) { return readingParameters; });
362     dbusIface->register_property_r<bool>(
363         "EmitsReadingsUpdate", sdbusplus::vtable::property_::none,
364         [this](const auto&) {
365             return reportActions.contains(ReportAction::emitsReadingsUpdate);
366         });
367     dbusIface->register_property_r<std::string>(
368         "Name", sdbusplus::vtable::property_::const_,
369         [this](const auto&) { return name; });
370     dbusIface->register_property_r<bool>(
371         "LogToMetricReportsCollection", sdbusplus::vtable::property_::const_,
372         [this](const auto&) {
373             return reportActions.contains(
374                 ReportAction::logToMetricReportsCollection);
375         });
376     dbusIface->register_property_rw<std::vector<std::string>>(
377         "ReportActions", sdbusplus::vtable::property_::emits_change,
378         [this](auto newVal, auto& oldVal) {
379             auto tmp = utils::transform<std::unordered_set>(
380                 newVal, [](const auto& reportAction) {
381                     return utils::toReportAction(reportAction);
382                 });
383             tmp.insert(ReportAction::logToMetricReportsCollection);
384 
385             if (tmp != reportActions)
386             {
387                 reportActions = tmp;
388                 persistency = storeConfiguration();
389                 oldVal = std::move(newVal);
390             }
391             return 1;
392         },
393         [this](const auto&) {
394             return utils::transform<std::vector>(
395                 reportActions, [](const auto reportAction) {
396                     return utils::enumToString(reportAction);
397                 });
398         });
399     dbusIface->register_property_r(
400         "AppendLimit", appendLimit.value_or(sensorCount),
401         sdbusplus::vtable::property_::emits_change,
402         [this](const auto&) { return appendLimit.value_or(sensorCount); });
403     dbusIface->register_property_rw(
404         "ReportUpdates", std::string(),
405         sdbusplus::vtable::property_::emits_change,
406         [this](auto newVal, auto& oldVal) {
407             setReportUpdates(utils::toReportUpdates(newVal));
408             oldVal = newVal;
409             return 1;
410         },
411         [this](const auto&) { return utils::enumToString(reportUpdates); });
412     dbusIface->register_property_r(
413         "Triggers", std::vector<sdbusplus::message::object_path>{},
414         sdbusplus::vtable::property_::emits_change, [this](const auto&) {
415             return utils::transform<std::vector>(
416                 triggerIds, [](const auto& triggerId) {
417                     return utils::pathAppend(utils::constants::triggerDirPath,
418                                              triggerId);
419                 });
420         });
421     dbusIface->register_method("Update", [this] {
422         if (reportingType == ReportingType::onRequest)
423         {
424             updateReadings();
425         }
426     });
427     constexpr bool skipPropertiesChangedSignal = true;
428     dbusIface->initialize(skipPropertiesChangedSignal);
429     return dbusIface;
430 }
431 
432 void Report::timerProcForPeriodicReport(boost::system::error_code ec,
433                                         Report& self)
434 {
435     if (ec)
436     {
437         return;
438     }
439 
440     self.updateReadings();
441     self.scheduleTimerForPeriodicReport(self.interval);
442 }
443 
444 void Report::timerProcForOnChangeReport(boost::system::error_code ec,
445                                         Report& self)
446 {
447     if (ec)
448     {
449         return;
450     }
451 
452     const auto ensure =
453         utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }};
454 
455     self.onChangeContext.emplace(self);
456 
457     const auto steadyTimestamp = self.clock->steadyTimestamp();
458 
459     for (auto& metric : self.metrics)
460     {
461         metric->updateReadings(steadyTimestamp);
462     }
463 
464     self.scheduleTimerForOnChangeReport();
465 }
466 
467 void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval)
468 {
469     timer.expires_after(timerInterval);
470     timer.async_wait([this](boost::system::error_code ec) {
471         timerProcForPeriodicReport(ec, *this);
472     });
473 }
474 
475 void Report::scheduleTimerForOnChangeReport()
476 {
477     constexpr Milliseconds timerInterval{100};
478 
479     timer.expires_after(timerInterval);
480     timer.async_wait([this](boost::system::error_code ec) {
481         timerProcForOnChangeReport(ec, *this);
482     });
483 }
484 
485 void Report::updateReadings()
486 {
487     if (!state.isActive())
488     {
489         return;
490     }
491 
492     if (reportUpdates == ReportUpdates::overwrite ||
493         reportingType == ReportingType::onRequest)
494     {
495         readingsBuffer.clear();
496     }
497 
498     for (const auto& metric : metrics)
499     {
500         if (!state.isActive())
501         {
502             break;
503         }
504 
505         for (const auto& [id, metadata, value, timestamp] :
506              metric->getUpdatedReadings())
507         {
508             if (reportUpdates == ReportUpdates::appendStopsWhenFull &&
509                 readingsBuffer.isFull())
510             {
511                 state.set<ReportFlags::enabled>(false);
512                 reportIface->signal_property("Enabled");
513                 break;
514             }
515             readingsBuffer.emplace(id, metadata, value, timestamp);
516         }
517     }
518 
519     std::get<0>(readings) =
520         std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp())
521             .count();
522 
523     if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
524     {
525         reportIface->signal_property("Readings");
526     }
527 }
528 
529 bool Report::shouldStoreMetricValues() const
530 {
531     return reportingType != ReportingType::onRequest &&
532            reportUpdates == ReportUpdates::appendStopsWhenFull;
533 }
534 
535 bool Report::storeConfiguration() const
536 {
537     try
538     {
539         nlohmann::json data;
540 
541         data["Enabled"] = state.get<ReportFlags::enabled>();
542         data["Version"] = reportVersion;
543         data["Id"] = id;
544         data["Name"] = name;
545         data["ReportingType"] = utils::toUnderlying(reportingType);
546         data["ReportActions"] =
547             utils::transform(reportActions, [](const auto reportAction) {
548                 return utils::toUnderlying(reportAction);
549             });
550         data["Interval"] = interval.count();
551         data["AppendLimit"] =
552             appendLimit.value_or(std::numeric_limits<uint64_t>::max());
553         data["ReportUpdates"] = utils::toUnderlying(reportUpdates);
554         data["ReadingParameters"] =
555             utils::transform(metrics, [](const auto& metric) {
556                 return metric->dumpConfiguration();
557             });
558 
559         if (shouldStoreMetricValues())
560         {
561             data["MetricValues"] = utils::toLabeledReadings(readings);
562         }
563 
564         reportStorage.store(reportFileName(), data);
565     }
566     catch (const std::exception& e)
567     {
568         phosphor::logging::log<phosphor::logging::level::ERR>(
569             "Failed to store a report in storage",
570             phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
571         return false;
572     }
573 
574     return true;
575 }
576 
577 interfaces::JsonStorage::FilePath Report::reportFileName() const
578 {
579     return interfaces::JsonStorage::FilePath{
580         std::to_string(std::hash<std::string>{}(id))};
581 }
582 
583 std::unordered_set<std::string>
584     Report::collectTriggerIds(boost::asio::io_context& ioc) const
585 {
586     utils::Messanger tmp(ioc);
587 
588     auto result = std::unordered_set<std::string>();
589 
590     tmp.on_receive<messages::CollectTriggerIdResp>(
591         [&result](const auto& msg) { result.insert(msg.triggerId); });
592 
593     tmp.send(messages::CollectTriggerIdReq{id});
594 
595     return result;
596 }
597 
598 void Report::metricUpdated()
599 {
600     if (onChangeContext)
601     {
602         onChangeContext->metricUpdated();
603         return;
604     }
605 
606     updateReadings();
607 }
608 
609 void Report::scheduleTimer()
610 {
611     switch (reportingType)
612     {
613         case ReportingType::periodic:
614         {
615             unregisterFromMetrics = nullptr;
616             scheduleTimerForPeriodicReport(interval);
617             break;
618         }
619         case ReportingType::onChange:
620         {
621             if (!unregisterFromMetrics)
622             {
623                 unregisterFromMetrics = [this] {
624                     for (auto& metric : metrics)
625                     {
626                         metric->unregisterFromUpdates(*this);
627                     }
628                 };
629 
630                 for (auto& metric : metrics)
631                 {
632                     metric->registerForUpdates(*this);
633                 }
634             }
635 
636             bool isTimerRequired = false;
637 
638             for (auto& metric : metrics)
639             {
640                 if (metric->isTimerRequired())
641                 {
642                     isTimerRequired = true;
643                 }
644             }
645 
646             if (isTimerRequired)
647             {
648                 scheduleTimerForOnChangeReport();
649             }
650             else
651             {
652                 timer.cancel();
653             }
654             break;
655         }
656         default:
657             unregisterFromMetrics = nullptr;
658             timer.cancel();
659             break;
660     }
661 }
662 
663 std::vector<ErrorMessage> Report::verify() const
664 {
665     std::vector<ErrorMessage> result;
666 
667     if ((reportingType == ReportingType::periodic &&
668          interval == Milliseconds{0}) ||
669         (reportingType != ReportingType::periodic &&
670          interval != Milliseconds{0}))
671     {
672         result.emplace_back(ErrorType::propertyConflict, "Interval");
673         result.emplace_back(ErrorType::propertyConflict, "ReportingType");
674     }
675 
676     return result;
677 }
678