xref: /openbmc/telemetry/src/report.cpp (revision fdb06a141c6d9bbf1088fee974b815d1f35d3bcb)
1 #include "report.hpp"
2 
3 #include "messages/collect_trigger_id.hpp"
4 #include "messages/trigger_presence_changed_ind.hpp"
5 #include "messages/update_report_ind.hpp"
6 #include "report_manager.hpp"
7 #include "utils/clock.hpp"
8 #include "utils/contains.hpp"
9 #include "utils/transform.hpp"
10 
11 #include <phosphor-logging/log.hpp>
12 #include <sdbusplus/vtable.hpp>
13 
14 #include <limits>
15 #include <numeric>
16 
17 Report::Report(boost::asio::io_context& ioc,
18                const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
19                const std::string& reportId, const std::string& reportName,
20                const ReportingType reportingTypeIn,
21                std::vector<ReportAction> reportActionsIn,
22                const Milliseconds intervalIn, const uint64_t appendLimitIn,
23                const ReportUpdates reportUpdatesIn,
24                interfaces::ReportManager& reportManager,
25                interfaces::JsonStorage& reportStorageIn,
26                std::vector<std::shared_ptr<interfaces::Metric>> metricsIn,
27                const interfaces::ReportFactory& reportFactory,
28                const bool enabledIn, std::unique_ptr<interfaces::Clock> clock) :
29     id(reportId),
30     name(reportName), reportingType(reportingTypeIn), interval(intervalIn),
31     reportActions(reportActionsIn.begin(), reportActionsIn.end()),
32     sensorCount(getSensorCount(metricsIn)),
33     appendLimit(deduceAppendLimit(appendLimitIn)),
34     reportUpdates(reportUpdatesIn),
35     readingsBuffer(deduceBufferSize(reportUpdates, reportingType)),
36     objServer(objServer), metrics(std::move(metricsIn)), timer(ioc),
37     triggerIds(collectTriggerIds(ioc)), reportStorage(reportStorageIn),
38     enabled(enabledIn), clock(std::move(clock)), messanger(ioc)
39 {
40     readingParameters =
41         toReadingParameters(utils::transform(metrics, [](const auto& metric) {
42             return metric->dumpConfiguration();
43         }));
44 
45     readingParametersPastVersion =
46         utils::transform(readingParameters, [](const auto& item) {
47             const auto& [sensorData, operationType, id, collectionTimeScope,
48                          collectionDuration] = item;
49 
50             return ReadingParametersPastVersion::value_type(
51                 std::get<0>(sensorData.front()), operationType, id,
52                 std::get<1>(sensorData.front()));
53         });
54 
55     reportActions.insert(ReportAction::logToMetricReportsCollection);
56 
57     deleteIface = objServer->add_unique_interface(
58         getPath(), deleteIfaceName,
59         [this, &ioc, &reportManager](auto& dbusIface) {
60             dbusIface.register_method("Delete", [this, &ioc, &reportManager] {
61                 if (persistency)
62                 {
63                     reportStorage.remove(fileName());
64                 }
65                 boost::asio::post(ioc, [this, &reportManager] {
66                     reportManager.removeReport(this);
67                 });
68             });
69         });
70 
71     persistency = storeConfiguration();
72     reportIface = makeReportInterface(reportFactory);
73 
74     if (reportingType == ReportingType::periodic)
75     {
76         scheduleTimer(interval);
77     }
78 
79     if (enabled)
80     {
81         for (auto& metric : this->metrics)
82         {
83             metric->initialize();
84         }
85     }
86 
87     messanger.on_receive<messages::TriggerPresenceChangedInd>(
88         [this](const auto& msg) {
89             const auto oldSize = triggerIds.size();
90 
91             if (msg.presence == messages::Presence::Exist)
92             {
93                 if (utils::contains(msg.reportIds, id))
94                 {
95                     triggerIds.insert(msg.triggerId);
96                 }
97                 else if (!utils::contains(msg.reportIds, id))
98                 {
99                     triggerIds.erase(msg.triggerId);
100                 }
101             }
102             else if (msg.presence == messages::Presence::Removed)
103             {
104                 triggerIds.erase(msg.triggerId);
105             }
106 
107             if (triggerIds.size() != oldSize)
108             {
109                 reportIface->signal_property("TriggerIds");
110             }
111         });
112 
113     messanger.on_receive<messages::UpdateReportInd>([this](const auto& msg) {
114         if (utils::contains(msg.reportIds, id))
115         {
116             updateReadings();
117         }
118     });
119 }
120 
121 uint64_t Report::getSensorCount(
122     std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
123 {
124     uint64_t sensorCount = 0;
125     for (auto& metric : metrics)
126     {
127         sensorCount += metric->sensorCount();
128     }
129     return sensorCount;
130 }
131 
132 std::optional<uint64_t>
133     Report::deduceAppendLimit(const uint64_t appendLimitIn) const
134 {
135     if (appendLimitIn == std::numeric_limits<uint64_t>::max())
136     {
137         return std::nullopt;
138     }
139     else
140     {
141         return appendLimitIn;
142     }
143 }
144 
145 uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn,
146                                   const ReportingType reportingTypeIn) const
147 {
148     if (reportUpdatesIn == ReportUpdates::overwrite ||
149         reportingTypeIn == ReportingType::onRequest)
150     {
151         return sensorCount;
152     }
153     else
154     {
155         return appendLimit.value_or(sensorCount);
156     }
157 }
158 
159 void Report::setReadingBuffer(const ReportUpdates newReportUpdates)
160 {
161     if (reportingType != ReportingType::onRequest &&
162         (reportUpdates == ReportUpdates::overwrite ||
163          newReportUpdates == ReportUpdates::overwrite))
164     {
165         readingsBuffer.clearAndResize(
166             deduceBufferSize(newReportUpdates, reportingType));
167     }
168 }
169 
170 void Report::setReportUpdates(const ReportUpdates newReportUpdates)
171 {
172     if (reportUpdates != newReportUpdates)
173     {
174         setReadingBuffer(newReportUpdates);
175         reportUpdates = newReportUpdates;
176     }
177 }
178 
179 std::unique_ptr<sdbusplus::asio::dbus_interface>
180     Report::makeReportInterface(const interfaces::ReportFactory& reportFactory)
181 {
182     auto dbusIface =
183         objServer->add_unique_interface(getPath(), reportIfaceName);
184     dbusIface->register_property_rw(
185         "Enabled", enabled, sdbusplus::vtable::property_::emits_change,
186         [this](bool newVal, const auto&) {
187             if (newVal != enabled)
188             {
189                 if (true == newVal && ReportingType::periodic == reportingType)
190                 {
191                     scheduleTimer(interval);
192                 }
193                 if (newVal)
194                 {
195                     for (auto& metric : metrics)
196                     {
197                         metric->initialize();
198                     }
199                 }
200                 else
201                 {
202                     for (auto& metric : metrics)
203                     {
204                         metric->deinitialize();
205                     }
206                 }
207 
208                 enabled = newVal;
209                 persistency = storeConfiguration();
210             }
211             return 1;
212         },
213         [this](const auto&) { return enabled; });
214     dbusIface->register_property_rw(
215         "Interval", interval.count(),
216         sdbusplus::vtable::property_::emits_change,
217         [this](uint64_t newVal, auto&) {
218             if (Milliseconds newValT{newVal};
219                 newValT >= ReportManager::minInterval)
220             {
221                 if (newValT != interval)
222                 {
223                     interval = newValT;
224                     persistency = storeConfiguration();
225                 }
226                 return 1;
227             }
228             throw sdbusplus::exception::SdBusError(
229                 static_cast<int>(std::errc::invalid_argument),
230                 "Invalid interval");
231         },
232         [this](const auto&) { return interval.count(); });
233     dbusIface->register_property_rw(
234         "Persistency", persistency, sdbusplus::vtable::property_::emits_change,
235         [this](bool newVal, const auto&) {
236             if (newVal == persistency)
237             {
238                 return 1;
239             }
240             if (newVal)
241             {
242                 persistency = storeConfiguration();
243             }
244             else
245             {
246                 reportStorage.remove(fileName());
247                 persistency = false;
248             }
249             return 1;
250         },
251         [this](const auto&) { return persistency; });
252 
253     dbusIface->register_property_r("Readings", readings,
254                                    sdbusplus::vtable::property_::emits_change,
255                                    [this](const auto&) { return readings; });
256     dbusIface->register_property_rw(
257         "ReportingType", std::string(),
258         sdbusplus::vtable::property_::emits_change,
259         [this](auto newVal, auto& oldVal) {
260             ReportingType tmp = utils::toReportingType(newVal);
261             if (tmp != reportingType)
262             {
263                 if (tmp == ReportingType::onChange)
264                 {
265                     throw sdbusplus::exception::SdBusError(
266                         static_cast<int>(std::errc::invalid_argument),
267                         "Invalid reportingType");
268                 }
269                 if (tmp == ReportingType::periodic)
270                 {
271                     if (interval < ReportManager::minInterval)
272                     {
273                         throw sdbusplus::exception::SdBusError(
274                             static_cast<int>(std::errc::invalid_argument),
275                             "Invalid interval");
276                     }
277                     if (enabled == true)
278                     {
279                         scheduleTimer(interval);
280                     }
281                 }
282                 else
283                 {
284                     timer.cancel();
285                 }
286                 reportingType = tmp;
287                 setReadingBuffer(reportUpdates);
288                 persistency = storeConfiguration();
289             }
290             oldVal = std::move(newVal);
291             return 1;
292         },
293         [this](const auto&) { return utils::enumToString(reportingType); });
294     dbusIface->register_property_r(
295         "ReadingParameters", readingParametersPastVersion,
296         sdbusplus::vtable::property_::const_,
297         [this](const auto&) { return readingParametersPastVersion; });
298     dbusIface->register_property_rw(
299         "ReadingParametersFutureVersion", readingParameters,
300         sdbusplus::vtable::property_::emits_change,
301         [this, &reportFactory](auto newVal, auto& oldVal) {
302             reportFactory.updateMetrics(metrics, enabled, newVal);
303             readingParameters = toReadingParameters(
304                 utils::transform(metrics, [](const auto& metric) {
305                     return metric->dumpConfiguration();
306                 }));
307             persistency = storeConfiguration();
308             oldVal = std::move(newVal);
309             return 1;
310         },
311         [this](const auto&) { return readingParameters; });
312     dbusIface->register_property_r(
313         "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::none,
314         [this](const auto&) {
315             return reportActions.contains(ReportAction::emitsReadingsUpdate);
316         });
317     dbusIface->register_property_r("Name", std::string{},
318                                    sdbusplus::vtable::property_::const_,
319                                    [this](const auto&) { return name; });
320     dbusIface->register_property_r(
321         "LogToMetricReportsCollection", bool{},
322         sdbusplus::vtable::property_::const_, [this](const auto&) {
323             return reportActions.contains(
324                 ReportAction::logToMetricReportsCollection);
325         });
326     dbusIface->register_property_rw(
327         "ReportActions", std::vector<std::string>{},
328         sdbusplus::vtable::property_::emits_change,
329         [this](auto newVal, auto& oldVal) {
330             auto tmp = utils::transform<std::unordered_set>(
331                 newVal, [](const auto& reportAction) {
332                     return utils::toReportAction(reportAction);
333                 });
334             tmp.insert(ReportAction::logToMetricReportsCollection);
335 
336             if (tmp != reportActions)
337             {
338                 reportActions = tmp;
339                 persistency = storeConfiguration();
340                 oldVal = std::move(newVal);
341             }
342             return 1;
343         },
344         [this](const auto&) {
345             return utils::transform<std::vector>(
346                 reportActions, [](const auto reportAction) {
347                     return utils::enumToString(reportAction);
348                 });
349         });
350     dbusIface->register_property_r(
351         "AppendLimit", appendLimit.value_or(sensorCount),
352         sdbusplus::vtable::property_::emits_change,
353         [this](const auto&) { return appendLimit.value_or(sensorCount); });
354     dbusIface->register_property_rw(
355         "ReportUpdates", std::string(),
356         sdbusplus::vtable::property_::emits_change,
357         [this](auto newVal, auto& oldVal) {
358             setReportUpdates(utils::toReportUpdates(newVal));
359             oldVal = newVal;
360             return 1;
361         },
362         [this](const auto&) { return utils::enumToString(reportUpdates); });
363     dbusIface->register_property_r(
364         "TriggerIds", std::vector<std::string>{},
365         sdbusplus::vtable::property_::emits_change, [this](const auto&) {
366             return std::vector<std::string>(triggerIds.begin(),
367                                             triggerIds.end());
368         });
369     dbusIface->register_method("Update", [this] {
370         if (reportingType == ReportingType::onRequest)
371         {
372             updateReadings();
373         }
374     });
375     constexpr bool skipPropertiesChangedSignal = true;
376     dbusIface->initialize(skipPropertiesChangedSignal);
377     return dbusIface;
378 }
379 
380 void Report::timerProc(boost::system::error_code ec, Report& self)
381 {
382     if (ec)
383     {
384         return;
385     }
386 
387     self.updateReadings();
388     self.scheduleTimer(self.interval);
389 }
390 
391 void Report::scheduleTimer(Milliseconds timerInterval)
392 {
393     timer.expires_after(timerInterval);
394     timer.async_wait(
395         [this](boost::system::error_code ec) { timerProc(ec, *this); });
396 }
397 
398 void Report::updateReadings()
399 {
400     if (!enabled)
401     {
402         return;
403     }
404 
405     if (reportUpdates == ReportUpdates::overwrite ||
406         reportingType == ReportingType::onRequest)
407     {
408         readingsBuffer.clear();
409     }
410 
411     for (const auto& metric : metrics)
412     {
413         for (const auto& [id, metadata, value, timestamp] :
414              metric->getReadings())
415         {
416             if (reportUpdates == ReportUpdates::appendStopsWhenFull &&
417                 readingsBuffer.isFull())
418             {
419                 enabled = false;
420                 for (auto& m : metrics)
421                 {
422                     m->deinitialize();
423                 }
424                 break;
425             }
426             readingsBuffer.emplace(id, metadata, value, timestamp);
427         }
428     }
429 
430     readings = {
431         std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp())
432             .count(),
433         std::vector<ReadingData>(readingsBuffer.begin(), readingsBuffer.end())};
434 
435     if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
436     {
437         reportIface->signal_property("Readings");
438     }
439 }
440 
441 bool Report::storeConfiguration() const
442 {
443     try
444     {
445         nlohmann::json data;
446 
447         data["Enabled"] = enabled;
448         data["Version"] = reportVersion;
449         data["Id"] = id;
450         data["Name"] = name;
451         data["ReportingType"] = utils::toUnderlying(reportingType);
452         data["ReportActions"] =
453             utils::transform(reportActions, [](const auto reportAction) {
454                 return utils::toUnderlying(reportAction);
455             });
456         data["Interval"] = interval.count();
457         data["AppendLimit"] =
458             appendLimit.value_or(std::numeric_limits<uint64_t>::max());
459         data["ReportUpdates"] = utils::toUnderlying(reportUpdates);
460         data["ReadingParameters"] =
461             utils::transform(metrics, [](const auto& metric) {
462                 return metric->dumpConfiguration();
463             });
464 
465         reportStorage.store(fileName(), data);
466     }
467     catch (const std::exception& e)
468     {
469         phosphor::logging::log<phosphor::logging::level::ERR>(
470             "Failed to store a report in storage",
471             phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
472         return false;
473     }
474 
475     return true;
476 }
477 
478 interfaces::JsonStorage::FilePath Report::fileName() const
479 {
480     return interfaces::JsonStorage::FilePath{
481         std::to_string(std::hash<std::string>{}(id))};
482 }
483 
484 std::unordered_set<std::string>
485     Report::collectTriggerIds(boost::asio::io_context& ioc) const
486 {
487     utils::Messanger tmp(ioc);
488 
489     auto result = std::unordered_set<std::string>();
490 
491     tmp.on_receive<messages::CollectTriggerIdResp>(
492         [&result](const auto& msg) { result.insert(msg.triggerId); });
493 
494     tmp.send(messages::CollectTriggerIdReq{id});
495 
496     return result;
497 }
498