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