Created metric class

Metric collects updates from sensor. Report displays metric readings
depending on reportingType.

Tested:
  - Added new units tests for Metric class
  - All other unit tests are passing

Change-Id: I19f4831fab163a4f9540cef7bb23e903ae90fddf
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/src/interfaces/metric.hpp b/src/interfaces/metric.hpp
index 50bf5d5..d63c979 100644
--- a/src/interfaces/metric.hpp
+++ b/src/interfaces/metric.hpp
@@ -14,6 +14,7 @@
   public:
     virtual ~Metric() = default;
 
+    virtual void initialize() = 0;
     virtual const std::vector<MetricValue>& getReadings() const = 0;
     virtual nlohmann::json to_json() const = 0;
 };
diff --git a/src/metric.cpp b/src/metric.cpp
index b40da5a..8d9f19b 100644
--- a/src/metric.cpp
+++ b/src/metric.cpp
@@ -1 +1,64 @@
 #include "metric.hpp"
+
+#include "interfaces/types.hpp"
+#include "utils/transform.hpp"
+
+#include <algorithm>
+
+Metric::Metric(std::vector<std::shared_ptr<interfaces::Sensor>> sensors,
+               std::string operationType, std::string id,
+               std::string metadata) :
+    sensors(std::move(sensors)),
+    operationType(std::move(operationType)), id(std::move(id)),
+    metadata(std::move(metadata))
+{}
+
+void Metric::initialize()
+{
+    readings = std::vector<MetricValue>(sensors.size(),
+                                        MetricValue{id, metadata, 0., 0u});
+
+    for (auto& sensor : sensors)
+    {
+        sensor->registerForUpdates(weak_from_this());
+    }
+}
+
+const std::vector<MetricValue>& Metric::getReadings() const
+{
+    return readings;
+}
+
+void Metric::sensorUpdated(interfaces::Sensor& sensor, uint64_t timestamp)
+{
+    MetricValue& mv = findMetric(sensor);
+    mv.timestamp = timestamp;
+}
+
+void Metric::sensorUpdated(interfaces::Sensor& sensor, uint64_t timestamp,
+                           double value)
+{
+    MetricValue& mv = findMetric(sensor);
+    mv.timestamp = timestamp;
+    mv.value = value;
+}
+
+MetricValue& Metric::findMetric(interfaces::Sensor& sensor)
+{
+    auto it =
+        std::find_if(sensors.begin(), sensors.end(),
+                     [&sensor](const auto& s) { return s.get() == &sensor; });
+    auto index = std::distance(sensors.begin(), it);
+    return readings.at(index);
+}
+
+nlohmann::json Metric::to_json() const
+{
+    auto sensorPaths = utils::transform(
+        sensors, [](const auto& sensor) -> sdbusplus::message::object_path {
+            return sdbusplus::message::object_path(sensor->id().service + ":" +
+                                                   sensor->id().path);
+        });
+    return LabeledReadingParameter::to_json(ReadingParameters::value_type(
+        std::move(sensorPaths), operationType, id, metadata));
+}
diff --git a/src/metric.hpp b/src/metric.hpp
index 76a60fb..6e27446 100644
--- a/src/metric.hpp
+++ b/src/metric.hpp
@@ -1,21 +1,30 @@
 #pragma once
 
 #include "interfaces/metric.hpp"
+#include "interfaces/sensor.hpp"
 #include "interfaces/sensor_listener.hpp"
 
