xref: /openbmc/telemetry/src/report.cpp (revision 1cdd7e4f89c75de7ec95ea545306e96ddf5a8a2b)
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             reportFactory.updateMetrics(
335                 metrics, state.get<ReportFlags::enabled>(), newVal);
336             readingParameters = toReadingParameters(
337                 utils::transform(metrics, [](const auto& metric) {
338                     return metric->dumpConfiguration();
339                 }));
340             persistency = storeConfiguration();
341             oldVal = std::move(newVal);
342             return 1;
343         },
344         [this](const auto&) { return readingParameters; });
345     dbusIface->register_property_r<bool>(
346         "EmitsReadingsUpdate", sdbusplus::vtable::property_::none,
347         [this](const auto&) {
348             return reportActions.contains(ReportAction::emitsReadingsUpdate);
349         });
350     dbusIface->register_property_r<std::string>(
351         "Name", sdbusplus::vtable::property_::const_,
352         [this](const auto&) { return name; });
353     dbusIface->register_property_r<bool>(
354         "LogToMetricReportsCollection", sdbusplus::vtable::property_::const_,
355         [this](const auto&) {
356             return reportActions.contains(
357                 ReportAction::logToMetricReportsCollection);
358         });
359     dbusIface->register_property_rw<std::vector<std::string>>(
360         "ReportActions", sdbusplus::vtable::property_::emits_change,
361         [this](auto newVal, auto& oldVal) {
362             auto tmp = utils::transform<std::unordered_set>(
363                 newVal, [](const auto& reportAction) {
364                     return utils::toReportAction(reportAction);
365                 });
366             tmp.insert(ReportAction::logToMetricReportsCollection);
367 
368             if (tmp != reportActions)
369             {
370                 reportActions = tmp;
371                 persistency = storeConfiguration();
372                 oldVal = std::move(newVal);
373             }
374             return 1;
375         },
376         [this](const auto&) {
377             return utils::transform<std::vector>(
378                 reportActions, [](const auto reportAction) {
379                     return utils::enumToString(reportAction);
380                 });
381         });
382     dbusIface->register_property_r(
383         "AppendLimit", appendLimit.value_or(sensorCount),
384         sdbusplus::vtable::property_::emits_change,
385         [this](const auto&) { return appendLimit.value_or(sensorCount); });
386     dbusIface->register_property_rw(
387         "ReportUpdates", std::string(),
388         sdbusplus::vtable::property_::emits_change,
389         [this](auto newVal, auto& oldVal) {
390             setReportUpdates(utils::toReportUpdates(newVal));
391             oldVal = newVal;
392             return 1;
393         },
394         [this](const auto&) { return utils::enumToString(reportUpdates); });
395     dbusIface->register_property_r(
396         "Triggers", std::vector<sdbusplus::message::object_path>{},
397         sdbusplus::vtable::property_::emits_change, [this](const auto&) {
398             return utils::transform<std::vector>(
399                 triggerIds, [](const auto& triggerId) {
400                     return utils::pathAppend(utils::constants::triggerDirPath,
401                                              triggerId);
402                 });
403         });
404     dbusIface->register_method("Update", [this] {
405         if (reportingType == ReportingType::onRequest)
406         {
407             updateReadings();
408         }
409     });
410     constexpr bool skipPropertiesChangedSignal = true;
411     dbusIface->initialize(skipPropertiesChangedSignal);
412     return dbusIface;
413 }
414 
415 void Report::timerProcForPeriodicReport(boost::system::error_code ec,
416                                         Report& self)
417 {
418     if (ec)
419     {
420         return;
421     }
422 
423     self.updateReadings();
424     self.scheduleTimerForPeriodicReport(self.interval);
425 }
426 
427 void Report::timerProcForOnChangeReport(boost::system::error_code ec,
428                                         Report& self)
429 {
430     if (ec)
431     {
432         return;
433     }
434 
435     const auto ensure =
436         utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }};
437 
438     self.onChangeContext.emplace(self);
439 
440     const auto steadyTimestamp = self.clock->steadyTimestamp();
441 
442     for (auto& metric : self.metrics)
443     {
444         metric->updateReadings(steadyTimestamp);
445     }
446 
447     self.scheduleTimerForOnChangeReport();
448 }
449 
450 void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval)
451 {
452     timer.expires_after(timerInterval);
453     timer.async_wait([this](boost::system::error_code ec) {
454         timerProcForPeriodicReport(ec, *this);
455     });
456 }
457 
458 void Report::scheduleTimerForOnChangeReport()
459 {
460     constexpr Milliseconds timerInterval{100};
461 
462     timer.expires_after(timerInterval);
463     timer.async_wait([this](boost::system::error_code ec) {
464         timerProcForOnChangeReport(ec, *this);
465     });
466 }
467 
468 void Report::updateReadings()
469 {
470     if (!state.isActive())
471     {
472         return;
473     }
474 
475     if (reportUpdates == ReportUpdates::overwrite ||
476         reportingType == ReportingType::onRequest)
477     {
478         readingsBuffer.clear();
479     }
480 
481     for (const auto& metric : metrics)
482     {
483         if (!state.isActive())
484         {
485             break;
486         }
487 
488         for (const auto& [id, metadata, value, timestamp] :
489              metric->getUpdatedReadings())
490         {
491             if (reportUpdates == ReportUpdates::appendStopsWhenFull &&
492                 readingsBuffer.isFull())
493             {
494                 state.set<ReportFlags::enabled>(false);
495                 reportIface->signal_property("Enabled");
496                 break;
497             }
498             readingsBuffer.emplace(id, metadata, value, timestamp);
499         }
500     }
501 
502     std::get<0>(readings) =
503         std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp())
504             .count();
505 
506     if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
507     {
508         reportIface->signal_property("Readings");
509     }
510 }
511 
512 bool Report::shouldStoreMetricValues() const
513 {
514     return reportingType != ReportingType::onRequest &&
515            reportUpdates == ReportUpdates::appendStopsWhenFull;
516 }
517 
518 bool Report::storeConfiguration() const
519 {
520     try
521     {
522         nlohmann::json data;
523 
524         data["Enabled"] = state.get<ReportFlags::enabled>();
525         data["Version"] = reportVersion;
526         data["Id"] = id;
527         data["Name"] = name;
528         data["ReportingType"] = utils::toUnderlying(reportingType);
529         data["ReportActions"] =
530             utils::transform(reportActions, [](const auto reportAction) {
531                 return utils::toUnderlying(reportAction);
532             });
533         data["Interval"] = interval.count();
534         data["AppendLimit"] =
535             appendLimit.value_or(std::numeric_limits<uint64_t>::max());
536         data["ReportUpdates"] = utils::toUnderlying(reportUpdates);
537         data["ReadingParameters"] =
538             utils::transform(metrics, [](const auto& metric) {
539                 return metric->dumpConfiguration();
540             });
541 
542         if (shouldStoreMetricValues())
543         {
544             data["MetricValues"] = utils::toLabeledReadings(readings);
545         }
546 
547         reportStorage.store(reportFileName(), data);
548     }
549     catch (const std::exception& e)
550     {
551         phosphor::logging::log<phosphor::logging::level::ERR>(
552             "Failed to store a report in storage",
553             phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
554         return false;
555     }
556 
557     return true;
558 }
559 
560 interfaces::JsonStorage::FilePath Report::reportFileName() const
561 {
562     return interfaces::JsonStorage::FilePath{
563         std::to_string(std::hash<std::string>{}(id))};
564 }
565 
566 std::unordered_set<std::string>
567     Report::collectTriggerIds(boost::asio::io_context& ioc) const
568 {
569     utils::Messanger tmp(ioc);
570 
571     auto result = std::unordered_set<std::string>();
572 
573     tmp.on_receive<messages::CollectTriggerIdResp>(
574         [&result](const auto& msg) { result.insert(msg.triggerId); });
575 
576     tmp.send(messages::CollectTriggerIdReq{id});
577 
578     return result;
579 }
580 
581 void Report::metricUpdated()
582 {
583     if (onChangeContext)
584     {
585         onChangeContext->metricUpdated();
586         return;
587     }
588 
589     updateReadings();
590 }
591 
592 void Report::scheduleTimer()
593 {
594     switch (reportingType)
595     {
596         case ReportingType::periodic:
597         {
598             unregisterFromMetrics = nullptr;
599             scheduleTimerForPeriodicReport(interval);
600             break;
601         }
602         case ReportingType::onChange:
603         {
604             if (!unregisterFromMetrics)
605             {
606                 unregisterFromMetrics = [this] {
607                     for (auto& metric : metrics)
608                     {
609                         metric->unregisterFromUpdates(*this);
610                     }
611                 };
612 
613                 for (auto& metric : metrics)
614                 {
615                     metric->registerForUpdates(*this);
616                 }
617             }
618 
619             bool isTimerRequired = false;
620 
621             for (auto& metric : metrics)
622             {
623                 if (metric->isTimerRequired())
624                 {
625                     isTimerRequired = true;
626                 }
627             }
628 
629             if (isTimerRequired)
630             {
631                 scheduleTimerForOnChangeReport();
632             }
633             else
634             {
635                 timer.cancel();
636             }
637             break;
638         }
639         default:
640             unregisterFromMetrics = nullptr;
641             timer.cancel();
642             break;
643     }
644 }
645 
646 std::vector<ErrorMessage> Report::verify() const
647 {
648     std::vector<ErrorMessage> result;
649 
650     if ((reportingType == ReportingType::periodic &&
651          interval == Milliseconds{0}) ||
652         (reportingType != ReportingType::periodic &&
653          interval != Milliseconds{0}))
654     {
655         result.emplace_back(ErrorType::propertyConflict, "Interval");
656         result.emplace_back(ErrorType::propertyConflict, "ReportingType");
657     }
658 
659     return result;
660 }
661