xref: /openbmc/telemetry/src/report_manager.cpp (revision 3a1c297a)
1 #include "report_manager.hpp"
2 
3 #include "report.hpp"
4 #include "types/report_types.hpp"
5 #include "utils/conversion.hpp"
6 #include "utils/dbus_path_utils.hpp"
7 #include "utils/make_id_name.hpp"
8 #include "utils/transform.hpp"
9 
10 #include <phosphor-logging/log.hpp>
11 #include <sdbusplus/exception.hpp>
12 #include <sdbusplus/unpack_properties.hpp>
13 
14 #include <optional>
15 #include <stdexcept>
16 #include <system_error>
17 
18 ReadingParameters
19     convertToReadingParameters(ReadingParametersPastVersion params)
20 {
21     return utils::transform(params, [](const auto& param) {
22         using namespace std::chrono_literals;
23 
24         const auto& [sensorPath, operationType, id, metadata] = param;
25 
26         return ReadingParameters::value_type(
27             std::vector<
28                 std::tuple<sdbusplus::message::object_path, std::string>>{
29                 {sensorPath, metadata}},
30             operationType, id, utils::enumToString(CollectionTimeScope::point),
31             0u);
32     });
33 }
34 
35 ReportManager::ReportManager(
36     std::unique_ptr<interfaces::ReportFactory> reportFactoryIn,
37     std::unique_ptr<interfaces::JsonStorage> reportStorageIn,
38     const std::shared_ptr<sdbusplus::asio::object_server>& objServerIn) :
39     reportFactory(std::move(reportFactoryIn)),
40     reportStorage(std::move(reportStorageIn)), objServer(objServerIn)
41 {
42     reports.reserve(maxReports);
43 
44     loadFromPersistent();
45 
46     reportManagerIface = objServer->add_unique_interface(
47         reportManagerPath, reportManagerIfaceName,
48         [this](auto& dbusIface) {
49         dbusIface.register_property_r("MaxReports", size_t{},
50                                       sdbusplus::vtable::property_::const_,
51                                       [](const auto&) { return maxReports; });
52         dbusIface.register_property_r(
53             "MinInterval", uint64_t{}, sdbusplus::vtable::property_::const_,
54             [](const auto&) -> uint64_t { return minInterval.count(); });
55         dbusIface.register_property_r(
56             "SupportedOperationTypes", std::vector<std::string>{},
57             sdbusplus::vtable::property_::const_,
58             [](const auto&) -> std::vector<std::string> {
59                 return utils::transform<std::vector>(
60                     utils::convDataOperationType,
61                     [](const auto& item) { return std::string(item.first); });
62             });
63 
64         dbusIface.register_method(
65             "AddReport",
66             [this](boost::asio::yield_context& yield,
67                    const std::string& reportId,
68                    const std::string& reportingType,
69                    const bool emitsReadingsUpdate,
70                    const bool logToMetricReportsCollection,
71                    const uint64_t interval,
72                    ReadingParametersPastVersion metricParams) {
73             constexpr auto enabledDefault = true;
74             constexpr ReportUpdates reportUpdatesDefault =
75                 ReportUpdates::overwrite;
76 
77             std::vector<ReportAction> reportActions;
78 
79             if (emitsReadingsUpdate)
80             {
81                 reportActions.emplace_back(ReportAction::emitsReadingsUpdate);
82             }
83             if (logToMetricReportsCollection)
84             {
85                 reportActions.emplace_back(
86                     ReportAction::logToMetricReportsCollection);
87             }
88 
89             return addReport(
90                        yield, reportId, reportId,
91                        utils::toReportingType(reportingType), reportActions,
92                        Milliseconds(interval), maxAppendLimit,
93                        reportUpdatesDefault,
94                        convertToReadingParameters(std::move(metricParams)),
95                        enabledDefault)
96                 .getPath();
97             });
98 
99         dbusIface.register_method(
100             "AddReportFutureVersion",
101             [this](boost::asio::yield_context& yield,
102                    const std::vector<
103                        std::pair<std::string, AddReportFutureVersionVariant>>&
104                        properties) {
105             std::optional<std::string> reportId;
106             std::optional<std::string> reportName;
107             std::optional<std::string> reportingType;
108             std::optional<std::vector<std::string>> reportActions;
109             std::optional<uint64_t> interval;
110             std::optional<uint64_t> appendLimit;
111             std::optional<std::string> reportUpdates;
112             std::optional<ReadingParameters> metricParams;
113             std::optional<ReadingParameters> readingParameters;
114             std::optional<bool> enabled;
115 
116             try
117             {
118                 sdbusplus::unpackProperties(
119                     properties, "Id", reportId, "Name", reportName,
120                     "ReportingType", reportingType, "ReportActions",
121                     reportActions, "Interval", interval, "AppendLimit",
122                     appendLimit, "ReportUpdates", reportUpdates, "MetricParams",
123                     metricParams, "Enabled", enabled, "ReadingParameters",
124                     readingParameters);
125             }
126             catch (const sdbusplus::exception::UnpackPropertyError& e)
127             {
128                 throw errors::InvalidArgument(e.propertyName);
129             }
130 
131             if (readingParameters == std::nullopt)
132             {
133                 readingParameters = metricParams;
134             }
135 
136             return addReport(
137                        yield, reportId.value_or(""), reportName.value_or(""),
138                        utils::toReportingType(reportingType.value_or(
139                            utils::enumToString(ReportingType::onRequest))),
140                        utils::transform(
141                            reportActions.value_or(std::vector<std::string>{}),
142                            [](const auto& reportAction) {
143                 return utils::toReportAction(reportAction);
144                            }),
145                        Milliseconds(interval.value_or(0)),
146                        appendLimit.value_or(maxAppendLimit),
147                        utils::toReportUpdates(reportUpdates.value_or(
148                            utils::enumToString(ReportUpdates::overwrite))),
149                        readingParameters.value_or(ReadingParameters{}),
150                        enabled.value_or(true))
151                 .getPath();
152             });
153         });
154 }
155 
156 void ReportManager::removeReport(const interfaces::Report* report)
157 {
158     reports.erase(
159         std::remove_if(reports.begin(), reports.end(),
160                        [report](const auto& x) { return report == x.get(); }),
161         reports.end());
162 }
163 
164 void ReportManager::verifyMetricParameters(
165     const std::vector<LabeledMetricParameters>& readingParams)
166 {
167     namespace ts = utils::tstring;
168 
169     for (auto readingParam : readingParams)
170     {
171         if (readingParam.at_label<ts::Id>().length() >
172             utils::constants::maxIdNameLength)
173         {
174             throw errors::InvalidArgument("ReadingParameters.Id", "Too long.");
175         }
176     }
177 }
178 
179 void ReportManager::verifyAddReport(
180     const std::string& reportId, const std::string& reportName,
181     const ReportingType reportingType, Milliseconds interval,
182     const ReportUpdates reportUpdates, const uint64_t appendLimit,
183     const std::vector<LabeledMetricParameters>& readingParams)
184 {
185     namespace ts = utils::tstring;
186 
187     if (reports.size() >= maxReports)
188     {
189         throw sdbusplus::exception::SdBusError(
190             static_cast<int>(std::errc::too_many_files_open),
191             "Reached maximal report count");
192     }
193 
194     if (appendLimit > maxAppendLimit &&
195         appendLimit != std::numeric_limits<uint64_t>::max())
196     {
197         throw errors::InvalidArgument("AppendLimit", "Out of range.");
198     }
199 
200     if ((reportingType == ReportingType::periodic && interval < minInterval) ||
201         (reportingType != ReportingType::periodic &&
202          interval != Milliseconds{0}))
203     {
204         throw errors::InvalidArgument("Interval");
205     }
206 
207     size_t metricCount = 0;
208     for (auto metricParam : readingParams)
209     {
210         auto metricParamsVec =
211             metricParam.at_label<utils::tstring::SensorPath>();
212         metricCount += metricParamsVec.size();
213     }
214 
215     if (readingParams.size() > maxNumberMetrics ||
216         metricCount > maxNumberMetrics)
217     {
218         throw errors::InvalidArgument("MetricParams", "Too many.");
219     }
220 
221     verifyMetricParameters(readingParams);
222 
223     for (const LabeledMetricParameters& item : readingParams)
224     {
225         utils::toOperationType(
226             utils::toUnderlying(item.at_label<ts::OperationType>()));
227     }
228 }
229 
230 interfaces::Report& ReportManager::addReport(
231     boost::asio::yield_context& yield, const std::string& reportId,
232     const std::string& reportName, const ReportingType reportingType,
233     const std::vector<ReportAction>& reportActions, Milliseconds interval,
234     const uint64_t appendLimit, const ReportUpdates reportUpdates,
235     ReadingParameters metricParams, const bool enabled)
236 {
237     auto labeledMetricParams = reportFactory->convertMetricParams(yield,
238                                                                   metricParams);
239 
240     return addReport(reportId, reportName, reportingType, reportActions,
241                      interval, appendLimit, reportUpdates,
242                      std::move(labeledMetricParams), enabled, Readings{});
243 }
244 
245 interfaces::Report& ReportManager::addReport(
246     const std::string& reportId, const std::string& reportName,
247     const ReportingType reportingType,
248     const std::vector<ReportAction>& reportActions, Milliseconds interval,
249     const uint64_t appendLimit, const ReportUpdates reportUpdates,
250     std::vector<LabeledMetricParameters> labeledMetricParams,
251     const bool enabled, Readings readings)
252 {
253     const auto existingReportIds = utils::transform(
254         reports, [](const auto& report) { return report->getId(); });
255 
256     auto [id, name] = utils::makeIdName(reportId, reportName, reportNameDefault,
257                                         existingReportIds);
258 
259     verifyAddReport(id, name, reportingType, interval, reportUpdates,
260                     appendLimit, labeledMetricParams);
261 
262     reports.emplace_back(
263         reportFactory->make(id, name, reportingType, reportActions, interval,
264                             appendLimit, reportUpdates, *this, *reportStorage,
265                             labeledMetricParams, enabled, std::move(readings)));
266     return *reports.back();
267 }
268 
269 void ReportManager::loadFromPersistent()
270 {
271     std::vector<interfaces::JsonStorage::FilePath> paths =
272         reportStorage->list();
273 
274     for (const auto& path : paths)
275     {
276         std::optional<nlohmann::json> data = reportStorage->load(path);
277         try
278         {
279             size_t version = data->at("Version").get<size_t>();
280             if (version != Report::reportVersion)
281             {
282                 throw std::logic_error("Invalid version");
283             }
284             bool enabled = data->at("Enabled").get<bool>();
285             std::string& id = data->at("Id").get_ref<std::string&>();
286             std::string& name = data->at("Name").get_ref<std::string&>();
287 
288             uint32_t reportingType = data->at("ReportingType").get<uint32_t>();
289             std::vector<ReportAction> reportActions = utils::transform(
290                 data->at("ReportActions").get<std::vector<uint32_t>>(),
291                 [](const auto reportAction) {
292                 return utils::toReportAction(reportAction);
293                 });
294             uint64_t interval = data->at("Interval").get<uint64_t>();
295             uint64_t appendLimit = data->at("AppendLimit").get<uint64_t>();
296             uint32_t reportUpdates = data->at("ReportUpdates").get<uint32_t>();
297             auto readingParameters =
298                 data->at("ReadingParameters")
299                     .get<std::vector<LabeledMetricParameters>>();
300 
301             Readings readings = {};
302 
303             if (auto it = data->find("MetricValues"); it != data->end())
304             {
305                 const auto labeledReadings = it->get<LabeledReadings>();
306                 readings = utils::toReadings(labeledReadings);
307             }
308 
309             addReport(id, name, utils::toReportingType(reportingType),
310                       reportActions, Milliseconds(interval), appendLimit,
311                       utils::toReportUpdates(reportUpdates),
312                       std::move(readingParameters), enabled,
313                       std::move(readings));
314         }
315         catch (const std::exception& e)
316         {
317             phosphor::logging::log<phosphor::logging::level::ERR>(
318                 "Failed to load report from storage",
319                 phosphor::logging::entry(
320                     "FILENAME=%s",
321                     static_cast<std::filesystem::path>(path).c_str()),
322                 phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
323             reportStorage->remove(path);
324         }
325     }
326 }
327