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