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