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