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