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/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