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