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 reportFactory.updateMetrics( 335 metrics, state.get<ReportFlags::enabled>(), newVal); 336 readingParameters = toReadingParameters( 337 utils::transform(metrics, [](const auto& metric) { 338 return metric->dumpConfiguration(); 339 })); 340 persistency = storeConfiguration(); 341 oldVal = std::move(newVal); 342 return 1; 343 }, 344 [this](const auto&) { return readingParameters; }); 345 dbusIface->register_property_r<bool>( 346 "EmitsReadingsUpdate", sdbusplus::vtable::property_::none, 347 [this](const auto&) { 348 return reportActions.contains(ReportAction::emitsReadingsUpdate); 349 }); 350 dbusIface->register_property_r<std::string>( 351 "Name", sdbusplus::vtable::property_::const_, 352 [this](const auto&) { return name; }); 353 dbusIface->register_property_r<bool>( 354 "LogToMetricReportsCollection", sdbusplus::vtable::property_::const_, 355 [this](const auto&) { 356 return reportActions.contains( 357 ReportAction::logToMetricReportsCollection); 358 }); 359 dbusIface->register_property_rw<std::vector<std::string>>( 360 "ReportActions", sdbusplus::vtable::property_::emits_change, 361 [this](auto newVal, auto& oldVal) { 362 auto tmp = utils::transform<std::unordered_set>( 363 newVal, [](const auto& reportAction) { 364 return utils::toReportAction(reportAction); 365 }); 366 tmp.insert(ReportAction::logToMetricReportsCollection); 367 368 if (tmp != reportActions) 369 { 370 reportActions = tmp; 371 persistency = storeConfiguration(); 372 oldVal = std::move(newVal); 373 } 374 return 1; 375 }, 376 [this](const auto&) { 377 return utils::transform<std::vector>( 378 reportActions, [](const auto reportAction) { 379 return utils::enumToString(reportAction); 380 }); 381 }); 382 dbusIface->register_property_r( 383 "AppendLimit", appendLimit.value_or(sensorCount), 384 sdbusplus::vtable::property_::emits_change, 385 [this](const auto&) { return appendLimit.value_or(sensorCount); }); 386 dbusIface->register_property_rw( 387 "ReportUpdates", std::string(), 388 sdbusplus::vtable::property_::emits_change, 389 [this](auto newVal, auto& oldVal) { 390 setReportUpdates(utils::toReportUpdates(newVal)); 391 oldVal = newVal; 392 return 1; 393 }, 394 [this](const auto&) { return utils::enumToString(reportUpdates); }); 395 dbusIface->register_property_r( 396 "Triggers", std::vector<sdbusplus::message::object_path>{}, 397 sdbusplus::vtable::property_::emits_change, [this](const auto&) { 398 return utils::transform<std::vector>( 399 triggerIds, [](const auto& triggerId) { 400 return utils::pathAppend(utils::constants::triggerDirPath, 401 triggerId); 402 }); 403 }); 404 dbusIface->register_method("Update", [this] { 405 if (reportingType == ReportingType::onRequest) 406 { 407 updateReadings(); 408 } 409 }); 410 constexpr bool skipPropertiesChangedSignal = true; 411 dbusIface->initialize(skipPropertiesChangedSignal); 412 return dbusIface; 413 } 414 415 void Report::timerProcForPeriodicReport(boost::system::error_code ec, 416 Report& self) 417 { 418 if (ec) 419 { 420 return; 421 } 422 423 self.updateReadings(); 424 self.scheduleTimerForPeriodicReport(self.interval); 425 } 426 427 void Report::timerProcForOnChangeReport(boost::system::error_code ec, 428 Report& self) 429 { 430 if (ec) 431 { 432 return; 433 } 434 435 const auto ensure = 436 utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }}; 437 438 self.onChangeContext.emplace(self); 439 440 const auto steadyTimestamp = self.clock->steadyTimestamp(); 441 442 for (auto& metric : self.metrics) 443 { 444 metric->updateReadings(steadyTimestamp); 445 } 446 447 self.scheduleTimerForOnChangeReport(); 448 } 449 450 void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval) 451 { 452 timer.expires_after(timerInterval); 453 timer.async_wait([this](boost::system::error_code ec) { 454 timerProcForPeriodicReport(ec, *this); 455 }); 456 } 457 458 void Report::scheduleTimerForOnChangeReport() 459 { 460 constexpr Milliseconds timerInterval{100}; 461 462 timer.expires_after(timerInterval); 463 timer.async_wait([this](boost::system::error_code ec) { 464 timerProcForOnChangeReport(ec, *this); 465 }); 466 } 467 468 void Report::updateReadings() 469 { 470 if (!state.isActive()) 471 { 472 return; 473 } 474 475 if (reportUpdates == ReportUpdates::overwrite || 476 reportingType == ReportingType::onRequest) 477 { 478 readingsBuffer.clear(); 479 } 480 481 for (const auto& metric : metrics) 482 { 483 if (!state.isActive()) 484 { 485 break; 486 } 487 488 for (const auto& [id, metadata, value, timestamp] : 489 metric->getUpdatedReadings()) 490 { 491 if (reportUpdates == ReportUpdates::appendStopsWhenFull && 492 readingsBuffer.isFull()) 493 { 494 state.set<ReportFlags::enabled>(false); 495 reportIface->signal_property("Enabled"); 496 break; 497 } 498 readingsBuffer.emplace(id, metadata, value, timestamp); 499 } 500 } 501 502 std::get<0>(readings) = 503 std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp()) 504 .count(); 505 506 if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate)) 507 { 508 reportIface->signal_property("Readings"); 509 } 510 } 511 512 bool Report::shouldStoreMetricValues() const 513 { 514 return reportingType != ReportingType::onRequest && 515 reportUpdates == ReportUpdates::appendStopsWhenFull; 516 } 517 518 bool Report::storeConfiguration() const 519 { 520 try 521 { 522 nlohmann::json data; 523 524 data["Enabled"] = state.get<ReportFlags::enabled>(); 525 data["Version"] = reportVersion; 526 data["Id"] = id; 527 data["Name"] = name; 528 data["ReportingType"] = utils::toUnderlying(reportingType); 529 data["ReportActions"] = 530 utils::transform(reportActions, [](const auto reportAction) { 531 return utils::toUnderlying(reportAction); 532 }); 533 data["Interval"] = interval.count(); 534 data["AppendLimit"] = 535 appendLimit.value_or(std::numeric_limits<uint64_t>::max()); 536 data["ReportUpdates"] = utils::toUnderlying(reportUpdates); 537 data["ReadingParameters"] = 538 utils::transform(metrics, [](const auto& metric) { 539 return metric->dumpConfiguration(); 540 }); 541 542 if (shouldStoreMetricValues()) 543 { 544 data["MetricValues"] = utils::toLabeledReadings(readings); 545 } 546 547 reportStorage.store(reportFileName(), data); 548 } 549 catch (const std::exception& e) 550 { 551 phosphor::logging::log<phosphor::logging::level::ERR>( 552 "Failed to store a report in storage", 553 phosphor::logging::entry("EXCEPTION_MSG=%s", e.what())); 554 return false; 555 } 556 557 return true; 558 } 559 560 interfaces::JsonStorage::FilePath Report::reportFileName() const 561 { 562 return interfaces::JsonStorage::FilePath{ 563 std::to_string(std::hash<std::string>{}(id))}; 564 } 565 566 std::unordered_set<std::string> 567 Report::collectTriggerIds(boost::asio::io_context& ioc) const 568 { 569 utils::Messanger tmp(ioc); 570 571 auto result = std::unordered_set<std::string>(); 572 573 tmp.on_receive<messages::CollectTriggerIdResp>( 574 [&result](const auto& msg) { result.insert(msg.triggerId); }); 575 576 tmp.send(messages::CollectTriggerIdReq{id}); 577 578 return result; 579 } 580 581 void Report::metricUpdated() 582 { 583 if (onChangeContext) 584 { 585 onChangeContext->metricUpdated(); 586 return; 587 } 588 589 updateReadings(); 590 } 591 592 void Report::scheduleTimer() 593 { 594 switch (reportingType) 595 { 596 case ReportingType::periodic: 597 { 598 unregisterFromMetrics = nullptr; 599 scheduleTimerForPeriodicReport(interval); 600 break; 601 } 602 case ReportingType::onChange: 603 { 604 if (!unregisterFromMetrics) 605 { 606 unregisterFromMetrics = [this] { 607 for (auto& metric : metrics) 608 { 609 metric->unregisterFromUpdates(*this); 610 } 611 }; 612 613 for (auto& metric : metrics) 614 { 615 metric->registerForUpdates(*this); 616 } 617 } 618 619 bool isTimerRequired = false; 620 621 for (auto& metric : metrics) 622 { 623 if (metric->isTimerRequired()) 624 { 625 isTimerRequired = true; 626 } 627 } 628 629 if (isTimerRequired) 630 { 631 scheduleTimerForOnChangeReport(); 632 } 633 else 634 { 635 timer.cancel(); 636 } 637 break; 638 } 639 default: 640 unregisterFromMetrics = nullptr; 641 timer.cancel(); 642 break; 643 } 644 } 645 646 std::vector<ErrorMessage> Report::verify() const 647 { 648 std::vector<ErrorMessage> result; 649 650 if ((reportingType == ReportingType::periodic && 651 interval == Milliseconds{0}) || 652 (reportingType != ReportingType::periodic && 653 interval != Milliseconds{0})) 654 { 655 result.emplace_back(ErrorType::propertyConflict, "Interval"); 656 result.emplace_back(ErrorType::propertyConflict, "ReportingType"); 657 } 658 659 return result; 660 } 661