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/meson.build b/meson.build
index 622043c..625f265 100644
--- a/meson.build
+++ b/meson.build
@@ -76,6 +76,7 @@
executable(
'telemetry',
[
+ 'src/details/collection_function.cpp',
'src/discrete_threshold.cpp',
'src/main.cpp',
'src/metric.cpp',
diff --git a/src/details/collection_function.cpp b/src/details/collection_function.cpp
new file mode 100644
index 0000000..d92fdf3
--- /dev/null
+++ b/src/details/collection_function.cpp
@@ -0,0 +1,172 @@
+#include "collection_function.hpp"
+
+#include <cmath>
+
+namespace details
+{
+
+class FunctionSingle : public CollectionFunction
+{
+ public:
+ ReadingItem calculate(const std::vector<ReadingItem>& readings,
+ uint64_t) const override
+ {
+ return readings.back();
+ }
+
+ ReadingItem calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const override
+ {
+ readings.assign({readings.back()});
+ return readings.back();
+ }
+};
+
+class FunctionMinimum : public CollectionFunction
+{
+ public:
+ ReadingItem calculate(const std::vector<ReadingItem>& readings,
+ uint64_t) const override
+ {
+ return *std::min_element(
+ readings.begin(), readings.end(),
+ [](const auto& left, const auto& right) {
+ return std::make_tuple(!std::isfinite(left.second),
+ left.second) <
+ std::make_tuple(!std::isfinite(right.second),
+ right.second);
+ });
+ }
+
+ ReadingItem calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const override
+ {
+ readings.assign({ReadingItem(calculate(readings, timestamp))});
+ return readings.back();
+ }
+};
+
+class FunctionMaximum : public CollectionFunction
+{
+ public:
+ ReadingItem calculate(const std::vector<ReadingItem>& readings,
+ uint64_t) const override
+ {
+ return *std::max_element(
+ readings.begin(), readings.end(),
+ [](const auto& left, const auto& right) {
+ return std::make_tuple(std::isfinite(left.second),
+ left.second) <
+ std::make_tuple(std::isfinite(right.second),
+ right.second);
+ });
+ }
+
+ ReadingItem calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const override
+ {
+ readings.assign({ReadingItem(calculate(readings, timestamp))});
+ return readings.back();
+ }
+};
+
+class FunctionAverage : public CollectionFunction
+{
+ public:
+ ReadingItem calculate(const std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const override
+ {
+ auto valueSum = 0.0;
+ auto timeSum = uint64_t{0};
+ for (auto it = readings.begin(); it != std::prev(readings.end()); ++it)
+ {
+ if (std::isfinite(it->second))
+ {
+ const auto kt = std::next(it);
+ const auto duration = kt->first - it->first;
+ valueSum += it->second * duration;
+ timeSum += duration;
+ }
+ }
+
+ const auto duration = timestamp - readings.back().first;
+ valueSum += readings.back().second * duration;
+ timeSum += duration;
+
+ return ReadingItem{timestamp, valueSum / timeSum};
+ }
+
+ ReadingItem calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const override
+ {
+ auto result = calculate(readings, timestamp);
+ if (std::isfinite(result.second))
+ {
+ readings.assign({ReadingItem(readings.front().first, result.second),
+ ReadingItem(timestamp, readings.back().second)});
+ }
+ return result;
+ }
+};
+
+class FunctionSummation : public CollectionFunction
+{
+ public:
+ ReadingItem calculate(const std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const override
+ {
+ auto valueSum = 0.0;
+ for (auto it = readings.begin(); it != std::prev(readings.end()); ++it)
+ {
+ if (std::isfinite(it->second))
+ {
+ const auto kt = std::next(it);
+ const auto duration = kt->first - it->first;
+ valueSum += it->second * duration;
+ }
+ }
+
+ const auto duration = timestamp - readings.back().first;
+ valueSum += readings.back().second * duration;
+
+ return ReadingItem{timestamp, valueSum};
+ }
+
+ ReadingItem calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const override
+ {
+ auto result = calculate(readings, timestamp);
+ if (std::isfinite(result.second) && timestamp > 0u)
+ {
+ readings.assign({ReadingItem(timestamp - 1u, result.second),
+ ReadingItem(timestamp, readings.back().second)});
+ }
+ return result;
+ }
+};
+
+std::shared_ptr<CollectionFunction>
+ makeCollectionFunction(OperationType operationType)
+{
+ using namespace std::string_literals;
+
+ switch (operationType)
+ {
+ case OperationType::single:
+ return std::make_shared<FunctionSingle>();
+ case OperationType::min:
+ return std::make_shared<FunctionMinimum>();
+ case OperationType::max:
+ return std::make_shared<FunctionMaximum>();
+ case OperationType::avg:
+ return std::make_shared<FunctionAverage>();
+ case OperationType::sum:
+ return std::make_shared<FunctionSummation>();
+ default:
+ throw std::runtime_error("op: "s +
+ utils::enumToString(operationType) +
+ " is not supported"s);
+ }
+}
+
+} // namespace details
diff --git a/src/details/collection_function.hpp b/src/details/collection_function.hpp
new file mode 100644
index 0000000..a8708e7
--- /dev/null
+++ b/src/details/collection_function.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "types/operation_type.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace details
+{
+
+using ReadingItem = std::pair<uint64_t, double>;
+
+class CollectionFunction
+{
+ public:
+ virtual ~CollectionFunction() = default;
+
+ virtual ReadingItem calculate(const std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const = 0;
+ virtual ReadingItem
+ calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ uint64_t timestamp) const = 0;
+};
+
+std::shared_ptr<CollectionFunction> makeCollectionFunction(OperationType);
+
+} // namespace details
diff --git a/src/interfaces/clock.hpp b/src/interfaces/clock.hpp
new file mode 100644
index 0000000..0c355b7
--- /dev/null
+++ b/src/interfaces/clock.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <chrono>
+
+namespace interfaces
+{
+
+class Clock
+{
+ public:
+ using duration = std::chrono::steady_clock::time_point::duration;
+ using rep = std::chrono::steady_clock::time_point::rep;
+ using period = std::chrono::steady_clock::time_point::period;
+ using time_point = std::chrono::steady_clock::time_point;
+
+ virtual ~Clock() = default;
+
+ virtual time_point now() const noexcept = 0;
+ virtual uint64_t timestamp() const noexcept = 0;
+};
+
+} // namespace interfaces
diff --git a/src/interfaces/metric.hpp b/src/interfaces/metric.hpp
index 3279fd7..62a8f44 100644
--- a/src/interfaces/metric.hpp
+++ b/src/interfaces/metric.hpp
@@ -16,7 +16,7 @@
virtual ~Metric() = default;
virtual void initialize() = 0;
- virtual const std::vector<MetricValue>& getReadings() const = 0;
+ virtual std::vector<MetricValue> getReadings() const = 0;
virtual LabeledMetricParameters dumpConfiguration() const = 0;
};
diff --git a/src/metric.cpp b/src/metric.cpp
index 9dd4e67..04d3fe9 100644
--- a/src/metric.cpp
+++ b/src/metric.cpp
@@ -1,25 +1,138 @@
#include "metric.hpp"
+#include "details/collection_function.hpp"
#include "types/report_types.hpp"
#include "utils/labeled_tuple.hpp"
#include "utils/transform.hpp"
#include <algorithm>
+class Metric::CollectionData
+{
+ public:
+ using ReadingItem = details::ReadingItem;
+
+ virtual ~CollectionData() = default;
+
+ virtual ReadingItem update(uint64_t timestamp) = 0;
+ virtual ReadingItem update(uint64_t timestamp, double value) = 0;
+};
+
+class Metric::DataPoint : public Metric::CollectionData
+{
+ public:
+ ReadingItem update(uint64_t timestamp) override
+ {
+ return ReadingItem{lastTimestamp, lastReading};
+ }
+
+ ReadingItem update(uint64_t timestamp, double reading) override
+ {
+ lastTimestamp = timestamp;
+ lastReading = reading;
+ return update(timestamp);
+ }
+
+ private:
+ uint64_t lastTimestamp = 0u;
+ double lastReading = 0.0;
+};
+
+class Metric::DataInterval : public Metric::CollectionData
+{
+ public:
+ DataInterval(std::shared_ptr<details::CollectionFunction> function,
+ CollectionDuration duration) :
+ function(std::move(function)),
+ duration(duration)
+ {}
+
+ ReadingItem update(uint64_t timestamp) override
+ {
+ if (readings.size() > 0)
+ {
+ auto it = readings.begin();
+ for (auto kt = std::next(readings.rbegin()); kt != readings.rend();
+ ++kt)
+ {
+ const auto& [nextItemTimestamp, nextItemReading] =
+ *std::prev(kt);
+ if (timestamp >= nextItemTimestamp &&
+ static_cast<uint64_t>(timestamp - nextItemTimestamp) >
+ duration.t.count())
+ {
+ it = kt.base();
+ break;
+ }
+ }
+ readings.erase(readings.begin(), it);
+
+ if (timestamp > duration.t.count())
+ {
+ readings.front().first = std::max(
+ readings.front().first, timestamp - duration.t.count());
+ }
+ }
+
+ return function->calculate(readings, timestamp);
+ }
+
+ ReadingItem update(uint64_t timestamp, double reading) override
+ {
+ readings.emplace_back(timestamp, reading);
+ return update(timestamp);
+ }
+
+ private:
+ std::shared_ptr<details::CollectionFunction> function;
+ std::vector<ReadingItem> readings;
+ CollectionDuration duration;
+};
+
+class Metric::DataStartup : public Metric::CollectionData
+{
+ public:
+ DataStartup(std::shared_ptr<details::CollectionFunction> function) :
+ function(std::move(function))
+ {}
+
+ ReadingItem update(uint64_t timestamp) override
+ {
+ return function->calculateForStartupInterval(readings, timestamp);
+ }
+
+ ReadingItem update(uint64_t timestamp, double reading) override
+ {
+ readings.emplace_back(timestamp, reading);
+ return function->calculateForStartupInterval(readings, timestamp);
+ }
+
+ private:
+ std::shared_ptr<details::CollectionFunction> function;
+ std::vector<ReadingItem> readings;
+};
+
Metric::Metric(Sensors sensorsIn, OperationType operationTypeIn,
std::string idIn, std::string metadataIn,
CollectionTimeScope timeScopeIn,
- CollectionDuration collectionDurationIn) :
+ CollectionDuration collectionDurationIn,
+ std::unique_ptr<interfaces::Clock> clockIn) :
id(idIn),
metadata(metadataIn),
readings(sensorsIn.size(),
- MetricValue{std::move(idIn), std::move(metadataIn), 0., 0u}),
+ MetricValue{std::move(idIn), std::move(metadataIn), 0.0, 0u}),
sensors(std::move(sensorsIn)), operationType(operationTypeIn),
- timeScope(timeScopeIn), collectionDuration(collectionDurationIn)
+ collectionTimeScope(timeScopeIn), collectionDuration(collectionDurationIn),
+ collectionAlgorithms(makeCollectionData(sensors.size(), operationType,
+ collectionTimeScope,
+ collectionDuration)),
+ clock(std::move(clockIn))
{
- tryUnpackJsonMetadata();
+ attemptUnpackJsonMetadata();
}
+Metric::~Metric() = default;
+
void Metric::initialize()
{
for (const auto& sensor : sensors)
@@ -28,32 +141,40 @@
}
}
-const std::vector<MetricValue>& Metric::getReadings() const
+std::vector<MetricValue> Metric::getReadings() const
{
- return readings;
+ const auto timestamp = clock->timestamp();
+
+ auto resultReadings = readings;
+
+ for (size_t i = 0; i < resultReadings.size(); ++i)
+ {
+ std::tie(resultReadings[i].timestamp, resultReadings[i].value) =
+ collectionAlgorithms[i]->update(timestamp);
+ }
+
+ return resultReadings;
}
void Metric::sensorUpdated(interfaces::Sensor& notifier, uint64_t timestamp)
{
- MetricValue& mv = findMetric(notifier);
- mv.timestamp = timestamp;
+ findAssociatedData(notifier).update(timestamp);
}
void Metric::sensorUpdated(interfaces::Sensor& notifier, uint64_t timestamp,
double value)
{
- MetricValue& mv = findMetric(notifier);
- mv.timestamp = timestamp;
- mv.value = value;
+ findAssociatedData(notifier).update(timestamp, value);
}
-MetricValue& Metric::findMetric(interfaces::Sensor& notifier)
+Metric::CollectionData&
+ Metric::findAssociatedData(const interfaces::Sensor& notifier)
{
auto it = std::find_if(
sensors.begin(), sensors.end(),
[¬ifier](const auto& sensor) { return sensor.get() == ¬ifier; });
auto index = std::distance(sensors.begin(), it);
- return readings.at(index);
+ return *collectionAlgorithms.at(index);
}
LabeledMetricParameters Metric::dumpConfiguration() const
@@ -63,10 +184,50 @@
});
return LabeledMetricParameters(std::move(sensorPath), operationType, id,
- metadata, timeScope, collectionDuration);
+ metadata, collectionTimeScope,
+ collectionDuration);
}
-void Metric::tryUnpackJsonMetadata()
+std::vector<std::unique_ptr<Metric::CollectionData>>
+ Metric::makeCollectionData(size_t size, OperationType op,
+ CollectionTimeScope timeScope,
+ CollectionDuration duration)
+{
+ using namespace std::string_literals;
+
+ std::vector<std::unique_ptr<Metric::CollectionData>> result;
+
+ result.reserve(size);
+
+ switch (timeScope)
+ {
+ case CollectionTimeScope::interval:
+ std::generate_n(
+ std::back_inserter(result), size,
+ [cf = details::makeCollectionFunction(op), duration] {
+ return std::make_unique<DataInterval>(cf, duration);
+ });
+ break;
+ case CollectionTimeScope::point:
+ std::generate_n(std::back_inserter(result), size,
+ [] { return std::make_unique<DataPoint>(); });
+ break;
+ case CollectionTimeScope::startup:
+ std::generate_n(std::back_inserter(result), size,
+ [cf = details::makeCollectionFunction(op)] {
+ return std::make_unique<DataStartup>(cf);
+ });
+ break;
+ default:
+ throw std::runtime_error("timeScope: "s +
+ utils::enumToString(timeScope) +
+ " is not supported"s);
+ }
+
+ return result;
+}
+
+void Metric::attemptUnpackJsonMetadata()
{
using MetricMetadata =
utils::LabeledTuple<std::tuple<std::vector<std::string>>,
@@ -91,6 +252,6 @@
}
}
}
- catch (const nlohmann::json::parse_error&)
+ catch (const nlohmann::json::exception&)
{}
}
diff --git a/src/metric.hpp b/src/metric.hpp
index 08684da..dfed383 100644
--- a/src/metric.hpp
+++ b/src/metric.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "interfaces/clock.hpp"
#include "interfaces/metric.hpp"
#include "interfaces/sensor.hpp"
#include "interfaces/sensor_listener.hpp"
@@ -11,24 +12,36 @@
{
public:
Metric(Sensors sensors, OperationType operationType, std::string id,
- std::string metadata, CollectionTimeScope, CollectionDuration);
+ std::string metadata, CollectionTimeScope, CollectionDuration,
+ std::unique_ptr<interfaces::Clock>);
+ ~Metric();
void initialize() override;
- const std::vector<MetricValue>& getReadings() const override;
+ std::vector<MetricValue> getReadings() const override;
void sensorUpdated(interfaces::Sensor&, uint64_t) override;
void sensorUpdated(interfaces::Sensor&, uint64_t, double value) override;
LabeledMetricParameters dumpConfiguration() const override;
private:
- void tryUnpackJsonMetadata();
+ class CollectionData;
+ class DataPoint;
+ class DataInterval;
+ class DataStartup;
- MetricValue& findMetric(interfaces::Sensor&);
+ static std::vector<std::unique_ptr<CollectionData>>
+ makeCollectionData(size_t size, OperationType, CollectionTimeScope,
+ CollectionDuration);
+
+ void attemptUnpackJsonMetadata();
+ CollectionData& findAssociatedData(const interfaces::Sensor& notifier);
std::string id;
std::string metadata;
std::vector<MetricValue> readings;
Sensors sensors;
OperationType operationType;
- CollectionTimeScope timeScope;
+ CollectionTimeScope collectionTimeScope;
CollectionDuration collectionDuration;
+ std::vector<std::unique_ptr<CollectionData>> collectionAlgorithms;
+ std::unique_ptr<interfaces::Clock> clock;
};
diff --git a/src/report_factory.cpp b/src/report_factory.cpp
index fb6edf9..091739c 100644
--- a/src/report_factory.cpp
+++ b/src/report_factory.cpp
@@ -3,6 +3,7 @@
#include "metric.hpp"
#include "report.hpp"
#include "sensor.hpp"
+#include "utils/clock.hpp"
#include "utils/conversion.hpp"
#include "utils/dbus_mapper.hpp"
#include "utils/transform.hpp"
@@ -33,7 +34,8 @@
param.at_label<ts::OperationType>(), param.at_label<ts::Id>(),
param.at_label<ts::MetricMetadata>(),
param.at_label<ts::CollectionTimeScope>(),
- param.at_label<ts::CollectionDuration>());
+ param.at_label<ts::CollectionDuration>(),
+ std::make_unique<Clock>());
});
return std::make_unique<Report>(
diff --git a/src/types/collection_duration.hpp b/src/types/collection_duration.hpp
index ddeb9cc..2db8278 100644
--- a/src/types/collection_duration.hpp
+++ b/src/types/collection_duration.hpp
@@ -6,6 +6,7 @@
#include <nlohmann/json.hpp>
#include <chrono>
+#include <cstdint>
BOOST_STRONG_TYPEDEF(Milliseconds, CollectionDuration)
diff --git a/src/types/duration_type.hpp b/src/types/duration_type.hpp
new file mode 100644
index 0000000..69d3862
--- /dev/null
+++ b/src/types/duration_type.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <chrono>
+#include <cstdint>
+
+using Milliseconds = std::chrono::duration<uint64_t, std::milli>;
diff --git a/src/utils/clock.hpp b/src/utils/clock.hpp
new file mode 100644
index 0000000..00fd8d0
--- /dev/null
+++ b/src/utils/clock.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "interfaces/clock.hpp"
+#include "types/duration_type.hpp"
+
+#include <chrono>
+
+class Clock : public interfaces::Clock
+{
+ public:
+ time_point now() const noexcept override
+ {
+ return std::chrono::steady_clock::now();
+ }
+
+ uint64_t timestamp() const noexcept override
+ {
+ return std::chrono::time_point_cast<Milliseconds>(now())
+ .time_since_epoch()
+ .count();
+ }
+};
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]));
}