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/report.cpp b/src/report.cpp
index 12d88c7..d9d89b8 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -6,6 +6,7 @@
 #include "report_manager.hpp"
 #include "utils/clock.hpp"
 #include "utils/contains.hpp"
+#include "utils/ensure.hpp"
 #include "utils/transform.hpp"
 
 #include <phosphor-logging/log.hpp>
@@ -13,6 +14,7 @@
 
 #include <limits>
 #include <numeric>
+#include <optional>
 
 Report::Report(boost::asio::io_context& ioc,
                const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
@@ -71,10 +73,7 @@
     persistency = storeConfiguration();
     reportIface = makeReportInterface(reportFactory);
 
-    if (reportingType == ReportingType::periodic)
-    {
-        scheduleTimer(interval);
-    }
+    updateReportingType(reportingType);
 
     if (enabled)
     {
@@ -119,7 +118,7 @@
 }
 
 uint64_t Report::getSensorCount(
-    std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
+    const std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
 {
     uint64_t sensorCount = 0;
     for (auto& metric : metrics)
@@ -188,7 +187,7 @@
             {
                 if (true == newVal && ReportingType::periodic == reportingType)
                 {
-                    scheduleTimer(interval);
+                    scheduleTimerForPeriodicReport(interval);
                 }
                 if (newVal)
                 {
@@ -260,12 +259,6 @@
             ReportingType tmp = utils::toReportingType(newVal);
             if (tmp != reportingType)
             {
-                if (tmp == ReportingType::onChange)
-                {
-                    throw sdbusplus::exception::SdBusError(
-                        static_cast<int>(std::errc::invalid_argument),
-                        "Invalid reportingType");
-                }
                 if (tmp == ReportingType::periodic)
                 {
                     if (interval < ReportManager::minInterval)
@@ -274,20 +267,13 @@
                             static_cast<int>(std::errc::invalid_argument),
                             "Invalid interval");
                     }
-                    if (enabled == true)
-                    {
-                        scheduleTimer(interval);
-                    }
                 }
-                else
-                {
-                    timer.cancel();
-                }
-                reportingType = tmp;
+
+                updateReportingType(tmp);
                 setReadingBuffer(reportUpdates);
                 persistency = storeConfiguration();
+                oldVal = std::move(newVal);
             }
-            oldVal = std::move(newVal);
             return 1;
         },
         [this](const auto&) { return utils::enumToString(reportingType); });
@@ -377,7 +363,8 @@
     return dbusIface;
 }
 
-void Report::timerProc(boost::system::error_code ec, Report& self)
+void Report::timerProcForPeriodicReport(boost::system::error_code ec,
+                                        Report& self)
 {
     if (ec)
     {
@@ -385,14 +372,58 @@
     }
 
     self.updateReadings();
-    self.scheduleTimer(self.interval);
+    self.scheduleTimerForPeriodicReport(self.interval);
 }
 
-void Report::scheduleTimer(Milliseconds timerInterval)
+void Report::timerProcForOnChangeReport(boost::system::error_code ec,
+                                        Report& self)
 {
+    if (ec)
+    {
+        return;
+    }
+
+    const auto ensure =
+        utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }};
+
+    self.onChangeContext.emplace(self);
+
+    const auto steadyTimestamp = self.clock->steadyTimestamp();
+
+    for (auto& metric : self.metrics)
+    {
+        metric->updateReadings(steadyTimestamp);
+    }
+
+    self.scheduleTimerForOnChangeReport();
+}
+
+void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval)
+{
+    if (!enabled)
+    {
+        return;
+    }
+
     timer.expires_after(timerInterval);
-    timer.async_wait(
-        [this](boost::system::error_code ec) { timerProc(ec, *this); });
+    timer.async_wait([this](boost::system::error_code ec) {
+        timerProcForPeriodicReport(ec, *this);
+    });
+}
+
+void Report::scheduleTimerForOnChangeReport()
+{
+    if (!enabled)
+    {
+        return;
+    }
+
+    constexpr Milliseconds timerInterval{100};
+
+    timer.expires_after(timerInterval);
+    timer.async_wait([this](boost::system::error_code ec) {
+        timerProcForOnChangeReport(ec, *this);
+    });
 }
 
 void Report::updateReadings()
@@ -495,3 +526,62 @@
 
     return result;
 }
+
+void Report::metricUpdated()
+{
+    if (onChangeContext)
+    {
+        onChangeContext->metricUpdated();
+        return;
+    }
+
+    updateReadings();
+}
+
+void Report::updateReportingType(ReportingType newReportingType)
+{
+    if (reportingType != newReportingType)
+    {
+        timer.cancel();
+        unregisterFromMetrics = nullptr;
+    }
+
+    reportingType = newReportingType;
+
+    switch (reportingType)
+    {
+        case ReportingType::periodic:
+        {
+            scheduleTimerForPeriodicReport(interval);
+            break;
+        }
+        case ReportingType::onChange:
+        {
+            unregisterFromMetrics = [this] {
+                for (auto& metric : metrics)
+                {
+                    metric->unregisterFromUpdates(*this);
+                }
+            };
+
+            bool isTimerRequired = false;
+
+            for (auto& metric : metrics)
+            {
+                metric->registerForUpdates(*this);
+                if (metric->isTimerRequired())
+                {
+                    isTimerRequired = true;
+                }
+            }
+
+            if (isTimerRequired)
+            {
+                scheduleTimerForOnChangeReport();
+            }
+            break;
+        }
+        default:
+            break;
+    }
+}