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