xref: /openbmc/telemetry/src/report.cpp (revision 51f0fd50)
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 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 1;
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 1;
180             }
181             throw sdbusplus::exception::SdBusError(
182                 static_cast<int>(std::errc::invalid_argument),
183                 "Invalid interval");
184         },
185         [this](const auto&) { return interval.count(); });
186     dbusIface->register_property_rw(
187         "Persistency", persistency, sdbusplus::vtable::property_::emits_change,
188         [this](bool newVal, const auto&) {
189             if (newVal == persistency)
190             {
191                 return 1;
192             }
193             if (newVal)
194             {
195                 persistency = storeConfiguration();
196             }
197             else
198             {
199                 reportStorage.remove(fileName());
200                 persistency = false;
201             }
202             return 1;
203         },
204         [this](const auto&) { return persistency; });
205 
206     auto readingsFlag = sdbusplus::vtable::property_::none;
207     if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
208     {
209         readingsFlag = sdbusplus::vtable::property_::emits_change;
210     }
211     dbusIface->register_property_r("Readings", readings, readingsFlag,
212                                    [this](const auto&) { return readings; });
213     dbusIface->register_property_r(
214         "ReportingType", std::string(), sdbusplus::vtable::property_::const_,
215         [this](const auto&) { return utils::enumToString(reportingType); });
216     dbusIface->register_property_r(
217         "ReadingParameters", readingParametersPastVersion,
218         sdbusplus::vtable::property_::const_,
219         [this](const auto&) { return readingParametersPastVersion; });
220     dbusIface->register_property_r(
221         "ReadingParametersFutureVersion", readingParameters,
222         sdbusplus::vtable::property_::const_,
223         [this](const auto&) { return readingParameters; });
224     dbusIface->register_property_r(
225         "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::const_,
226         [this](const auto&) {
227             return utils::contains(reportActions,
228                                    ReportAction::emitsReadingsUpdate);
229         });
230     dbusIface->register_property_r("Name", std::string{},
231                                    sdbusplus::vtable::property_::const_,
232                                    [this](const auto&) { return name; });
233     dbusIface->register_property_r(
234         "LogToMetricReportsCollection", bool{},
235         sdbusplus::vtable::property_::const_, [this](const auto&) {
236             return utils::contains(reportActions,
237                                    ReportAction::logToMetricReportsCollection);
238         });
239     dbusIface->register_property_r(
240         "ReportActions", std::vector<std::string>{},
241         sdbusplus::vtable::property_::const_, [this](const auto&) {
242             return utils::transform(reportActions, [](const auto reportAction) {
243                 return utils::enumToString(reportAction);
244             });
245         });
246     dbusIface->register_property_r("AppendLimit", appendLimit,
247                                    sdbusplus::vtable::property_::emits_change,
248                                    [this](const auto&) { return appendLimit; });
249     dbusIface->register_property_rw(
250         "ReportUpdates", std::string(),
251         sdbusplus::vtable::property_::emits_change,
252         [this](auto newVal, auto& oldVal) {
253             ReportManager::verifyReportUpdates(utils::toReportUpdates(newVal));
254             setReportUpdates(utils::toReportUpdates(newVal));
255             oldVal = newVal;
256             return 1;
257         },
258         [this](const auto&) { return utils::enumToString(reportUpdates); });
259     dbusIface->register_method("Update", [this] {
260         if (reportingType == ReportingType::onRequest)
261         {
262             updateReadings();
263         }
264     });
265     constexpr bool skipPropertiesChangedSignal = true;
266     dbusIface->initialize(skipPropertiesChangedSignal);
267     return dbusIface;
268 }
269 
270 void Report::timerProc(boost::system::error_code ec, Report& self)
271 {
272     if (ec)
273     {
274         return;
275     }
276 
277     self.updateReadings();
278     self.scheduleTimer(self.interval);
279 }
280 
281 void Report::scheduleTimer(Milliseconds timerInterval)
282 {
283     timer.expires_after(timerInterval);
284     timer.async_wait(
285         [this](boost::system::error_code ec) { timerProc(ec, *this); });
286 }
287 
288 void Report::updateReadings()
289 {
290     if (!enabled)
291     {
292         return;
293     }
294 
295     if (reportUpdates == ReportUpdates::overwrite ||
296         reportingType == ReportingType::onRequest)
297     {
298         readingsBuffer.clear();
299     }
300 
301     for (const auto& metric : metrics)
302     {
303         for (const auto& [id, metadata, value, timestamp] :
304              metric->getReadings())
305         {
306             if (reportUpdates == ReportUpdates::appendStopsWhenFull &&
307                 readingsBuffer.isFull())
308             {
309                 enabled = false;
310                 for (auto& metric : metrics)
311                 {
312                     metric->deinitialize();
313                 }
314                 break;
315             }
316             readingsBuffer.emplace(id, metadata, value, timestamp);
317         }
318     }
319 
320     readings = {
321         std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp())
322             .count(),
323         std::vector<ReadingData>(readingsBuffer.begin(), readingsBuffer.end())};
324 
325     reportIface->signal_property("Readings");
326 }
327 
328 bool Report::storeConfiguration() const
329 {
330     try
331     {
332         nlohmann::json data;
333 
334         data["Enabled"] = enabled;
335         data["Version"] = reportVersion;
336         data["Id"] = id;
337         data["Name"] = name;
338         data["ReportingType"] = utils::toUnderlying(reportingType);
339         data["ReportActions"] =
340             utils::transform(reportActions, [](const auto reportAction) {
341                 return utils::toUnderlying(reportAction);
342             });
343         data["Interval"] = interval.count();
344         data["AppendLimit"] = appendLimit;
345         data["ReportUpdates"] = utils::toUnderlying(reportUpdates);
346         data["ReadingParameters"] =
347             utils::transform(metrics, [](const auto& metric) {
348                 return metric->dumpConfiguration();
349             });
350 
351         reportStorage.store(fileName(), data);
352     }
353     catch (const std::exception& e)
354     {
355         phosphor::logging::log<phosphor::logging::level::ERR>(
356             "Failed to store a report in storage",
357             phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
358         return false;
359     }
360 
361     return true;
362 }
363 interfaces::JsonStorage::FilePath Report::fileName() const
364 {
365     return interfaces::JsonStorage::FilePath{
366         std::to_string(std::hash<std::string>{}(id))};
367 }
368