#include "report.hpp" #include "errors.hpp" #include "messages/collect_trigger_id.hpp" #include "messages/trigger_presence_changed_ind.hpp" #include "messages/update_report_ind.hpp" #include "report_manager.hpp" #include "utils/clock.hpp" #include "utils/contains.hpp" #include "utils/dbus_path_utils.hpp" #include "utils/ensure.hpp" #include "utils/transform.hpp" #include #include #include #include #include Report::Report(boost::asio::io_context& ioc, const std::shared_ptr& objServer, const std::string& reportId, const std::string& reportName, const ReportingType reportingTypeIn, std::vector reportActionsIn, const Milliseconds intervalIn, const uint64_t appendLimitIn, const ReportUpdates reportUpdatesIn, interfaces::ReportManager& reportManager, interfaces::JsonStorage& reportStorageIn, std::vector> metricsIn, const interfaces::ReportFactory& reportFactory, const bool enabledIn, std::unique_ptr clock, Readings readingsIn) : id(reportId), path(utils::pathAppend(utils::constants::reportDirPath, id)), name(reportName), reportingType(reportingTypeIn), interval(intervalIn), reportActions(reportActionsIn.begin(), reportActionsIn.end()), metricCount(getMetricCount(metricsIn)), appendLimit(appendLimitIn), reportUpdates(reportUpdatesIn), readings(std::move(readingsIn)), readingsBuffer(std::get<1>(readings), deduceBufferSize(reportUpdates, reportingType)), objServer(objServer), metrics(std::move(metricsIn)), timer(ioc), triggerIds(collectTriggerIds(ioc)), reportStorage(reportStorageIn), clock(std::move(clock)), messanger(ioc) { readingParameters = toReadingParameters(utils::transform(metrics, [](const auto& metric) { return metric->dumpConfiguration(); })); reportActions.insert(ReportAction::logToMetricReportsCollection); deleteIface = objServer->add_unique_interface( getPath(), deleteIfaceName, [this, &ioc, &reportManager](auto& dbusIface) { dbusIface.register_method("Delete", [this, &ioc, &reportManager] { if (persistency) { persistency = false; reportIface->signal_property("Persistency"); } boost::asio::post(ioc, [this, &reportManager] { reportManager.removeReport(this); }); }); }); auto errorMessages = verify(reportingType, interval); state.set(enabledIn, errorMessages.empty()); reportIface = makeReportInterface(reportFactory); persistency = storeConfiguration(); messanger.on_receive( [this](const auto& msg) { const auto oldSize = triggerIds.size(); if (msg.presence == messages::Presence::Exist) { if (utils::contains(msg.reportIds, id)) { triggerIds.insert(msg.triggerId); } else if (!utils::contains(msg.reportIds, id)) { triggerIds.erase(msg.triggerId); } } else if (msg.presence == messages::Presence::Removed) { triggerIds.erase(msg.triggerId); } if (triggerIds.size() != oldSize) { reportIface->signal_property("Triggers"); } }); messanger.on_receive([this](const auto& msg) { if (utils::contains(msg.reportIds, id)) { updateReadings(); } }); } Report::~Report() { if (persistency) { if (shouldStoreMetricValues()) { storeConfiguration(); } } else { reportStorage.remove(reportFileName()); } } void Report::activate() { for (auto& metric : metrics) { metric->initialize(); } scheduleTimer(); } void Report::deactivate() { for (auto& metric : metrics) { metric->deinitialize(); } unregisterFromMetrics = nullptr; timer.cancel(); } uint64_t Report::getMetricCount( const std::vector>& metrics) { uint64_t metricCount = 0; for (auto& metric : metrics) { metricCount += metric->metricCount(); } return metricCount; } uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn, const ReportingType reportingTypeIn) const { if (reportUpdatesIn == ReportUpdates::overwrite || reportingTypeIn == ReportingType::onRequest) { return metricCount; } else { return appendLimit; } } void Report::setReadingBuffer(const ReportUpdates newReportUpdates) { const auto newBufferSize = deduceBufferSize(newReportUpdates, reportingType); if (readingsBuffer.size() != newBufferSize) { readingsBuffer.clearAndResize(newBufferSize); } } void Report::setReportUpdates(const ReportUpdates newReportUpdates) { if (reportUpdates != newReportUpdates) { setReadingBuffer(newReportUpdates); reportUpdates = newReportUpdates; } } std::unique_ptr Report::makeReportInterface(const interfaces::ReportFactory& reportFactory) { auto dbusIface = objServer->add_unique_interface(getPath(), reportIfaceName); dbusIface->register_property_rw( "Enabled", sdbusplus::vtable::property_::emits_change, [this](bool newVal, auto& oldValue) { if (newVal != state.get()) { state.set(oldValue = newVal); persistency = storeConfiguration(); } return 1; }, [this](const auto&) { return state.get(); }); dbusIface->register_method( "SetReportingProperties", [this](std::string newReportingType, uint64_t newInterval) { ReportingType newReportingTypeT = reportingType; if (!newReportingType.empty()) { newReportingTypeT = utils::toReportingType(newReportingType); } Milliseconds newIntervalT = interval; if (newInterval != std::numeric_limits::max()) { newIntervalT = Milliseconds(newInterval); } auto errorMessages = verify(newReportingTypeT, newIntervalT); if (!errorMessages.empty()) { if (newIntervalT != interval) { throw errors::InvalidArgument("Interval"); } throw errors::InvalidArgument("ReportingType"); } if (reportingType != newReportingTypeT) { reportingType = newReportingTypeT; reportIface->signal_property("ReportingType"); } if (interval != newIntervalT) { interval = newIntervalT; reportIface->signal_property("Interval"); } if (state.set(errorMessages.empty()) == StateEvent::active) { scheduleTimer(); } persistency = storeConfiguration(); setReadingBuffer(reportUpdates); }); dbusIface->register_property_r( "Interval", sdbusplus::vtable::property_::emits_change, [this](const auto&) { return interval.count(); }); dbusIface->register_property_rw( "Persistency", sdbusplus::vtable::property_::emits_change, [this](bool newVal, auto& oldVal) { if (newVal == persistency) { return 1; } if (newVal) { persistency = oldVal = storeConfiguration(); } else { reportStorage.remove(reportFileName()); persistency = oldVal = false; } return 1; }, [this](const auto&) { return persistency; }); dbusIface->register_property_r("Readings", readings, sdbusplus::vtable::property_::emits_change, [this](const auto&) { return readings; }); dbusIface->register_property_r( "ReportingType", sdbusplus::vtable::property_::emits_change, [this](const auto&) { return utils::enumToString(reportingType); }); dbusIface->register_property_rw( "ReadingParameters", readingParameters, sdbusplus::vtable::property_::emits_change, [this, &reportFactory](auto newVal, auto& oldVal) { auto labeledMetricParams = reportFactory.convertMetricParams(newVal); ReportManager::verifyMetricParams(labeledMetricParams); reportFactory.updateMetrics(metrics, state.get(), labeledMetricParams); readingParameters = toReadingParameters( utils::transform(metrics, [](const auto& metric) { return metric->dumpConfiguration(); })); metricCount = getMetricCount(metrics); setReadingBuffer(reportUpdates); persistency = storeConfiguration(); oldVal = std::move(newVal); return 1; }, [this](const auto&) { return readingParameters; }); dbusIface->register_property_r("EmitsReadingsUpdate", sdbusplus::vtable::property_::none, [this](const auto&) { return reportActions.contains(ReportAction::emitsReadingsUpdate); }); dbusIface->register_property_r( "Name", sdbusplus::vtable::property_::const_, [this](const auto&) { return name; }); dbusIface->register_property_r("LogToMetricReportsCollection", sdbusplus::vtable::property_::const_, [this](const auto&) { return reportActions.contains( ReportAction::logToMetricReportsCollection); }); dbusIface->register_property_rw>( "ReportActions", sdbusplus::vtable::property_::emits_change, [this](auto newVal, auto& oldVal) { auto tmp = utils::transform( newVal, [](const auto& reportAction) { return utils::toReportAction(reportAction); }); tmp.insert(ReportAction::logToMetricReportsCollection); if (tmp != reportActions) { reportActions = tmp; persistency = storeConfiguration(); oldVal = std::move(newVal); } return 1; }, [this](const auto&) { return utils::transform(reportActions, [](const auto reportAction) { return utils::enumToString(reportAction); }); }); dbusIface->register_property_r( "AppendLimit", sdbusplus::vtable::property_::emits_change, [this](const auto&) { return appendLimit; }); dbusIface->register_property_rw("ReportUpdates", std::string(), sdbusplus::vtable::property_::emits_change, [this](auto newVal, auto& oldVal) { setReportUpdates(utils::toReportUpdates(newVal)); oldVal = newVal; return 1; }, [this](const auto&) { return utils::enumToString(reportUpdates); }); dbusIface->register_property_r( "Triggers", std::vector{}, sdbusplus::vtable::property_::emits_change, [this](const auto&) { return utils::transform(triggerIds, [](const auto& triggerId) { return utils::pathAppend(utils::constants::triggerDirPath, triggerId); }); }); dbusIface->register_method("Update", [this] { if (reportingType == ReportingType::onRequest) { updateReadings(); } }); constexpr bool skipPropertiesChangedSignal = true; dbusIface->initialize(skipPropertiesChangedSignal); return dbusIface; } void Report::timerProcForPeriodicReport(boost::system::error_code ec, Report& self) { if (ec) { return; } self.updateReadings(); self.scheduleTimerForPeriodicReport(self.interval); } void Report::timerProcForOnChangeReport(boost::system::error_code ec, Report& self) { if (ec) { return; } const auto ensure = utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }}; self.onChangeContext.emplace(self); const auto steadyTimestamp = self.clock->steadyTimestamp(); for (auto& metric : self.metrics) { metric->updateReadings(steadyTimestamp); } self.scheduleTimerForOnChangeReport(); } void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval) { try { timer.expires_after(timerInterval); timer.async_wait([this](boost::system::error_code ec) { timerProcForPeriodicReport(ec, *this); }); } catch (const boost::system::system_error& exception) { phosphor::logging::log( "Failed to schedule timer for periodic report: ", phosphor::logging::entry("EXCEPTION_MSG=%s", exception.what())); } } void Report::scheduleTimerForOnChangeReport() { constexpr Milliseconds timerInterval{100}; timer.expires_after(timerInterval); timer.async_wait([this](boost::system::error_code ec) { timerProcForOnChangeReport(ec, *this); }); } void Report::updateReadings() { if (!state.isActive()) { return; } if (reportUpdates == ReportUpdates::overwrite || reportingType == ReportingType::onRequest) { readingsBuffer.clear(); } for (const auto& metric : metrics) { if (!state.isActive()) { break; } for (const auto& [metadata, value, timestamp] : metric->getUpdatedReadings()) { if (reportUpdates == ReportUpdates::appendStopsWhenFull && readingsBuffer.isFull()) { state.set(false); reportIface->signal_property("Enabled"); break; } readingsBuffer.emplace(metadata, value, timestamp); } } std::get<0>(readings) = std::chrono::duration_cast(clock->systemTimestamp()) .count(); if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate)) { reportIface->signal_property("Readings"); } } bool Report::shouldStoreMetricValues() const { return reportingType != ReportingType::onRequest && reportUpdates == ReportUpdates::appendStopsWhenFull; } bool Report::storeConfiguration() const { try { nlohmann::json data; data["Enabled"] = state.get(); data["Version"] = reportVersion; data["Id"] = id; data["Name"] = name; data["ReportingType"] = utils::toUnderlying(reportingType); data["ReportActions"] = utils::transform(reportActions, [](const auto reportAction) { return utils::toUnderlying(reportAction); }); data["Interval"] = interval.count(); data["AppendLimit"] = appendLimit; data["ReportUpdates"] = utils::toUnderlying(reportUpdates); data["ReadingParameters"] = utils::transform( metrics, [](const auto& metric) { return metric->dumpConfiguration(); }); if (shouldStoreMetricValues()) { data["MetricValues"] = utils::toLabeledReadings(readings); } reportStorage.store(reportFileName(), data); } catch (const std::exception& e) { phosphor::logging::log( "Failed to store a report in storage", phosphor::logging::entry("EXCEPTION_MSG=%s", e.what())); return false; } return true; } interfaces::JsonStorage::FilePath Report::reportFileName() const { return interfaces::JsonStorage::FilePath{ std::to_string(std::hash{}(id))}; } std::unordered_set Report::collectTriggerIds(boost::asio::io_context& ioc) const { utils::Messanger tmp(ioc); auto result = std::unordered_set(); tmp.on_receive( [&result](const auto& msg) { result.insert(msg.triggerId); }); tmp.send(messages::CollectTriggerIdReq{id}); return result; } void Report::metricUpdated() { if (onChangeContext) { onChangeContext->metricUpdated(); return; } updateReadings(); } void Report::scheduleTimer() { switch (reportingType) { case ReportingType::periodic: { unregisterFromMetrics = nullptr; scheduleTimerForPeriodicReport(interval); break; } case ReportingType::onChange: { if (!unregisterFromMetrics) { unregisterFromMetrics = [this] { for (auto& metric : metrics) { metric->unregisterFromUpdates(*this); } }; for (auto& metric : metrics) { metric->registerForUpdates(*this); } } bool isTimerRequired = false; for (auto& metric : metrics) { if (metric->isTimerRequired()) { isTimerRequired = true; } } if (isTimerRequired) { scheduleTimerForOnChangeReport(); } else { timer.cancel(); } break; } default: unregisterFromMetrics = nullptr; timer.cancel(); break; } } std::vector Report::verify(ReportingType reportingType, Milliseconds interval) { if (interval != Milliseconds{0} && interval < ReportManager::minInterval) { throw errors::InvalidArgument("Interval"); } std::vector result; if ((reportingType == ReportingType::periodic && interval == Milliseconds{0}) || (reportingType != ReportingType::periodic && interval != Milliseconds{0})) { result.emplace_back(ErrorType::propertyConflict, "Interval"); result.emplace_back(ErrorType::propertyConflict, "ReportingType"); } return result; }