1 #include "report.hpp" 2 3 #include "messages/collect_trigger_id.hpp" 4 #include "messages/trigger_presence_changed_ind.hpp" 5 #include "messages/update_report_ind.hpp" 6 #include "report_manager.hpp" 7 #include "utils/clock.hpp" 8 #include "utils/contains.hpp" 9 #include "utils/transform.hpp" 10 11 #include <phosphor-logging/log.hpp> 12 #include <sdbusplus/vtable.hpp> 13 14 #include <limits> 15 #include <numeric> 16 17 Report::Report(boost::asio::io_context& ioc, 18 const std::shared_ptr<sdbusplus::asio::object_server>& objServer, 19 const std::string& reportId, const std::string& reportName, 20 const ReportingType reportingTypeIn, 21 std::vector<ReportAction> reportActionsIn, 22 const Milliseconds intervalIn, const uint64_t appendLimitIn, 23 const ReportUpdates reportUpdatesIn, 24 interfaces::ReportManager& reportManager, 25 interfaces::JsonStorage& reportStorageIn, 26 std::vector<std::shared_ptr<interfaces::Metric>> metricsIn, 27 const interfaces::ReportFactory& reportFactory, 28 const bool enabledIn, std::unique_ptr<interfaces::Clock> clock) : 29 id(reportId), 30 name(reportName), reportingType(reportingTypeIn), interval(intervalIn), 31 reportActions(reportActionsIn.begin(), reportActionsIn.end()), 32 sensorCount(getSensorCount(metricsIn)), 33 appendLimit(deduceAppendLimit(appendLimitIn)), 34 reportUpdates(reportUpdatesIn), 35 readingsBuffer(deduceBufferSize(reportUpdates, reportingType)), 36 objServer(objServer), metrics(std::move(metricsIn)), timer(ioc), 37 triggerIds(collectTriggerIds(ioc)), reportStorage(reportStorageIn), 38 enabled(enabledIn), clock(std::move(clock)), messanger(ioc) 39 { 40 readingParameters = 41 toReadingParameters(utils::transform(metrics, [](const auto& metric) { 42 return metric->dumpConfiguration(); 43 })); 44 45 readingParametersPastVersion = 46 utils::transform(readingParameters, [](const auto& item) { 47 const auto& [sensorData, operationType, id, collectionTimeScope, 48 collectionDuration] = item; 49 50 return ReadingParametersPastVersion::value_type( 51 std::get<0>(sensorData.front()), operationType, id, 52 std::get<1>(sensorData.front())); 53 }); 54 55 reportActions.insert(ReportAction::logToMetricReportsCollection); 56 57 deleteIface = objServer->add_unique_interface( 58 getPath(), deleteIfaceName, 59 [this, &ioc, &reportManager](auto& dbusIface) { 60 dbusIface.register_method("Delete", [this, &ioc, &reportManager] { 61 if (persistency) 62 { 63 reportStorage.remove(fileName()); 64 } 65 boost::asio::post(ioc, [this, &reportManager] { 66 reportManager.removeReport(this); 67 }); 68 }); 69 }); 70 71 persistency = storeConfiguration(); 72 reportIface = makeReportInterface(reportFactory); 73 74 if (reportingType == ReportingType::periodic) 75 { 76 scheduleTimer(interval); 77 } 78 79 if (enabled) 80 { 81 for (auto& metric : this->metrics) 82 { 83 metric->initialize(); 84 } 85 } 86 87 messanger.on_receive<messages::TriggerPresenceChangedInd>( 88 [this](const auto& msg) { 89 const auto oldSize = triggerIds.size(); 90 91 if (msg.presence == messages::Presence::Exist) 92 { 93 if (utils::contains(msg.reportIds, id)) 94 { 95 triggerIds.insert(msg.triggerId); 96 } 97 else if (!utils::contains(msg.reportIds, id)) 98 { 99 triggerIds.erase(msg.triggerId); 100 } 101 } 102 else if (msg.presence == messages::Presence::Removed) 103 { 104 triggerIds.erase(msg.triggerId); 105 } 106 107 if (triggerIds.size() != oldSize) 108 { 109 reportIface->signal_property("TriggerIds"); 110 } 111 }); 112 113 messanger.on_receive<messages::UpdateReportInd>([this](const auto& msg) { 114 if (utils::contains(msg.reportIds, id)) 115 { 116 updateReadings(); 117 } 118 }); 119 } 120 121 uint64_t Report::getSensorCount( 122 std::vector<std::shared_ptr<interfaces::Metric>>& metrics) 123 { 124 uint64_t sensorCount = 0; 125 for (auto& metric : metrics) 126 { 127 sensorCount += metric->sensorCount(); 128 } 129 return sensorCount; 130 } 131 132 std::optional<uint64_t> 133 Report::deduceAppendLimit(const uint64_t appendLimitIn) const 134 { 135 if (appendLimitIn == std::numeric_limits<uint64_t>::max()) 136 { 137 return std::nullopt; 138 } 139 else 140 { 141 return appendLimitIn; 142 } 143 } 144 145 uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn, 146 const ReportingType reportingTypeIn) const 147 { 148 if (reportUpdatesIn == ReportUpdates::overwrite || 149 reportingTypeIn == ReportingType::onRequest) 150 { 151 return sensorCount; 152 } 153 else 154 { 155 return appendLimit.value_or(sensorCount); 156 } 157 } 158 159 void Report::setReadingBuffer(const ReportUpdates newReportUpdates) 160 { 161 if (reportingType != ReportingType::onRequest && 162 (reportUpdates == ReportUpdates::overwrite || 163 newReportUpdates == ReportUpdates::overwrite)) 164 { 165 readingsBuffer.clearAndResize( 166 deduceBufferSize(newReportUpdates, reportingType)); 167 } 168 } 169 170 void Report::setReportUpdates(const ReportUpdates newReportUpdates) 171 { 172 if (reportUpdates != newReportUpdates) 173 { 174 setReadingBuffer(newReportUpdates); 175 reportUpdates = newReportUpdates; 176 } 177 } 178 179 std::unique_ptr<sdbusplus::asio::dbus_interface> 180 Report::makeReportInterface(const interfaces::ReportFactory& reportFactory) 181 { 182 auto dbusIface = 183 objServer->add_unique_interface(getPath(), reportIfaceName); 184 dbusIface->register_property_rw( 185 "Enabled", enabled, sdbusplus::vtable::property_::emits_change, 186 [this](bool newVal, const auto&) { 187 if (newVal != enabled) 188 { 189 if (true == newVal && ReportingType::periodic == reportingType) 190 { 191 scheduleTimer(interval); 192 } 193 if (newVal) 194 { 195 for (auto& metric : metrics) 196 { 197 metric->initialize(); 198 } 199 } 200 else 201 { 202 for (auto& metric : metrics) 203 { 204 metric->deinitialize(); 205 } 206 } 207 208 enabled = newVal; 209 persistency = storeConfiguration(); 210 } 211 return 1; 212 }, 213 [this](const auto&) { return enabled; }); 214 dbusIface->register_property_rw( 215 "Interval", interval.count(), 216 sdbusplus::vtable::property_::emits_change, 217 [this](uint64_t newVal, auto&) { 218 if (Milliseconds newValT{newVal}; 219 newValT >= ReportManager::minInterval) 220 { 221 if (newValT != interval) 222 { 223 interval = newValT; 224 persistency = storeConfiguration(); 225 } 226 return 1; 227 } 228 throw sdbusplus::exception::SdBusError( 229 static_cast<int>(std::errc::invalid_argument), 230 "Invalid interval"); 231 }, 232 [this](const auto&) { return interval.count(); }); 233 dbusIface->register_property_rw( 234 "Persistency", persistency, sdbusplus::vtable::property_::emits_change, 235 [this](bool newVal, const auto&) { 236 if (newVal == persistency) 237 { 238 return 1; 239 } 240 if (newVal) 241 { 242 persistency = storeConfiguration(); 243 } 244 else 245 { 246 reportStorage.remove(fileName()); 247 persistency = false; 248 } 249 return 1; 250 }, 251 [this](const auto&) { return persistency; }); 252 253 dbusIface->register_property_r("Readings", readings, 254 sdbusplus::vtable::property_::emits_change, 255 [this](const auto&) { return readings; }); 256 dbusIface->register_property_rw( 257 "ReportingType", std::string(), 258 sdbusplus::vtable::property_::emits_change, 259 [this](auto newVal, auto& oldVal) { 260 ReportingType tmp = utils::toReportingType(newVal); 261 if (tmp != reportingType) 262 { 263 if (tmp == ReportingType::onChange) 264 { 265 throw sdbusplus::exception::SdBusError( 266 static_cast<int>(std::errc::invalid_argument), 267 "Invalid reportingType"); 268 } 269 if (tmp == ReportingType::periodic) 270 { 271 if (interval < ReportManager::minInterval) 272 { 273 throw sdbusplus::exception::SdBusError( 274 static_cast<int>(std::errc::invalid_argument), 275 "Invalid interval"); 276 } 277 if (enabled == true) 278 { 279 scheduleTimer(interval); 280 } 281 } 282 else 283 { 284 timer.cancel(); 285 } 286 reportingType = tmp; 287 setReadingBuffer(reportUpdates); 288 persistency = storeConfiguration(); 289 } 290 oldVal = std::move(newVal); 291 return 1; 292 }, 293 [this](const auto&) { return utils::enumToString(reportingType); }); 294 dbusIface->register_property_r( 295 "ReadingParameters", readingParametersPastVersion, 296 sdbusplus::vtable::property_::const_, 297 [this](const auto&) { return readingParametersPastVersion; }); 298 dbusIface->register_property_rw( 299 "ReadingParametersFutureVersion", readingParameters, 300 sdbusplus::vtable::property_::emits_change, 301 [this, &reportFactory](auto newVal, auto& oldVal) { 302 reportFactory.updateMetrics(metrics, enabled, newVal); 303 readingParameters = toReadingParameters( 304 utils::transform(metrics, [](const auto& metric) { 305 return metric->dumpConfiguration(); 306 })); 307 persistency = storeConfiguration(); 308 oldVal = std::move(newVal); 309 return 1; 310 }, 311 [this](const auto&) { return readingParameters; }); 312 dbusIface->register_property_r( 313 "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::none, 314 [this](const auto&) { 315 return reportActions.contains(ReportAction::emitsReadingsUpdate); 316 }); 317 dbusIface->register_property_r("Name", std::string{}, 318 sdbusplus::vtable::property_::const_, 319 [this](const auto&) { return name; }); 320 dbusIface->register_property_r( 321 "LogToMetricReportsCollection", bool{}, 322 sdbusplus::vtable::property_::const_, [this](const auto&) { 323 return reportActions.contains( 324 ReportAction::logToMetricReportsCollection); 325 }); 326 dbusIface->register_property_rw( 327 "ReportActions", std::vector<std::string>{}, 328 sdbusplus::vtable::property_::emits_change, 329 [this](auto newVal, auto& oldVal) { 330 auto tmp = utils::transform<std::unordered_set>( 331 newVal, [](const auto& reportAction) { 332 return utils::toReportAction(reportAction); 333 }); 334 tmp.insert(ReportAction::logToMetricReportsCollection); 335 336 if (tmp != reportActions) 337 { 338 reportActions = tmp; 339 persistency = storeConfiguration(); 340 oldVal = std::move(newVal); 341 } 342 return 1; 343 }, 344 [this](const auto&) { 345 return utils::transform<std::vector>( 346 reportActions, [](const auto reportAction) { 347 return utils::enumToString(reportAction); 348 }); 349 }); 350 dbusIface->register_property_r( 351 "AppendLimit", appendLimit.value_or(sensorCount), 352 sdbusplus::vtable::property_::emits_change, 353 [this](const auto&) { return appendLimit.value_or(sensorCount); }); 354 dbusIface->register_property_rw( 355 "ReportUpdates", std::string(), 356 sdbusplus::vtable::property_::emits_change, 357 [this](auto newVal, auto& oldVal) { 358 setReportUpdates(utils::toReportUpdates(newVal)); 359 oldVal = newVal; 360 return 1; 361 }, 362 [this](const auto&) { return utils::enumToString(reportUpdates); }); 363 dbusIface->register_property_r( 364 "TriggerIds", std::vector<std::string>{}, 365 sdbusplus::vtable::property_::emits_change, [this](const auto&) { 366 return std::vector<std::string>(triggerIds.begin(), 367 triggerIds.end()); 368 }); 369 dbusIface->register_method("Update", [this] { 370 if (reportingType == ReportingType::onRequest) 371 { 372 updateReadings(); 373 } 374 }); 375 constexpr bool skipPropertiesChangedSignal = true; 376 dbusIface->initialize(skipPropertiesChangedSignal); 377 return dbusIface; 378 } 379 380 void Report::timerProc(boost::system::error_code ec, Report& self) 381 { 382 if (ec) 383 { 384 return; 385 } 386 387 self.updateReadings(); 388 self.scheduleTimer(self.interval); 389 } 390 391 void Report::scheduleTimer(Milliseconds timerInterval) 392 { 393 timer.expires_after(timerInterval); 394 timer.async_wait( 395 [this](boost::system::error_code ec) { timerProc(ec, *this); }); 396 } 397 398 void Report::updateReadings() 399 { 400 if (!enabled) 401 { 402 return; 403 } 404 405 if (reportUpdates == ReportUpdates::overwrite || 406 reportingType == ReportingType::onRequest) 407 { 408 readingsBuffer.clear(); 409 } 410 411 for (const auto& metric : metrics) 412 { 413 for (const auto& [id, metadata, value, timestamp] : 414 metric->getReadings()) 415 { 416 if (reportUpdates == ReportUpdates::appendStopsWhenFull && 417 readingsBuffer.isFull()) 418 { 419 enabled = false; 420 for (auto& m : metrics) 421 { 422 m->deinitialize(); 423 } 424 break; 425 } 426 readingsBuffer.emplace(id, metadata, value, timestamp); 427 } 428 } 429 430 readings = { 431 std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp()) 432 .count(), 433 std::vector<ReadingData>(readingsBuffer.begin(), readingsBuffer.end())}; 434 435 if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate)) 436 { 437 reportIface->signal_property("Readings"); 438 } 439 } 440 441 bool Report::storeConfiguration() const 442 { 443 try 444 { 445 nlohmann::json data; 446 447 data["Enabled"] = enabled; 448 data["Version"] = reportVersion; 449 data["Id"] = id; 450 data["Name"] = name; 451 data["ReportingType"] = utils::toUnderlying(reportingType); 452 data["ReportActions"] = 453 utils::transform(reportActions, [](const auto reportAction) { 454 return utils::toUnderlying(reportAction); 455 }); 456 data["Interval"] = interval.count(); 457 data["AppendLimit"] = 458 appendLimit.value_or(std::numeric_limits<uint64_t>::max()); 459 data["ReportUpdates"] = utils::toUnderlying(reportUpdates); 460 data["ReadingParameters"] = 461 utils::transform(metrics, [](const auto& metric) { 462 return metric->dumpConfiguration(); 463 }); 464 465 reportStorage.store(fileName(), data); 466 } 467 catch (const std::exception& e) 468 { 469 phosphor::logging::log<phosphor::logging::level::ERR>( 470 "Failed to store a report in storage", 471 phosphor::logging::entry("EXCEPTION_MSG=%s", e.what())); 472 return false; 473 } 474 475 return true; 476 } 477 478 interfaces::JsonStorage::FilePath Report::fileName() const 479 { 480 return interfaces::JsonStorage::FilePath{ 481 std::to_string(std::hash<std::string>{}(id))}; 482 } 483 484 std::unordered_set<std::string> 485 Report::collectTriggerIds(boost::asio::io_context& ioc) const 486 { 487 utils::Messanger tmp(ioc); 488 489 auto result = std::unordered_set<std::string>(); 490 491 tmp.on_receive<messages::CollectTriggerIdResp>( 492 [&result](const auto& msg) { result.insert(msg.triggerId); }); 493 494 tmp.send(messages::CollectTriggerIdReq{id}); 495 496 return result; 497 } 498