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