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