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