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