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