NVMeMetricStore: handles MetricUpdated signal

Add a completeCB parameter to the Metric implementation to call
MetricUpdate() method of the MetricStore.

Also added the comment for the DBus interface to handle invalid metric
signal.

Google-Bug-Id: 348660385
Tested: 348660385#comment3
  (with all changes from go/gbmcl-ext/q/topic:%22nvme+cache%22)
Signed-off-by: Hao Jiang <jianghao@google.com>
Change-Id: I4adacc43aec92747fe7830b5e696fabbc79e6df4
diff --git a/src/NVMeCacheImpl.hpp b/src/NVMeCacheImpl.hpp
index f31328e..262c4c4 100644
--- a/src/NVMeCacheImpl.hpp
+++ b/src/NVMeCacheImpl.hpp
@@ -14,14 +14,18 @@
 class ControllerLogPageMetric : public MetricBase<ClockType>
 {
   public:
-    ControllerLogPageMetric(std::weak_ptr<NVMeControllerEnabled> controller_,
-                            std::shared_ptr<Scheduler<ClockType>> scheduler_,
-                            ClockType::duration interval_) :
-        MetricBase<ClockType>(scheduler_, interval_), controller(controller_)
+    ControllerLogPageMetric(
+        std::weak_ptr<NVMeControllerEnabled> controller,
+        std::shared_ptr<Scheduler<ClockType>> scheduler,
+        ClockType::duration interval,
+        std::function<void(std::shared_ptr<MetricBase<ClockType>>)>&&
+            completeCB) :
+        MetricBase<ClockType>(scheduler, interval),
+        controller(std::move(controller)), completeCB(std::move(completeCB))
     {}
 
     /* interface overrides */
-    constexpr std::string_view getIdentifier() const noexcept override
+    static constexpr std::string_view logpageIdentifier() noexcept
     {
         if constexpr (LID == NVME_LOG_LID_SMART)
         {
@@ -35,6 +39,12 @@
 
         return {};
     }
+
+    constexpr std::string_view getIdentifier() const noexcept override
+    {
+        return logpageIdentifier();
+    }
+
     bool isCacheValid() const noexcept override
     {
         return cacheValid;
@@ -98,6 +108,9 @@
                 std::cerr << std::format(
                     "[{}, {}]retry exhausted to fresh log page", path, id);
                 self->cacheValid = false;
+                // Notify the MatrixStore of the for the invalid Cache
+                self->completeCB(self);
+
                 // Notify the RefreshCB with error code; the task will
                 // be reschuduled after this->interval
                 cb(std::make_error_code(std::errc::io_error), 4096, true);
@@ -107,14 +120,20 @@
             self->cacheValid = true;
             std::get<0>(self->cache) = startTime;
             std::get<1>(self->cache) = ClockType::now();
-            std::get<2>(self->cache)
-                .insert(std::get<2>(self->cache).begin(), data.begin(),
-                        data.end());
+
+            auto& cacheData = std::get<2>(self->cache);
+            cacheData.clear();
+            cacheData.insert(cacheData.begin(), data.begin(), data.end());
+
+            // notify the MetricStore for completion
+            self->completeCB(self);
+
             cb({}, data.size(), true);
         });
     }
     /* member variables */
     std::weak_ptr<NVMeControllerEnabled> controller;
+    std::function<void(std::shared_ptr<MetricBase<ClockType>>)> completeCB;
 
     std::tuple<std::chrono::time_point<ClockType>,
                std::chrono::time_point<ClockType>, std::vector<uint8_t>>
diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
index dd3987f..4932970 100644
--- a/src/NVMeController.cpp
+++ b/src/NVMeController.cpp
@@ -180,17 +180,58 @@
     this->metricStore.emplace(this->NVMeController::io,
                               this->NVMeController::conn,
                               this->NVMeController::path);
+    // NOLINTBEGIN(performance-unnecessary-value-param)
+    auto completeCB =
+        [weakCntrl{weak_from_this()}, path{path}](
+            std::string_view name,
+            std::shared_ptr<MetricBase<std::chrono::steady_clock>> metric) {
+        auto cntrl = weakCntrl.lock();
+        if (!cntrl || !cntrl->metricStore)
+        {
+            // TODO: log_level = INFO
+            std::cerr << std::format(
+                "[{}]Metrics interface for controller expired, update signal skipped.\n",
+                path);
+        }
+        if (!metric->isCacheValid())
+        {
+            // broadcast invalid metric signal
+            cntrl->metricStore->metricUpdated(std::string{name}, {});
+            return;
+        }
 
-    this->metricStore.emplace(
-        this->NVMeController::io, this->NVMeController::conn,
-        this->NVMeController::path,
-        std::shared_ptr<ControllerLogPageMetric<NVME_LOG_LID_SMART,
-                                                std::chrono::steady_clock>>(
-            new ControllerLogPageMetric<NVME_LOG_LID_SMART,
-                                        std::chrono::steady_clock>(
-                this->weak_from_this(), {},
-                std::chrono::duration_cast<std::chrono::steady_clock::duration>(
-                    std::chrono::seconds(60)))));
+        auto data = metric->getCache();
+        std::vector<uint8_t> headerBuf;
+        headerBuf.resize(sizeof(MetricHeader));
+        MetricHeader& header =
+            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+            *reinterpret_cast<MetricHeader*>(headerBuf.data());
+        header.lens = boost::endian::little_uint32_t(sizeof(header));
+        header.version = 0;
+        header.dataFormat = 0;
+        header.startTime = boost::endian::little_uint64_t(
+            std::get<0>(data).time_since_epoch().count());
+        header.finishTime = boost::endian::little_uint64_t(
+            std::get<1>(data).time_since_epoch().count());
+        cntrl->metricStore->metricUpdated(std::string{name}, headerBuf);
+    };
+    // NOLINTEND(performance-unnecessary-value-param)
+
+    using SMARTMetric =
+        ControllerLogPageMetric<NVME_LOG_LID_SMART, std::chrono::steady_clock>;
+
+    // NOLINTBEGIN(modernize-make-shared)
+    auto smart = std::shared_ptr<SMARTMetric>(new SMARTMetric(
+        this->weak_from_this(), {},
+        // TODO: configurable interval
+        std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+            std::chrono::seconds(60)),
+        std::bind_front(completeCB, SMARTMetric::logpageIdentifier())));
+    // NOLINTEND(modernize-make-shared)
+
+    this->metricStore.emplace(this->NVMeController::io,
+                              this->NVMeController::conn,
+                              this->NVMeController::path, std::move(smart));
 
     status = Status::Enabled;
 }
diff --git a/yaml/xyz/openbmc_project/NVMe/MetricStore.interface.yaml b/yaml/xyz/openbmc_project/NVMe/MetricStore.interface.yaml
index 676f5ce..6a7f403 100644
--- a/yaml/xyz/openbmc_project/NVMe/MetricStore.interface.yaml
+++ b/yaml/xyz/openbmc_project/NVMe/MetricStore.interface.yaml
@@ -21,7 +21,9 @@
           - name: MetricHeader
             type: array[byte]
             description: >
-                Metric Header being broadcasted as a signal on each update
+                Metric Header being broadcasted as a signal on each update.  A
+                zero-length header will be transmitted when the Metric turns
+                into invalid.
 
 methods:
     - name: GetMetric