-class Metric : public interfaces::Metric, public interfaces::SensorListener
+class Metric :
+    public interfaces::Metric,
+    public interfaces::SensorListener,
+    public std::enable_shared_from_this<Metric>
 {
   public:
-    const std::vector<MetricValue>& getReadings() const override
-    {
-        return readings;
-    }
+    Metric(std::vector<std::shared_ptr<interfaces::Sensor>> sensors,
+           std::string operationType, std::string id, std::string metadata);
 
-    void sensorUpdated(interfaces::Sensor&, uint64_t) override
-    {}
-    void sensorUpdated(interfaces::Sensor&, uint64_t, double value) override
-    {}
+    void initialize() override;
+    const std::vector<MetricValue>& getReadings() const override;
+    void sensorUpdated(interfaces::Sensor&, uint64_t) override;
+    void sensorUpdated(interfaces::Sensor&, uint64_t, double value) override;
+    nlohmann::json to_json() const override;
 
   private:
+    MetricValue& findMetric(interfaces::Sensor&);
+
+    std::vector<std::shared_ptr<interfaces::Sensor>> sensors;
+    std::string operationType;
+    std::string id;
+    std::string metadata;
     std::vector<MetricValue> readings;
 };
diff --git a/src/report.cpp b/src/report.cpp
index 6698ec3..c1b7d5f 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -27,6 +27,11 @@
     fileName(std::to_string(std::hash<std::string>{}(name))),
     reportStorage(reportStorageIn)
 {
+    for (auto& metric : this->metrics)
+    {
+        metric->initialize();
+    }
+
     deleteIface = objServer->add_unique_interface(
         path, deleteIfaceName, [this, &ioc, &reportManager](auto& dbusIface) {
             dbusIface.register_method("Delete", [this, &ioc, &reportManager] {
@@ -137,7 +142,7 @@
             return sum + metric->getReadings().size();
         });
 
-    readingsCache.resize(numElements);
+    std::tuple_element_t<1, Readings> readingsCache(numElements);
 
     auto it = readingsCache.begin();
 
@@ -151,7 +156,7 @@
     }
 
     std::get<0>(readings) = std::time(0);
-    std::get<1>(readings) = readingsCache;
+    std::get<1>(readings) = std::move(readingsCache);
 
     reportIface->signal_property("Readings");
 }
diff --git a/src/report.hpp b/src/report.hpp
index 2020f99..b227392 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -59,7 +59,6 @@
     ReadingParameters readingParameters;
     bool persistency;
     Readings readings = {};
-    std::tuple_element_t<1, Readings> readingsCache = {};
     std::shared_ptr<sdbusplus::asio::object_server> objServer;
     std::unique_ptr<sdbusplus::asio::dbus_interface> reportIface;
     std::unique_ptr<sdbusplus::asio::dbus_interface> deleteIface;
diff --git a/src/report_factory.cpp b/src/report_factory.cpp
index 5ac32f3..e4689aa 100644
--- a/src/report_factory.cpp
+++ b/src/report_factory.cpp
@@ -1,5 +1,6 @@
 #include "report_factory.hpp"
 
+#include "metric.hpp"
 #include "report.hpp"
 #include "sensor.hpp"
 #include "utils/transform.hpp"
@@ -19,10 +20,84 @@
     interfaces::ReportManager& reportManager,
     interfaces::JsonStorage& reportStorage) const
 {
+    std::optional<std::vector<ReportFactory::SensorTree>> sensorTree;
+
     std::vector<std::shared_ptr<interfaces::Metric>> metrics;
+    metrics.reserve(metricParams.size());
+
+    for (const auto& [sensorPaths, op, id, metadata] : metricParams)
+    {
+        if (!sensorTree && yield && sensorPaths.size() > 0)
+        {
+            sensorTree = getSensorTree(*yield);
+        }
+
+        std::vector<std::shared_ptr<interfaces::Sensor>> sensors =
+            getSensors(sensorTree, sensorPaths);
+
+        metrics.emplace_back(
+            std::make_shared<Metric>(std::move(sensors), op, id, metadata));
+    }
 
     return std::make_unique<Report>(
         bus->get_io_context(), objServer, name, reportingType,
         emitsReadingsSignal, logToMetricReportsCollection, period, metricParams,
         reportManager, reportStorage, std::move(metrics));
 }
