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