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