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