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