xref: /openbmc/telemetry/src/report.cpp (revision dcc4e1936173a93251a02066432bc2bcbc386240)
1 #include "report.hpp"
2 
3 #include "report_manager.hpp"
4 #include "utils/transform.hpp"
5 
6 #include <phosphor-logging/log.hpp>
7 #include <sdbusplus/vtable.hpp>
8 
9 #include <numeric>
10 
11 Report::Report(boost::asio::io_context& ioc,
12                const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
13                const std::string& reportName,
14                const std::string& reportingTypeIn,
15                const bool emitsReadingsUpdateIn,
16                const bool logToMetricReportsCollectionIn,
17                const Milliseconds intervalIn,
18                interfaces::ReportManager& reportManager,
19                interfaces::JsonStorage& reportStorageIn,
20                std::vector<std::shared_ptr<interfaces::Metric>> metricsIn) :
21     name(reportName),
22     path(reportDir + name), reportingType(reportingTypeIn),
23     interval(intervalIn), emitsReadingsUpdate(emitsReadingsUpdateIn),
24     logToMetricReportsCollection(logToMetricReportsCollectionIn),
25     objServer(objServer), metrics(std::move(metricsIn)), timer(ioc),
26     fileName(std::to_string(std::hash<std::string>{}(name))),
27     reportStorage(reportStorageIn)
28 {
29     readingParameters =
30         toReadingParameters(utils::transform(metrics, [](const auto& metric) {
31             return metric->dumpConfiguration();
32         }));
33 
34     readingParametersPastVersion =
35         utils::transform(readingParameters, [](const auto& item) {
36             return ReadingParametersPastVersion::value_type(
37                 std::get<0>(item).front(), std::get<1>(item), std::get<2>(item),
38                 std::get<3>(item));
39         });
40 
41     deleteIface = objServer->add_unique_interface(
42         path, deleteIfaceName, [this, &ioc, &reportManager](auto& dbusIface) {
43             dbusIface.register_method("Delete", [this, &ioc, &reportManager] {
44                 if (persistency)
45                 {
46                     reportStorage.remove(fileName);
47                 }
48                 boost::asio::post(ioc, [this, &reportManager] {
49                     reportManager.removeReport(this);
50                 });
51             });
52         });
53 
54     persistency = storeConfiguration();
55     reportIface = makeReportInterface();
56 
57     if (reportingType == "Periodic")
58     {
59         scheduleTimer(interval);
60     }
61 
62     for (auto& metric : this->metrics)
63     {
64         metric->initialize();
65     }
66 }
67 
68 std::unique_ptr<sdbusplus::asio::dbus_interface> Report::makeReportInterface()
69 {
70     auto dbusIface = objServer->add_unique_interface(path, reportIfaceName);
71     dbusIface->register_property_rw(
72         "Interval", interval.count(),
73         sdbusplus::vtable::property_::emits_change,
74         [this](uint64_t newVal, auto&) {
75             Milliseconds newValT(newVal);
76             if (newValT < ReportManager::minInterval)
77             {
78                 return false;
79             }
80             interval = newValT;
81             return true;
82         },
83         [this](const auto&) { return interval.count(); });
84     dbusIface->register_property_rw(
85         "Persistency", persistency, sdbusplus::vtable::property_::emits_change,
86         [this](bool newVal, const auto&) {
87             if (newVal == persistency)
88             {
89                 return true;
90             }
91             if (newVal)
92             {
93                 persistency = storeConfiguration();
94             }
95             else
96             {
97                 reportStorage.remove(fileName);
98                 persistency = false;
99             }
100             return true;
101         },
102         [this](const auto&) { return persistency; });
103 
104     auto readingsFlag = sdbusplus::vtable::property_::none;
105     if (emitsReadingsUpdate)
106     {
107         readingsFlag = sdbusplus::vtable::property_::emits_change;
108     }
109     dbusIface->register_property_r("Readings", readings, readingsFlag,
110                                    [this](const auto&) { return readings; });
111     dbusIface->register_property_r(
112         "ReportingType", reportingType, sdbusplus::vtable::property_::const_,
113         [this](const auto&) { return reportingType; });
114     dbusIface->register_property_r(
115         "ReadingParameters", readingParametersPastVersion,
116         sdbusplus::vtable::property_::const_,
117         [this](const auto&) { return readingParametersPastVersion; });
118     dbusIface->register_property_r(
119         "ReadingParametersFutureVersion", readingParameters,
120         sdbusplus::vtable::property_::const_,
121         [this](const auto&) { return readingParameters; });
122     dbusIface->register_property_r(
123         "EmitsReadingsUpdate", emitsReadingsUpdate,
124         sdbusplus::vtable::property_::const_,
125         [this](const auto&) { return emitsReadingsUpdate; });
126     dbusIface->register_property_r(
127         "LogToMetricReportsCollection", logToMetricReportsCollection,
128         sdbusplus::vtable::property_::const_,
129         [this](const auto&) { return logToMetricReportsCollection; });
130     dbusIface->register_method("Update", [this] {
131         if (reportingType == "OnRequest")
132         {
133             updateReadings();
134         }
135     });
136     constexpr bool skipPropertiesChangedSignal = true;
137     dbusIface->initialize(skipPropertiesChangedSignal);
138     return dbusIface;
139 }
140 
141 void Report::timerProc(boost::system::error_code ec, Report& self)
142 {
143     if (ec)
144     {
145         return;
146     }
147 
148     self.updateReadings();
149     self.scheduleTimer(self.interval);
150 }
151 
152 void Report::scheduleTimer(Milliseconds timerInterval)
153 {
154     timer.expires_after(timerInterval);
155     timer.async_wait(
156         [this](boost::system::error_code ec) { timerProc(ec, *this); });
157 }
158 
159 void Report::updateReadings()
160 {
161     using ReadingsValue = std::tuple_element_t<1, Readings>;
162     std::get<ReadingsValue>(cachedReadings).clear();
163 
164     for (const auto& metric : metrics)
165     {
166         for (const auto& reading : metric->getReadings())
167         {
168             std::get<1>(cachedReadings)
169                 .emplace_back(reading.id, reading.metadata, reading.value,
170                               reading.timestamp);
171         }
172     }
173 
174     using ReadingsTimestamp = std::tuple_element_t<0, Readings>;
175     std::get<ReadingsTimestamp>(cachedReadings) = std::time(0);
176 
177     std::swap(readings, cachedReadings);
178 
179     reportIface->signal_property("Readings");
180 }
181 
182 bool Report::storeConfiguration() const
183 {
184     try
185     {
186         nlohmann::json data;
187 
188         data["Version"] = reportVersion;
189         data["Name"] = name;
190         data["ReportingType"] = reportingType;
191         data["EmitsReadingsUpdate"] = emitsReadingsUpdate;
192         data["LogToMetricReportsCollection"] = logToMetricReportsCollection;
193         data["Interval"] = interval.count();
194         data["ReadingParameters"] =
195             utils::transform(metrics, [](const auto& metric) {
196                 return metric->dumpConfiguration();
197             });
198 
199         reportStorage.store(fileName, data);
200     }
201     catch (const std::exception& e)
202     {
203         phosphor::logging::log<phosphor::logging::level::ERR>(
204             "Failed to store a report in storage",
205             phosphor::logging::entry("EXCEPTION_MSG=%s", e.what()));
206         return false;
207     }
208 
209     return true;
210 }
211