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