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