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