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