xref: /openbmc/telemetry/tests/src/test_metric.cpp (revision f7ea2997)
1 #include "fakes/clock_fake.hpp"
2 #include "helpers.hpp"
3 #include "metric.hpp"
4 #include "mocks/metric_listener_mock.hpp"
5 #include "mocks/sensor_mock.hpp"
6 #include "params/metric_params.hpp"
7 #include "utils/conv_container.hpp"
8 #include "utils/conversion.hpp"
9 #include "utils/tstring.hpp"
10 
11 #include <gmock/gmock.h>
12 
13 using namespace testing;
14 using namespace std::chrono_literals;
15 
16 namespace tstring = utils::tstring;
17 
18 constexpr Milliseconds systemTimestamp = 42ms;
19 
20 class TestMetric : public Test
21 {
22   public:
23     TestMetric()
24     {
25         clockFake.steady.reset();
26         clockFake.system.set(systemTimestamp);
27     }
28 
29     static std::vector<std::shared_ptr<SensorMock>>
30         makeSensorMocks(size_t amount)
31     {
32         std::vector<std::shared_ptr<SensorMock>> result;
33         for (size_t i = 0; i < amount; ++i)
34         {
35             auto& metricMock =
36                 result.emplace_back(std::make_shared<NiceMock<SensorMock>>());
37             ON_CALL(*metricMock, metadata()).WillByDefault(Return("metadata"));
38         }
39         return result;
40     }
41 
42     std::shared_ptr<Metric> makeSut(const MetricParams& p)
43     {
44         return std::make_shared<Metric>(
45             utils::convContainer<std::shared_ptr<interfaces::Sensor>>(
46                 sensorMocks),
47             p.operationType(), p.id(), p.collectionTimeScope(),
48             p.collectionDuration(), std::move(clockFakePtr));
49     }
50 
51     MetricParams params = MetricParams()
52                               .id("id")
53                               .operationType(OperationType::avg)
54                               .collectionTimeScope(CollectionTimeScope::point)
55                               .collectionDuration(CollectionDuration(0ms));
56     std::vector<std::shared_ptr<SensorMock>> sensorMocks = makeSensorMocks(1u);
57     std::unique_ptr<ClockFake> clockFakePtr = std::make_unique<ClockFake>();
58     ClockFake& clockFake = *clockFakePtr;
59     NiceMock<MetricListenerMock> listenerMock;
60     std::shared_ptr<Metric> sut;
61 };
62 
63 TEST_F(TestMetric, subscribesForSensorDuringInitialization)
64 {
65     sut = makeSut(params);
66 
67     EXPECT_CALL(*sensorMocks.front(),
68                 registerForUpdates(Truly([sut = sut.get()](const auto& a0) {
69                     return a0.lock().get() == sut;
70                 })));
71 
72     sut->initialize();
73 }
74 
75 TEST_F(TestMetric, unsubscribesForSensorDuringDeinitialization)
76 {
77     sut = makeSut(params);
78 
79     EXPECT_CALL(*sensorMocks.front(),
80                 unregisterFromUpdates(Truly([sut = sut.get()](const auto& a0) {
81                     return a0.lock().get() == sut;
82                 })));
83 
84     sut->deinitialize();
85 }
86 
87 TEST_F(TestMetric, containsEmptyReadingAfterCreated)
88 {
89     sut = makeSut(params);
90 
91     ASSERT_THAT(sut->getReadings(),
92                 ElementsAre(MetricValue({"id", "metadata", 0., 0u})));
93 }
94 
95 TEST_F(TestMetric,
96        notifiesRegisteredListenersOnManualUpdateWhenMetricValueChanges)
97 {
98     sut = makeSut(params.collectionTimeScope(CollectionTimeScope::startup));
99     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
100     sut->registerForUpdates(listenerMock);
101 
102     EXPECT_CALL(listenerMock, metricUpdated()).Times(2);
103 
104     sut->updateReadings(Milliseconds{50u});
105     sut->updateReadings(Milliseconds{100u});
106 }
107 
108 TEST_F(TestMetric,
109        doesntNotifyRegisteredListenersOnManualUpdateWhenMetricValueDoesntChange)
110 {
111     sut = makeSut(params.collectionTimeScope(CollectionTimeScope::startup)
112                       .operationType(OperationType::max));
113     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
114     sut->registerForUpdates(listenerMock);
115 
116     EXPECT_CALL(listenerMock, metricUpdated()).Times(0);
117 
118     sut->updateReadings(Milliseconds{50u});
119     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{180}, 11.);
120     sut->updateReadings(Milliseconds{100u});
121 }
122 
123 class TestMetricAfterInitialization : public TestMetric
124 {
125   public:
126     void SetUp() override
127     {
128         sut = makeSut(params);
129         sut->initialize();
130     }
131 };
132 
133 TEST_F(TestMetricAfterInitialization, containsEmptyReading)
134 {
135     ASSERT_THAT(sut->getReadings(),
136                 ElementsAre(MetricValue({"id", "metadata", 0., 0u})));
137 }
138 
139 TEST_F(TestMetricAfterInitialization, updatesMetricValuesOnSensorUpdate)
140 {
141     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
142 
143     ASSERT_THAT(
144         sut->getReadings(),
145         ElementsAre(MetricValue{"id", "metadata", 31.2,
146                                 std::chrono::duration_cast<Milliseconds>(
147                                     clockFake.system.timestamp())
148                                     .count()}));
149 }
150 
151 TEST_F(TestMetricAfterInitialization,
152        throwsWhenUpdateIsPerformedOnUnknownSensor)
153 {
154     auto sensor = std::make_shared<StrictMock<SensorMock>>();
155     EXPECT_THROW(sut->sensorUpdated(*sensor, Milliseconds{10}, 20.0),
156                  std::out_of_range);
157 }
158 
159 TEST_F(TestMetricAfterInitialization, dumpsConfiguration)
160 {
161     namespace ts = utils::tstring;
162 
163     ON_CALL(*sensorMocks.front(), id())
164         .WillByDefault(Return(SensorMock::makeId("service1", "path1")));
165     ON_CALL(*sensorMocks.front(), metadata())
166         .WillByDefault(Return("metadata1"));
167 
168     const auto conf = sut->dumpConfiguration();
169 
170     LabeledMetricParameters expected = {};
171     expected.at_label<ts::Id>() = params.id();
172     expected.at_label<ts::OperationType>() = params.operationType();
173     expected.at_label<ts::CollectionTimeScope>() = params.collectionTimeScope();
174     expected.at_label<ts::CollectionDuration>() = params.collectionDuration();
175     expected.at_label<ts::SensorPath>() = {
176         LabeledSensorInfo("service1", "path1", "metadata1")};
177 
178     EXPECT_THAT(conf, Eq(expected));
179 }
180 
181 TEST_F(TestMetricAfterInitialization, notifiesRegisteredListeners)
182 {
183     EXPECT_CALL(listenerMock, metricUpdated());
184 
185     sut->registerForUpdates(listenerMock);
186     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
187 }
188 
189 TEST_F(TestMetricAfterInitialization,
190        doesntNotifyRegisteredListenersWhenValueDoesntChange)
191 {
192     EXPECT_CALL(listenerMock, metricUpdated());
193 
194     sut->registerForUpdates(listenerMock);
195     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
196     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{70}, 31.2);
197 }
198 
199 TEST_F(TestMetricAfterInitialization, doesntNotifyAfterUnRegisterListener)
200 {
201     EXPECT_CALL(listenerMock, metricUpdated()).Times(0);
202 
203     sut->registerForUpdates(listenerMock);
204     sut->unregisterFromUpdates(listenerMock);
205     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
206 }
207 
208 class TestMetricCalculationFunctions :
209     public TestMetric,
210     public WithParamInterface<MetricParams>
211 {
212   public:
213     void SetUp() override
214     {
215         sut = makeSut(params.operationType(GetParam().operationType())
216                           .collectionTimeScope(GetParam().collectionTimeScope())
217                           .collectionDuration(GetParam().collectionDuration()));
218     }
219 
220     static std::vector<std::pair<Milliseconds, double>> defaultReadings()
221     {
222         std::vector<std::pair<Milliseconds, double>> ret;
223         ret.emplace_back(0ms, std::numeric_limits<double>::quiet_NaN());
224         ret.emplace_back(10ms, 14.);
225         ret.emplace_back(1ms, 3.);
226         ret.emplace_back(5ms, 7.);
227         return ret;
228     }
229 };
230 
231 MetricParams defaultCollectionFunctionParams()
232 {
233     return MetricParams()
234         .readings(TestMetricCalculationFunctions::defaultReadings())
235         .expectedReading(systemTimestamp + 16ms, 7.0);
236 }
237 
238 MetricParams defaultPointParams()
239 {
240     return defaultCollectionFunctionParams()
241         .collectionTimeScope(CollectionTimeScope::point)
242         .expectedIsTimerRequired(false);
243 }
244 
245 INSTANTIATE_TEST_SUITE_P(
246     TimeScopePointReturnsLastReading, TestMetricCalculationFunctions,
247     Values(defaultPointParams().operationType(OperationType::min),
248            defaultPointParams().operationType(OperationType::max),
249            defaultPointParams().operationType(OperationType::sum),
250            defaultPointParams().operationType(OperationType::avg)));
251 
252 MetricParams defaultMinParams()
253 {
254     return defaultCollectionFunctionParams().operationType(OperationType::min);
255 }
256 
257 INSTANTIATE_TEST_SUITE_P(
258     ReturnsMinForGivenTimeScope, TestMetricCalculationFunctions,
259     Values(defaultMinParams()
260                .collectionTimeScope(CollectionTimeScope::interval)
261                .collectionDuration(CollectionDuration(100ms))
262                .expectedReading(systemTimestamp + 16ms, 3.0),
263            defaultMinParams()
264                .collectionTimeScope(CollectionTimeScope::interval)
265                .collectionDuration(CollectionDuration(3ms))
266                .expectedReading(systemTimestamp + 16ms, 7.0),
267            defaultMinParams()
268                .collectionTimeScope(CollectionTimeScope::startup)
269                .expectedReading(systemTimestamp + 16ms, 3.0)
270                .expectedIsTimerRequired(false)));
271 
272 MetricParams defaultMaxParams()
273 {
274     return defaultCollectionFunctionParams().operationType(OperationType::max);
275 }
276 
277 INSTANTIATE_TEST_SUITE_P(
278     ReturnsMaxForGivenTimeScope, TestMetricCalculationFunctions,
279     Values(defaultMaxParams()
280                .collectionTimeScope(CollectionTimeScope::interval)
281                .collectionDuration(CollectionDuration(100ms))
282                .expectedReading(systemTimestamp + 16ms, 14.0),
283            defaultMaxParams()
284                .collectionTimeScope(CollectionTimeScope::interval)
285                .collectionDuration(CollectionDuration(6ms))
286                .expectedReading(systemTimestamp + 16ms, 14.0),
287            defaultMaxParams()
288                .collectionTimeScope(CollectionTimeScope::interval)
289                .collectionDuration(CollectionDuration(5ms))
290                .expectedReading(systemTimestamp + 16ms, 7.0),
291            defaultMaxParams()
292                .collectionTimeScope(CollectionTimeScope::startup)
293                .expectedReading(systemTimestamp + 16ms, 14.0)
294                .expectedIsTimerRequired(false)));
295 
296 MetricParams defaultSumParams()
297 {
298     return defaultCollectionFunctionParams().operationType(OperationType::sum);
299 }
300 
301 INSTANTIATE_TEST_SUITE_P(
302     ReturnsSumForGivenTimeScope, TestMetricCalculationFunctions,
303     Values(defaultSumParams()
304                .collectionTimeScope(CollectionTimeScope::interval)
305                .collectionDuration(CollectionDuration(100ms))
306                .expectedReading(systemTimestamp + 16ms,
307                                 14. * 0.01 + 3. * 0.001 + 7. * 0.005),
308            defaultSumParams()
309                .collectionTimeScope(CollectionTimeScope::interval)
310                .collectionDuration(CollectionDuration(8ms))
311                .expectedReading(systemTimestamp + 16ms,
312                                 14. * 0.002 + 3. * 0.001 + 7 * 0.005),
313            defaultSumParams()
314                .collectionTimeScope(CollectionTimeScope::interval)
315                .collectionDuration(CollectionDuration(6ms))
316                .expectedReading(systemTimestamp + 16ms, 3. * 0.001 + 7 * 0.005),
317            defaultSumParams()
318                .collectionTimeScope(CollectionTimeScope::startup)
319                .expectedReading(systemTimestamp + 16ms,
320                                 14. * 0.01 + 3. * 0.001 + 7 * 0.005)));
321 
322 MetricParams defaultAvgParams()
323 {
324     return defaultCollectionFunctionParams().operationType(OperationType::avg);
325 }
326 
327 INSTANTIATE_TEST_SUITE_P(
328     ReturnsAvgForGivenTimeScope, TestMetricCalculationFunctions,
329     Values(defaultAvgParams()
330                .collectionTimeScope(CollectionTimeScope::interval)
331                .collectionDuration(CollectionDuration(100ms))
332                .expectedReading(systemTimestamp + 16ms,
333                                 (14. * 10 + 3. * 1 + 7 * 5) / 16.),
334            defaultAvgParams()
335                .collectionTimeScope(CollectionTimeScope::interval)
336                .collectionDuration(CollectionDuration(8ms))
337                .expectedReading(systemTimestamp + 16ms,
338                                 (14. * 2 + 3. * 1 + 7 * 5) / 8.),
339            defaultAvgParams()
340                .collectionTimeScope(CollectionTimeScope::interval)
341                .collectionDuration(CollectionDuration(6ms))
342                .expectedReading(systemTimestamp + 16ms, (3. * 1 + 7 * 5) / 6.),
343            defaultAvgParams()
344                .collectionTimeScope(CollectionTimeScope::startup)
345                .expectedReading(systemTimestamp + 16ms,
346                                 (14. * 10 + 3. * 1 + 7 * 5) / 16.)));
347 
348 TEST_P(TestMetricCalculationFunctions, calculatesReadingValue)
349 {
350     for (auto [timestamp, reading] : GetParam().readings())
351     {
352         sut->sensorUpdated(*sensorMocks.front(), clockFake.steadyTimestamp(),
353                            reading);
354         clockFake.advance(timestamp);
355     }
356 
357     const auto [expectedTimestamp, expectedReading] =
358         GetParam().expectedReading();
359     const auto readings = sut->getReadings();
360 
361     EXPECT_THAT(readings,
362                 ElementsAre(MetricValue{"id", "metadata", expectedReading,
363                                         expectedTimestamp.count()}));
364 }
365 
366 TEST_P(TestMetricCalculationFunctions,
367        calculatedReadingValueWithIntermediateCalculations)
368 {
369     for (auto [timestamp, reading] : GetParam().readings())
370     {
371         sut->sensorUpdated(*sensorMocks.front(), clockFake.steadyTimestamp(),
372                            reading);
373         clockFake.advance(timestamp);
374         sut->getReadings();
375     }
376 
377     const auto [expectedTimestamp, expectedReading] =
378         GetParam().expectedReading();
379     const auto readings = sut->getReadings();
380 
381     EXPECT_THAT(readings,
382                 ElementsAre(MetricValue{"id", "metadata", expectedReading,
383                                         expectedTimestamp.count()}));
384 }
385 
386 TEST_P(TestMetricCalculationFunctions, returnsIsTimerRequired)
387 {
388     EXPECT_THAT(sut->isTimerRequired(),
389                 Eq(GetParam().expectedIsTimerRequired()));
390 }
391