added support for Collection Functions
new supported operations: min,max,sum,avg
new supported time scopes: interval,startup
added unit test to verify that each collection function returns correct
timestamp and value
Tested:
- POST/GET on telemetry features in bmcweb, no regression detected
- Using dbus API metric with collection function works as expected
Change-Id: Ib364c433915e07fd7a102f00109525362c40ab8a
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/tests/meson.build b/tests/meson.build
index a3a78ec..3059953 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -11,6 +11,7 @@
executable(
'telemetry-ut',
[
+ '../src/details/collection_function.cpp',
'../src/discrete_threshold.cpp',
'../src/metric.cpp',
'../src/numeric_threshold.cpp',
diff --git a/tests/src/dbus_environment.cpp b/tests/src/dbus_environment.cpp
index 9e025d7..07e0aff 100644
--- a/tests/src/dbus_environment.cpp
+++ b/tests/src/dbus_environment.cpp
@@ -66,14 +66,13 @@
return [p = std::move(promise)]() { p->set_value(true); };
}
-bool DbusEnvironment::waitForFuture(std::string_view name,
- std::chrono::milliseconds timeout)
+bool DbusEnvironment::waitForFuture(std::string_view name, Milliseconds timeout)
{
return waitForFuture(getFuture(name), timeout);
}
bool DbusEnvironment::waitForFutures(std::string_view name,
- std::chrono::milliseconds timeout)
+ Milliseconds timeout)
{
auto& data = futures[std::string(name)];
auto ret = waitForFutures(
@@ -98,7 +97,7 @@
return {};
}
-void DbusEnvironment::sleepFor(std::chrono::milliseconds timeout)
+void DbusEnvironment::sleepFor(Milliseconds timeout)
{
auto end = std::chrono::high_resolution_clock::now() + timeout;
@@ -111,14 +110,13 @@
synchronizeIoc();
}
-std::chrono::milliseconds
- DbusEnvironment::measureTime(std::function<void()> fun)
+Milliseconds DbusEnvironment::measureTime(std::function<void()> fun)
{
auto begin = std::chrono::high_resolution_clock::now();
fun();
auto end = std::chrono::high_resolution_clock::now();
- return std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
+ return std::chrono::duration_cast<Milliseconds>(end - begin);
}
boost::asio::io_context DbusEnvironment::ioc;
diff --git a/tests/src/dbus_environment.hpp b/tests/src/dbus_environment.hpp
index 8146bcb..0ddf241 100644
--- a/tests/src/dbus_environment.hpp
+++ b/tests/src/dbus_environment.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include "types/duration_type.hpp"
+
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/asio/property.hpp>
@@ -22,8 +24,8 @@
static std::shared_ptr<sdbusplus::asio::object_server> getObjServer();
static const char* serviceName();
static std::function<void()> setPromise(std::string_view name);
- static void sleepFor(std::chrono::milliseconds);
- static std::chrono::milliseconds measureTime(std::function<void()>);
+ static void sleepFor(Milliseconds);
+ static Milliseconds measureTime(std::function<void()>);
static void synchronizeIoc()
{
@@ -39,12 +41,12 @@
}
template <class T, class F>
- static T waitForFutures(
- std::vector<std::future<T>> futures, T init, F&& accumulator,
- std::chrono::milliseconds timeout = std::chrono::seconds(10))
+ static T waitForFutures(std::vector<std::future<T>> futures, T init,
+ F&& accumulator,
+ Milliseconds timeout = std::chrono::seconds(10))
{
- constexpr auto precission = std::chrono::milliseconds(10);
- auto elapsed = std::chrono::milliseconds(0);
+ constexpr auto precission = Milliseconds(10);
+ auto elapsed = Milliseconds(0);
auto sum = init;
for (auto& future : futures)
@@ -73,9 +75,8 @@
}
template <class T>
- static T waitForFuture(
- std::future<T> future,
- std::chrono::milliseconds timeout = std::chrono::seconds(10))
+ static T waitForFuture(std::future<T> future,
+ Milliseconds timeout = std::chrono::seconds(10))
{
std::vector<std::future<T>> futures;
futures.emplace_back(std::move(future));
@@ -85,13 +86,11 @@
[](auto, const auto& value) { return value; }, timeout);
}
- static bool waitForFuture(
- std::string_view name,
- std::chrono::milliseconds timeout = std::chrono::seconds(10));
+ static bool waitForFuture(std::string_view name,
+ Milliseconds timeout = std::chrono::seconds(10));
- static bool waitForFutures(
- std::string_view name,
- std::chrono::milliseconds timeout = std::chrono::seconds(10));
+ static bool waitForFutures(std::string_view name,
+ Milliseconds timeout = std::chrono::seconds(10));
private:
static std::future<bool> getFuture(std::string_view name);
diff --git a/tests/src/fakes/clock_fake.hpp b/tests/src/fakes/clock_fake.hpp
new file mode 100644
index 0000000..28c2940
--- /dev/null
+++ b/tests/src/fakes/clock_fake.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "interfaces/clock.hpp"
+#include "types/duration_type.hpp"
+
+class ClockFake : public interfaces::Clock
+{
+ public:
+ time_point now() const noexcept override
+ {
+ return timePoint;
+ }
+
+ uint64_t timestamp() const noexcept override
+ {
+ return toTimestamp(now());
+ }
+
+ uint64_t advance(Milliseconds delta) noexcept
+ {
+ timePoint += delta;
+ return timestamp();
+ }
+
+ void set(Milliseconds timeSinceEpoch) noexcept
+ {
+ timePoint = time_point{timeSinceEpoch};
+ }
+
+ static uint64_t toTimestamp(Milliseconds time)
+ {
+ return time.count();
+ }
+
+ static uint64_t toTimestamp(time_point tp)
+ {
+ return std::chrono::time_point_cast<Milliseconds>(tp)
+ .time_since_epoch()
+ .count();
+ }
+
+ private:
+ time_point timePoint = std::chrono::steady_clock::now();
+};
diff --git a/tests/src/mocks/metric_mock.hpp b/tests/src/mocks/metric_mock.hpp
index 98bad87..f66f38d 100644
--- a/tests/src/mocks/metric_mock.hpp
+++ b/tests/src/mocks/metric_mock.hpp
@@ -12,12 +12,11 @@
using namespace testing;
ON_CALL(*this, getReadings())
- .WillByDefault(ReturnRefOfCopy(std::vector<MetricValue>()));
+ .WillByDefault(Return(std::vector<MetricValue>()));
}
MOCK_METHOD(void, initialize, (), (override));
- MOCK_METHOD(const std::vector<MetricValue>&, getReadings, (),
- (const, override));
+ MOCK_METHOD(std::vector<MetricValue>, getReadings, (), (const, override));
MOCK_METHOD(LabeledMetricParameters, dumpConfiguration, (),
(const, override));
};
diff --git a/tests/src/params/metric_params.hpp b/tests/src/params/metric_params.hpp
index f099472..4654060 100644
--- a/tests/src/params/metric_params.hpp
+++ b/tests/src/params/metric_params.hpp
@@ -4,7 +4,11 @@
#include "types/collection_time_scope.hpp"
#include "types/operation_type.hpp"
+#include <chrono>
+#include <cstdint>
+#include <ostream>
#include <string>
+#include <vector>
class MetricParams final
{
@@ -64,6 +68,28 @@
return collectionDurationProperty;
}
+ MetricParams& readings(std::vector<std::pair<Milliseconds, double>> value)
+ {
+ readingsProperty = std::move(value);
+ return *this;
+ }
+
+ const std::vector<std::pair<Milliseconds, double>>& readings() const
+ {
+ return readingsProperty;
+ }
+
+ MetricParams& expectedReading(Milliseconds delta, double reading)
+ {
+ expectedReadingProperty = std::make_pair(delta, reading);
+ return *this;
+ }
+
+ const std::pair<Milliseconds, double>& expectedReading() const
+ {
+ return expectedReadingProperty;
+ }
+
private:
OperationType operationTypeProperty = {};
std::string idProperty = "MetricId";
@@ -71,4 +97,24 @@
CollectionTimeScope collectionTimeScopeProperty = {};
CollectionDuration collectionDurationProperty =
CollectionDuration(Milliseconds(0u));
+ std::vector<std::pair<Milliseconds, double>> readingsProperty = {};
+ std::pair<Milliseconds, double> expectedReadingProperty = {};
};
+
+inline std::ostream& operator<<(std::ostream& os, const MetricParams& mp)
+{
+ using utils::enumToString;
+
+ os << "{ op: " << enumToString(mp.operationType())
+ << ", timeScope: " << enumToString(mp.collectionTimeScope())
+ << ", duration: " << mp.collectionDuration().t.count()
+ << ", readings: { ";
+ for (auto [timestamp, reading] : mp.readings())
+ {
+ os << reading << "(" << timestamp.count() << "ms), ";
+ }
+
+ auto [timestamp, reading] = mp.expectedReading();
+ os << " }, expected: " << reading << "(" << timestamp.count() << "ms) }";
+ return os;
+}
diff --git a/tests/src/params/trigger_params.hpp b/tests/src/params/trigger_params.hpp
index ac8656a..753cbb1 100644
--- a/tests/src/params/trigger_params.hpp
+++ b/tests/src/params/trigger_params.hpp
@@ -84,9 +84,7 @@
std::vector<LabeledSensorInfo> labeledSensorsProperty = {
{"service1", "/xyz/openbmc_project/sensors/temperature/BMC_Temp",
"metadata1"}};
-
std::vector<std::string> reportNamesProperty = {"Report1"};
-
LabeledTriggerThresholdParams labeledThresholdsProperty =
std::vector<numeric::LabeledThresholdParam>{
numeric::LabeledThresholdParam{numeric::Type::lowerCritical,
diff --git a/tests/src/test_metric.cpp b/tests/src/test_metric.cpp
index 6c68513..87e32f9 100644
--- a/tests/src/test_metric.cpp
+++ b/tests/src/test_metric.cpp
@@ -1,3 +1,4 @@
+#include "fakes/clock_fake.hpp"
#include "helpers.hpp"
#include "metric.hpp"
#include "mocks/sensor_mock.hpp"
@@ -35,17 +36,18 @@
utils::convContainer<std::shared_ptr<interfaces::Sensor>>(
sensorMocks),
p.operationType(), p.id(), p.metadata(), p.collectionTimeScope(),
- p.collectionDuration());
+ p.collectionDuration(), std::move(clockFakePtr));
}
- MetricParams params =
- MetricParams()
- .id("id")
- .metadata("metadata")
- .operationType(OperationType::avg)
- .collectionTimeScope(CollectionTimeScope::interval)
- .collectionDuration(CollectionDuration(42ms));
+ MetricParams params = MetricParams()
+ .id("id")
+ .metadata("metadata")
+ .operationType(OperationType::avg)
+ .collectionTimeScope(CollectionTimeScope::point)
+ .collectionDuration(CollectionDuration(0ms));
std::vector<std::shared_ptr<SensorMock>> sensorMocks = makeSensorMocks(1u);
+ std::unique_ptr<ClockFake> clockFakePtr = std::make_unique<ClockFake>();
+ ClockFake& clockFake = *clockFakePtr;
std::shared_ptr<Metric> sut;
};
@@ -157,14 +159,196 @@
const auto conf = sut->dumpConfiguration();
LabeledMetricParameters expected = {};
- expected.at_label<ts::Id>() = "id";
- expected.at_label<ts::MetricMetadata>() = "metadata";
- expected.at_label<ts::OperationType>() = OperationType::avg;
- expected.at_label<ts::CollectionTimeScope>() =
- CollectionTimeScope::interval;
- expected.at_label<ts::CollectionDuration>() = CollectionDuration(42ms);
+ expected.at_label<ts::Id>() = params.id();
+ expected.at_label<ts::MetricMetadata>() = params.metadata();
+ expected.at_label<ts::OperationType>() = params.operationType();
+ expected.at_label<ts::CollectionTimeScope>() = params.collectionTimeScope();
+ expected.at_label<ts::CollectionDuration>() = params.collectionDuration();
expected.at_label<ts::SensorPath>() = {
LabeledSensorParameters("service1", "path1")};
EXPECT_THAT(conf, Eq(expected));
}
+
+class TestMetricCalculationFunctions :
+ public TestMetric,
+ public WithParamInterface<MetricParams>
+{
+ public:
+ void SetUp() override
+ {
+ clockFakePtr->set(0ms);
+
+ sut = makeSut(params.operationType(GetParam().operationType())
+ .collectionTimeScope(GetParam().collectionTimeScope())
+ .collectionDuration(GetParam().collectionDuration()));
+ }
+
+ static std::vector<std::pair<Milliseconds, double>> defaultReadings()
+ {
+ std::vector<std::pair<Milliseconds, double>> ret;
+ ret.emplace_back(0ms, std::numeric_limits<double>::quiet_NaN());
+ ret.emplace_back(10ms, 14.);
+ ret.emplace_back(1ms, 3.);
+ ret.emplace_back(5ms, 7.);
+ return ret;
+ }
+};
+
+MetricParams defaultSingleParams()
+{
+ return MetricParams()
+ .operationType(OperationType::single)
+ .readings(TestMetricCalculationFunctions::defaultReadings())
+ .expectedReading(11ms, 7.0);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ OperationSingleReturnsLastReading, TestMetricCalculationFunctions,
+ Values(
+ defaultSingleParams().collectionTimeScope(CollectionTimeScope::point),
+ defaultSingleParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(100ms)),
+ defaultSingleParams().collectionTimeScope(
+ CollectionTimeScope::startup)));
+
+MetricParams defaultPointParams()
+{
+ return defaultSingleParams().collectionTimeScope(
+ CollectionTimeScope::point);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ TimeScopePointReturnsLastReading, TestMetricCalculationFunctions,
+ Values(defaultPointParams().operationType(OperationType::single),
+ defaultPointParams().operationType(OperationType::min),
+ defaultPointParams().operationType(OperationType::max),
+ defaultPointParams().operationType(OperationType::sum),
+ defaultPointParams().operationType(OperationType::avg)));
+
+MetricParams defaultMinParams()
+{
+ return defaultSingleParams().operationType(OperationType::min);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ReturnsMinForGivenTimeScope, TestMetricCalculationFunctions,
+ Values(defaultMinParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(100ms))
+ .expectedReading(10ms, 3.0),
+ defaultMinParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(3ms))
+ .expectedReading(13ms, 7.0),
+ defaultMinParams()
+ .collectionTimeScope(CollectionTimeScope::startup)
+ .expectedReading(10ms, 3.0)));
+
+MetricParams defaultMaxParams()
+{
+ return defaultSingleParams().operationType(OperationType::max);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ReturnsMaxForGivenTimeScope, TestMetricCalculationFunctions,
+ Values(defaultMaxParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(100ms))
+ .expectedReading(0ms, 14.0),
+ defaultMaxParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(6ms))
+ .expectedReading(10ms, 14.0),
+ defaultMaxParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(5ms))
+ .expectedReading(11ms, 7.0),
+ defaultMaxParams()
+ .collectionTimeScope(CollectionTimeScope::startup)
+ .expectedReading(0ms, 14.0)));
+
+MetricParams defaultSumParams()
+{
+ return defaultSingleParams().operationType(OperationType::sum);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ReturnsSumForGivenTimeScope, TestMetricCalculationFunctions,
+ Values(defaultSumParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(100ms))
+ .expectedReading(16ms, 14. * 10 + 3. * 1 + 7 * 5),
+ defaultSumParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(8ms))
+ .expectedReading(16ms, 14. * 2 + 3. * 1 + 7 * 5),
+ defaultSumParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(6ms))
+ .expectedReading(16ms, 3. * 1 + 7 * 5),
+ defaultSumParams()
+ .collectionTimeScope(CollectionTimeScope::startup)
+ .expectedReading(16ms, 14. * 10 + 3. * 1 + 7 * 5)));
+
+MetricParams defaultAvgParams()
+{
+ return defaultSingleParams().operationType(OperationType::avg);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ReturnsAvgForGivenTimeScope, TestMetricCalculationFunctions,
+ Values(defaultAvgParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(100ms))
+ .expectedReading(16ms, (14. * 10 + 3. * 1 + 7 * 5) / 16.),
+ defaultAvgParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(8ms))
+ .expectedReading(16ms, (14. * 2 + 3. * 1 + 7 * 5) / 8.),
+ defaultAvgParams()
+ .collectionTimeScope(CollectionTimeScope::interval)
+ .collectionDuration(CollectionDuration(6ms))
+ .expectedReading(16ms, (3. * 1 + 7 * 5) / 6.),
+ defaultAvgParams()
+ .collectionTimeScope(CollectionTimeScope::startup)
+ .expectedReading(16ms, (14. * 10 + 3. * 1 + 7 * 5) / 16.)));
+
+TEST_P(TestMetricCalculationFunctions, calculatesReadingValue)
+{
+ for (auto [timestamp, reading] : GetParam().readings())
+ {
+ sut->sensorUpdated(*sensorMocks.front(), clockFake.timestamp(),
+ reading);
+ clockFake.advance(timestamp);
+ }
+
+ const auto [expectedTimestamp, expectedReading] =
+ GetParam().expectedReading();
+ const auto readings = sut->getReadings();
+
+ EXPECT_THAT(readings, ElementsAre(MetricValue{
+ "id", "metadata", expectedReading,
+ ClockFake::toTimestamp(expectedTimestamp)}));
+}
+
+TEST_P(TestMetricCalculationFunctions,
+ calculatedReadingValueWithIntermediateCalculations)
+{
+ for (auto [timestamp, reading] : GetParam().readings())
+ {
+ sut->sensorUpdated(*sensorMocks.front(), clockFake.timestamp(),
+ reading);
+ clockFake.advance(timestamp);
+ sut->getReadings();
+ }
+
+ const auto [expectedTimestamp, expectedReading] =
+ GetParam().expectedReading();
+ const auto readings = sut->getReadings();
+
+ EXPECT_THAT(readings, ElementsAre(MetricValue{
+ "id", "metadata", expectedReading,
+ ClockFake::toTimestamp(expectedTimestamp)}));
+}
diff --git a/tests/src/test_report.cpp b/tests/src/test_report.cpp
index 96f74f0..441ebd9 100644
--- a/tests/src/test_report.cpp
+++ b/tests/src/test_report.cpp
@@ -46,7 +46,7 @@
for (size_t i = 0; i < metricParameters.size(); ++i)
{
ON_CALL(*metricMocks[i], getReadings())
- .WillByDefault(ReturnRefOfCopy(std::vector({readings[i]})));
+ .WillByDefault(Return(std::vector({readings[i]})));
ON_CALL(*metricMocks[i], dumpConfiguration())
.WillByDefault(Return(metricParameters[i]));
}