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