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