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