1 #include "report.hpp" 2 3 #include "messages/collect_trigger_id.hpp" 4 #include "messages/trigger_presence_changed_ind.hpp" 5 #include "messages/update_report_ind.hpp" 6 #include "report_manager.hpp" 7 #include "utils/clock.hpp" 8 #include "utils/contains.hpp" 9 #include "utils/transform.hpp" 10 11 #include <phosphor-logging/log.hpp> 12 #include <sdbusplus/vtable.hpp> 13 14 #include <limits> 15 #include <numeric> 16 17 Report::Report(boost::asio::io_context& ioc, 18 const std::shared_ptr<sdbusplus::asio::object_server>& objServer, 19 const std::string& reportId, const std::string& reportName, 20 const ReportingType reportingTypeIn, 21 std::vector<ReportAction> reportActionsIn, 22 const Milliseconds intervalIn, const uint64_t appendLimitIn, 23 const ReportUpdates reportUpdatesIn, 24 interfaces::ReportManager& reportManager, 25 interfaces::JsonStorage& reportStorageIn, 26 std::vector<std::shared_ptr<interfaces::Metric>> metricsIn, 27 const bool enabledIn, std::unique_ptr<interfaces::Clock> clock) : 28 id(reportId), 29 name(reportName), reportingType(reportingTypeIn), interval(intervalIn), 30 reportActions(std::move(reportActionsIn)), 31 sensorCount(getSensorCount(metricsIn)), 32 appendLimit(deduceAppendLimit(appendLimitIn)), 33 reportUpdates(reportUpdatesIn), 34 readingsBuffer(deduceBufferSize(reportUpdates, reportingType)), 35 objServer(objServer), metrics(std::move(metricsIn)), timer(ioc), 36 triggerIds(collectTriggerIds(ioc)), reportStorage(reportStorageIn), 37 enabled(enabledIn), clock(std::move(clock)), messanger(ioc) 38 { 39 readingParameters = 40 toReadingParameters(utils::transform(metrics, [](const auto& metric) { 41 return metric->dumpConfiguration(); 42 })); 43 44 readingParametersPastVersion = 45 utils::transform(readingParameters, [](const auto& item) { 46 const auto& [sensorData, operationType, id, collectionTimeScope, 47 collectionDuration] = item; 48 49 return ReadingParametersPastVersion::value_type( 50 std::get<0>(sensorData.front()), operationType, id, 51 std::get<1>(sensorData.front())); 52 }); 53 54 deleteIface = objServer->add_unique_interface( 55 getPath(), deleteIfaceName, 56 [this, &ioc, &reportManager](auto& dbusIface) { 57 dbusIface.register_method("Delete", [this, &ioc, &reportManager] { 58 if (persistency) 59 { 60 reportStorage.remove(fileName()); 61 } 62 boost::asio::post(ioc, [this, &reportManager] { 63 reportManager.removeReport(this); 64 }); 65 }); 66 }); 67 68 persistency = storeConfiguration(); 69 reportIface = makeReportInterface(); 70 71 if (reportingType == ReportingType::periodic) 72 { 73 scheduleTimer(interval); 74 } 75 76 if (enabled) 77 { 78 for (auto& metric : this->metrics) 79 { 80 metric->initialize(); 81 } 82 } 83 84 messanger.on_receive<messages::TriggerPresenceChangedInd>( 85 [this](const auto& msg) { 86 const auto oldSize = triggerIds.size(); 87 88 if (msg.presence == messages::Presence::Exist) 89 { 90 if (utils::contains(msg.reportIds, id)) 91 { 92 triggerIds.insert(msg.triggerId); 93 } 94 else if (!utils::contains(msg.reportIds, id)) 95 { 96 triggerIds.erase(msg.triggerId); 97 } 98 } 99 else if (msg.presence == messages::Presence::Removed) 100 { 101 triggerIds.erase(msg.triggerId); 102 } 103 104 if (triggerIds.size() != oldSize) 105 { 106 reportIface->signal_property("TriggerIds"); 107 } 108 }); 109 110 messanger.on_receive<messages::UpdateReportInd>([this](const auto& msg) { 111 if (utils::contains(msg.reportIds, id)) 112 { 113 updateReadings(); 114 } 115 }); 116 } 117 118 uint64_t Report::getSensorCount( 119 std::vector<std::shared_ptr<interfaces::Metric>>& metrics) 120 { 121 uint64_t sensorCount = 0; 122 for (auto& metric : metrics) 123 { 124 sensorCount += metric->sensorCount(); 125 } 126 return sensorCount; 127 } 128 129 std::optional<uint64_t> 130 Report::deduceAppendLimit(const uint64_t appendLimitIn) const 131 { 132 if (appendLimitIn == std::numeric_limits<uint64_t>::max()) 133 { 134 return std::nullopt; 135 } 136 else 137 { 138 return appendLimitIn; 139 } 140 } 141 142 uint64_t Report::deduceBufferSize(const ReportUpdates reportUpdatesIn, 143 const ReportingType reportingTypeIn) const 144 { 145 if (reportUpdatesIn == ReportUpdates::overwrite || 146 reportingTypeIn == ReportingType::onRequest) 147 { 148 return sensorCount; 149 } 150 else 151 { 152 return appendLimit.value_or(sensorCount); 153 } 154 } 155 156 void Report::setReportUpdates(const ReportUpdates newReportUpdates) 157 { 158 if (reportUpdates != newReportUpdates) 159 { 160 if (reportingType != ReportingType::onRequest && 161 (reportUpdates == ReportUpdates::overwrite || 162 newReportUpdates == ReportUpdates::overwrite)) 163 { 164 readingsBuffer.clearAndResize( 165 deduceBufferSize(newReportUpdates, reportingType)); 166 } 167 reportUpdates = newReportUpdates; 168 } 169 } 170 171 std::unique_ptr<sdbusplus::asio::dbus_interface> Report::makeReportInterface() 172 { 173 auto dbusIface = 174 objServer->add_unique_interface(getPath(), reportIfaceName); 175 dbusIface->register_property_rw( 176 "Enabled", enabled, sdbusplus::vtable::property_::emits_change, 177 [this](bool newVal, const auto&) { 178 if (newVal != enabled) 179 { 180 if (true == newVal && ReportingType::periodic == reportingType) 181 { 182 scheduleTimer(interval); 183 } 184 if (newVal) 185 { 186 for (auto& metric : metrics) 187 { 188 metric->initialize(); 189 } 190 } 191 else 192 { 193 for (auto& metric : metrics) 194 { 195 metric->deinitialize(); 196 } 197 } 198 199 enabled = newVal; 200 persistency = storeConfiguration(); 201 } 202 return 1; 203 }, 204 [this](const auto&) { return enabled; }); 205 dbusIface->register_property_rw( 206 "Interval", interval.count(), 207 sdbusplus::vtable::property_::emits_change, 208 [this](uint64_t newVal, auto&) { 209 if (Milliseconds newValT{newVal}; 210 newValT >= ReportManager::minInterval) 211 { 212 if (newValT != interval) 213 { 214 interval = newValT; 215 persistency = storeConfiguration(); 216 } 217 return 1; 218 } 219 throw sdbusplus::exception::SdBusError( 220 static_cast<int>(std::errc::invalid_argument), 221 "Invalid interval"); 222 }, 223 [this](const auto&) { return interval.count(); }); 224 dbusIface->register_property_rw( 225 "Persistency", persistency, sdbusplus::vtable::property_::emits_change, 226 [this](bool newVal, const auto&) { 227 if (newVal == persistency) 228 { 229 return 1; 230 } 231 if (newVal) 232 { 233 persistency = storeConfiguration(); 234 } 235 else 236 { 237 reportStorage.remove(fileName()); 238 persistency = false; 239 } 240 return 1; 241 }, 242 [this](const auto&) { return persistency; }); 243 244 auto readingsFlag = sdbusplus::vtable::property_::none; 245 if (utils::contains(reportActions, ReportAction::emitsReadingsUpdate)) 246 { 247 readingsFlag = sdbusplus::vtable::property_::emits_change; 248 } 249 dbusIface->register_property_r("Readings", readings, readingsFlag, 250 [this](const auto&) { return readings; }); 251 dbusIface->register_property_r( 252 "ReportingType", std::string(), sdbusplus::vtable::property_::const_, 253 [this](const auto&) { return utils::enumToString(reportingType); }); 254 dbusIface->register_property_r( 255 "ReadingParameters", readingParametersPastVersion, 256 sdbusplus::vtable::property_::const_, 257 [this](const auto&) { return readingParametersPastVersion; }); 258 dbusIface->register_property_r( 259 "ReadingParametersFutureVersion", readingParameters, 260 sdbusplus::vtable::property_::const_, 261 [this](const auto&) { return readingParameters; }); 262 dbusIface->register_property_r( 263 "EmitsReadingsUpdate", bool{}, sdbusplus::vtable::property_::const_, 264 [this](const auto&) { 265 return utils::contains(reportActions, 266 ReportAction::emitsReadingsUpdate); 267 }); 268 dbusIface->register_property_r("Name", std::string{}, 269 sdbusplus::vtable::property_::const_, 270 [this](const auto&) { return name; }); 271 dbusIface->register_property_r( 272 "LogToMetricReportsCollection", bool{}, 273 sdbusplus::vtable::property_::const_, [this](const auto&) { 274 return utils::contains(reportActions, 275 ReportAction::logToMetricReportsCollection); 276 }); 277 dbusIface->register_property_r( 278 "ReportActions", std::vector<std::string>{}, 279 sdbusplus::vtable::property_::const_, [this](const auto&) { 280 return utils::transform(reportActions, [](const auto reportAction) { 281 return utils::enumToString(reportAction); 282 }); 283 }); 284 dbusIface->register_property_r( 285 "AppendLimit", appendLimit.value_or(sensorCount), 286 sdbusplus::vtable::property_::emits_change, 287 [this](const auto&) { return appendLimit.value_or(sensorCount); }); 288 dbusIface->register_property_rw( 289 "ReportUpdates", std::string(), 290 sdbusplus::vtable::property_::emits_change, 291 [this](auto newVal, auto& oldVal) { 292 setReportUpdates(utils::toReportUpdates(newVal)); 293 oldVal = newVal; 294 return 1; 295 }, 296 [this](const auto&) { return utils::enumToString(reportUpdates); }); 297 dbusIface->register_property_r( 298 "TriggerIds", std::vector<std::string>{}, 299 sdbusplus::vtable::property_::emits_change, [this](const auto&) { 300 return std::vector<std::string>(triggerIds.begin(), 301 triggerIds.end()); 302 }); 303 dbusIface->register_method("Update", [this] { 304 if (reportingType == ReportingType::onRequest) 305 { 306 updateReadings(); 307 } 308 }); 309 constexpr bool skipPropertiesChangedSignal = true; 310 dbusIface->initialize(skipPropertiesChangedSignal); 311 return dbusIface; 312 } 313 314 void Report::timerProc(boost::system::error_code ec, Report& self) 315 { 316 if (ec) 317 { 318 return; 319 } 320 321 self.updateReadings(); 322 self.scheduleTimer(self.interval); 323 } 324 325 void Report::scheduleTimer(Milliseconds timerInterval) 326 { 327 timer.expires_after(timerInterval); 328 timer.async_wait( 329 [this](boost::system::error_code ec) { timerProc(ec, *this); }); 330 } 331 332 void Report::updateReadings() 333 { 334 if (!enabled) 335 { 336 return; 337 } 338 339 if (reportUpdates == ReportUpdates::overwrite || 340 reportingType == ReportingType::onRequest) 341 { 342 readingsBuffer.clear(); 343 } 344 345 for (const auto& metric : metrics) 346 { 347 for (const auto& [id, metadata, value, timestamp] : 348 metric->getReadings()) 349 { 350 if (reportUpdates == ReportUpdates::appendStopsWhenFull && 351 readingsBuffer.isFull()) 352 { 353 enabled = false; 354 for (auto& m : metrics) 355 { 356 m->deinitialize(); 357 } 358 break; 359 } 360 readingsBuffer.emplace(id, metadata, value, timestamp); 361 } 362 } 363 364 readings = { 365 std::chrono::duration_cast<Milliseconds>(clock->systemTimestamp()) 366 .count(), 367 std::vector<ReadingData>(readingsBuffer.begin(), readingsBuffer.end())}; 368 369 reportIface->signal_property("Readings"); 370 } 371 372 bool Report::storeConfiguration() const 373 { 374 try 375 { 376 nlohmann::json data; 377 378 data["Enabled"] = enabled; 379 data["Version"] = reportVersion; 380 data["Id"] = id; 381 data["Name"] = name; 382 data["ReportingType"] = utils::toUnderlying(reportingType); 383 data["ReportActions"] = 384 utils::transform(reportActions, [](const auto reportAction) { 385 return utils::toUnderlying(reportAction); 386 }); 387 data["Interval"] = interval.count(); 388 data["AppendLimit"] = 389 appendLimit.value_or(std::numeric_limits<uint64_t>::max()); 390 data["ReportUpdates"] = utils::toUnderlying(reportUpdates); 391 data["ReadingParameters"] = 392 utils::transform(metrics, [](const auto& metric) { 393 return metric->dumpConfiguration(); 394 }); 395 396 reportStorage.store(fileName(), data); 397 } 398 catch (const std::exception& e) 399 { 400 phosphor::logging::log<phosphor::logging::level::ERR>( 401 "Failed to store a report in storage", 402 phosphor::logging::entry("EXCEPTION_MSG=%s", e.what())); 403 return false; 404 } 405 406 return true; 407 } 408 409 interfaces::JsonStorage::FilePath Report::fileName() const 410 { 411 return interfaces::JsonStorage::FilePath{ 412 std::to_string(std::hash<std::string>{}(id))}; 413 } 414 415 std::unordered_set<std::string> 416 Report::collectTriggerIds(boost::asio::io_context& ioc) const 417 { 418 utils::Messanger tmp(ioc); 419 420 auto result = std::unordered_set<std::string>(); 421 422 tmp.on_receive<messages::CollectTriggerIdResp>( 423 [&result](const auto& msg) { result.insert(msg.triggerId); }); 424 425 tmp.send(messages::CollectTriggerIdReq{id}); 426 427 return result; 428 } 429