xref: /openbmc/telemetry/src/report.cpp (revision 583ba441654657bb4ba9d051b747144a7258c159)
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(
22     boost::asio::io_context& ioc,
23     const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
24     const std::string& reportId, const std::string& reportName,
25     const ReportingType reportingTypeIn,
26     std::vector<ReportAction> reportActionsIn, const Milliseconds intervalIn,
27     const uint64_t appendLimitIn, 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, const bool enabledIn,
32     std::unique_ptr<interfaces::Clock> clock, Readings readingsIn) :
33     id(reportId), path(utils::pathAppend(utils::constants::reportDirPath, id)),
34     name(reportName), reportingType(reportingTypeIn), interval(intervalIn),
35     reportActions(reportActionsIn.begin(), reportActionsIn.end()),
36     metricCount(getMetricCount(metricsIn)), appendLimit(appendLimitIn),
37     reportUpdates(reportUpdatesIn), readings(std::move(readingsIn)),
38     readingsBuffer(std::get<1>(readings),
39                    deduceBufferSize(reportUpdates, reportingType)),
40     objServer(objServer), metrics(std::move(metricsIn)), timer(ioc),
41     triggerIds(collectTriggerIds(ioc)), reportStorage(reportStorageIn),
42     clock(std::move(clock)), messanger(ioc)
43 {
44     readingParameters =
45         toReadingParameters(utils::transform(metrics, [](const auto& metric) {
46             return metric->dumpConfiguration();
47         }));
48 
49     reportActions.insert(ReportAction::logToMetricReportsCollection);
50 
51     deleteIface = objServer->add_unique_interface(
52         getPath(), deleteIfaceName,
53         [this, &ioc, &reportManager](auto& dbusIface) {
54             dbusIface.register_method("Delete", [this, &ioc, &reportManager] {
55                 if (persistency)
56                 {
57                     persistency = false;
58 
59                     reportIface->signal_property("Persistency");
60                 }
61 
62                 boost::asio::post(ioc, [this, &reportManager] {
63                     reportManager.removeReport(this);
64                 });
65             });
66         });
67 
68     auto errorMessages = verify(reportingType, interval);
69     state.set<ReportFlags::enabled, ReportFlags::valid>(enabledIn,
70                                                         errorMessages.empty());
71 
72     reportIface = makeReportInterface(reportFactory);
73     persistency = storeConfiguration();
74 
75     messanger.on_receive<messages::TriggerPresenceChangedInd>(
76         [this](const auto& msg) {
77             const auto oldSize = triggerIds.size();
78 
79             if (msg.presence == messages::Presence::Exist)
80             {
81                 if (utils::contains(msg.reportIds, id))
82                 {
83                     triggerIds.insert(msg.triggerId);
84                 }
85                 else if (!utils::contains(msg.reportIds, id))
86                 {
87                     triggerIds.erase(msg.triggerId);
88                 }
89             }
90             else if (msg.presence == messages::Presence::Removed)
91             {
92                 triggerIds.erase(msg.triggerId);
93             }
94 
95             if (triggerIds.size() != oldSize)
96             {
97                 reportIface->signal_property("Triggers");
98             }
99         });
100 
101     messanger.on_receive<messages::UpdateReportInd>([this](const auto& msg) {
102         if (utils::contains(msg.reportIds, id))
103         {
104             updateReadings();
105         }
106     });
107 }
108 
~Report()109 Report::~Report()
110 {
111     if (persistency)
112     {
113         if (shouldStoreMetricValues())
114         {
115             storeConfiguration();
116         }
117     }
118     else
119     {
120         reportStorage.remove(reportFileName());
121     }
122 }
123 
activate()124 void Report::activate()
125 {
126     for (auto& metric : metrics)
127     {
128         metric->initialize();
129     }
130 
131     scheduleTimer();
132 }
133 
deactivate()134 void Report::deactivate()
135 {
136     for (auto& metric : metrics)
137     {
138         metric->deinitialize();
139     }
140 
141     unregisterFromMetrics = nullptr;
142     timer.cancel();
143 }
144 
getMetricCount(const std::vector<std::shared_ptr<interfaces::Metric>> & metrics)145 uint64_t Report::getMetricCount(
146     const std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
147 {
148     uint64_t metricCount = 0;
149     for (auto& metric : metrics)
150     {
151         metricCount += metric->metricCount();
152     }
153     return metricCount;
154 }
155 
deduceBufferSize(const ReportUpdates reportUpdatesIn,const ReportingType reportingTypeIn) const156 uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn,
157                                   const ReportingType reportingTypeIn) const
158 {
159     if (reportUpdatesIn == ReportUpdates::overwrite ||
160         reportingTypeIn == ReportingType::onRequest)
161     {
162         return metricCount;
163     }
164     else
165     {
166         return appendLimit;
167     }
168 }
169 
setReadingBuffer(const ReportUpdates newReportUpdates)170 void Report::setReadingBuffer(const ReportUpdates newReportUpdates)
171 {
172     const auto newBufferSize =
173         deduceBufferSize(newReportUpdates, reportingType);
174     if (readingsBuffer.size() != newBufferSize)
175     {
176         readingsBuffer.clearAndResize(newBufferSize);
177     }
178 }
179 
setReportUpdates(const ReportUpdates newReportUpdates)180 void Report::setReportUpdates(const ReportUpdates newReportUpdates)
181 {
182     if (reportUpdates != newReportUpdates)
183     {
184         setReadingBuffer(newReportUpdates);
185         reportUpdates = newReportUpdates;
186     }
187 }
188 
makeReportInterface(const interfaces::ReportFactory & reportFactory)189 std::unique_ptr<sdbusplus::asio::dbus_interface> Report::makeReportInterface(
190     const interfaces::ReportFactory& reportFactory)
191 {
192     auto dbusIface =
193         objServer->add_unique_interface(getPath(), reportIfaceName);
194     dbusIface->register_property_rw<bool>(
195         "Enabled", sdbusplus::vtable::property_::emits_change,
196         [this](bool newVal, auto& oldValue) {
197             if (newVal != state.get<ReportFlags::enabled>())
198             {
199                 state.set<ReportFlags::enabled>(oldValue = newVal);
200 
201                 persistency = storeConfiguration();
202             }
203             return 1;
204         },
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         },
278         [this](const auto&) { return persistency; });
279 
280     dbusIface->register_property_r("Readings", readings,
281                                    sdbusplus::vtable::property_::emits_change,
282                                    [this](const auto&) { return readings; });
283     dbusIface->register_property_r<std::string>(
284         "ReportingType", sdbusplus::vtable::property_::emits_change,
285         [this](const auto&) { return utils::enumToString(reportingType); });
286     dbusIface->register_property_rw(
287         "ReadingParameters", readingParameters,
288         sdbusplus::vtable::property_::emits_change,
289         [this, &reportFactory](auto newVal, auto& oldVal) {
290             auto labeledMetricParams =
291                 reportFactory.convertMetricParams(newVal);
292             ReportManager::verifyMetricParams(labeledMetricParams);
293             reportFactory.updateMetrics(metrics,
294                                         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>(
308         "EmitsReadingsUpdate", 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>(
316         "LogToMetricReportsCollection", 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>(
340                 reportActions, [](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>(
360                 triggerIds, [](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 
timerProcForPeriodicReport(boost::system::error_code ec,Report & self)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 
timerProcForOnChangeReport(boost::system::error_code ec,Report & self)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 
scheduleTimerForPeriodicReport(Milliseconds timerInterval)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 
scheduleTimerForOnChangeReport()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 
updateReadings()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 
shouldStoreMetricValues() const482 bool Report::shouldStoreMetricValues() const
483 {
484     return reportingType != ReportingType::onRequest &&
485            reportUpdates == ReportUpdates::appendStopsWhenFull;
486 }
487 
storeConfiguration() const488 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"] =
500             utils::transform(reportActions, [](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"] =
507             utils::transform(metrics, [](const auto& metric) {
508                 return metric->dumpConfiguration();
509             });
510 
511         if (shouldStoreMetricValues())
512         {
513             data["MetricValues"] = utils::toLabeledReadings(readings);
514         }
515 
516         reportStorage.store(reportFileName(), data);
517     }
518     catch (const std::exception& e)
519     {
520         phosphor::logging::log<phosphor::logging::level::ERR>(
521             "Failed to store a report in storage",
522             phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
523         return false;
524     }
525 
526     return true;
527 }
528 
reportFileName() const529 interfaces::JsonStorage::FilePath Report::reportFileName() const
530 {
531     return interfaces::JsonStorage::FilePath{
532         std::to_string(std::hash<std::string>{}(id))};
533 }
534 
collectTriggerIds(boost::asio::io_context & ioc) const535 std::unordered_set<std::string> Report::collectTriggerIds(
536     boost::asio::io_context& ioc) const
537 {
538     utils::Messanger tmp(ioc);
539 
540     auto result = std::unordered_set<std::string>();
541 
542     tmp.on_receive<messages::CollectTriggerIdResp>([&result](const auto& msg) {
543         result.insert(msg.triggerId);
544     });
545 
546     tmp.send(messages::CollectTriggerIdReq{id});
547 
548     return result;
549 }
550 
metricUpdated()551 void Report::metricUpdated()
552 {
553     if (onChangeContext)
554     {
555         onChangeContext->metricUpdated();
556         return;
557     }
558 
559     updateReadings();
560 }
561 
scheduleTimer()562 void Report::scheduleTimer()
563 {
564     switch (reportingType)
565     {
566         case ReportingType::periodic:
567         {
568             unregisterFromMetrics = nullptr;
569             scheduleTimerForPeriodicReport(interval);
570             break;
571         }
572         case ReportingType::onChange:
573         {
574             if (!unregisterFromMetrics)
575             {
576                 unregisterFromMetrics = [this] {
577                     for (auto& metric : metrics)
578                     {
579                         metric->unregisterFromUpdates(*this);
580                     }
581                 };
582 
583                 for (auto& metric : metrics)
584                 {
585                     metric->registerForUpdates(*this);
586                 }
587             }
588 
589             bool isTimerRequired = false;
590 
591             for (auto& metric : metrics)
592             {
593                 if (metric->isTimerRequired())
594                 {
595                     isTimerRequired = true;
596                 }
597             }
598 
599             if (isTimerRequired)
600             {
601                 scheduleTimerForOnChangeReport();
602             }
603             else
604             {
605                 timer.cancel();
606             }
607             break;
608         }
609         default:
610             unregisterFromMetrics = nullptr;
611             timer.cancel();
612             break;
613     }
614 }
615 
verify(ReportingType reportingType,Milliseconds interval)616 std::vector<ErrorMessage> Report::verify(ReportingType reportingType,
617                                          Milliseconds interval)
618 {
619     if (interval != Milliseconds{0} && interval < ReportManager::minInterval)
620     {
621         throw errors::InvalidArgument("Interval");
622     }
623 
624     std::vector<ErrorMessage> result;
625 
626     if ((reportingType == ReportingType::periodic &&
627          interval == Milliseconds{0}) ||
628         (reportingType != ReportingType::periodic &&
629          interval != Milliseconds{0}))
630     {
631         result.emplace_back(ErrorType::propertyConflict, "Interval");
632         result.emplace_back(ErrorType::propertyConflict, "ReportingType");
633     }
634 
635     return result;
636 }
637