NVMeCache: Add implementation for Identify Metric

Identify Metric is used to store the cached info for Identify info for
NVMe controllers and namespace.

Also instantiate the Identify Metric for controllers.

Tested: b/348660385#comment12
Google-Bug-Id: 348660385
Signed-off-by: Hao Jiang <jianghao@google.com>
Change-Id: I92f96cb63e68d9071b5e85eea3810e91616d948c
diff --git a/src/NVMeCacheImpl.hpp b/src/NVMeCacheImpl.hpp
index 124197b..5f2b05a 100644
--- a/src/NVMeCacheImpl.hpp
+++ b/src/NVMeCacheImpl.hpp
@@ -5,6 +5,7 @@
 
 #include "NVMeCache.hpp"
 #include "NVMeController.hpp"
+#include "NVMeVolume.hpp"
 
 /**
  * Metric implementation for LogPage
@@ -112,7 +113,7 @@
             if (ec)
             {
                 if (self->errorCount <
-                    ControllerLogPageMetric<LID>::errorCountMax)
+                    ControllerLogPageMetric<LID, ClockType>::errorCountMax)
                 {
                     // TODO: error_level = WARN
                     std::cerr << std::format(
@@ -162,3 +163,158 @@
     unsigned errorCount{0};
     static constexpr unsigned errorCountMax = 5;
 };
+
+/**
+ * Metric implementation for Identify info
+ */
+template <nvme_identify_cns CNS, class ClockType>
+class IdentifyMetric : public MetricBase<ClockType>
+{
+  public:
+    template <class T>
+        requires((CNS == NVME_IDENTIFY_CNS_CTRL &&
+                  std::is_base_of_v<NVMeControllerEnabled, T>) ||
+                 (CNS == NVME_IDENTIFY_CNS_NS &&
+                  std::is_base_of_v<NVMeVolume, T>))
+    IdentifyMetric(std::shared_ptr<T> resource,
+                   std::shared_ptr<Scheduler<ClockType>> scheduler,
+                   ClockType::duration interval,
+                   std::function<void(std::shared_ptr<MetricBase<ClockType>>)>&&
+                       completeCB) :
+        MetricBase<ClockType>(scheduler, interval),
+        completeCB(std::move(completeCB))
+    {
+        if constexpr (CNS == NVME_IDENTIFY_CNS_CTRL)
+        {
+            controller = resource;
+            namespaceId = 0;
+        }
+        else if constexpr (CNS == NVME_IDENTIFY_CNS_NS)
+        {
+            auto subsys = resource->subsys.lock();
+            assert(subsys &&
+                   "Fail to construct NS Identify Metric on expired subsystem");
+            controller = subsys->getPrimaryController();
+            resource->namespaceId();
+        }
+        else
+        {
+            static_assert(false, "unsupported CNS for IdentifyMetric");
+        }
+    }
+
+    /* interface overrides */
+    inline static constexpr std::string_view identifyIdentifier() noexcept
+    {
+        return "IdentifyMetric";
+    }
+    constexpr std::string_view getIdentifier() const noexcept override
+    {
+        return identifyIdentifier();
+    }
+
+    bool isCacheValid() const noexcept override
+    {
+        return cacheValid;
+    }
+    std::tuple<std::chrono::time_point<ClockType>,
+               std::chrono::time_point<ClockType>, std::span<const uint8_t>>
+        getCache() const noexcept override
+    {
+        std::chrono::time_point<ClockType> start = std::get<0>(cache);
+        std::chrono::time_point<ClockType> stop = std::get<1>(cache);
+        auto& data = std::get<2>(cache);
+        return {start, stop,
+                std::span<const uint8_t>(data.begin(), data.end())};
+    }
+
+    /* internal overrides */
+  private:
+    // read data from device and update the
+    // implemetation should extend this virtual function and do the real
+    // data fetch from device.
+    void readDevice(std::function<void(std::error_code ec, size_t size,
+                                       bool complete)>&& cb) noexcept override
+    {
+        auto startTime = ClockType::now();
+
+        auto cntrlPtr = controller.lock();
+        assert(cntrlPtr &&
+               cntrlPtr->status == NVMeControllerEnabled::Status::Enabled &&
+               "NVMe Metric refresh requires enabled controller");
+        assert(cntrlPtr->nvmeIntf &&
+               "NVMe Metric refresh requires active MI interface");
+
+        cntrlPtr->nvmeIntf->adminIdentify(
+            cntrlPtr->nvmeCtrl, CNS, namespaceId,
+            0 /* CNTID will not be used for CNS = 00h/01h */,
+            [weakSelf{std::move(this->weak_from_this())}, startTime,
+             path{cntrlPtr->path}, id{getIdentifier()}, cb{std::move(cb)}](
+                const nvme_ex_ptr& ex, std::span<uint8_t> data) {
+            auto self =
+                std::dynamic_pointer_cast<IdentifyMetric<CNS, ClockType>>(
+                    weakSelf.lock());
+            if (!self)
+            {
+                // TODO: error_level = INFO
+                std::cerr << std::format(
+                    "[{}, {}]metric released before callback\n", path, id);
+                // The Scheduler will clear the invalid cache Task
+                cb({}, 4096, true);
+                return;
+            }
+            if (ex)
+            {
+                if (self->errorCount <
+                    IdentifyMetric<CNS, ClockType>::errorCountMax)
+                {
+                    // TODO: error_level = WARN
+                    std::cerr << std::format(
+                        "[{}, {}]fail to fresh identify at retry({}): {}\n",
+                        path, id, self->errorCount++, ex->description());
+
+                    // Schedule the retry after 1s
+                    cb(std::make_error_code(std::errc::io_error), 4096, false);
+                    return;
+                }
+                std::cerr << std::format(
+                    "[{}, {}]retry exhausted to fresh identify: {}\n", path, id,
+                    ex->description());
+                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);
+                return;
+            }
+            self->errorCount = 0;
+            self->cacheValid = true;
+            std::get<0>(self->cache) = startTime;
+            std::get<1>(self->cache) = ClockType::now();
+
+            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;
+    uint32_t namespaceId;
+
+    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>>
+        cache;
+    bool cacheValid{false};
+    unsigned errorCount{0};
+    static constexpr unsigned errorCountMax = 5;
+};
diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
index ca604f9..6f270b1 100644
--- a/src/NVMeController.cpp
+++ b/src/NVMeController.cpp
@@ -221,6 +221,9 @@
     };
     // NOLINTEND(performance-unnecessary-value-param)
 
+    using IdentifyMetric =
+        IdentifyMetric<NVME_IDENTIFY_CNS_CTRL, SchedulerClockType>;
+
     using ErrorInfoMetric =
         ControllerLogPageMetric<NVME_LOG_LID_ERROR, SchedulerClockType>;
     using SMARTMetric =
@@ -235,6 +238,13 @@
                                 SchedulerClockType>;
 
     // NOLINTBEGIN(modernize-make-shared)
+    auto identify = std::shared_ptr<IdentifyMetric>(new IdentifyMetric(
+        this->shared_from_this(), scheduler,
+        // Identify info is persist throughout the controller lifecycle
+        // i.e. from start() to stop()
+        IdentifyMetric::ClockType::duration::max(),
+        std::bind_front(completeCB, IdentifyMetric::identifyIdentifier())));
+
     auto errorInfo = std::shared_ptr<ErrorInfoMetric>(new ErrorInfoMetric(
         this->weak_from_this(), scheduler,
         // TODO: configurable interval
@@ -275,8 +285,9 @@
 
     this->metricStore.emplace(
         this->NVMeController::io, this->NVMeController::conn,
-        this->NVMeController::path, std::move(errorInfo), std::move(smart),
-        std::move(fwSlot), std::move(selfTest), std::move(hostTelemetry));
+        this->NVMeController::path, std::move(identify), std::move(errorInfo),
+        std::move(smart), std::move(fwSlot), std::move(selfTest),
+        std::move(hostTelemetry));
 
     status = Status::Enabled;
 }
diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
index 9295b52..018304e 100644
--- a/src/NVMeController.hpp
+++ b/src/NVMeController.hpp
@@ -132,6 +132,8 @@
  */
 template <nvme_cmd_get_log_lid LID, class ClockType = std::chrono::steady_clock>
 class ControllerLogPageMetric;
+template <nvme_identify_cns CNS, class ClockType>
+class IdentifyMetric;
 
 /**
  * @brief A class for the NVMe controller that has been enabled (CC.EN = 1)
@@ -166,6 +168,8 @@
   private:
     template <nvme_cmd_get_log_lid LID, class ClockType>
     friend class ControllerLogPageMetric;
+    template <nvme_identify_cns CNS, class ClockType>
+    friend class IdentifyMetric;
 
     enum class Status : uint8_t
     {