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