xref: /openbmc/telemetry/src/metric.cpp (revision e6d4887453f0b23b46c012da7cb26f2beb38ef0e)
1 #include "metric.hpp"
2 
3 #include "details/collection_function.hpp"
4 #include "types/report_types.hpp"
5 #include "types/sensor_types.hpp"
6 #include "utils/labeled_tuple.hpp"
7 #include "utils/transform.hpp"
8 
9 #include <sdbusplus/exception.hpp>
10 
11 #include <algorithm>
12 
13 class Metric::CollectionData
14 {
15   public:
16     using ReadingItem = details::ReadingItem;
17 
18     virtual ~CollectionData() = default;
19 
20     virtual std::optional<double> update(Milliseconds timestamp) = 0;
21     virtual double update(Milliseconds timestamp, double value) = 0;
22 };
23 
24 class Metric::DataPoint : public Metric::CollectionData
25 {
26   public:
27     std::optional<double> update(Milliseconds) override
28     {
29         return lastReading;
30     }
31 
32     double update(Milliseconds, double reading) override
33     {
34         lastReading = reading;
35         return reading;
36     }
37 
38   private:
39     std::optional<double> lastReading;
40 };
41 
42 class Metric::DataInterval : public Metric::CollectionData
43 {
44   public:
45     DataInterval(std::shared_ptr<details::CollectionFunction> function,
46                  CollectionDuration duration) :
47         function(std::move(function)),
48         duration(duration)
49     {
50         if (duration.t.count() == 0)
51         {
52             throw sdbusplus::exception::SdBusError(
53                 static_cast<int>(std::errc::invalid_argument),
54                 "Invalid CollectionDuration");
55         }
56     }
57 
58     std::optional<double> update(Milliseconds timestamp) override
59     {
60         if (readings.empty())
61         {
62             return std::nullopt;
63         }
64 
65         cleanup(timestamp);
66 
67         return function->calculate(readings, timestamp);
68     }
69 
70     double update(Milliseconds timestamp, double reading) override
71     {
72         readings.emplace_back(timestamp, reading);
73 
74         cleanup(timestamp);
75 
76         return function->calculate(readings, timestamp);
77     }
78 
79   private:
80     void cleanup(Milliseconds timestamp)
81     {
82         auto it = readings.begin();
83         for (auto kt = std::next(readings.rbegin()); kt != readings.rend();
84              ++kt)
85         {
86             const auto& [nextItemTimestamp, nextItemReading] = *std::prev(kt);
87             if (timestamp >= nextItemTimestamp &&
88                 timestamp - nextItemTimestamp > duration.t)
89             {
90                 it = kt.base();
91                 break;
92             }
93         }
94         readings.erase(readings.begin(), it);
95 
96         if (timestamp > duration.t)
97         {
98             readings.front().first =
99                 std::max(readings.front().first, timestamp - duration.t);
100         }
101     }
102 
103     std::shared_ptr<details::CollectionFunction> function;
104     std::vector<ReadingItem> readings;
105     CollectionDuration duration;
106 };
107 
108 class Metric::DataStartup : public Metric::CollectionData
109 {
110   public:
111     explicit DataStartup(
112         std::shared_ptr<details::CollectionFunction> function) :
113         function(std::move(function))
114     {}
115 
116     std::optional<double> update(Milliseconds timestamp) override
117     {
118         if (readings.empty())
119         {
120             return std::nullopt;
121         }
122 
123         return function->calculateForStartupInterval(readings, timestamp);
124     }
125 
126     double update(Milliseconds timestamp, double reading) override
127     {
128         readings.emplace_back(timestamp, reading);
129         return function->calculateForStartupInterval(readings, timestamp);
130     }
131 
132   private:
133     std::shared_ptr<details::CollectionFunction> function;
134     std::vector<ReadingItem> readings;
135 };
136 
137 Metric::Metric(Sensors sensorsIn, OperationType operationTypeIn,
138                std::string idIn, CollectionTimeScope timeScopeIn,
139                CollectionDuration collectionDurationIn,
140                std::unique_ptr<interfaces::Clock> clockIn) :
141     id(std::move(idIn)),
142     sensors(std::move(sensorsIn)), operationType(operationTypeIn),
143     collectionTimeScope(timeScopeIn), collectionDuration(collectionDurationIn),
144     collectionAlgorithms(makeCollectionData(sensors.size(), operationType,
145                                             collectionTimeScope,
146                                             collectionDuration)),
147     clock(std::move(clockIn))
148 {
149     readings = utils::transform(sensors, [this](const auto& sensor) {
150         return MetricValue{id, sensor->metadata(), 0.0, 0u};
151     });
152 }
153 
154 Metric::~Metric() = default;
155 
156 void Metric::initialize()
157 {
158     for (const auto& sensor : sensors)
159     {
160         sensor->registerForUpdates(weak_from_this());
161     }
162 }
163 
164 void Metric::deinitialize()
165 {
166     for (const auto& sensor : sensors)
167     {
168         sensor->unregisterFromUpdates(weak_from_this());
169     }
170 }
171 
172 std::vector<MetricValue> Metric::getReadings() const
173 {
174     const auto steadyTimestamp = clock->steadyTimestamp();
175     const auto systemTimestamp = clock->systemTimestamp();
176 
177     auto resultReadings = readings;
178 
179     for (size_t i = 0; i < resultReadings.size(); ++i)
180     {
181         if (const auto value = collectionAlgorithms[i]->update(steadyTimestamp))
182         {
183             resultReadings[i].timestamp =
184                 std::chrono::duration_cast<Milliseconds>(systemTimestamp)
185                     .count();
186             resultReadings[i].value = *value;
187         }
188     }
189 
190     return resultReadings;
191 }
192 
193 void Metric::sensorUpdated(interfaces::Sensor& notifier, Milliseconds timestamp)
194 {
195     findAssociatedData(notifier).update(timestamp);
196 }
197 
198 void Metric::sensorUpdated(interfaces::Sensor& notifier, Milliseconds timestamp,
199                            double value)
200 {
201     findAssociatedData(notifier).update(timestamp, value);
202 }
203 
204 Metric::CollectionData&
205     Metric::findAssociatedData(const interfaces::Sensor& notifier)
206 {
207     auto it = std::find_if(
208         sensors.begin(), sensors.end(),
209         [&notifier](const auto& sensor) { return sensor.get() == &notifier; });
210     auto index = std::distance(sensors.begin(), it);
211     return *collectionAlgorithms.at(index);
212 }
213 
214 LabeledMetricParameters Metric::dumpConfiguration() const
215 {
216     auto sensorPath = utils::transform(sensors, [this](const auto& sensor) {
217         return LabeledSensorInfo(sensor->id().service, sensor->id().path,
218                                  sensor->metadata());
219     });
220 
221     return LabeledMetricParameters(std::move(sensorPath), operationType, id,
222                                    collectionTimeScope, collectionDuration);
223 }
224 
225 std::vector<std::unique_ptr<Metric::CollectionData>>
226     Metric::makeCollectionData(size_t size, OperationType op,
227                                CollectionTimeScope timeScope,
228                                CollectionDuration duration)
229 {
230     using namespace std::string_literals;
231 
232     std::vector<std::unique_ptr<Metric::CollectionData>> result;
233 
234     result.reserve(size);
235 
236     switch (timeScope)
237     {
238         case CollectionTimeScope::interval:
239             std::generate_n(
240                 std::back_inserter(result), size,
241                 [cf = details::makeCollectionFunction(op), duration] {
242                     return std::make_unique<DataInterval>(cf, duration);
243                 });
244             break;
245         case CollectionTimeScope::point:
246             std::generate_n(std::back_inserter(result), size,
247                             [] { return std::make_unique<DataPoint>(); });
248             break;
249         case CollectionTimeScope::startup:
250             std::generate_n(std::back_inserter(result), size,
251                             [cf = details::makeCollectionFunction(op)] {
252                                 return std::make_unique<DataStartup>(cf);
253                             });
254             break;
255         default:
256             throw std::runtime_error("timeScope: "s +
257                                      utils::enumToString(timeScope) +
258                                      " is not supported"s);
259     }
260 
261     return result;
262 }
263 
264 uint64_t Metric::sensorCount() const
265 {
266     return sensors.size();
267 }
268