+
+std::vector<std::shared_ptr<interfaces::Sensor>> ReportFactory::getSensors(
+    const std::optional<std::vector<ReportFactory::SensorTree>>& tree,
+    const std::vector<sdbusplus::message::object_path>& sensorPaths) const
+{
+    if (tree)
+    {
+        std::vector<std::shared_ptr<interfaces::Sensor>> sensors;
+
+        for (const auto& [sensor, ifacesMap] : *tree)
+        {
+            auto it = std::find(sensorPaths.begin(), sensorPaths.end(), sensor);
+            if (it != sensorPaths.end())
+            {
+                for (const auto& [service, ifaces] : ifacesMap)
+                {
+                    sensors.emplace_back(sensorCache.makeSensor<Sensor>(
+                        service, sensor, bus->get_io_context(), bus));
+                }
+            }
+        }
+
+        return sensors;
+    }
+    else
+    {
+        return utils::transform(
+            sensorPaths,
+            [this](const std::string& sensor)
+                -> std::shared_ptr<interfaces::Sensor> {
+                std::string::size_type pos = sensor.find_first_of(":");
+                auto service = sensor.substr(0, pos);
+                auto path = sensor.substr(pos + 1);
+                return sensorCache.makeSensor<Sensor>(
+                    service, path, bus->get_io_context(), bus);
+            });
+    }
+}
+
+std::vector<ReportFactory::SensorTree>
+    ReportFactory::getSensorTree(boost::asio::yield_context& yield) const
+{
+    std::array<const char*, 1> interfaces = {
+        "xyz.openbmc_project.Sensor.Value"};
+    boost::system::error_code ec;
+
+    auto result = bus->yield_method_call<std::vector<SensorTree>>(
+        yield, ec, "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+        "/xyz/openbmc_project/sensors", 2, interfaces);
+    if (ec)
+    {
+        throw std::runtime_error("failed");
+    }
+    return result;
+}
diff --git a/src/report_factory.hpp b/src/report_factory.hpp
index bdf9268..0346441 100644
--- a/src/report_factory.hpp
+++ b/src/report_factory.hpp
@@ -2,6 +2,7 @@
 
 #include "interfaces/report_factory.hpp"
 #include "interfaces/sensor.hpp"
+#include "sensor_cache.hpp"
 
 #include <boost/asio/io_context.hpp>
 #include <sdbusplus/asio/object_server.hpp>
@@ -22,6 +23,19 @@
         interfaces::JsonStorage& reportStorage) const override;
 
   private:
+    using SensorPath = std::string;
+    using ServiceName = std::string;
+    using Ifaces = std::vector<std::string>;
+    using SensorIfaces = std::vector<std::pair<ServiceName, Ifaces>>;
+    using SensorTree = std::pair<SensorPath, SensorIfaces>;
+
+    std::vector<std::shared_ptr<interfaces::Sensor>> getSensors(
+        const std::optional<std::vector<SensorTree>>& tree,
+        const std::vector<sdbusplus::message::object_path>& sensorPaths) const;
+    std::vector<SensorTree>
+        getSensorTree(boost::asio::yield_context& yield) const;
+
     std::shared_ptr<sdbusplus::asio::connection> bus;
     std::shared_ptr<sdbusplus::asio::object_server> objServer;
+    mutable SensorCache sensorCache;
 };
diff --git a/src/report_manager.cpp b/src/report_manager.cpp
index d23b589..eb2d767 100644
--- a/src/report_manager.cpp
+++ b/src/report_manager.cpp
@@ -16,6 +16,7 @@
     reportStorage(std::move(reportStorageIn)), objServer(objServerIn)
 {
     reports.reserve(maxReports);
+
     loadFromPersistent();
 
     reportManagerIface = objServer->add_unique_interface(
diff --git a/src/telemetry.hpp b/src/telemetry.hpp
index 4a6a6b0..4cc138e 100644
--- a/src/telemetry.hpp
+++ b/src/telemetry.hpp
@@ -3,6 +3,7 @@
 #include "persistent_json_storage.hpp"
 #include "report_factory.hpp"
 #include "report_manager.hpp"
+#include "sensor_cache.hpp"
 
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>