xref: /openbmc/telemetry/src/report.cpp (revision b8cc78ddf9cc87c83176c7bda575ceef2678d00f)
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) :
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)
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 uint64_t Report::deduceAppendLimit(const uint64_t appendLimitIn) const
93 {
94     if (appendLimitIn == std::numeric_limits<uint64_t>::max())
95     {
96         return sensorCount;
97     }
98     else
99     {
100         return appendLimitIn;
101     }
102 }
103 
104 uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn,
105                                   const ReportingType reportingTypeIn) const
106 {
107     if (reportUpdatesIn == ReportUpdates::overwrite ||
108         reportingTypeIn == ReportingType::onRequest)
109     {
110         return sensorCount;
111     }
112     else
113     {
114         return appendLimit;
115     }
116 }
117 
118 void Report::setReportUpdates(const ReportUpdates newReportUpdates)
119 {
120     if (reportUpdates != newReportUpdates)
121     {
122         if (reportingType != ReportingType::onRequest &&
123             (reportUpdates == ReportUpdates::overwrite ||
124              newReportUpdates == ReportUpdates::overwrite))
125         {
126             readingsBuffer.clearAndResize(
127                 deduceBufferSize(newReportUpdates, reportingType));
128         }
129         reportUpdates = newReportUpdates;
130     }
131 }
132 
133 std::unique_ptr<sdbusplus::asio::dbus_interface> Report::makeReportInterface()
134 {
135     auto dbusIface =
136         objServer->add_unique_interface(getPath(), reportIfaceName);
137     dbusIface->register_property_rw(
138         "Enabled", enabled, sdbusplus::vtable::property_::emits_change,
139         [this](bool newVal, const auto&) {
140             if (newVal != enabled)
141             {
142                 if (true == newVal && ReportingType::periodic == reportingType)
143                 {
144                     scheduleTimer(interval);
145                 }
146                 if (newVal)
147                 {
148                     for (auto& metric : metrics)
149                     {
150                         metric->initialize();
151                     }
152                 }
153                 else
154                 {
155                     for (auto& metric : metrics)
156                     {
157                         metric->deinitialize();
158                     }
159                 }
160 
161                 enabled = newVal;
162                 persistency = storeConfiguration();
163             }
164             return true;
165         },
166         [this](const auto&) { return enabled; });
167     dbusIface->register_property_rw(
168         "Interval", interval.count(),
169         sdbusplus::vtable::property_::emits_change,
170         [this](uint64_t newVal, auto&) {
171             if (Milliseconds newValT{newVal};
172                 newValT >= ReportManager::minInterval)
173             {
174                 if (newValT != interval)
175                 {
176                     interval = newValT;
177                     persistency = storeConfiguration();
178                 }
179                 return true;
180             }
181             return false;
182         },
183         [this](const auto&) { return interval.count(); });
184     dbusIface->register_property_rw(
185         "Persistency", persistency, sdbusplus::vtable::property_::emits_change,
186         [this](bool newVal, const auto&) {
187             if (newVal == persistency)
188             {
189                 return true;
190             }
191             if (newVal)
192             {
193                 persistency = storeConfiguration();
194             }
195             else
196             {
197                 reportStorage.remove(fileName());
198                 persistency = false;
199             }
200             return true;
201         },
202         [this](const auto&) { return persistency; });
203 
204     auto readingsFlag = sdbusplus::vtable::property_::none;
205     if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
206     {
207         readingsFlag = sdbusplus::vtable::property_::emits_change;
208     }
209     dbusIface->register_property_r("Readings", readings, readingsFlag,
210                                    [this](const auto&) { return readings; });
211     dbusIface->register_property_r(
212         "ReportingType", std::string(), sdbusplus::vtable::property_::const_,
213         [this](const auto&) { return utils::enumToString(reportingType); });
214     dbusIface->register_property_r(
215         "ReadingParameters", readingParametersPastVersion,
216         sdbusplus::vtable::property_::const_,
217         [this](const auto&) { return readingParametersPastVersion; });
218     dbusIface->register_property_r(
219         "ReadingParametersFutureVersion", readingParameters,
220         sdbusplus::vtable::property_::const_,
221         [this](const auto&) { return readingParameters; });
222     dbusIface->register_property_r(
223         "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::const_,
224         [this](const auto&) {
225             return utils::contains(reportActions,
226                                    ReportAction::emitsReadingsUpdate);
227         });
228     dbusIface->register_property_r("Name", std::string{},
229                                    sdbusplus::vtable::property_::const_,
230                                    [this](const auto&) { return name; });
231     dbusIface->register_property_r(
232         "LogToMetricReportsCollection", bool{},
233         sdbusplus::vtable::property_::const_, [this](const auto&) {
234             return utils::contains(reportActions,
235                                    ReportAction::logToMetricReportsCollection);
236         });
237     dbusIface->register_property_r(
238         "ReportActions", std::vector<std::string>{},
239         sdbusplus::vtable::property_::const_, [this](const auto&) {
240             return utils::transform(reportActions, [](const auto reportAction) {
241                 return utils::enumToString(reportAction);
242             });
243         });
244     dbusIface->register_property_r("AppendLimit", appendLimit,
245                                    sdbusplus::vtable::property_::emits_change,
246                                    [this](const auto&) { return appendLimit; });
247     dbusIface->register_property_rw(
248         "ReportUpdates", std::string(),
249         sdbusplus::vtable::property_::emits_change,
250         [this](auto newVal, auto& oldVal) {
251             ReportManager::verifyReportUpdates(utils::toReportUpdates(newVal));
252             setReportUpdates(utils::toReportUpdates(newVal));
253             oldVal = newVal;
254             return true;
255         },
256         [this](const auto&) { return utils::enumToString(reportUpdates); });
257     dbusIface->register_method("Update", [this] {
258         if (reportingType == ReportingType::onRequest)
259         {
260             updateReadings();
261         }
262     });
263     constexpr bool skipPropertiesChangedSignal = true;
264     dbusIface->initialize(skipPropertiesChangedSignal);
265     return dbusIface;
266 }
267 
268 void Report::timerProc(boost::system::error_code ec, Report& self)
269 {
270     if (ec)
271     {
272         return;
273     }
274 
275     self.updateReadings();
276     self.scheduleTimer(self.interval);
277 }
278 
279 void Report::scheduleTimer(Milliseconds timerInterval)
280 {
281     timer.expires_after(timerInterval);
282     timer.async_wait(
283         [this](boost::system::error_code ec) { timerProc(ec, *this); });
284 }
285 
286 void Report::updateReadings()
287 {
288     if (!enabled)
289     {
290         return;
291     }
292 
293     if (reportUpdates == ReportUpdates::overwrite ||
294         reportingType == ReportingType::onRequest)
295     {
296         readingsBuffer.clear();
297     }
298 
299     for (const auto& metric : metrics)
300     {
301         for (const auto& [id, metadata, value, timestamp] :
302              metric->getReadings())
303         {
304             if (reportUpdates == ReportUpdates::appendStopsWhenFull &&
305                 readingsBuffer.isFull())
306             {
307                 enabled = false;
308                 for (auto& metric : metrics)
309                 {
310                     metric->deinitialize();
311                 }
312                 break;
313             }
314             readingsBuffer.emplace(id, metadata, value, timestamp);
315         }
316     }
317 
318     readings = {
319         Clock().timestamp(),
320         std::vector<ReadingData>(readingsBuffer.begin(), readingsBuffer.end())};
321 
322     reportIface->signal_property("Readings");
323 }
324 
325 bool Report::storeConfiguration() const
326 {
327     try
328     {
329         nlohmann::json data;
330 
331         data["Enabled"] = enabled;
332         data["Version"] = reportVersion;
333         data["Id"] = id;
334         data["Name"] = name;
335         data["ReportingType"] = utils::toUnderlying(reportingType);
336         data["ReportActions"] =
337             utils::transform(reportActions, [](const auto reportAction) {
338                 return utils::toUnderlying(reportAction);
339             });
340         data["Interval"] = interval.count();
341         data["AppendLimit"] = appendLimit;
342         data["ReportUpdates"] = utils::toUnderlying(reportUpdates);
343         data["ReadingParameters"] =
344             utils::transform(metrics, [](const auto& metric) {
345                 return metric->dumpConfiguration();
346             });
347 
348         reportStorage.store(fileName(), data);
349     }
350     catch (const std::exception& e)
351     {
352         phosphor::logging::log<phosphor::logging::level::ERR>(
353             "Failed to store a report in storage",
354             phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
355         return false;
356     }
357 
358     return true;
359 }
360 interfaces::JsonStorage::FilePath Report::fileName() const
361 {
362     return interfaces::JsonStorage::FilePath{
363         std::to_string(std::hash<std::string>{}(id))};
364 }
365