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