NVMeSubsystem: instantiate the scheduler for Metric

Instantiate the scheduler for each subsystem to schedule the Metrics
cache refresh.

The life time of scheduler is between [Intiatilzing, Terminating) of
attached subsystem.

The scheduler is started as the last step of the Intiatilzing and right
before the Start status.

Check the design doc:
  go/nvme-mi-cache-time-scheduling#heading=h.f584d1vzjwgg

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: Ie0f663e23430698abcca3451bceaeeec2be72360
diff --git a/src/NVMeCache.hpp b/src/NVMeCache.hpp
index 09afbdc..e0dce89 100644
--- a/src/NVMeCache.hpp
+++ b/src/NVMeCache.hpp
@@ -106,6 +106,11 @@
     explicit Scheduler(boost::asio::io_context& io);
     ~Scheduler() = default;
 
+    // find the schuduler with give path for a storage componenet (e.g.
+    // Controller)
+    static std::shared_ptr<Scheduler<ClockType_>>
+        getScheduler(const std::string& path);
+
     void enqueue(std::chrono::time_point<ClockType> scheduledTime,
                  std::shared_ptr<MetricBase<ClockType>> task);
     void dequeue();
diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
index 4932970..fa460e7 100644
--- a/src/NVMeController.cpp
+++ b/src/NVMeController.cpp
@@ -19,6 +19,7 @@
 // using sdbusplus::xyz::openbmc_project::Inventory::Item::server::
 //     StorageController;
 using sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin;
+using SchedulerClockType = std::chrono::steady_clock;
 
 std::shared_ptr<NVMeControllerEnabled>
     NVMeControllerEnabled::create(NVMeController&& nvmeController)
@@ -180,11 +181,14 @@
     this->metricStore.emplace(this->NVMeController::io,
                               this->NVMeController::conn,
                               this->NVMeController::path);
+
+    auto scheduler = Scheduler<SchedulerClockType>::getScheduler(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) {
+        [weakCntrl{weak_from_this()},
+         path{path}](std::string_view name,
+                     std::shared_ptr<MetricBase<SchedulerClockType>> metric) {
         auto cntrl = weakCntrl.lock();
         if (!cntrl || !cntrl->metricStore)
         {
@@ -218,13 +222,13 @@
     // NOLINTEND(performance-unnecessary-value-param)
 
     using SMARTMetric =
-        ControllerLogPageMetric<NVME_LOG_LID_SMART, std::chrono::steady_clock>;
+        ControllerLogPageMetric<NVME_LOG_LID_SMART, SchedulerClockType>;
 
     // NOLINTBEGIN(modernize-make-shared)
     auto smart = std::shared_ptr<SMARTMetric>(new SMARTMetric(
-        this->weak_from_this(), {},
+        this->weak_from_this(), scheduler,
         // TODO: configurable interval
-        std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+        std::chrono::duration_cast<SchedulerClockType::duration>(
             std::chrono::seconds(60)),
         std::bind_front(completeCB, SMARTMetric::logpageIdentifier())));
     // NOLINTEND(modernize-make-shared)
diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
index bf6f31e..accdd1a 100644
--- a/src/NVMeSubsys.cpp
+++ b/src/NVMeSubsys.cpp
@@ -1,6 +1,7 @@
 #include "NVMeSubsys.hpp"
 
 #include "AsioHelper.hpp"
+#include "NVMeCache.hpp"
 #include "NVMeDrive.hpp"
 #include "NVMeError.hpp"
 #include "NVMePlugin.hpp"
@@ -12,9 +13,53 @@
 
 #include <boost/asio/spawn.hpp>
 
+#include <algorithm>
 #include <charconv>
 #include <filesystem>
 #include <stdexcept>
+#include <unordered_map>
+
+using SchedulerClockType = std::chrono::steady_clock;
+std::unordered_map<std::string, std::shared_ptr<Scheduler<SchedulerClockType>>>
+    schedulers;
+
+template <>
+std::shared_ptr<Scheduler<SchedulerClockType>>
+    Scheduler<SchedulerClockType>::getScheduler(const std::string& path)
+{
+    auto itr = std::find_if(
+        schedulers.begin(), schedulers.end(),
+        [&path](const std::pair<std::string,
+                                std::shared_ptr<Scheduler<SchedulerClockType>>>&
+                    pair) {
+        // check if the given path is subdir of the subsystem path
+        const std::string& parent = pair.first;
+        if (parent.empty() || path.empty())
+        {
+            return false;
+        }
+        if (parent.size() > path.size())
+        {
+            return false;
+        }
+        if (!path.starts_with(parent))
+        {
+            return false;
+        }
+        if (parent.back() != '/' && path.size() > parent.size() &&
+            path.at(parent.size()) != '/')
+        {
+            return false;
+        }
+
+        return true;
+    });
+    if (itr == schedulers.end())
+    {
+        return {};
+    }
+    return itr->second;
+}
 
 void NVMeSubsystem::createAssociation()
 {
@@ -216,6 +261,15 @@
             self->plugin->start();
         }
 
+        // start Metric Scheduler
+        auto scheduler =
+            Scheduler<SchedulerClockType>::getScheduler(self->path);
+        assert(
+            scheduler &&
+            "scheduler should be created at the beginning of Initializing Status");
+        scheduler->start();
+        scheduler->dequeue();
+
         self->status = Status::Start;
     });
 }
@@ -249,6 +303,10 @@
         assert(status == Status::Start || status == Status::Aborting);
 
         status = Status::Terminating;
+
+        // erase the scheduler to stop furthur Metric scheduling
+        schedulers.erase(path);
+
         if (plugin)
         {
             plugin->stop();
@@ -334,6 +392,12 @@
     assert(status == Status::Stop);
 
     status = Status::Intiatilzing;
+
+    // create scheduler
+    assert(schedulers.find(path) == schedulers.end() &&
+           "scheduler should exsit only between [Intiatilzing, Terminating)");
+    schedulers.emplace(path, new Scheduler<SchedulerClockType>(io));
+
     markAvailable(toggle);
 
     // add controllers for the subsystem