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