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