added support for onChange report
Report is now notified when metric changes and updates reading values.
Tested:
- Added new unit tests
- OnChange report updates Readings when metric values changes
Change-Id: I3be9ef7aa0486cb15bac627aa1de5cc632613b3b
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/src/metrics/collection_data.cpp b/src/metrics/collection_data.cpp
new file mode 100644
index 0000000..9512252
--- /dev/null
+++ b/src/metrics/collection_data.cpp
@@ -0,0 +1,162 @@
+#include "metrics/collection_data.hpp"
+
+#include "metrics/collection_function.hpp"
+
+namespace metrics
+{
+
+bool CollectionData::updateLastValue(double value)
+{
+ const bool changed = lastValue != value;
+ lastValue = value;
+ return changed;
+}
+
+class DataPoint : public CollectionData
+{
+ public:
+ std::optional<double> update(Milliseconds) override
+ {
+ return lastReading;
+ }
+
+ double update(Milliseconds, double reading) override
+ {
+ lastReading = reading;
+ return reading;
+ }
+
+ private:
+ std::optional<double> lastReading;
+};
+
+class DataInterval : public CollectionData
+{
+ public:
+ DataInterval(std::shared_ptr<CollectionFunction> function,
+ CollectionDuration duration) :
+ function(std::move(function)),
+ duration(duration)
+ {
+ if (duration.t.count() == 0)
+ {
+ throw sdbusplus::exception::SdBusError(
+ static_cast<int>(std::errc::invalid_argument),
+ "Invalid CollectionDuration");
+ }
+ }
+
+ std::optional<double> update(Milliseconds timestamp) override
+ {
+ if (readings.empty())
+ {
+ return std::nullopt;
+ }
+
+ cleanup(timestamp);
+
+ return function->calculate(readings, timestamp);
+ }
+
+ double update(Milliseconds timestamp, double reading) override
+ {
+ readings.emplace_back(timestamp, reading);
+
+ cleanup(timestamp);
+
+ return function->calculate(readings, timestamp);
+ }
+
+ private:
+ void cleanup(Milliseconds timestamp)
+ {
+ 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 &&
+ timestamp - nextItemTimestamp > duration.t)
+ {
+ it = kt.base();
+ break;
+ }
+ }
+ readings.erase(readings.begin(), it);
+
+ if (timestamp > duration.t)
+ {
+ readings.front().first =
+ std::max(readings.front().first, timestamp - duration.t);
+ }
+ }
+
+ std::shared_ptr<CollectionFunction> function;
+ std::vector<ReadingItem> readings;
+ CollectionDuration duration;
+};
+
+class DataStartup : public CollectionData
+{
+ public:
+ explicit DataStartup(std::shared_ptr<CollectionFunction> function) :
+ function(std::move(function))
+ {}
+
+ std::optional<double> update(Milliseconds timestamp) override
+ {
+ if (readings.empty())
+ {
+ return std::nullopt;
+ }
+
+ return function->calculateForStartupInterval(readings, timestamp);
+ }
+
+ double update(Milliseconds timestamp, double reading) override
+ {
+ readings.emplace_back(timestamp, reading);
+ return function->calculateForStartupInterval(readings, timestamp);
+ }
+
+ private:
+ std::shared_ptr<CollectionFunction> function;
+ std::vector<ReadingItem> readings;
+};
+
+std::vector<std::unique_ptr<CollectionData>>
+ makeCollectionData(size_t size, OperationType op,
+ CollectionTimeScope timeScope,
+ CollectionDuration duration)
+{
+ using namespace std::string_literals;
+
+ std::vector<std::unique_ptr<CollectionData>> result;
+
+ result.reserve(size);
+
+ switch (timeScope)
+ {
+ case CollectionTimeScope::interval:
+ std::generate_n(std::back_inserter(result), size,
+ [cf = 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 = makeCollectionFunction(op)] {
+ return std::make_unique<DataStartup>(cf);
+ });
+ break;
+ }
+
+ return result;
+}
+
+} // namespace metrics
diff --git a/src/metrics/collection_data.hpp b/src/metrics/collection_data.hpp
new file mode 100644
index 0000000..251e704
--- /dev/null
+++ b/src/metrics/collection_data.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "types/collection_duration.hpp"
+#include "types/collection_time_scope.hpp"
+#include "types/duration_types.hpp"
+#include "types/operation_type.hpp"
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+namespace metrics
+{
+
+class CollectionData
+{
+ public:
+ virtual ~CollectionData() = default;
+
+ virtual std::optional<double> update(Milliseconds timestamp) = 0;
+ virtual double update(Milliseconds timestamp, double value) = 0;
+ bool updateLastValue(double value);
+
+ private:
+ std::optional<double> lastValue;
+};
+
+std::vector<std::unique_ptr<CollectionData>>
+ makeCollectionData(size_t size, OperationType, CollectionTimeScope,
+ CollectionDuration);
+
+} // namespace metrics
diff --git a/src/metrics/collection_function.cpp b/src/metrics/collection_function.cpp
new file mode 100644
index 0000000..717bb68
--- /dev/null
+++ b/src/metrics/collection_function.cpp
@@ -0,0 +1,176 @@
+#include "metrics/collection_function.hpp"
+
+#include <cmath>
+
+namespace metrics
+{
+
+class FunctionMinimum : public CollectionFunction
+{
+ public:
+ double calculate(const std::vector<ReadingItem>& readings,
+ Milliseconds) 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);
+ })
+ ->second;
+ }
+
+ double calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ Milliseconds timestamp) const override
+ {
+ readings.assign(
+ {ReadingItem(timestamp, calculate(readings, timestamp))});
+ return readings.back().second;
+ }
+};
+
+class FunctionMaximum : public CollectionFunction
+{
+ public:
+ double calculate(const std::vector<ReadingItem>& readings,
+ Milliseconds) 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);
+ })
+ ->second;
+ }
+
+ double calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ Milliseconds timestamp) const override
+ {
+ readings.assign(
+ {ReadingItem(timestamp, calculate(readings, timestamp))});
+ return readings.back().second;
+ }
+};
+
+class FunctionAverage : public CollectionFunction
+{
+ public:
+ double calculate(const std::vector<ReadingItem>& readings,
+ Milliseconds timestamp) const override
+ {
+ auto valueSum = 0.0;
+ auto timeSum = Milliseconds{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.count();
+ timeSum += duration;
+ }
+ }
+
+ const auto duration = timestamp - readings.back().first;
+ valueSum += readings.back().second * duration.count();
+ timeSum += duration;
+
+ return valueSum / std::max(timeSum.count(), uint64_t{1u});
+ }
+
+ double calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ Milliseconds timestamp) const override
+ {
+ auto result = calculate(readings, timestamp);
+ if (std::isfinite(result))
+ {
+ readings.assign({ReadingItem(readings.front().first, result),
+ ReadingItem(timestamp, readings.back().second)});
+ }
+ return result;
+ }
+};
+
+class FunctionSummation : public CollectionFunction
+{
+ using Multiplier = std::chrono::duration<double>;
+
+ public:
+ double calculate(const std::vector<ReadingItem>& readings,
+ const Milliseconds 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 multiplier =
+ calculateMultiplier(kt->first - it->first);
+ valueSum += it->second * multiplier.count();
+ }
+ }
+
+ const auto multiplier =
+ calculateMultiplier(timestamp - readings.back().first);
+ valueSum += readings.back().second * multiplier.count();
+
+ return valueSum;
+ }
+
+ double
+ calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ const Milliseconds timestamp) const override
+ {
+ const auto result = calculate(readings, timestamp);
+ if (readings.size() > 2 && std::isfinite(result))
+ {
+ const auto multiplier =
+ calculateMultiplier(timestamp - readings.front().first).count();
+ if (multiplier > 0.)
+ {
+ const auto prevValue = result / multiplier;
+ readings.assign(
+ {ReadingItem(readings.front().first, prevValue),
+ ReadingItem(timestamp, readings.back().second)});
+ }
+ }
+ return result;
+ }
+
+ private:
+ static constexpr Multiplier calculateMultiplier(Milliseconds duration)
+ {
+ constexpr auto m = Multiplier{Seconds{1}};
+ return Multiplier{duration / m};
+ }
+};
+
+std::shared_ptr<CollectionFunction>
+ makeCollectionFunction(OperationType operationType)
+{
+ using namespace std::string_literals;
+
+ switch (operationType)
+ {
+ 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 metrics
diff --git a/src/metrics/collection_function.hpp b/src/metrics/collection_function.hpp
new file mode 100644
index 0000000..610f015
--- /dev/null
+++ b/src/metrics/collection_function.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "types/duration_types.hpp"
+#include "types/operation_type.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace metrics
+{
+
+using ReadingItem = std::pair<Milliseconds, double>;
+
+class CollectionFunction
+{
+ public:
+ virtual ~CollectionFunction() = default;
+
+ virtual double calculate(const std::vector<ReadingItem>& readings,
+ Milliseconds timestamp) const = 0;
+ virtual double
+ calculateForStartupInterval(std::vector<ReadingItem>& readings,
+ Milliseconds timestamp) const = 0;
+};
+
+std::shared_ptr<CollectionFunction> makeCollectionFunction(OperationType);
+
+} // namespace metrics