1 #include "report.hpp" 2 3 #include "report_manager.hpp" 4 #include "utils/clock.hpp" 5 #include "utils/contains.hpp" 6 #include "utils/transform.hpp" 7 8 #include <phosphor-logging/log.hpp> 9 #include <sdbusplus/vtable.hpp> 10 11 #include <limits> 12 #include <numeric> 13 14 Report::Report(boost::asio::io_context& ioc, 15 const std::shared_ptr<sdbusplus::asio::object_server>& objServer, 16 const std::string& reportId, const std::string& reportName, 17 const ReportingType reportingTypeIn, 18 std::vector<ReportAction> reportActionsIn, 19 const Milliseconds intervalIn, const uint64_t appendLimitIn, 20 const ReportUpdates reportUpdatesIn, 21 interfaces::ReportManager& reportManager, 22 interfaces::JsonStorage& reportStorageIn, 23 std::vector<std::shared_ptr<interfaces::Metric>> metricsIn, 24 const bool enabledIn, std::unique_ptr<interfaces::Clock> clock) : 25 id(reportId), 26 name(reportName), reportingType(reportingTypeIn), interval(intervalIn), 27 reportActions(std::move(reportActionsIn)), 28 sensorCount(getSensorCount(metricsIn)), 29 appendLimit(deduceAppendLimit(appendLimitIn)), 30 reportUpdates(reportUpdatesIn), 31 readingsBuffer(deduceBufferSize(reportUpdates, reportingType)), 32 objServer(objServer), metrics(std::move(metricsIn)), timer(ioc), 33 reportStorage(reportStorageIn), enabled(enabledIn), clock(std::move(clock)) 34 { 35 readingParameters = 36 toReadingParameters(utils::transform(metrics, [](const auto& metric) { 37 return metric->dumpConfiguration(); 38 })); 39 40 readingParametersPastVersion = 41 utils::transform(readingParameters, [](const auto& item) { 42 const auto& [sensorData, operationType, id, collectionTimeScope, 43 collectionDuration] = item; 44 45 return ReadingParametersPastVersion::value_type( 46 std::get<0>(sensorData.front()), operationType, id, 47 std::get<1>(sensorData.front())); 48 }); 49 50 deleteIface = objServer->add_unique_interface( 51 getPath(), deleteIfaceName, 52 [this, &ioc, &reportManager](auto& dbusIface) { 53 dbusIface.register_method("Delete", [this, &ioc, &reportManager] { 54 if (persistency) 55 { 56 reportStorage.remove(fileName()); 57 } 58 boost::asio::post(ioc, [this, &reportManager] { 59 reportManager.removeReport(this); 60 }); 61 }); 62 }); 63 64 persistency = storeConfiguration(); 65 reportIface = makeReportInterface(); 66 67 if (reportingType == ReportingType::periodic) 68 { 69 scheduleTimer(interval); 70 } 71 72 if (enabled) 73 { 74 for (auto& metric : this->metrics) 75 { 76 metric->initialize(); 77 } 78 } 79 } 80 81 uint64_t Report::getSensorCount( 82 std::vector<std::shared_ptr<interfaces::Metric>>& metrics) 83 { 84 uint64_t sensorCount = 0; 85 for (auto& metric : metrics) 86 { 87 sensorCount += metric->sensorCount(); 88 } 89 return sensorCount; 90 } 91 92 std::optional<uint64_t> 93 Report::deduceAppendLimit(const uint64_t appendLimitIn) const 94 { 95 if (appendLimitIn == std::numeric_limits<uint64_t>::max()) 96 { 97 return std::nullopt; 98 } 99 else 100 { 101 return appendLimitIn; 102 } 103 } 104 105 uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn, 106 const ReportingType reportingTypeIn) const 107 { 108 if (reportUpdatesIn == ReportUpdates::overwrite || 109 reportingTypeIn == ReportingType::onRequest) 110 { 111 return sensorCount; 112 } 113 else 114 { 115 return appendLimit.value_or(sensorCount); 116 } 117 } 118 119 void Report::setReportUpdates(const ReportUpdates newReportUpdates) 120 { 121 if (reportUpdates != newReportUpdates) 122 { 123 if (reportingType != ReportingType::onRequest && 124 (reportUpdates == ReportUpdates::overwrite || 125 newReportUpdates == ReportUpdates::overwrite)) 126 { 127 readingsBuffer.clearAndResize( 128 deduceBufferSize(newReportUpdates, reportingType)); 129 } 130 reportUpdates = newReportUpdates; 131 } 132 } 133 134 std::unique_ptr<sdbusplus::asio::dbus_interface> Report::makeReportInterface() 135 { 136 auto dbusIface = 137 objServer->add_unique_interface(getPath(), reportIfaceName); 138 dbusIface->register_property_rw( 139 "Enabled", enabled, sdbusplus::vtable::property_::emits_change, 140 [this](bool newVal, const auto&) { 141 if (newVal != enabled) 142 { 143 if (true == newVal && ReportingType::periodic == reportingType) 144 { 145 scheduleTimer(interval); 146 } 147 if (newVal) 148 { 149 for (auto& metric : metrics) 150 { 151 metric->initialize(); 152 } 153 } 154 else 155 { 156 for (auto& metric : metrics) 157 { 158 metric->deinitialize(); 159 } 160 } 161 162 enabled = newVal; 163 persistency = storeConfiguration(); 164 } 165 return 1; 166 }, 167 [this](const auto&) { return enabled; }); 168 dbusIface->register_property_rw( 169 "Interval", interval.count(), 170 sdbusplus::vtable::property_::emits_change, 171 [this](uint64_t newVal, auto&) { 172 if (Milliseconds newValT{newVal}; 173 newValT >= ReportManager::minInterval) 174 { 175 if (newValT != interval) 176 { 177 interval = newValT; 178 persistency = storeConfiguration(); 179 } 180 return 1; 181 } 182 throw sdbusplus::exception::SdBusError( 183 static_cast<int>(std::errc::invalid_argument), 184 "Invalid interval"); 185 }, 186 [this](const auto&) { return interval.count(); }); 187 dbusIface->register_property_rw( 188 "Persistency", persistency, sdbusplus::vtable::property_::emits_change, 189 [this](bool newVal, const auto&) { 190 if (newVal == persistency) 191 { 192 return 1; 193 } 194 if (newVal) 195 { 196 persistency = storeConfiguration(); 197 } 198 else 199 { 200 reportStorage.remove(fileName()); 201 persistency = false; 202 } 203 return 1; 204 }, 205 [this](const auto&) { return persistency; }); 206 207 auto readingsFlag = sdbusplus::vtable::property_::none; 208 if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate)) 209 { 210 readingsFlag = sdbusplus::vtable::property_::emits_change; 211 } 212 dbusIface->register_property_r("Readings", readings, readingsFlag, 213 [this](const auto&) { return readings; }); 214 dbusIface->register_property_r( 215 "ReportingType", std::string(), sdbusplus::vtable::property_::const_, 216 [this](const auto&) { return utils::enumToString(reportingType); }); 217 dbusIface->register_property_r( 218 "ReadingParameters", readingParametersPastVersion, 219 sdbusplus::vtable::property_::const_, 220 [this](const auto&) { return readingParametersPastVersion; }); 221 dbusIface->register_property_r( 222 "ReadingParametersFutureVersion", readingParameters, 223 sdbusplus::vtable::property_::const_, 224 [this](const auto&) { return readingParameters; }); 225 dbusIface->register_property_r( 226 "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::const_, 227 [this](const auto&) { 228 return utils::contains(reportActions, 229 ReportAction::emitsReadingsUpdate); 230 }); 231 dbusIface->register_property_r("Name", std::string{}, 232 sdbusplus::vtable::property_::const_, 233 [this](const auto&) { return name; }); 234 dbusIface->register_property_r( 235 "LogToMetricReportsCollection", bool{}, 236 sdbusplus::vtable::property_::const_, [this](const auto&) { 237 return utils::contains(reportActions, 238 ReportAction::logToMetricReportsCollection); 239 }); 240 dbusIface->register_property_r( 241 "ReportActions", std::vector<std::string>{}, 242 sdbusplus::vtable::property_::const_, [this](const auto&) { 243 return utils::transform(reportActions, [](const auto reportAction) { 244 return utils::enumToString(reportAction); 245 }); 246 }); 247 dbusIface->register_property_r( 248 "AppendLimit", appendLimit.value_or(sensorCount), 249 sdbusplus::vtable::property_::emits_change, 250 [this](const auto&) { return appendLimit.value_or(sensorCount); }); 251 dbusIface->register_property_rw( 252 "ReportUpdates", std::string(), 253 sdbusplus::vtable::property_::emits_change, 254 [this](auto newVal, auto& oldVal) { 255 setReportUpdates(utils::toReportUpdates(newVal)); 256 oldVal = newVal; 257 return 1; 258 }, 259 [this](const auto&) { return utils::enumToString(reportUpdates); }); 260 dbusIface->register_method("Update", [this] { 261 if (reportingType == ReportingType::onRequest) 262 { 263 updateReadings(); 264 } 265 }); 266 constexpr bool skipPropertiesChangedSignal = true; 267 dbusIface->initialize(skipPropertiesChangedSignal); 268 return dbusIface; 269 } 270 271 void Report::timerProc(boost::system::error_code ec, Report& self) 272 { 273 if (ec) 274 { 275 return; 276 } 277 278 self.updateReadings(); 279 self.scheduleTimer(self.interval); 280 } 281 282 void Report::scheduleTimer(Milliseconds timerInterval) 283 { 284 timer.expires_after(timerInterval); 285 timer.async_wait( 286 [this](boost::system::error_code ec) { timerProc(ec, *this); }); 287 } 288 289 void Report::updateReadings() 290 { 291 if (!enabled) 292 { 293 return; 294 } 295 296 if (reportUpdates == ReportUpdates::overwrite || 297 reportingType == ReportingType::onRequest) 298 { 299 readingsBuffer.clear(); 300 } 301 302 for (const auto& metric : metrics) 303 { 304 for (const auto& [id, metadata, value, timestamp] : 305 metric->getReadings()) 306 { 307 if (reportUpdates == ReportUpdates::appendStopsWhenFull && 308 readingsBuffer.isFull()) 309 { 310 enabled = false; 311 for (auto& m : metrics) 312 { 313 m->deinitialize(); 314 } 315 break; 316 } 317 readingsBuffer.emplace(id, metadata, value, timestamp); 318 } 319 } 320 321 readings = { 322 std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp()) 323 .count(), 324 std::vector<ReadingData>(readingsBuffer.begin(), readingsBuffer.end())}; 325 326 reportIface->signal_property("Readings"); 327 } 328 329 bool Report::storeConfiguration() const 330 { 331 try 332 { 333 nlohmann::json data; 334 335 data["Enabled"] = enabled; 336 data["Version"] = reportVersion; 337 data["Id"] = id; 338 data["Name"] = name; 339 data["ReportingType"] = utils::toUnderlying(reportingType); 340 data["ReportActions"] = 341 utils::transform(reportActions, [](const auto reportAction) { 342 return utils::toUnderlying(reportAction); 343 }); 344 data["Interval"] = interval.count(); 345 data["AppendLimit"] = 346 appendLimit.value_or(std::numeric_limits<uint64_t>::max()); 347 data["ReportUpdates"] = utils::toUnderlying(reportUpdates); 348 data["ReadingParameters"] = 349 utils::transform(metrics, [](const auto& metric) { 350 return metric->dumpConfiguration(); 351 }); 352 353 reportStorage.store(fileName(), data); 354 } 355 catch (const std::exception& e) 356 { 357 phosphor::logging::log<phosphor::logging::level::ERR>( 358 "Failed to store a report in storage", 359 phosphor::logging::entry("EXCEPTION_MSG=%s", e.what())); 360 return false; 361 } 362 363 return true; 364 } 365 interfaces::JsonStorage::FilePath Report::fileName() const 366 { 367 return interfaces::JsonStorage::FilePath{ 368 std::to_string(std::hash<std::string>{}(id))}; 369 } 370