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