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 if (reportingType != ReportingType::onRequest && 208 (reportUpdates == ReportUpdates::overwrite || 209 newReportUpdates == ReportUpdates::overwrite)) 210 { 211 readingsBuffer.clearAndResize( 212 deduceBufferSize(newReportUpdates, reportingType)); 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 std::unique_ptr<sdbusplus::asio::dbus_interface> 226 Report::makeReportInterface(const interfaces::ReportFactory& reportFactory) 227 { 228 auto dbusIface = 229 objServer->add_unique_interface(getPath(), reportIfaceName); 230 dbusIface->register_property_rw<bool>( 231 "Enabled", sdbusplus::vtable::property_::emits_change, 232 [this](bool newVal, auto& oldValue) { 233 if (newVal != state.get<ReportFlags::enabled>()) 234 { 235 state.set<ReportFlags::enabled>(oldValue = newVal); 236 237 persistency = storeConfiguration(); 238 } 239 return 1; 240 }, 241 [this](const auto&) { return state.get<ReportFlags::enabled>(); }); 242 dbusIface->register_property_r< 243 std::vector<std::tuple<std::string, std::string>>>( 244 "ErrorMessages", sdbusplus::vtable::property_::emits_change, 245 [this](const auto&) { 246 return utils::transform(errorMessages, [](const auto& em) { 247 return std::tuple<std::string, std::string>( 248 utils::enumToString(em.error), em.arg0); 249 }); 250 }); 251 dbusIface->register_property_rw<uint64_t>( 252 "Interval", sdbusplus::vtable::property_::emits_change, 253 [this](uint64_t newVal, auto& oldVal) { 254 const Milliseconds newValT{newVal}; 255 if (newValT < ReportManager::minInterval && 256 newValT != Milliseconds{0}) 257 { 258 throw sdbusplus::exception::SdBusError( 259 static_cast<int>(std::errc::invalid_argument), 260 "Invalid interval"); 261 } 262 263 if (newValT != interval) 264 { 265 oldVal = newVal; 266 interval = newValT; 267 268 errorMessages = verify(); 269 if (state.set<ReportFlags::valid>(errorMessages.empty()) == 270 StateEvent::active) 271 { 272 scheduleTimer(); 273 } 274 275 persistency = storeConfiguration(); 276 } 277 return 1; 278 }, 279 [this](const auto&) { return interval.count(); }); 280 dbusIface->register_property_rw<bool>( 281 "Persistency", sdbusplus::vtable::property_::emits_change, 282 [this](bool newVal, auto& oldVal) { 283 if (newVal == persistency) 284 { 285 return 1; 286 } 287 if (newVal) 288 { 289 persistency = oldVal = storeConfiguration(); 290 } 291 else 292 { 293 reportStorage.remove(reportFileName()); 294 persistency = oldVal = false; 295 } 296 return 1; 297 }, 298 [this](const auto&) { return persistency; }); 299 300 dbusIface->register_property_r("Readings", readings, 301 sdbusplus::vtable::property_::emits_change, 302 [this](const auto&) { return readings; }); 303 dbusIface->register_property_rw<std::string>( 304 "ReportingType", sdbusplus::vtable::property_::emits_change, 305 [this](auto newVal, auto& oldVal) { 306 ReportingType tmp = utils::toReportingType(newVal); 307 if (tmp != reportingType) 308 { 309 reportingType = tmp; 310 oldVal = std::move(newVal); 311 312 errorMessages = verify(); 313 if (state.set<ReportFlags::valid>(errorMessages.empty()) == 314 StateEvent::active) 315 { 316 scheduleTimer(); 317 } 318 319 persistency = storeConfiguration(); 320 321 setReadingBuffer(reportUpdates); 322 } 323 return 1; 324 }, 325 [this](const auto&) { return utils::enumToString(reportingType); }); 326 dbusIface->register_property_r( 327 "ReadingParameters", readingParametersPastVersion, 328 sdbusplus::vtable::property_::const_, 329 [this](const auto&) { return readingParametersPastVersion; }); 330 dbusIface->register_property_rw( 331 "ReadingParametersFutureVersion", readingParameters, 332 sdbusplus::vtable::property_::emits_change, 333 [this, &reportFactory](auto newVal, auto& oldVal) { 334 auto labeledMetricParams = 335 reportFactory.convertMetricParams(newVal); 336 ReportManager::verifyMetricParameters(labeledMetricParams); 337 reportFactory.updateMetrics(metrics, 338 state.get<ReportFlags::enabled>(), 339 labeledMetricParams); 340 readingParameters = toReadingParameters( 341 utils::transform(metrics, [](const auto& metric) { 342 return metric->dumpConfiguration(); 343 })); 344 persistency = storeConfiguration(); 345 oldVal = std::move(newVal); 346 return 1; 347 }, 348 [this](const auto&) { return readingParameters; }); 349 dbusIface->register_property_r<bool>( 350 "EmitsReadingsUpdate", sdbusplus::vtable::property_::none, 351 [this](const auto&) { 352 return reportActions.contains(ReportAction::emitsReadingsUpdate); 353 }); 354 dbusIface->register_property_r<std::string>( 355 "Name", sdbusplus::vtable::property_::const_, 356 [this](const auto&) { return name; }); 357 dbusIface->register_property_r<bool>( 358 "LogToMetricReportsCollection", sdbusplus::vtable::property_::const_, 359 [this](const auto&) { 360 return reportActions.contains( 361 ReportAction::logToMetricReportsCollection); 362 }); 363 dbusIface->register_property_rw<std::vector<std::string>>( 364 "ReportActions", sdbusplus::vtable::property_::emits_change, 365 [this](auto newVal, auto& oldVal) { 366 auto tmp = utils::transform<std::unordered_set>( 367 newVal, [](const auto& reportAction) { 368 return utils::toReportAction(reportAction); 369 }); 370 tmp.insert(ReportAction::logToMetricReportsCollection); 371 372 if (tmp != reportActions) 373 { 374 reportActions = tmp; 375 persistency = storeConfiguration(); 376 oldVal = std::move(newVal); 377 } 378 return 1; 379 }, 380 [this](const auto&) { 381 return utils::transform<std::vector>( 382 reportActions, [](const auto reportAction) { 383 return utils::enumToString(reportAction); 384 }); 385 }); 386 dbusIface->register_property_r( 387 "AppendLimit", appendLimit.value_or(sensorCount), 388 sdbusplus::vtable::property_::emits_change, 389 [this](const auto&) { return appendLimit.value_or(sensorCount); }); 390 dbusIface->register_property_rw( 391 "ReportUpdates", std::string(), 392 sdbusplus::vtable::property_::emits_change, 393 [this](auto newVal, auto& oldVal) { 394 setReportUpdates(utils::toReportUpdates(newVal)); 395 oldVal = newVal; 396 return 1; 397 }, 398 [this](const auto&) { return utils::enumToString(reportUpdates); }); 399 dbusIface->register_property_r( 400 "Triggers", std::vector<sdbusplus::message::object_path>{}, 401 sdbusplus::vtable::property_::emits_change, [this](const auto&) { 402 return utils::transform<std::vector>( 403 triggerIds, [](const auto& triggerId) { 404 return utils::pathAppend(utils::constants::triggerDirPath, 405 triggerId); 406 }); 407 }); 408 dbusIface->register_method("Update", [this] { 409 if (reportingType == ReportingType::onRequest) 410 { 411 updateReadings(); 412 } 413 }); 414 constexpr bool skipPropertiesChangedSignal = true; 415 dbusIface->initialize(skipPropertiesChangedSignal); 416 return dbusIface; 417 } 418 419 void Report::timerProcForPeriodicReport(boost::system::error_code ec, 420 Report& self) 421 { 422 if (ec) 423 { 424 return; 425 } 426 427 self.updateReadings(); 428 self.scheduleTimerForPeriodicReport(self.interval); 429 } 430 431 void Report::timerProcForOnChangeReport(boost::system::error_code ec, 432 Report& self) 433 { 434 if (ec) 435 { 436 return; 437 } 438 439 const auto ensure = 440 utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }}; 441 442 self.onChangeContext.emplace(self); 443 444 const auto steadyTimestamp = self.clock->steadyTimestamp(); 445 446 for (auto& metric : self.metrics) 447 { 448 metric->updateReadings(steadyTimestamp); 449 } 450 451 self.scheduleTimerForOnChangeReport(); 452 } 453 454 void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval) 455 { 456 timer.expires_after(timerInterval); 457 timer.async_wait([this](boost::system::error_code ec) { 458 timerProcForPeriodicReport(ec, *this); 459 }); 460 } 461 462 void Report::scheduleTimerForOnChangeReport() 463 { 464 constexpr Milliseconds timerInterval{100}; 465 466 timer.expires_after(timerInterval); 467 timer.async_wait([this](boost::system::error_code ec) { 468 timerProcForOnChangeReport(ec, *this); 469 }); 470 } 471 472 void Report::updateReadings() 473 { 474 if (!state.isActive()) 475 { 476 return; 477 } 478 479 if (reportUpdates == ReportUpdates::overwrite || 480 reportingType == ReportingType::onRequest) 481 { 482 readingsBuffer.clear(); 483 } 484 485 for (const auto& metric : metrics) 486 { 487 if (!state.isActive()) 488 { 489 break; 490 } 491 492 for (const auto& [id, metadata, value, timestamp] : 493 metric->getUpdatedReadings()) 494 { 495 if (reportUpdates == ReportUpdates::appendStopsWhenFull && 496 readingsBuffer.isFull()) 497 { 498 state.set<ReportFlags::enabled>(false); 499 reportIface->signal_property("Enabled"); 500 break; 501 } 502 readingsBuffer.emplace(id, metadata, value, timestamp); 503 } 504 } 505 506 std::get<0>(readings) = 507 std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp()) 508 .count(); 509 510 if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate)) 511 { 512 reportIface->signal_property("Readings"); 513 } 514 } 515 516 bool Report::shouldStoreMetricValues() const 517 { 518 return reportingType != ReportingType::onRequest && 519 reportUpdates == ReportUpdates::appendStopsWhenFull; 520 } 521 522 bool Report::storeConfiguration() const 523 { 524 try 525 { 526 nlohmann::json data; 527 528 data["Enabled"] = state.get<ReportFlags::enabled>(); 529 data["Version"] = reportVersion; 530 data["Id"] = id; 531 data["Name"] = name; 532 data["ReportingType"] = utils::toUnderlying(reportingType); 533 data["ReportActions"] = 534 utils::transform(reportActions, [](const auto reportAction) { 535 return utils::toUnderlying(reportAction); 536 }); 537 data["Interval"] = interval.count(); 538 data["AppendLimit"] = 539 appendLimit.value_or(std::numeric_limits<uint64_t>::max()); 540 data["ReportUpdates"] = utils::toUnderlying(reportUpdates); 541 data["ReadingParameters"] = 542 utils::transform(metrics, [](const auto& metric) { 543 return metric->dumpConfiguration(); 544 }); 545 546 if (shouldStoreMetricValues()) 547 { 548 data["MetricValues"] = utils::toLabeledReadings(readings); 549 } 550 551 reportStorage.store(reportFileName(), data); 552 } 553 catch (const std::exception& e) 554 { 555 phosphor::logging::log<phosphor::logging::level::ERR>( 556 "Failed to store a report in storage", 557 phosphor::logging::entry("EXCEPTION_MSG=%s", e.what())); 558 return false; 559 } 560 561 return true; 562 } 563 564 interfaces::JsonStorage::FilePath Report::reportFileName() const 565 { 566 return interfaces::JsonStorage::FilePath{ 567 std::to_string(std::hash<std::string>{}(id))}; 568 } 569 570 std::unordered_set<std::string> 571 Report::collectTriggerIds(boost::asio::io_context& ioc) const 572 { 573 utils::Messanger tmp(ioc); 574 575 auto result = std::unordered_set<std::string>(); 576 577 tmp.on_receive<messages::CollectTriggerIdResp>( 578 [&result](const auto& msg) { result.insert(msg.triggerId); }); 579 580 tmp.send(messages::CollectTriggerIdReq{id}); 581 582 return result; 583 } 584 585 void Report::metricUpdated() 586 { 587 if (onChangeContext) 588 { 589 onChangeContext->metricUpdated(); 590 return; 591 } 592 593 updateReadings(); 594 } 595 596 void Report::scheduleTimer() 597 { 598 switch (reportingType) 599 { 600 case ReportingType::periodic: 601 { 602 unregisterFromMetrics = nullptr; 603 scheduleTimerForPeriodicReport(interval); 604 break; 605 } 606 case ReportingType::onChange: 607 { 608 if (!unregisterFromMetrics) 609 { 610 unregisterFromMetrics = [this] { 611 for (auto& metric : metrics) 612 { 613 metric->unregisterFromUpdates(*this); 614 } 615 }; 616 617 for (auto& metric : metrics) 618 { 619 metric->registerForUpdates(*this); 620 } 621 } 622 623 bool isTimerRequired = false; 624 625 for (auto& metric : metrics) 626 { 627 if (metric->isTimerRequired()) 628 { 629 isTimerRequired = true; 630 } 631 } 632 633 if (isTimerRequired) 634 { 635 scheduleTimerForOnChangeReport(); 636 } 637 else 638 { 639 timer.cancel(); 640 } 641 break; 642 } 643 default: 644 unregisterFromMetrics = nullptr; 645 timer.cancel(); 646 break; 647 } 648 } 649 650 std::vector<ErrorMessage> Report::verify() const 651 { 652 std::vector<ErrorMessage> result; 653 654 if ((reportingType == ReportingType::periodic && 655 interval == Milliseconds{0}) || 656 (reportingType != ReportingType::periodic && 657 interval != Milliseconds{0})) 658 { 659 result.emplace_back(ErrorType::propertyConflict, "Interval"); 660 result.emplace_back(ErrorType::propertyConflict, "ReportingType"); 661 } 662 663 return result; 664 } 665