xref: /openbmc/telemetry/src/metric.cpp (revision 8069771c0db62887b72aa2b8c51cd64eb5f99b2d)
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 std::vector<MetricValue> Metric::getReadings() const
145 {
146     const auto timestamp = clock->timestamp();
147 
148     auto resultReadings = readings;
149 
150     for (size_t i = 0; i < resultReadings.size(); ++i)
151     {
152         std::tie(resultReadings[i].timestamp, resultReadings[i].value) =
153             collectionAlgorithms[i]->update(timestamp);
154     }
155 
156     return resultReadings;
157 }
158 
159 void Metric::sensorUpdated(interfaces::Sensor& notifier, uint64_t timestamp)
160 {
161     findAssociatedData(notifier).update(timestamp);
162 }
163 
164 void Metric::sensorUpdated(interfaces::Sensor& notifier, uint64_t timestamp,
165                            double value)
166 {
167     findAssociatedData(notifier).update(timestamp, value);
168 }
169 
170 Metric::CollectionData&
171     Metric::findAssociatedData(const interfaces::Sensor& notifier)
172 {
173     auto it = std::find_if(
174         sensors.begin(), sensors.end(),
175         [&notifier](const auto& sensor) { return sensor.get() == &notifier; });
176     auto index = std::distance(sensors.begin(), it);
177     return *collectionAlgorithms.at(index);
178 }
179 
180 LabeledMetricParameters Metric::dumpConfiguration() const
181 {
182     auto sensorPath = utils::transform(sensors, [this](const auto& sensor) {
183         return LabeledSensorParameters(sensor->id().service, sensor->id().path);
184     });
185 
186     return LabeledMetricParameters(std::move(sensorPath), operationType, id,
187                                    metadata, collectionTimeScope,
188                                    collectionDuration);
189 }
190 
191 std::vector<std::unique_ptr<Metric::CollectionData>>
192     Metric::makeCollectionData(size_t size, OperationType op,
193                                CollectionTimeScope timeScope,
194                                CollectionDuration duration)
195 {
196     using namespace std::string_literals;
197 
198     std::vector<std::unique_ptr<Metric::CollectionData>> result;
199 
200     result.reserve(size);
201 
202     switch (timeScope)
203     {
204         case CollectionTimeScope::interval:
205             std::generate_n(
206                 std::back_inserter(result), size,
207                 [cf = details::makeCollectionFunction(op), duration] {
208                     return std::make_unique<DataInterval>(cf, duration);
209                 });
210             break;
211         case CollectionTimeScope::point:
212             std::generate_n(std::back_inserter(result), size,
213                             [] { return std::make_unique<DataPoint>(); });
214             break;
215         case CollectionTimeScope::startup:
216             std::generate_n(std::back_inserter(result), size,
217                             [cf = details::makeCollectionFunction(op)] {
218                                 return std::make_unique<DataStartup>(cf);
219                             });
220             break;
221         default:
222             throw std::runtime_error("timeScope: "s +
223                                      utils::enumToString(timeScope) +
224                                      " is not supported"s);
225     }
226 
227     return result;
228 }
229 
230 void Metric::attemptUnpackJsonMetadata()
231 {
232     using MetricMetadata =
233         utils::LabeledTuple<std::tuple<std::vector<std::string>>,
234                             utils::tstring::MetricProperties>;
235 
236     using ReadingMetadata =
237         utils::LabeledTuple<std::tuple<std::string, std::string>,
238                             utils::tstring::SensorDbusPath,
239                             utils::tstring::SensorRedfishUri>;
240     try
241     {
242         const MetricMetadata parsedMetadata =
243             nlohmann::json::parse(metadata).get<MetricMetadata>();
244 
245         if (readings.size() == parsedMetadata.at_index<0>().size())
246         {
247             for (size_t i = 0; i < readings.size(); ++i)
248             {
249                 ReadingMetadata readingMetadata{
250                     sensors[i]->id().path, parsedMetadata.at_index<0>()[i]};
251                 readings[i].metadata = readingMetadata.dump();
252             }
253         }
254     }
255     catch (const nlohmann::json::exception&)
256     {}
257 }
258