1 #include "report.hpp"
2
3 #include "errors.hpp"
4 #include "messages/collect_trigger_id.hpp"
5 #include "messages/trigger_presence_changed_ind.hpp"
6 #include "messages/update_report_ind.hpp"
7 #include "report_manager.hpp"
8 #include "utils/clock.hpp"
9 #include "utils/contains.hpp"
10 #include "utils/dbus_path_utils.hpp"
11 #include "utils/ensure.hpp"
12 #include "utils/transform.hpp"
13
14 #include <phosphor-logging/log.hpp>
15 #include <sdbusplus/vtable.hpp>
16
17 #include <limits>
18 #include <numeric>
19 #include <optional>
20
Report(boost::asio::io_context & ioc,const std::shared_ptr<sdbusplus::asio::object_server> & objServer,const std::string & reportId,const std::string & reportName,const ReportingType reportingTypeIn,std::vector<ReportAction> reportActionsIn,const Milliseconds intervalIn,const uint64_t appendLimitIn,const ReportUpdates reportUpdatesIn,interfaces::ReportManager & reportManager,interfaces::JsonStorage & reportStorageIn,std::vector<std::shared_ptr<interfaces::Metric>> metricsIn,const interfaces::ReportFactory & reportFactory,const bool enabledIn,std::unique_ptr<interfaces::Clock> clock,Readings readingsIn)21 Report::Report(boost::asio::io_context& ioc,
22 const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
23 const std::string& reportId, const std::string& reportName,
24 const ReportingType reportingTypeIn,
25 std::vector<ReportAction> reportActionsIn,
26 const Milliseconds intervalIn, const uint64_t appendLimitIn,
27 const ReportUpdates reportUpdatesIn,
28 interfaces::ReportManager& reportManager,
29 interfaces::JsonStorage& reportStorageIn,
30 std::vector<std::shared_ptr<interfaces::Metric>> metricsIn,
31 const interfaces::ReportFactory& reportFactory,
32 const bool enabledIn, std::unique_ptr<interfaces::Clock> clock,
33 Readings readingsIn) :
34 id(reportId), path(utils::pathAppend(utils::constants::reportDirPath, id)),
35 name(reportName), reportingType(reportingTypeIn), interval(intervalIn),
36 reportActions(reportActionsIn.begin(), reportActionsIn.end()),
37 metricCount(getMetricCount(metricsIn)), appendLimit(appendLimitIn),
38 reportUpdates(reportUpdatesIn), readings(std::move(readingsIn)),
39 readingsBuffer(std::get<1>(readings),
40 deduceBufferSize(reportUpdates, reportingType)),
41 objServer(objServer), metrics(std::move(metricsIn)), timer(ioc),
42 triggerIds(collectTriggerIds(ioc)), reportStorage(reportStorageIn),
43 clock(std::move(clock)), messanger(ioc)
44 {
45 readingParameters =
46 toReadingParameters(utils::transform(metrics, [](const auto& metric) {
47 return metric->dumpConfiguration();
48 }));
49
50 reportActions.insert(ReportAction::logToMetricReportsCollection);
51
52 deleteIface = objServer->add_unique_interface(
53 getPath(), deleteIfaceName,
54 [this, &ioc, &reportManager](auto& dbusIface) {
55 dbusIface.register_method("Delete", [this, &ioc, &reportManager] {
56 if (persistency)
57 {
58 persistency = false;
59
60 reportIface->signal_property("Persistency");
61 }
62
63 boost::asio::post(ioc, [this, &reportManager] {
64 reportManager.removeReport(this);
65 });
66 });
67 });
68
69 auto errorMessages = verify(reportingType, interval);
70 state.set<ReportFlags::enabled, ReportFlags::valid>(enabledIn,
71 errorMessages.empty());
72
73 reportIface = makeReportInterface(reportFactory);
74 persistency = storeConfiguration();
75
76 messanger.on_receive<messages::TriggerPresenceChangedInd>(
77 [this](const auto& msg) {
78 const auto oldSize = triggerIds.size();
79
80 if (msg.presence == messages::Presence::Exist)
81 {
82 if (utils::contains(msg.reportIds, id))
83 {
84 triggerIds.insert(msg.triggerId);
85 }
86 else if (!utils::contains(msg.reportIds, id))
87 {
88 triggerIds.erase(msg.triggerId);
89 }
90 }
91 else if (msg.presence == messages::Presence::Removed)
92 {
93 triggerIds.erase(msg.triggerId);
94 }
95
96 if (triggerIds.size() != oldSize)
97 {
98 reportIface->signal_property("Triggers");
99 }
100 });
101
102 messanger.on_receive<messages::UpdateReportInd>([this](const auto& msg) {
103 if (utils::contains(msg.reportIds, id))
104 {
105 updateReadings();
106 }
107 });
108 }
109
~Report()110 Report::~Report()
111 {
112 if (persistency)
113 {
114 if (shouldStoreMetricValues())
115 {
116 storeConfiguration();
117 }
118 }
119 else
120 {
121 reportStorage.remove(reportFileName());
122 }
123 }
124
activate()125 void Report::activate()
126 {
127 for (auto& metric : metrics)
128 {
129 metric->initialize();
130 }
131
132 scheduleTimer();
133 }
134
deactivate()135 void Report::deactivate()
136 {
137 for (auto& metric : metrics)
138 {
139 metric->deinitialize();
140 }
141
142 unregisterFromMetrics = nullptr;
143 timer.cancel();
144 }
145
getMetricCount(const std::vector<std::shared_ptr<interfaces::Metric>> & metrics)146 uint64_t Report::getMetricCount(
147 const std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
148 {
149 uint64_t metricCount = 0;
150 for (auto& metric : metrics)
151 {
152 metricCount += metric->metricCount();
153 }
154 return metricCount;
155 }
156
deduceBufferSize(const ReportUpdates reportUpdatesIn,const ReportingType reportingTypeIn) const157 uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn,
158 const ReportingType reportingTypeIn) const
159 {
160 if (reportUpdatesIn == ReportUpdates::overwrite ||
161 reportingTypeIn == ReportingType::onRequest)
162 {
163 return metricCount;
164 }
165 else
166 {
167 return appendLimit;
168 }
169 }
170
setReadingBuffer(const ReportUpdates newReportUpdates)171 void Report::setReadingBuffer(const ReportUpdates newReportUpdates)
172 {
173 const auto newBufferSize = deduceBufferSize(newReportUpdates,
174 reportingType);
175 if (readingsBuffer.size() != newBufferSize)
176 {
177 readingsBuffer.clearAndResize(newBufferSize);
178 }
179 }
180
setReportUpdates(const ReportUpdates newReportUpdates)181 void Report::setReportUpdates(const ReportUpdates newReportUpdates)
182 {
183 if (reportUpdates != newReportUpdates)
184 {
185 setReadingBuffer(newReportUpdates);
186 reportUpdates = newReportUpdates;
187 }
188 }
189
190 std::unique_ptr<sdbusplus::asio::dbus_interface>
makeReportInterface(const interfaces::ReportFactory & reportFactory)191 Report::makeReportInterface(const interfaces::ReportFactory& reportFactory)
192 {
193 auto dbusIface = objServer->add_unique_interface(getPath(),
194 reportIfaceName);
195 dbusIface->register_property_rw<bool>(
196 "Enabled", sdbusplus::vtable::property_::emits_change,
197 [this](bool newVal, auto& oldValue) {
198 if (newVal != state.get<ReportFlags::enabled>())
199 {
200 state.set<ReportFlags::enabled>(oldValue = newVal);
201
202 persistency = storeConfiguration();
203 }
204 return 1;
205 }, [this](const auto&) { return state.get<ReportFlags::enabled>(); });
206 dbusIface->register_method(
207 "SetReportingProperties",
208 [this](std::string newReportingType, uint64_t newInterval) {
209 ReportingType newReportingTypeT = reportingType;
210
211 if (!newReportingType.empty())
212 {
213 newReportingTypeT = utils::toReportingType(newReportingType);
214 }
215
216 Milliseconds newIntervalT = interval;
217
218 if (newInterval != std::numeric_limits<uint64_t>::max())
219 {
220 newIntervalT = Milliseconds(newInterval);
221 }
222
223 auto errorMessages = verify(newReportingTypeT, newIntervalT);
224
225 if (!errorMessages.empty())
226 {
227 if (newIntervalT != interval)
228 {
229 throw errors::InvalidArgument("Interval");
230 }
231
232 throw errors::InvalidArgument("ReportingType");
233 }
234
235 if (reportingType != newReportingTypeT)
236 {
237 reportingType = newReportingTypeT;
238 reportIface->signal_property("ReportingType");
239 }
240
241 if (interval != newIntervalT)
242 {
243 interval = newIntervalT;
244 reportIface->signal_property("Interval");
245 }
246
247 if (state.set<ReportFlags::valid>(errorMessages.empty()) ==
248 StateEvent::active)
249 {
250 scheduleTimer();
251 }
252
253 persistency = storeConfiguration();
254
255 setReadingBuffer(reportUpdates);
256 });
257 dbusIface->register_property_r<uint64_t>(
258 "Interval", sdbusplus::vtable::property_::emits_change,
259 [this](const auto&) { return interval.count(); });
260 dbusIface->register_property_rw<bool>(
261 "Persistency", sdbusplus::vtable::property_::emits_change,
262 [this](bool newVal, auto& oldVal) {
263 if (newVal == persistency)
264 {
265 return 1;
266 }
267 if (newVal)
268 {
269 persistency = oldVal = storeConfiguration();
270 }
271 else
272 {
273 reportStorage.remove(reportFileName());
274 persistency = oldVal = false;
275 }
276 return 1;
277 }, [this](const auto&) { return persistency; });
278
279 dbusIface->register_property_r("Readings", readings,
280 sdbusplus::vtable::property_::emits_change,
281 [this](const auto&) { return readings; });
282 dbusIface->register_property_r<std::string>(
283 "ReportingType", sdbusplus::vtable::property_::emits_change,
284 [this](const auto&) { return utils::enumToString(reportingType); });
285 dbusIface->register_property_rw(
286 "ReadingParameters", readingParameters,
287 sdbusplus::vtable::property_::emits_change,
288 [this, &reportFactory](auto newVal, auto& oldVal) {
289 auto labeledMetricParams = reportFactory.convertMetricParams(newVal);
290 ReportManager::verifyMetricParams(labeledMetricParams);
291 reportFactory.updateMetrics(metrics, state.get<ReportFlags::enabled>(),
292 labeledMetricParams);
293 readingParameters = toReadingParameters(
294 utils::transform(metrics, [](const auto& metric) {
295 return metric->dumpConfiguration();
296 }));
297 metricCount = getMetricCount(metrics);
298 setReadingBuffer(reportUpdates);
299 persistency = storeConfiguration();
300 oldVal = std::move(newVal);
301 return 1;
302 }, [this](const auto&) { return readingParameters; });
303 dbusIface->register_property_r<bool>("EmitsReadingsUpdate",
304 sdbusplus::vtable::property_::none,
305 [this](const auto&) {
306 return reportActions.contains(ReportAction::emitsReadingsUpdate);
307 });
308 dbusIface->register_property_r<std::string>(
309 "Name", sdbusplus::vtable::property_::const_,
310 [this](const auto&) { return name; });
311 dbusIface->register_property_r<bool>("LogToMetricReportsCollection",
312 sdbusplus::vtable::property_::const_,
313 [this](const auto&) {
314 return reportActions.contains(
315 ReportAction::logToMetricReportsCollection);
316 });
317 dbusIface->register_property_rw<std::vector<std::string>>(
318 "ReportActions", sdbusplus::vtable::property_::emits_change,
319 [this](auto newVal, auto& oldVal) {
320 auto tmp = utils::transform<std::unordered_set>(
321 newVal, [](const auto& reportAction) {
322 return utils::toReportAction(reportAction);
323 });
324 tmp.insert(ReportAction::logToMetricReportsCollection);
325
326 if (tmp != reportActions)
327 {
328 reportActions = tmp;
329 persistency = storeConfiguration();
330 oldVal = std::move(newVal);
331 }
332 return 1;
333 }, [this](const auto&) {
334 return utils::transform<std::vector>(reportActions,
335 [](const auto reportAction) {
336 return utils::enumToString(reportAction);
337 });
338 });
339 dbusIface->register_property_r<uint64_t>(
340 "AppendLimit", sdbusplus::vtable::property_::emits_change,
341 [this](const auto&) { return appendLimit; });
342 dbusIface->register_property_rw("ReportUpdates", std::string(),
343 sdbusplus::vtable::property_::emits_change,
344 [this](auto newVal, auto& oldVal) {
345 setReportUpdates(utils::toReportUpdates(newVal));
346 oldVal = newVal;
347 return 1;
348 }, [this](const auto&) { return utils::enumToString(reportUpdates); });
349 dbusIface->register_property_r(
350 "Triggers", std::vector<sdbusplus::message::object_path>{},
351 sdbusplus::vtable::property_::emits_change, [this](const auto&) {
352 return utils::transform<std::vector>(triggerIds,
353 [](const auto& triggerId) {
354 return utils::pathAppend(utils::constants::triggerDirPath,
355 triggerId);
356 });
357 });
358 dbusIface->register_method("Update", [this] {
359 if (reportingType == ReportingType::onRequest)
360 {
361 updateReadings();
362 }
363 });
364 constexpr bool skipPropertiesChangedSignal = true;
365 dbusIface->initialize(skipPropertiesChangedSignal);
366 return dbusIface;
367 }
368
timerProcForPeriodicReport(boost::system::error_code ec,Report & self)369 void Report::timerProcForPeriodicReport(boost::system::error_code ec,
370 Report& self)
371 {
372 if (ec)
373 {
374 return;
375 }
376
377 self.updateReadings();
378 self.scheduleTimerForPeriodicReport(self.interval);
379 }
380
timerProcForOnChangeReport(boost::system::error_code ec,Report & self)381 void Report::timerProcForOnChangeReport(boost::system::error_code ec,
382 Report& self)
383 {
384 if (ec)
385 {
386 return;
387 }
388
389 const auto ensure =
390 utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }};
391
392 self.onChangeContext.emplace(self);
393
394 const auto steadyTimestamp = self.clock->steadyTimestamp();
395
396 for (auto& metric : self.metrics)
397 {
398 metric->updateReadings(steadyTimestamp);
399 }
400
401 self.scheduleTimerForOnChangeReport();
402 }
403
scheduleTimerForPeriodicReport(Milliseconds timerInterval)404 void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval)
405 {
406 try
407 {
408 timer.expires_after(timerInterval);
409 timer.async_wait([this](boost::system::error_code ec) {
410 timerProcForPeriodicReport(ec, *this);
411 });
412 }
413 catch (const boost::system::system_error& exception)
414 {
415 phosphor::logging::log<phosphor::logging::level::ERR>(
416 "Failed to schedule timer for periodic report: ",
417 phosphor::logging::entry("EXCEPTION_MSG=%s", exception.what()));
418 }
419 }
420
scheduleTimerForOnChangeReport()421 void Report::scheduleTimerForOnChangeReport()
422 {
423 constexpr Milliseconds timerInterval{100};
424
425 timer.expires_after(timerInterval);
426 timer.async_wait([this](boost::system::error_code ec) {
427 timerProcForOnChangeReport(ec, *this);
428 });
429 }
430
updateReadings()431 void Report::updateReadings()
432 {
433 if (!state.isActive())
434 {
435 return;
436 }
437
438 if (reportUpdates == ReportUpdates::overwrite ||
439 reportingType == ReportingType::onRequest)
440 {
441 readingsBuffer.clear();
442 }
443
444 for (const auto& metric : metrics)
445 {
446 if (!state.isActive())
447 {
448 break;
449 }
450
451 for (const auto& [metadata, value, timestamp] :
452 metric->getUpdatedReadings())
453 {
454 if (reportUpdates == ReportUpdates::appendStopsWhenFull &&
455 readingsBuffer.isFull())
456 {
457 state.set<ReportFlags::enabled>(false);
458 reportIface->signal_property("Enabled");
459 break;
460 }
461 readingsBuffer.emplace(metadata, value, timestamp);
462 }
463 }
464
465 std::get<0>(readings) =
466 std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp())
467 .count();
468
469 if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate))
470 {
471 reportIface->signal_property("Readings");
472 }
473 }
474
shouldStoreMetricValues() const475 bool Report::shouldStoreMetricValues() const
476 {
477 return reportingType != ReportingType::onRequest &&
478 reportUpdates == ReportUpdates::appendStopsWhenFull;
479 }
480
storeConfiguration() const481 bool Report::storeConfiguration() const
482 {
483 try
484 {
485 nlohmann::json data;
486
487 data["Enabled"] = state.get<ReportFlags::enabled>();
488 data["Version"] = reportVersion;
489 data["Id"] = id;
490 data["Name"] = name;
491 data["ReportingType"] = utils::toUnderlying(reportingType);
492 data["ReportActions"] = utils::transform(reportActions,
493 [](const auto reportAction) {
494 return utils::toUnderlying(reportAction);
495 });
496 data["Interval"] = interval.count();
497 data["AppendLimit"] = appendLimit;
498 data["ReportUpdates"] = utils::toUnderlying(reportUpdates);
499 data["ReadingParameters"] = utils::transform(
500 metrics,
501 [](const auto& metric) { return metric->dumpConfiguration(); });
502
503 if (shouldStoreMetricValues())
504 {
505 data["MetricValues"] = utils::toLabeledReadings(readings);
506 }
507
508 reportStorage.store(reportFileName(), data);
509 }
510 catch (const std::exception& e)
511 {
512 phosphor::logging::log<phosphor::logging::level::ERR>(
513 "Failed to store a report in storage",
514 phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
515 return false;
516 }
517
518 return true;
519 }
520
reportFileName() const521 interfaces::JsonStorage::FilePath Report::reportFileName() const
522 {
523 return interfaces::JsonStorage::FilePath{
524 std::to_string(std::hash<std::string>{}(id))};
525 }
526
527 std::unordered_set<std::string>
collectTriggerIds(boost::asio::io_context & ioc) const528 Report::collectTriggerIds(boost::asio::io_context& ioc) const
529 {
530 utils::Messanger tmp(ioc);
531
532 auto result = std::unordered_set<std::string>();
533
534 tmp.on_receive<messages::CollectTriggerIdResp>(
535 [&result](const auto& msg) { result.insert(msg.triggerId); });
536
537 tmp.send(messages::CollectTriggerIdReq{id});
538
539 return result;
540 }
541
metricUpdated()542 void Report::metricUpdated()
543 {
544 if (onChangeContext)
545 {
546 onChangeContext->metricUpdated();
547 return;
548 }
549
550 updateReadings();
551 }
552
scheduleTimer()553 void Report::scheduleTimer()
554 {
555 switch (reportingType)
556 {
557 case ReportingType::periodic:
558 {
559 unregisterFromMetrics = nullptr;
560 scheduleTimerForPeriodicReport(interval);
561 break;
562 }
563 case ReportingType::onChange:
564 {
565 if (!unregisterFromMetrics)
566 {
567 unregisterFromMetrics = [this] {
568 for (auto& metric : metrics)
569 {
570 metric->unregisterFromUpdates(*this);
571 }
572 };
573
574 for (auto& metric : metrics)
575 {
576 metric->registerForUpdates(*this);
577 }
578 }
579
580 bool isTimerRequired = false;
581
582 for (auto& metric : metrics)
583 {
584 if (metric->isTimerRequired())
585 {
586 isTimerRequired = true;
587 }
588 }
589
590 if (isTimerRequired)
591 {
592 scheduleTimerForOnChangeReport();
593 }
594 else
595 {
596 timer.cancel();
597 }
598 break;
599 }
600 default:
601 unregisterFromMetrics = nullptr;
602 timer.cancel();
603 break;
604 }
605 }
606
verify(ReportingType reportingType,Milliseconds interval)607 std::vector<ErrorMessage> Report::verify(ReportingType reportingType,
608 Milliseconds interval)
609 {
610 if (interval != Milliseconds{0} && interval < ReportManager::minInterval)
611 {
612 throw errors::InvalidArgument("Interval");
613 }
614
615 std::vector<ErrorMessage> result;
616
617 if ((reportingType == ReportingType::periodic &&
618 interval == Milliseconds{0}) ||
619 (reportingType != ReportingType::periodic &&
620 interval != Milliseconds{0}))
621 {
622 result.emplace_back(ErrorType::propertyConflict, "Interval");
623 result.emplace_back(ErrorType::propertyConflict, "ReportingType");
624 }
625
626 return result;
627 }
628