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