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