xref: /openbmc/telemetry/tests/src/test_metric.cpp (revision 55824558920fbbfa8c9df64f56fa8efc981b7bb9)
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())
38                 .WillByDefault(Return("metadata" + std::to_string(i)));
39         }
40         return result;
41     }
42 
43     std::shared_ptr<Metric> makeSut(const MetricParams& p)
44     {
45         return std::make_shared<Metric>(
46             utils::convContainer<std::shared_ptr<interfaces::Sensor>>(
47                 sensorMocks),
48             p.operationType(), p.id(), p.collectionTimeScope(),
49             p.collectionDuration(), std::move(clockFakePtr));
50     }
51 
52     MetricParams params = MetricParams()
53                               .id("id")
54                               .operationType(OperationType::avg)
55                               .collectionTimeScope(CollectionTimeScope::point)
56                               .collectionDuration(CollectionDuration(0ms));
57     std::vector<std::shared_ptr<SensorMock>> sensorMocks = makeSensorMocks(1u);
58     std::unique_ptr<ClockFake> clockFakePtr = std::make_unique<ClockFake>();
59     ClockFake& clockFake = *clockFakePtr;
60     NiceMock<MetricListenerMock> listenerMock;
61     std::shared_ptr<Metric> sut;
62 };
63 
64 TEST_F(TestMetric, subscribesForSensorDuringInitialization)
65 {
66     sut = makeSut(params);
67 
68     EXPECT_CALL(*sensorMocks.front(),
69                 registerForUpdates(Truly([sut = sut.get()](const auto& a0) {
70                     return a0.lock().get() == sut;
71                 })));
72 
73     sut->initialize();
74 }
75 
76 TEST_F(TestMetric, unsubscribesForSensorDuringDeinitialization)
77 {
78     sut = makeSut(params);
79 
80     EXPECT_CALL(*sensorMocks.front(),
81                 unregisterFromUpdates(Truly([sut = sut.get()](const auto& a0) {
82                     return a0.lock().get() == sut;
83                 })));
84 
85     sut->deinitialize();
86 }
87 
88 TEST_F(TestMetric, containsEmptyReadingAfterCreated)
89 {
90     sut = makeSut(params);
91 
92     ASSERT_THAT(sut->getUpdatedReadings(), ElementsAre());
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->getUpdatedReadings(), ElementsAre());
136 }
137 
138 TEST_F(TestMetricAfterInitialization, updatesMetricValuesOnSensorUpdate)
139 {
140     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
141 
142     ASSERT_THAT(
143         sut->getUpdatedReadings(),
144         ElementsAre(MetricValue{"id", "metadata0", 31.2,
145                                 std::chrono::duration_cast<Milliseconds>(
146                                     clockFake.system.timestamp())
147                                     .count()}));
148 }
149 
150 TEST_F(TestMetricAfterInitialization,
151        throwsWhenUpdateIsPerformedOnUnknownSensor)
152 {
153     auto sensor = std::make_shared<StrictMock<SensorMock>>();
154     EXPECT_THROW(sut->sensorUpdated(*sensor, Milliseconds{10}, 20.0),
155                  std::out_of_range);
156 }
157 
158 TEST_F(TestMetricAfterInitialization, dumpsConfiguration)
159 {
160     namespace ts = utils::tstring;
161 
162     ON_CALL(*sensorMocks.front(), id())
163         .WillByDefault(Return(SensorMock::makeId("service1", "path1")));
164     ON_CALL(*sensorMocks.front(), metadata())
165         .WillByDefault(Return("metadata1"));
166 
167     const auto conf = sut->dumpConfiguration();
168 
169     LabeledMetricParameters expected = {};
170     expected.at_label<ts::Id>() = params.id();
171     expected.at_label<ts::OperationType>() = params.operationType();
172     expected.at_label<ts::CollectionTimeScope>() = params.collectionTimeScope();
173     expected.at_label<ts::CollectionDuration>() = params.collectionDuration();
174     expected.at_label<ts::SensorPath>() = {
175         LabeledSensorInfo("service1", "path1", "metadata1")};
176 
177     EXPECT_THAT(conf, Eq(expected));
178 }
179 
180 TEST_F(TestMetricAfterInitialization, notifiesRegisteredListeners)
181 {
182     EXPECT_CALL(listenerMock, metricUpdated());
183 
184     sut->registerForUpdates(listenerMock);
185     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
186 }
187 
188 TEST_F(TestMetricAfterInitialization,
189        doesntNotifyRegisteredListenersWhenValueDoesntChange)
190 {
191     EXPECT_CALL(listenerMock, metricUpdated());
192 
193     sut->registerForUpdates(listenerMock);
194     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
195     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{70}, 31.2);
196 }
197 
198 TEST_F(TestMetricAfterInitialization, doesntNotifyAfterUnRegisterListener)
199 {
200     EXPECT_CALL(listenerMock, metricUpdated()).Times(0);
201 
202     sut->registerForUpdates(listenerMock);
203     sut->unregisterFromUpdates(listenerMock);
204     sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
205 }
206 
207 class TestMetricCalculationFunctions :
208     public TestMetric,
209     public WithParamInterface<MetricParams>
210 {
211   public:
212     void SetUp() override
213     {
214         sut = makeSut(params.operationType(GetParam().operationType())
215                           .collectionTimeScope(GetParam().collectionTimeScope())
216                           .collectionDuration(GetParam().collectionDuration()));
217     }
218 
219     static std::vector<std::pair<Milliseconds, double>> defaultReadings()
220     {
221         std::vector<std::pair<Milliseconds, double>> ret;
222         ret.emplace_back(0ms, std::numeric_limits<double>::quiet_NaN());
223         ret.emplace_back(10ms, 14.);
224         ret.emplace_back(1ms, 3.);
225         ret.emplace_back(5ms, 7.);
226         return ret;
227     }
228 };
229 
230 MetricParams defaultCollectionFunctionParams()
231 {
232     return MetricParams()
233         .readings(TestMetricCalculationFunctions::defaultReadings())
234         .expectedReading(systemTimestamp + 16ms, 7.0);
235 }
236 
237 MetricParams defaultPointParams()
238 {
239     return defaultCollectionFunctionParams()
240         .collectionTimeScope(CollectionTimeScope::point)
241         .expectedIsTimerRequired(false);
242 }
243 
244 INSTANTIATE_TEST_SUITE_P(
245     TimeScopePointReturnsLastReading, TestMetricCalculationFunctions,
246     Values(defaultPointParams().operationType(OperationType::min),
247            defaultPointParams().operationType(OperationType::max),
248            defaultPointParams().operationType(OperationType::sum),
249            defaultPointParams().operationType(OperationType::avg)));
250 
251 MetricParams defaultMinParams()
252 {
253     return defaultCollectionFunctionParams().operationType(OperationType::min);
254 }
255 
256 INSTANTIATE_TEST_SUITE_P(
257     ReturnsMinForGivenTimeScope, TestMetricCalculationFunctions,
258     Values(defaultMinParams()
259                .collectionTimeScope(CollectionTimeScope::interval)
260                .collectionDuration(CollectionDuration(100ms))
261                .expectedReading(systemTimestamp + 16ms, 3.0),
262            defaultMinParams()
263                .collectionTimeScope(CollectionTimeScope::interval)
264                .collectionDuration(CollectionDuration(3ms))
265                .expectedReading(systemTimestamp + 16ms, 7.0),
266            defaultMinParams()
267                .collectionTimeScope(CollectionTimeScope::startup)
268                .expectedReading(systemTimestamp + 16ms, 3.0)
269                .expectedIsTimerRequired(false)));
270 
271 MetricParams defaultMaxParams()
272 {
273     return defaultCollectionFunctionParams().operationType(OperationType::max);
274 }
275 
276 INSTANTIATE_TEST_SUITE_P(
277     ReturnsMaxForGivenTimeScope, TestMetricCalculationFunctions,
278     Values(defaultMaxParams()
279                .collectionTimeScope(CollectionTimeScope::interval)
280                .collectionDuration(CollectionDuration(100ms))
281                .expectedReading(systemTimestamp + 16ms, 14.0),
282            defaultMaxParams()
283                .collectionTimeScope(CollectionTimeScope::interval)
284                .collectionDuration(CollectionDuration(6ms))
285                .expectedReading(systemTimestamp + 16ms, 14.0),
286            defaultMaxParams()
287                .collectionTimeScope(CollectionTimeScope::interval)
288                .collectionDuration(CollectionDuration(5ms))
289                .expectedReading(systemTimestamp + 16ms, 7.0),
290            defaultMaxParams()
291                .collectionTimeScope(CollectionTimeScope::startup)
292                .expectedReading(systemTimestamp + 16ms, 14.0)
293                .expectedIsTimerRequired(false)));
294 
295 MetricParams defaultSumParams()
296 {
297     return defaultCollectionFunctionParams().operationType(OperationType::sum);
298 }
299 
300 INSTANTIATE_TEST_SUITE_P(
301     ReturnsSumForGivenTimeScope, TestMetricCalculationFunctions,
302     Values(defaultSumParams()
303                .collectionTimeScope(CollectionTimeScope::interval)
304                .collectionDuration(CollectionDuration(100ms))
305                .expectedReading(systemTimestamp + 16ms,
306                                 14. * 0.01 + 3. * 0.001 + 7. * 0.005),
307            defaultSumParams()
308                .collectionTimeScope(CollectionTimeScope::interval)
309                .collectionDuration(CollectionDuration(8ms))
310                .expectedReading(systemTimestamp + 16ms,
311                                 14. * 0.002 + 3. * 0.001 + 7 * 0.005),
312            defaultSumParams()
313                .collectionTimeScope(CollectionTimeScope::interval)
314                .collectionDuration(CollectionDuration(6ms))
315                .expectedReading(systemTimestamp + 16ms, 3. * 0.001 + 7 * 0.005),
316            defaultSumParams()
317                .collectionTimeScope(CollectionTimeScope::startup)
318                .expectedReading(systemTimestamp + 16ms,
319                                 14. * 0.01 + 3. * 0.001 + 7 * 0.005)));
320 
321 MetricParams defaultAvgParams()
322 {
323     return defaultCollectionFunctionParams().operationType(OperationType::avg);
324 }
325 
326 INSTANTIATE_TEST_SUITE_P(
327     ReturnsAvgForGivenTimeScope, TestMetricCalculationFunctions,
328     Values(defaultAvgParams()
329                .collectionTimeScope(CollectionTimeScope::interval)
330                .collectionDuration(CollectionDuration(100ms))
331                .expectedReading(systemTimestamp + 16ms,
332                                 (14. * 10 + 3. * 1 + 7 * 5) / 16.),
333            defaultAvgParams()
334                .collectionTimeScope(CollectionTimeScope::interval)
335                .collectionDuration(CollectionDuration(8ms))
336                .expectedReading(systemTimestamp + 16ms,
337                                 (14. * 2 + 3. * 1 + 7 * 5) / 8.),
338            defaultAvgParams()
339                .collectionTimeScope(CollectionTimeScope::interval)
340                .collectionDuration(CollectionDuration(6ms))
341                .expectedReading(systemTimestamp + 16ms, (3. * 1 + 7 * 5) / 6.),
342            defaultAvgParams()
343                .collectionTimeScope(CollectionTimeScope::startup)
344                .expectedReading(systemTimestamp + 16ms,
345                                 (14. * 10 + 3. * 1 + 7 * 5) / 16.)));
346 
347 TEST_P(TestMetricCalculationFunctions, calculatesReadingValue)
348 {
349     for (auto [timestamp, reading] : GetParam().readings())
350     {
351         sut->sensorUpdated(*sensorMocks.front(), clockFake.steadyTimestamp(),
352                            reading);
353         clockFake.advance(timestamp);
354     }
355 
356     const auto [expectedTimestamp, expectedReading] =
357         GetParam().expectedReading();
358     const auto readings = sut->getUpdatedReadings();
359 
360     EXPECT_THAT(readings,
361                 ElementsAre(MetricValue{"id", "metadata0", expectedReading,
362                                         expectedTimestamp.count()}));
363 }
364 
365 TEST_P(TestMetricCalculationFunctions,
366        calculatedReadingValueWithIntermediateCalculations)
367 {
368     for (auto [timestamp, reading] : GetParam().readings())
369     {
370         sut->sensorUpdated(*sensorMocks.front(), clockFake.steadyTimestamp(),
371                            reading);
372         clockFake.advance(timestamp);
373         sut->getUpdatedReadings();
374     }
375 
376     const auto [expectedTimestamp, expectedReading] =
377         GetParam().expectedReading();
378     const auto readings = sut->getUpdatedReadings();
379 
380     EXPECT_THAT(readings,
381                 ElementsAre(MetricValue{"id", "metadata0", expectedReading,
382                                         expectedTimestamp.count()}));
383 }
384 
385 TEST_P(TestMetricCalculationFunctions, returnsIsTimerRequired)
386 {
387     EXPECT_THAT(sut->isTimerRequired(),
388                 Eq(GetParam().expectedIsTimerRequired()));
389 }
390 
391 class TestMetricWithMultipleSensors : public TestMetric
392 {
393   public:
394     TestMetricWithMultipleSensors()
395     {
396         sensorMocks = makeSensorMocks(7u);
397 
398         sut = makeSut(params);
399         sut->initialize();
400     }
401 };
402 
403 TEST_F(TestMetricWithMultipleSensors, readingsContainsAllReadingsInOrder)
404 {
405     for (size_t i = 0; i < sensorMocks.size(); ++i)
406     {
407         sut->sensorUpdated(*sensorMocks[i], Milliseconds{i + 100}, i + 10.0);
408         sut->getUpdatedReadings();
409     }
410 
411     clockFake.system.set(Milliseconds{72});
412 
413     EXPECT_THAT(sut->getUpdatedReadings(),
414                 ElementsAre(MetricValue{"id", "metadata0", 10.0, 72},
415                             MetricValue{"id", "metadata1", 11.0, 72},
416                             MetricValue{"id", "metadata2", 12.0, 72},
417                             MetricValue{"id", "metadata3", 13.0, 72},
418                             MetricValue{"id", "metadata4", 14.0, 72},
419                             MetricValue{"id", "metadata5", 15.0, 72},
420                             MetricValue{"id", "metadata6", 16.0, 72}));
421 }
422 
423 TEST_F(TestMetricWithMultipleSensors, readingsContainOnlyAvailableSensors)
424 {
425     for (auto i : {5u, 3u, 6u, 0u})
426     {
427         sut->sensorUpdated(*sensorMocks[i], Milliseconds{i + 100}, i + 10.0);
428         sut->getUpdatedReadings();
429     }
430 
431     clockFake.system.set(Milliseconds{62});
432 
433     EXPECT_THAT(sut->getUpdatedReadings(),
434                 ElementsAre(MetricValue{"id", "metadata5", 15.0, 62},
435                             MetricValue{"id", "metadata3", 13.0, 62},
436                             MetricValue{"id", "metadata6", 16.0, 62},
437                             MetricValue{"id", "metadata0", 10.0, 62}));
438 }
439 
440 TEST_F(TestMetricWithMultipleSensors, readingsContainsAllReadingsOutOfOrder)
441 {
442     for (auto i : {6u, 5u, 3u, 4u, 0u, 2u, 1u})
443     {
444         sut->sensorUpdated(*sensorMocks[i], Milliseconds{i + 100}, i + 10.0);
445         sut->getUpdatedReadings();
446     }
447 
448     clockFake.system.set(Milliseconds{52});
449 
450     EXPECT_THAT(sut->getUpdatedReadings(),
451                 ElementsAre(MetricValue{"id", "metadata6", 16.0, 52},
452                             MetricValue{"id", "metadata5", 15.0, 52},
453                             MetricValue{"id", "metadata3", 13.0, 52},
454                             MetricValue{"id", "metadata4", 14.0, 52},
455                             MetricValue{"id", "metadata0", 10.0, 52},
456                             MetricValue{"id", "metadata2", 12.0, 52},
457                             MetricValue{"id", "metadata1", 11.0, 52}));
458 }
459