Major changes for supporting multihost, including some code refactoring.

Local Unit Test:
https://paste.googleplex.com/5874223018672128

Tested on AMD single-host:
https://paste.googleplex.com/5323461163220992

Tested on ARM multi-host:
https://paste.googleplex.com/5179956709294080

Google-Bug-Id: 378795793
Change-Id: I904a3dee8a5c1812649bd1ba610b328d16f98e09
diff --git a/include/api.hpp b/include/api.hpp
new file mode 100644
index 0000000..52cb283
--- /dev/null
+++ b/include/api.hpp
@@ -0,0 +1,207 @@
+#pragma once
+
+#include "node_config.hpp"
+#include "resource.hpp"
+
+#include <map>
+#include <memory>
+#include <string_view>
+
+namespace boot_time_monitor
+{
+
+namespace api
+{
+
+namespace btm = boot_time_monitor;
+
+/**
+ * @brief Interface for managing boot time monitoring data for different nodes.
+ *
+ * This interface defines the contract for setting and retrieving boot
+ * checkpoints and durations for specific nodes or groups of nodes identified
+ * by tags. It also handles notifications for boot completion and provides
+ * methods to check the reboot status of a node.
+ */
+class IApi
+{
+  public:
+    virtual ~IApi() = default;
+
+    /**
+     * @brief Sets a boot checkpoint for a specific node.
+     *
+     * @param nodeConfig The configuration of the target node.
+     * @param checkpointName The name of the checkpoint.
+     * @param wallTime The wall clock time (epoch time in ms) when the
+     *                 checkpoint was reached. If 0, the current time is used.
+     * @param selfMeasuredDuration The duration (in ms) measured by the
+     *                             component reporting the checkpoint. If
+     * non-zero, an additional checkpoint marking the beginning of this duration
+     * might be recorded.
+     */
+    virtual void SetNodeCheckpoint(const btm::NodeConfig& nodeConfig,
+                                   std::string_view checkpointName,
+                                   int64_t wallTime,
+                                   int64_t selfMeasuredDuration) = 0;
+
+    /**
+     * @brief Sets a boot checkpoint for all nodes matching a specific tag.
+     *
+     * @param tag The tag to identify the target nodes.
+     * @param checkpointName The name of the checkpoint.
+     * @param wallTime The wall clock time (epoch time in ms) when the
+     *                 checkpoint was reached. If 0, the current time is used.
+     * @param selfMeasuredDuration The duration (in ms) measured by the
+     *                             component reporting the checkpoint.
+     */
+    virtual void SetNodesCheckpointByTag(std::string_view tag,
+                                         std::string_view checkpointName,
+                                         int64_t wallTime,
+                                         int64_t selfMeasuredDuration) = 0;
+
+    /**
+     * @brief Sets a boot duration measurement for a specific node.
+     *
+     * @param nodeConfig The configuration of the target node.
+     * @param durationName The name of the duration measurement.
+     * @param duration The measured duration in milliseconds.
+     */
+    virtual void SetNodeDuration(const btm::NodeConfig& nodeConfig,
+                                 std::string_view durationName,
+                                 int64_t duration) = 0;
+
+    /**
+     * @brief Sets a boot duration measurement for all nodes matching a specific
+     * tag.
+     *
+     * @param tag The tag to identify the target nodes.
+     * @param durationName The name of the duration measurement.
+     * @param duration The measured duration in milliseconds.
+     */
+    virtual void SetNodesDurationByTag(std::string_view tag,
+                                       std::string_view durationName,
+                                       int64_t duration) = 0;
+
+    /**
+     * @brief Sets a Power Supply Unit (PSU) related boot checkpoint for all
+     * nodes. PSU events are typically broadcast to all nodes.
+     *
+     * @param checkpointName The name of the PSU checkpoint.
+     * @param wallTime The wall clock time (epoch time in ms).
+     * @param selfMeasuredDuration The self-measured duration (in ms).
+     */
+    virtual void SetPSUCheckpoint(std::string_view checkpointName,
+                                  int64_t wallTime,
+                                  int64_t selfMeasuredDuration) = 0;
+
+    /**
+     * @brief Sets a Power Supply Unit (PSU) related boot duration for all
+     * nodes. PSU events are typically broadcast to all nodes.
+     *
+     * @param durationName The name of the PSU duration.
+     * @param duration The measured duration in milliseconds.
+     */
+    virtual void SetPSUDuration(std::string_view durationName,
+                                int64_t duration) = 0;
+
+    /**
+     * @brief Notifies that the boot process for a specific node is complete.
+     *        This typically involves finalizing the current boot record.
+     *
+     * @param nodeConfig The configuration of the completed node.
+     */
+    virtual void NotifyNodeComplete(const btm::NodeConfig& nodeConfig) = 0;
+
+    /**
+     * @brief Notifies that the boot process for all nodes matching a specific
+     * tag is complete.
+     *
+     * @param tag The tag identifying the completed nodes.
+     */
+    virtual void NotifyNodesCompleteByTag(std::string_view tag) = 0;
+
+    /**
+     * @brief Retrieves the list of recorded checkpoints for a specific node.
+     *
+     * @param nodeConfig The configuration of the target node.
+     * @return A vector of tuples, each containing (checkpoint name, wall time
+     * ms, monotonic time ms).
+     */
+    virtual std::vector<std::tuple<std::string, int64_t, int64_t>>
+        GetNodeCheckpointList(const btm::NodeConfig& nodeConfig) = 0;
+
+    /**
+     * @brief Retrieves the list of recorded additional durations for a specific
+     * node.
+     *
+     * @param nodeConfig The configuration of the target node.
+     * @return A vector of tuples, each containing (duration name, duration ms).
+     */
+    virtual std::vector<std::tuple<std::string, int64_t>>
+        GetNodeAdditionalDurations(const btm::NodeConfig& nodeConfig) = 0;
+
+    /**
+     * @brief Checks if a specific node is currently in the process of
+     * rebooting.
+     *
+     * @param nodeConfig The configuration of the target node.
+     * @return True if the node is considered to be rebooting, false otherwise.
+     */
+    virtual bool IsNodeRebooting(const btm::NodeConfig& nodeConfig) = 0;
+};
+
+/**
+ * @brief Concrete implementation of the IApi interface.
+ *
+ * This class manages multiple `IResource` instances (one per node)
+ * and provides thread-safe access to them via a mutex. It acts as the central
+ * point for interacting with boot time data storage.
+ */
+class Api : public IApi
+{
+  public:
+    virtual ~Api() override;
+    void SetNodeCheckpoint(const btm::NodeConfig& nodeConfig,
+                           std::string_view checkpointName, int64_t wallTime,
+                           int64_t selfMeasuredDuration) override;
+    void SetNodesCheckpointByTag(std::string_view tag,
+                                 std::string_view checkpointName,
+                                 int64_t wallTime,
+                                 int64_t selfMeasuredDuration) override;
+    void SetNodeDuration(const btm::NodeConfig& nodeConfig,
+                         std::string_view durationName,
+                         int64_t duration) override;
+    void SetNodesDurationByTag(std::string_view tag,
+                               std::string_view durationName,
+                               int64_t duration) override;
+    void SetPSUCheckpoint(std::string_view checkpointName, int64_t wallTime,
+                          int64_t selfMeasuredDuration) override;
+    void SetPSUDuration(std::string_view durationName,
+                        int64_t duration) override;
+    void NotifyNodeComplete(const btm::NodeConfig& nodeConfig) override;
+    void NotifyNodesCompleteByTag(std::string_view tag) override;
+    std::vector<std::tuple<std::string, int64_t, int64_t>>
+        GetNodeCheckpointList(const btm::NodeConfig& nodeConfig) override;
+    std::vector<std::tuple<std::string, int64_t>>
+        GetNodeAdditionalDurations(const btm::NodeConfig& nodeConfig) override;
+    bool IsNodeRebooting(const btm::NodeConfig& nodeConfig) override;
+
+    /**
+     * @brief Registers a new node with the API.
+     *
+     * This creates a corresponding resource handler (e.g., a File resource)
+     * for the given node configuration.
+     *
+     * @param nodeConfig The configuration of the node to register.
+     */
+    void RegisterNode(const btm::NodeConfig& nodeConfig);
+
+  private:
+    // Map storing the resource handler for each registered node.
+    std::map<NodeConfig, std::unique_ptr<resource::IResource>> mResourceMap;
+    std::mutex mApiMutex; // An api mutex is required for protecting
+                          // misordering of the record.
+};
+} // namespace api
+} // namespace boot_time_monitor
diff --git a/include/bmc_monitor_app.hpp b/include/bmc_monitor_app.hpp
deleted file mode 100644
index 2ff50fc..0000000
--- a/include/bmc_monitor_app.hpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-#include "boot_manager.hpp"
-#include "dbus_handler.hpp"
-#include "utils.hpp"
-
-#include <sdbusplus/asio/connection.hpp>
-#include <sdbusplus/bus.hpp>
-
-#include <memory>
-#include <string_view>
-
-namespace boot_time_monitor
-{
-
-class BMCMonitorApp
-{
-  public:
-    constexpr static std::string_view kNodeName = "bmc";
-    constexpr static std::string_view kObjPath =
-        "/xyz/openbmc_project/time/boot/bmc";
-
-    BMCMonitorApp(sdbusplus::bus::bus& bus,
-                  const std::shared_ptr<sdbusplus::asio::connection>& conn);
-
-  private:
-    sdbusplus::server::manager::manager objManager;
-    std::shared_ptr<Util> util;
-    std::shared_ptr<FileUtil> cpCSV;
-    std::shared_ptr<FileUtil> durCSV;
-    std::shared_ptr<BootManager> bootManager;
-    std::shared_ptr<DbusHandler> dbusHandler;
-};
-
-} // namespace boot_time_monitor
diff --git a/include/boot_manager.hpp b/include/boot_manager.hpp
deleted file mode 100644
index 32c0022..0000000
--- a/include/boot_manager.hpp
+++ /dev/null
@@ -1,108 +0,0 @@
-#pragma once
-
-#include "utils.hpp"
-
-#include <cstdint>
-#include <memory>
-#include <string_view>
-
-namespace boot_time_monitor
-{
-
-/**
- * An interface class for the Boot Manager APIs
- */
-class BootManagerIface
-{
-  public:
-    virtual ~BootManagerIface() = default;
-
-    /**
-     * Set a new checkpoint to Boot Manager
-     *
-     * @param[in] cpName - Checkpoint name
-     * @param[in] externalWallTime - Current epoch time in milliseconds
-     * @param[in] duration - Self measured duration in milliseconds
-     */
-    virtual void setCheckpoint(std::string_view cpName,
-                               int64_t externalWallTime, int64_t duration) = 0;
-
-    /**
-     * Set a new duration to Boot Manager
-     *
-     * @param[in] durName - Duration name
-     * @param[in] duration - Self measured duration in milliseconds
-     */
-    virtual void setDuration(std::string_view durName, int64_t duration) = 0;
-
-    /**
-     * Inform Boot Manager that current reboot process has completed.
-     */
-    virtual void notifyComplete() = 0;
-
-    /**
-     * Check if we are in a reboot process
-     *
-     * @return true if it's rebooting
-     */
-    virtual bool isRebooting() = 0;
-
-    /**
-     * Get current checkpoints
-     *
-     * @return vector of current checkpoints
-     */
-    virtual const std::vector<Checkpoint>& getCheckpoints() const = 0;
-
-    /**
-     * Get current durations
-     *
-     * @return vector of current durations
-     */
-    virtual const std::vector<Duration>& getDurations() const = 0;
-
-    /**
-     * Get previous checkpoints
-     *
-     * @return vector of previous checkpoints
-     */
-    virtual const std::vector<Checkpoint>& getPreCheckpoints() const = 0;
-
-    /**
-     * Get previous durations
-     *
-     * @return vector of previous durations
-     */
-    virtual const std::vector<Duration>& getPreDurations() const = 0;
-};
-
-class BootManager : public BootManagerIface
-{
-  public:
-    constexpr static std::string_view kBeginStageSuffix = ":BEGIN";
-
-    BootManager(std::shared_ptr<UtilIface> util,
-                const std::shared_ptr<FileUtilIface>& cpCSV,
-                const std::shared_ptr<FileUtilIface>& durCSV);
-    void setCheckpoint(std::string_view cpName, int64_t externalWallTime,
-                       int64_t duration) override;
-    void setDuration(std::string_view durName, int64_t duration) override;
-    void notifyComplete() override;
-    bool isRebooting() override;
-    const std::vector<Checkpoint>& getCheckpoints() const override;
-    const std::vector<Duration>& getDurations() const override;
-    const std::vector<Checkpoint>& getPreCheckpoints() const override;
-    const std::vector<Duration>& getPreDurations() const override;
-
-  private:
-    std::vector<Checkpoint> checkpoints;
-    std::vector<Duration> durations;
-    std::vector<Checkpoint> preCheckpoints;
-    std::vector<Duration> preDurations;
-
-    std::shared_ptr<UtilIface> util;
-    std::shared_ptr<FileUtilIface> cpCSV;
-    std::shared_ptr<FileUtilIface> durCSV;
-};
-
-} // namespace boot_time_monitor
diff --git a/include/dbus_handler.hpp b/include/dbus_handler.hpp
index 9767bab..b8d39e1 100644
--- a/include/dbus_handler.hpp
+++ b/include/dbus_handler.hpp
@@ -1,7 +1,7 @@
 #pragma once
+#include "api.hpp"
 
-#include "boot_manager.hpp"
-#include "utils.hpp"
+#include <fmt/printf.h>
 
 #include <sdbusplus/asio/object_server.hpp>
 #include <sdbusplus/bus/match.hpp>
@@ -16,7 +16,32 @@
 namespace boot_time_monitor
 {
 
-class DbusHandler :
+namespace dbus
+{
+
+namespace btm = boot_time_monitor;
+
+/**
+ * @brief Configuration for the D-Bus handler.
+ */
+struct DbusConfig
+{
+    /** @brief The D-Bus object path for this handler instance. */
+    std::string dbusObjPath;
+};
+
+/**
+ * @brief Handles D-Bus requests related to boot time monitoring for a specific
+ * node.
+ *
+ * This class implements the D-Bus interfaces for Checkpoint, Duration, and
+ * Statistic defined under `xyz.openbmc_project.Time.Boot`. It acts as a
+ * bridge between incoming D-Bus calls and the underlying boot time monitoring
+ * API (`api::IApi`). Each instance of this handler is associated with a
+ * specific node configuration (`NodeConfig`) and interacts with the central API
+ * to record or retrieve boot time data for that node.
+ */
+class Handler :
     sdbusplus::server::object::object<
         sdbusplus::server::xyz::openbmc_project::time::boot::Duration>,
     sdbusplus::server::object::object<
@@ -25,23 +50,84 @@
         sdbusplus::server::xyz::openbmc_project::time::boot::Statistic>
 {
   public:
-    DbusHandler(sdbusplus::bus::bus& dbus, const std::string& objPath,
-                std::shared_ptr<BootManagerIface> bootManager,
-                std::shared_ptr<UtilIface> util);
+    /**
+     * @brief Constructs the D-Bus handler.
+     *
+     * @param dbus The sdbusplus bus connection.
+     * @param nodeConfig Configuration of the node this handler manages.
+     * @param dbusConfig D-Bus specific configuration (object path).
+     * @param api Shared pointer to the central boot time monitoring API.
+     */
+    Handler(sdbusplus::bus::bus& dbus, const btm::NodeConfig& nodeConfig,
+            const btm::dbus::DbusConfig& dbusConfig,
+            std::shared_ptr<btm::api::IApi> api);
 
+    /**
+     * @brief D-Bus method implementation for setting a boot checkpoint.
+     *
+     * Forwards the request to the appropriate API method
+     * (`SetNodeCheckpoint` or `SetPSUCheckpoint`).
+     *
+     * @param checkpointName The name of the checkpoint.
+     * @param wallTime The wall clock time (epoch time in ms).
+     * @param selfMeasuredDuration Optional self-measured duration (ms).
+     */
     void setCheckpoint(std::string checkpointName, int64_t wallTime,
                        int64_t selfMeasuredDuration) override;
+
+    /**
+     * @brief D-Bus method implementation for retrieving the checkpoint list.
+     *
+     * Retrieves the data from the API via `GetNodeCheckpointList`.
+     *
+     * @return A vector of tuples: (checkpoint name, wall time ms, monotonic
+     * time ms).
+     */
     std::vector<std::tuple<std::string, int64_t, int64_t>>
         getCheckpointList() override;
+
+    /**
+     * @brief D-Bus method implementation to signal boot completion.
+     *
+     * Calls `NotifyNodeComplete` on the API if the node is currently rebooting.
+     */
     void rebootComplete() override;
+
+    /**
+     * @brief D-Bus method implementation for setting a boot duration.
+     *
+     * Forwards the request to the appropriate API method
+     * (`SetNodeDuration` or `SetPSUDuration`).
+     *
+     * @param durationName The name of the duration measurement.
+     * @param duration The measured duration in milliseconds.
+     */
     void setDuration(std::string durationName, int64_t duration) override;
+
+    /**
+     * @brief D-Bus method implementation for retrieving additional durations.
+     *
+     * Retrieves the data from the API via `GetNodeAdditionalDurations`.
+     *
+     * @return A vector of tuples: (duration name, duration ms).
+     */
     std::vector<std::tuple<std::string, int64_t>>
         getAdditionalDurations() override;
+
+    /**
+     * @brief D-Bus property getter implementation for checking reboot status.
+     *
+     * Retrieves the status from the API via `IsNodeRebooting`.
+     *
+     * @return True if the node is considered to be rebooting, false otherwise.
+     */
     bool isRebooting() const override;
 
   private:
-    std::shared_ptr<BootManagerIface> bootManager;
-    std::shared_ptr<UtilIface> util;
+    /** @brief Configuration of the node associated with this handler. */
+    NodeConfig mNodeConfig;
+    /** @brief Shared pointer to the central boot time monitoring API. */
+    std::shared_ptr<api::IApi> mApi;
 };
-
+} // namespace dbus
 } // namespace boot_time_monitor
diff --git a/include/gen.hpp b/include/gen.hpp
new file mode 100644
index 0000000..3102410
--- /dev/null
+++ b/include/gen.hpp
@@ -0,0 +1,128 @@
+#pragma once
+
+#include "dbus_handler.hpp"
+#include "node_config.hpp"
+#include "psm_handler.hpp"
+
+#include <fmt/printf.h>
+
+#include <string_view>
+#include <vector>
+
+namespace boot_time_monitor
+{
+/**
+ * @brief Contains helper functions for generating configuration objects and
+ * names.
+ *
+ * This namespace provides utility functions to create configuration structures
+ * (like PSMConfig and DbusConfig) and standardized node names based on system
+ * properties like the number of hosts or BMCs and their indices.
+ */
+namespace gen
+{
+namespace btm = boot_time_monitor;
+
+/**
+ * @brief Generates the PSM (Platform State Manager) configuration for a
+ * specific node.
+ *
+ * This function determines the correct D-Bus service names and object paths
+ * for monitoring host state and OS status based on whether the system is
+ * single-host or multi-host. For multi-host systems, it uses the node's
+ * index within the provided list to construct unique identifiers.
+ *
+ * @param nodeConfigs A vector containing the configurations of all nodes.
+ * @param nodeConfig The configuration of the specific node for which to
+ * generate the PSM config.
+ * @return psm::PSMConfig The generated PSM configuration containing D-Bus
+ *                        service names and object paths. Returns an empty
+ *                        config if the node is not found or the list is empty.
+ */
+psm::PSMConfig GenPSMConfig(const std::vector<btm::NodeConfig>& nodeConfigs,
+                            const btm::NodeConfig& nodeConfig)
+{
+    if (nodeConfigs.size() == 0)
+    {
+        fmt::print("[{}] node list is empty!\n", __FUNCTION__);
+        return {};
+    }
+
+    // Singlehost rule
+    if (nodeConfigs.size() == 1)
+    {
+        return {
+            .hostStateDbusService = "xyz.openbmc_project.State.Host",
+            .hostStateDbusObjPath = "/xyz/openbmc_project/state/host0",
+            .osStateDbusService = "xyz.openbmc_project.State.OperatingSystem",
+            .osStateDbusObjPath = "/xyz/openbmc_project/state/os",
+        };
+    }
+
+    // Multihost platforms have a different PSM Dbus Service and Path rule.
+    // Create dBus path depends on its relative indices.
+    auto it = std::find(nodeConfigs.begin(), nodeConfigs.end(), nodeConfig);
+    if (it == nodeConfigs.end())
+    {
+        fmt::print("[{}] node config not found!\n", __FUNCTION__);
+        return {};
+    }
+
+    int idx = std::distance(nodeConfigs.begin(), it) + 1;
+
+    return {
+        .hostStateDbusService = fmt::format("xyz.openbmc_project.State.Host{}",
+                                            idx),
+        .hostStateDbusObjPath = fmt::format("/xyz/openbmc_project/state/host{}",
+                                            idx),
+        .osStateDbusService =
+            fmt::format("xyz.openbmc_project.State.OperatingSystem{}", idx),
+        .osStateDbusObjPath = "/xyz/openbmc_project/state/os",
+    };
+}
+
+/**
+ * @brief Generates the D-Bus configuration for a specific node.
+ *
+ * Constructs the D-Bus object path for the boot time monitor interfaces
+ * based on the node's name.
+ *
+ * @param nodeConfig The configuration of the node.
+ * @return dbus::DbusConfig The generated D-Bus configuration containing the
+ *                          object path.
+ */
+dbus::DbusConfig GenDbusConfig(const NodeConfig& nodeConfig)
+{
+    return {.dbusObjPath = fmt::format("/xyz/openbmc_project/time/boot/{}",
+                                       nodeConfig.nodeName)};
+}
+
+/**
+ * @brief Generates a standard host node name based on its index.
+ *
+ * @param idx The zero-based index of the host.
+ * @return std::string The generated host node name (e.g., "host0", "host1").
+ */
+std::string GenHostNodeName(int idx)
+{
+    return fmt::format("host{}", idx);
+}
+
+/**
+ * @brief Generates a standard BMC node name based on the total number of BMCs
+ * and its index.
+ * @param bmcAmount The total number of BMCs in the system.
+ * @param idx The zero-based index of this BMC.
+ * @return std::string The generated BMC node name (e.g., "bmc" for single BMC,
+ * "bmc0", "bmc1" for multiple).
+ */
+std::string GenBmcNodeName(int bmcAmount, int idx)
+{
+    if (bmcAmount == 1)
+        return "bmc";
+
+    return fmt::format("bmc{}", idx);
+}
+
+} // namespace gen
+} // namespace boot_time_monitor
diff --git a/include/host_monitor_app.hpp b/include/host_monitor_app.hpp
deleted file mode 100644
index 850237d..0000000
--- a/include/host_monitor_app.hpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include "boot_manager.hpp"
-#include "dbus_handler.hpp"
-#include "utils.hpp"
-
-#include <sdbusplus/bus.hpp>
-#include <sdbusplus/bus/match.hpp>
-#include <sdbusplus/server/manager.hpp>
-
-#include <memory>
-#include <string_view>
-
-namespace boot_time_monitor
-{
-
-class HostMonitorApp
-{
-  public:
-    HostMonitorApp(sdbusplus::bus::bus& bus, uint32_t hostNum);
-
-  private:
-    const std::string kNodeName;
-    const std::string kObjPath;
-    sdbusplus::server::manager::manager objManager;
-    std::unique_ptr<sdbusplus::bus::match::match> hostStateWatcher;
-    std::unique_ptr<sdbusplus::bus::match::match> osStatusWatcher;
-
-    std::shared_ptr<Util> util;
-    std::shared_ptr<FileUtil> cpCSV;
-    std::shared_ptr<FileUtil> durCSV;
-    std::shared_ptr<BootManager> bootManager;
-    std::shared_ptr<DbusHandler> dbusHandler;
-};
-
-} // namespace boot_time_monitor
diff --git a/include/node_config.hpp b/include/node_config.hpp
new file mode 100644
index 0000000..595da02
--- /dev/null
+++ b/include/node_config.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <string_view>
+
+namespace boot_time_monitor
+{
+/** @brief Tag identifying a BMC node. Used for grouping operations. */
+inline constexpr std::string_view kBootTimeTagBMC = "BMC";
+/** @brief Tag identifying a Host node. Used for grouping operations. */
+inline constexpr std::string_view kBootTimeTagHost = "Host";
+
+/**
+ * @brief Represents the configuration for a specific node being monitored.
+ *
+ * This structure holds the unique name of a node (e.g., "host0", "bmc") and
+ * an optional tag (e.g., "Host", "BMC") used for grouping nodes. It provides
+ * comparison operators for use in containers like `std::map` and an output
+ * stream operator for easy printing.
+ */
+struct NodeConfig
+{
+    /**
+     * @brief The unique identifier name for the node (e.g., "host0", "bmc").
+     */
+    std::string nodeName;
+    /** @brief An optional tag for grouping nodes (e.g., "Host", "BMC"). */
+    std::string tag = "";
+
+    /**
+     * @brief Less-than comparison operator based on `nodeName`.
+     * @param other The NodeConfig to compare against.
+     * @return True if this nodeName is lexicographically less than
+     * other.nodeName.
+     */
+    bool operator<(const NodeConfig& other) const
+    {
+        return nodeName < other.nodeName;
+    }
+
+    /**
+     * @brief Equality comparison operator based on `nodeName`.
+     * @param other The NodeConfig to compare against.
+     * @return True if nodeNames are equal.
+     */
+    bool operator==(const NodeConfig& other) const
+    {
+        return nodeName == other.nodeName;
+    }
+
+    /**
+     * @brief Output stream operator for printing the NodeConfig.
+     * @param os The output stream.
+     * @param k The NodeConfig object to print.
+     * @return The output stream with the NodeConfig printed in the format:
+     * `{"<nodeName>", "<tag>"}`.
+     */
+    friend std::ostream& operator<<(std::ostream& os, const NodeConfig& k)
+    {
+        os << "{\"" << k.nodeName << "\", \"" << k.tag << "\"}";
+        return os;
+    }
+};
+} // namespace boot_time_monitor
diff --git a/include/psm_handler.hpp b/include/psm_handler.hpp
new file mode 100644
index 0000000..db2b5ab
--- /dev/null
+++ b/include/psm_handler.hpp
@@ -0,0 +1,94 @@
+#pragma once
+
+#include "api.hpp"
+#include "node_config.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <string>
+
+namespace boot_time_monitor
+{
+/**
+ * @brief Contains functionality related to interacting with the
+ * [Phosphor-state-manager](https://github.com/openbmc/phosphor-state-manager)
+ * (PSM).
+ */
+namespace psm
+{
+
+namespace btm = boot_time_monitor;
+
+/**
+ * @brief Configuration for interacting with PSM D-Bus interfaces.
+ *
+ * Stores the necessary D-Bus service names and object paths required to
+ * monitor host state and operating system status properties.
+ */
+struct PSMConfig
+{
+    /** @brief D-Bus service name for the Host State interface (e.g.,
+     * "xyz.openbmc_project.State.Host"). */
+    std::string hostStateDbusService;
+    /** @brief D-Bus object path for the Host State interface (e.g.,
+     * "/xyz/openbmc_project/state/host0"). */
+    std::string hostStateDbusObjPath;
+    /** @brief D-Bus service name for the OS State interface (e.g.,
+     * "xyz.openbmc_project.State.OperatingSystem"). */
+    std::string osStateDbusService;
+    /** @brief D-Bus object path for the OS State interface (e.g.,
+     * "/xyz/openbmc_project/state/os"). */
+    std::string osStateDbusObjPath;
+};
+
+/**
+ * @brief Handles monitoring of Platform State Manager (PSM) D-Bus signals for a
+ * specific node.
+ *
+ * This class sets up D-Bus match rules to listen for property changes on
+ * the Host State (`xyz.openbmc_project.State.Host`) and OS Status
+ * (`xyz.openbmc_project.State.OperatingSystem.Status`) interfaces defined
+ * in the `PSMConfig`. When a relevant property changes (e.g.,
+ * `CurrentHostState`, `OperatingSystemState`), it translates the new state
+ * into a checkpoint name and records it using the provided `IApi` instance
+ * for the associated `NodeConfig`.
+ */
+class Handler
+{
+  public:
+    /**
+     * @brief Constructs the PSM Handler.
+     *
+     * Initializes previous state variables by querying current D-Bus properties
+     * and sets up D-Bus match rules to monitor for future changes.
+     *
+     * @param bus The sdbusplus bus connection.
+     * @param nodeConfig Configuration of the node this handler monitors.
+     * @param psmConfig D-Bus configuration for PSM interfaces.
+     * @param api Shared pointer to the central boot time monitoring API.
+     */
+    Handler(sdbusplus::bus::bus& bus, const btm::NodeConfig& nodeConfig,
+            const btm::psm::PSMConfig& psmConfig,
+            std::shared_ptr<btm::api::IApi> api);
+
+  private:
+    /** @brief D-Bus match rule watcher for Host State property changes. */
+    std::unique_ptr<sdbusplus::bus::match::match> hostStateWatcher;
+    /** @brief D-Bus match rule watcher for OS Status property changes. */
+    std::unique_ptr<sdbusplus::bus::match::match> osStatusWatcher;
+
+    /** @brief Stores the previously observed Host State to detect changes. */
+    std::string mPreHostState;
+    /** @brief Stores the previously observed OS State/Status to detect changes.
+     */
+    std::string mPreOSState;
+    /** @brief Configuration of the node associated with this handler. */
+    btm::NodeConfig mNodeConfig;
+    /** @brief Shared pointer to the central boot time monitoring API. */
+    std::shared_ptr<btm::api::IApi> mApi;
+};
+} // namespace psm
+} // namespace boot_time_monitor
diff --git a/include/resource.hpp b/include/resource.hpp
new file mode 100644
index 0000000..a7c339c
--- /dev/null
+++ b/include/resource.hpp
@@ -0,0 +1,280 @@
+#pragma once
+
+#include <fstream>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+namespace boot_time_monitor
+{
+
+/**
+ * @brief Contains interfaces and implementations for resource management (data
+ * storage).
+ *
+ * This namespace defines how boot time data (checkpoints and durations) is
+ * stored and retrieved. Currently, it provides a file-based implementation.
+ */
+namespace resource
+{
+
+namespace btm = boot_time_monitor;
+
+/** @brief Default directory for storing boot time monitor data files. */
+extern std::string BTMonitorDir;
+
+/** @brief Suffix appended to filenames when a boot cycle is completed. */
+const std::string kCompletedSuffix = ".completed";
+/** @brief Suffix added to a checkpoint name to indicate the start of a
+ * self-measured duration. */
+const std::string kBeginStageSuffix = ":BEGIN";
+/** @brief Base filename for storing checkpoint data. */
+const std::string kCheckpointFile = "checkpoints.csv";
+/** @brief Base filename for storing duration data. */
+const std::string kDurationFile = "durations.csv";
+
+/**
+ * @brief Represents a single boot checkpoint record.
+ */
+struct Checkpoint
+{
+    /** @brief The name identifying the checkpoint. */
+    std::string name;
+    /** @brief The wall clock time (epoch time in ms) when the checkpoint was
+     * reached. */
+    int64_t wallTime;
+    /** @brief The monotonic clock time (uptime in ms) when the checkpoint was
+     * reached. */
+    int64_t monoTime;
+
+    /** @brief Constructs a Checkpoint object. */
+    Checkpoint(std::string name, int64_t wallTime, int64_t monoTime) :
+        name(std::move(name)), wallTime(wallTime), monoTime(monoTime)
+    {}
+};
+
+/**
+ * @brief Represents a single boot duration measurement record.
+ */
+struct Duration
+{
+    /** @brief The name identifying the duration measurement. */
+    std::string name;
+    /** @brief The measured duration in milliseconds. */
+    int64_t duration;
+
+    /** @brief Constructs a Duration object. */
+    Duration(std::string name, int64_t duration) :
+        name(std::move(name)), duration(duration)
+    {}
+};
+
+/**
+ * @brief Interface for managing boot time data storage for a single node.
+ *
+ * Defines the contract for storing checkpoints and durations, retrieving cached
+ * data, marking a boot cycle as complete, and checking the current reboot
+ * status.
+ */
+class IResource
+{
+  public:
+    virtual ~IResource() = default;
+
+    /**
+     * @brief Records a boot checkpoint.
+     *
+     * @param name The name of the checkpoint.
+     * @param epochTime The wall clock time (epoch time in ms). If 0, current
+     * time is used.
+     * @param duration The self-measured duration (in ms). If non-zero, a
+     * ":BEGIN" checkpoint might be added.
+     */
+    virtual void SetCheckpoint(std::string_view name, int64_t epochTime,
+                               int64_t duration) = 0;
+
+    /**
+     * @brief Records a boot duration measurement.
+     *
+     * @param name The name of the duration measurement.
+     * @param duration The measured duration in milliseconds.
+     */
+    virtual void SetDuration(std::string_view name, int64_t duration) = 0;
+
+    /**
+     * @brief Retrieves the cached list of checkpoints for the current or last
+     * completed boot.
+     *
+     * @return A vector of Checkpoint objects. Returns current boot data if
+     * IsRebooting() is true, otherwise returns data from the last completed
+     * boot.
+     */
+    virtual std::vector<btm::resource::Checkpoint> GetCheckpointCache() = 0;
+
+    /**
+     * @brief Retrieves the cached list of durations for the current or last
+     * completed boot.
+     *
+     * @return A vector of Duration objects. Returns current boot data if
+     * IsRebooting() is true, otherwise returns data from the last completed
+     * boot.
+     */
+    virtual std::vector<btm::resource::Duration> GetDurationCache() = 0;
+
+    /**
+     * @brief Marks the current boot cycle as complete.
+     *        This typically involves finalizing storage (e.g., renaming files)
+     * and clearing current caches.
+     */
+    virtual void MarkComplete() = 0;
+
+    /**
+     * @brief Checks if the node is currently considered to be in a reboot
+     * cycle.
+     *
+     * @return True if data is being actively recorded for the current boot,
+     * false otherwise.
+     */
+    virtual bool IsRebooting() = 0;
+};
+
+/**
+ * @brief File-based implementation of the IResource interface.
+ *
+ * Stores checkpoint and duration data in separate CSV files per node.
+ * Manages current boot data and data from the last completed boot cycle.
+ * Provides caching for efficient retrieval.
+ */
+class File : public IResource
+{
+  public:
+    /**
+     * @brief Constructs a File resource handler for a specific node.
+     *
+     * Initializes file streams and loads data from existing files (current and
+     * completed).
+     *
+     * @param nodeName The name of the node this resource belongs to.
+     */
+    File(std::string_view nodeName);
+
+    ~File() = default;
+
+    // Declare noncopyable
+    File(const File&) = delete;
+    File& operator=(const File&) = delete;
+
+    // Declare nonmoveable
+    File(File&&) = delete;
+    File& operator=(File&&) = delete;
+
+    /** @copydoc IResource::SetCheckpoint */
+    void SetCheckpoint(std::string_view name, int64_t epochTime,
+                       int64_t duration) override;
+
+    /** @copydoc IResource::SetDuration */
+    void SetDuration(std::string_view name, int64_t duration) override;
+
+    /** @copydoc IResource::GetCheckpointCache */
+    std::vector<btm::resource::Checkpoint> GetCheckpointCache() override;
+
+    /** @copydoc IResource::GetDurationCache */
+    std::vector<btm::resource::Duration> GetDurationCache() override;
+
+    /** @copydoc IResource::MarkComplete */
+    void MarkComplete() override;
+
+    /** @copydoc IResource::IsRebooting */
+    bool IsRebooting() override;
+
+  private:
+    /** @brief Mutex protecting file access and cache modifications. */
+    std::mutex mFileMutex;
+    /** @brief The name of the node associated with this resource. */
+    std::string mNodeName;
+    /** @brief Output file stream for the current checkpoint file. */
+    std::ofstream mCpOfs;
+    /** @brief Output file stream for the current duration file. */
+    std::ofstream mDurOfs;
+    /** @brief Cache for checkpoints recorded during the current boot cycle. */
+    std::vector<btm::resource::Checkpoint> mCheckpointCache;
+    /** @brief Cache for checkpoints from the last completed boot cycle. */
+    std::vector<btm::resource::Checkpoint> mCheckpointCompletedCache;
+    /** @brief Cache for durations recorded during the current boot cycle. */
+    std::vector<btm::resource::Duration> mDurationCache;
+    /** @brief Cache for durations from the last completed boot cycle. */
+    std::vector<btm::resource::Duration> mDurationCompletedCache;
+
+    /**
+     * @brief Appends a checkpoint record to the current checkpoint file and
+     * cache.
+     * @param name Checkpoint name.
+     * @param wallTimeInMs Wall clock time (ms).
+     * @param monoTimeInMs Monotonic clock time (ms).
+     */
+    void AppendCheckpointToFile(std::string name, int64_t wallTimeInMs,
+                                int64_t monoTimeInMs);
+    /**
+     * @brief Appends a duration record to the current duration file and cache.
+     * @param name Duration name.
+     * @param durationInMs Duration value (ms).
+     */
+    void AppendDurationToFile(std::string name, int64_t durationInMs);
+    /**
+     * @brief Opens or creates a file for appending and returns the stream.
+     * @param filename Full path to the file.
+     * @return std::ofstream The opened file stream.
+     * @throws std::invalid_argument if the stream cannot be opened.
+     */
+    std::ofstream StartFileInstance(std::string_view filename);
+    /**
+     * @brief Finalizes a data file by closing it and renaming it with the
+     * ".complete" suffix. Reopens the original filename for the next boot.
+     * @param ofs The output stream to close and manage.
+     * @param filename The base filename (without suffix).
+     */
+    void CompleteCurrentFile(std::ofstream& ofs, std::string filename);
+    /**
+     * @brief Checks if the file associated with the stream is currently empty.
+     * @param ofs The output stream to check.
+     * @return True if the file is empty, false otherwise.
+     * @throws std::runtime_error if `tellp` fails.
+     */
+    bool IsEmptyFile(std::ofstream& ofs);
+    /**
+     * @brief Loads checkpoint data from a specified file (current or
+     * completed).
+     * @param loadComplete If true, loads from the ".complete" file; otherwise,
+     * loads from the current file.
+     * @return std::vector<btm::resource::Checkpoint> The loaded checkpoints.
+     */
+    std::vector<btm::resource::Checkpoint> LoadCheckpoints(bool loadComplete);
+    /**
+     * @brief Loads duration data from a specified file (current or completed).
+     * @param loadComplete If true, loads from the ".complete" file; otherwise,
+     * loads from the current file.
+     * @return std::vector<btm::resource::Duration> The loaded durations.
+     */
+    std::vector<btm::resource::Duration> LoadDurations(bool loadComplete);
+    /**
+     * @brief Gets the current system uptime in milliseconds from /proc/uptime.
+     * @return std::optional<int64_t> Uptime in ms, or std::nullopt if reading
+     * fails.
+     */
+    std::optional<int64_t> GetUpTimeInMs();
+
+    /** @brief Gets the current wall clock time (epoch) in milliseconds. */
+    inline int64_t GetWallTimeInMs();
+    /** @brief Validates if a name contains only allowed characters
+     * ([0-9a-zA-Z_:]). */
+    inline bool IsValidName(std::string_view name);
+    /** @brief Constructs the full path for the checkpoint file for this node.
+     */
+    inline std::string GetCpFullFileName();
+    /** @brief Constructs the full path for the duration file for this node. */
+    inline std::string GetDurFullFileName();
+};
+} // namespace resource
+} // namespace boot_time_monitor
diff --git a/include/systemd_handler.hpp b/include/systemd_handler.hpp
new file mode 100644
index 0000000..4b5a0e0
--- /dev/null
+++ b/include/systemd_handler.hpp
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "api.hpp"
+
+#include <boost/asio/steady_timer.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <functional>
+#include <vector>
+
+namespace boot_time_monitor
+{
+/**
+ * @brief Contains functionality related to interacting with systemd for boot
+ * time information.
+ */
+namespace systemd
+{
+
+namespace btm = boot_time_monitor;
+
+/**
+ * @brief Handles monitoring of systemd boot timestamps for BMC nodes.
+ *
+ * This class periodically queries systemd's D-Bus interface
+ * (`org.freedesktop.systemd1.Manager`) to retrieve various monotonic
+ * timestamps related to the boot process (Firmware, Loader, Kernel, InitRD,
+ * Userspace, Finish). It waits until the `FinishTimestampMonotonic` is
+ * available, indicating the userspace boot is complete. Once available, it
+ * calculates the duration of each stage and records these durations using the
+ * provided `IApi` instance, specifically targeting nodes tagged with
+ * `kBootTimeTagBMC`.
+ */
+class Handler
+{
+  public:
+    /**
+     * @brief Constructs the systemd Handler.
+     *
+     * Initializes the handler and starts a timer (`m_bmc_finish_timer`) to
+     * periodically check for systemd boot completion.
+     *
+     * @param conn Shared pointer to the sdbusplus ASIO connection.
+     * @param api Shared pointer to the central boot time monitoring API.
+     */
+    Handler(std::shared_ptr<sdbusplus::asio::connection> conn,
+            std::shared_ptr<btm::api::IApi> api);
+
+  private:
+    /** @brief Interval for polling systemd properties until boot is finished.
+     */
+    const std::chrono::seconds kCheckBmcFinishInterval =
+        std::chrono::seconds(10);
+    /**
+     * @brief Timer callback function to check systemd boot timestamps.
+     *
+     * Queries systemd D-Bus properties for boot timestamps. If the finish
+     * timestamp is available, calculates stage durations and records them via
+     * `mApi`. Otherwise, reschedules the timer.
+     *
+     * @param ec Boost error code from the timer operation.
+     * @param conn Shared pointer to the sdbusplus ASIO connection.
+     */
+    void CheckBmcFinish(const boost::system::error_code& ec,
+                        std::shared_ptr<sdbusplus::asio::connection>& conn);
+
+    /** @brief Shared pointer to the central boot time monitoring API. */
+    std::shared_ptr<btm::api::IApi> mApi;
+    /** @brief ASIO timer used to periodically check for systemd boot
+     * completion. */
+    boost::asio::steady_timer mBMCFinishTimer;
+};
+} // namespace systemd
+} // namespace boot_time_monitor
diff --git a/include/utils.hpp b/include/utils.hpp
deleted file mode 100644
index 97aad84..0000000
--- a/include/utils.hpp
+++ /dev/null
@@ -1,200 +0,0 @@
-#pragma once
-
-#include "config.h"
-
-#include <cstdint>
-#include <fstream>
-#include <memory>
-#include <optional>
-#include <string>
-#include <string_view>
-#include <vector>
-
-namespace boot_time_monitor
-{
-
-const std::string kBTMonitorDir = bootTimeDataDir;
-const std::string kCheckpointFile = "checkpoints.csv";
-const std::string kDurationFile = "durations.csv";
-const std::string kCompletedSuffix = ".completed";
-
-struct Checkpoint
-{
-    std::string name;
-    int64_t wallTime;
-    int64_t monoTime;
-
-    Checkpoint(std::string name, int64_t wallTime, int64_t monoTime) :
-        name(std::move(name)), wallTime(wallTime), monoTime(monoTime)
-    {}
-};
-
-struct Duration
-{
-    std::string name;
-    int64_t duration;
-
-    Duration(std::string name, int64_t duration) :
-        name(std::move(name)), duration(duration)
-    {}
-};
-
-/**
- * An interface class for the Util APIs
- */
-class UtilIface
-{
-  public:
-    virtual ~UtilIface() = default;
-
-    /**
-     * Read uptime from `/proc/uptime`
-     *
-     * @return Current uptime in milliseconds
-     */
-    virtual std::optional<int64_t> getUpTimeInMs() = 0;
-
-    /**
-     * Get system time and covert it to the epoch time in milliseconds
-     *
-     * @return Current epoch time in milliseconds
-     */
-    virtual int64_t getWallTimeInMs() = 0;
-
-    /**
-     * Check if the input name is valid. This can be used to check both
-     * checkpoint name and duration name
-     *
-     * @return true if the input name is valid
-     */
-    virtual bool isValidName(std::string_view name) = 0;
-
-    /**
-     * Get full file path of checkpoint record for a specific node
-     *
-     * @param[in] nodeName - Node name
-     * @param[in] wantCompleted - Want `kCompletedSuffix` in the file path or
-     * not
-     * @return Full file path
-     */
-    virtual std::string getCPPath(std::string_view nodeName,
-                                  bool wantCompleted) = 0;
-
-    /**
-     * Get full file path of duration record for a specific node
-     *
-     * @param[in] nodeName - Node name
-     * @param[in] wantCompleted - Want `kCompletedSuffix` in the file path or
-     * not
-     * @return Full file path
-     */
-    virtual std::string getDurPath(std::string_view nodeName,
-                                   bool wantCompleted) = 0;
-
-    /**
-     * Read 4 bytes data from target address
-     *
-     * @param[in] target - Target address
-     * @return 4 bytes data from the target address or std::nullopt if any error
-     * happens during `readMem4Bytes`
-     */
-    virtual std::optional<uint32_t> readMem4Bytes(uint32_t target) = 0;
-};
-
-/**
- * Utility implementation class
- */
-class Util : public UtilIface
-{
-  public:
-    std::optional<int64_t> getUpTimeInMs() override;
-    int64_t getWallTimeInMs() override;
-    bool isValidName(std::string_view name) override;
-    std::string getCPPath(std::string_view nodeName,
-                          bool wantCompleted) override;
-    std::string getDurPath(std::string_view nodeName,
-                           bool wantCompleted) override;
-    std::optional<uint32_t> readMem4Bytes(uint32_t target) override;
-};
-
-/**
- * An interface class for the FileUtil APIs
- */
-class FileUtilIface
-{
-  public:
-    virtual ~FileUtilIface() = default;
-
-    /**
-     * Add a new checkpoint record in file
-     *
-     * @param[in] name - Checkpoint name
-     * @param[in] wallTimeInMs - Current epoch time in milliseconds
-     * @param[in] monoTimeInMs - Current monotonic time in milliseconds
-     * (Typically it's from `/proc/uptime`)
-     */
-    virtual void addCheckpoint(std::string_view name, int64_t wallTimeInMs,
-                               int64_t monoTimeInMs) = 0;
-
-    /**
-     * Add a new duration record in file
-     *
-     * @param[in] name - Duration name
-     * @param[in] durationInMs - Duration in milliseconds
-     */
-    virtual void addDuration(std::string_view name, int64_t durationInMs) = 0;
-
-    /**
-     * Close current file and rename it with suffix `kCompletedSuffix`
-     */
-    virtual void completeCurrent() = 0;
-
-    /**
-     * Load checkpoints from file
-     *
-     * @param[in] loadCompleted - Load file with `kCompletedSuffix` or not
-     * @return Smart pointer of vector of checkpoints
-     */
-    virtual std::unique_ptr<std::vector<Checkpoint>>
-        loadCheckpoints(bool loadCompleted) = 0;
-
-    /**
-     * Load durations from file
-     *
-     * @param[in] loadCompleted - Load file with `kCompletedSuffix` or not
-     * @return Smart pointer of vector of durations
-     */
-    virtual std::unique_ptr<std::vector<Duration>>
-        loadDurations(bool loadCompleted) = 0;
-
-    /**
-     * Is current file empty
-     *
-     * @return true if file is empty
-     */
-    virtual bool isEmpty() = 0;
-};
-
-/**
- * FileUtil implementation class
- */
-class FileUtil : public FileUtilIface
-{
-  public:
-    explicit FileUtil(std::string_view filename);
-    void addCheckpoint(std::string_view name, int64_t wallTimeInMs,
-                       int64_t monoTimeInMs) override;
-    void addDuration(std::string_view name, int64_t durationInMs) override;
-    void completeCurrent() override;
-    std::unique_ptr<std::vector<Checkpoint>>
-        loadCheckpoints(bool loadCompleted) override;
-    std::unique_ptr<std::vector<Duration>>
-        loadDurations(bool loadCompleted) override;
-    bool isEmpty() override;
-
-  protected:
-    std::string filename;
-    std::unique_ptr<std::ofstream> ofs;
-};
-
-} // namespace boot_time_monitor
diff --git a/meson.build b/meson.build
index 3f03f92..823e9da 100644
--- a/meson.build
+++ b/meson.build
@@ -48,4 +48,4 @@
 
 if not get_option('tests').disabled()
   subdir('test')
-endif
+endif
\ No newline at end of file
diff --git a/src/api.cpp b/src/api.cpp
new file mode 100644
index 0000000..c085907
--- /dev/null
+++ b/src/api.cpp
@@ -0,0 +1,201 @@
+#include "api.hpp"
+
+#include <fmt/printf.h>
+
+namespace boot_time_monitor
+{
+
+namespace api
+{
+
+namespace btm = boot_time_monitor;
+
+Api::~Api() = default;
+
+void Api::SetNodeCheckpoint(const btm::NodeConfig& nodeConfig,
+                            std::string_view checkpointName, int64_t wallTime,
+                            int64_t selfMeasuredDuration)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    auto it = mResourceMap.find(nodeConfig);
+
+    if (it == mResourceMap.end())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   nodeConfig.nodeName);
+        return;
+    }
+
+    it->second.get()->SetCheckpoint(checkpointName, wallTime,
+                                    selfMeasuredDuration);
+}
+
+void Api::SetNodeDuration(const btm::NodeConfig& nodeConfig,
+                          std::string_view durationName, int64_t duration)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    auto it = mResourceMap.find(nodeConfig);
+
+    if (it == mResourceMap.end())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   nodeConfig.nodeName);
+        return;
+    }
+
+    it->second.get()->SetDuration(durationName, duration);
+}
+
+void Api::SetNodesCheckpointByTag(std::string_view tag,
+                                  std::string_view checkpointName,
+                                  int64_t wallTime,
+                                  int64_t selfMeasuredDuration)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    for (const auto& [config, node] : mResourceMap)
+    {
+        if (config.tag == tag)
+        {
+            node.get()->SetCheckpoint(checkpointName, wallTime,
+                                      selfMeasuredDuration);
+        }
+    }
+}
+
+void Api::SetNodesDurationByTag(std::string_view tag,
+                                std::string_view durationName, int64_t duration)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    for (const auto& [config, node] : mResourceMap)
+    {
+        if (config.tag == tag)
+        {
+            node.get()->SetDuration(durationName, duration);
+        }
+    }
+}
+
+void Api::SetPSUCheckpoint(std::string_view checkpointName, int64_t wallTime,
+                           int64_t selfMeasuredDuration)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    // For "Power Supply Unit" checkpoint should broadcast to all nodes.
+    for (const auto& [_, node] : mResourceMap)
+    {
+        node.get()->SetCheckpoint(checkpointName, wallTime,
+                                  selfMeasuredDuration);
+    }
+}
+
+void Api::SetPSUDuration(std::string_view durationName, int64_t duration)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    // For "Power Supply Unit" duration should broadcast to all nodes.
+    for (const auto& [_, node] : mResourceMap)
+    {
+        node.get()->SetDuration(durationName, duration);
+    }
+}
+
+void Api::NotifyNodeComplete(const btm::NodeConfig& nodeConfig)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    auto it = mResourceMap.find(nodeConfig);
+
+    if (it == mResourceMap.end())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   nodeConfig.nodeName);
+        return;
+    }
+
+    it->second.get()->MarkComplete();
+}
+
+void Api::NotifyNodesCompleteByTag(std::string_view tag)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    for (const auto& [config, node] : mResourceMap)
+    {
+        if (config.tag == tag)
+        {
+            node.get()->MarkComplete();
+        }
+    }
+}
+
+std::vector<std::tuple<std::string, int64_t, int64_t>>
+    Api::GetNodeCheckpointList(const btm::NodeConfig& nodeConfig)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    auto it = mResourceMap.find(nodeConfig);
+
+    if (it == mResourceMap.end())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   nodeConfig.nodeName);
+        return {};
+    }
+
+    std::vector<std::tuple<std::string, int64_t, int64_t>> result;
+
+    for (const auto& cp : it->second.get()->GetCheckpointCache())
+    {
+        result.emplace_back(std::make_tuple(cp.name, cp.wallTime, cp.monoTime));
+    }
+
+    return result;
+}
+std::vector<std::tuple<std::string, int64_t>>
+    Api::GetNodeAdditionalDurations(const btm::NodeConfig& nodeConfig)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    auto it = mResourceMap.find(nodeConfig);
+
+    if (it == mResourceMap.end())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   nodeConfig.nodeName);
+        return {};
+    }
+
+    std::vector<std::tuple<std::string, int64_t>> result;
+
+    for (const auto& dur : it->second.get()->GetDurationCache())
+    {
+        result.emplace_back(std::make_tuple(dur.name, dur.duration));
+    }
+
+    return result;
+}
+
+bool Api::IsNodeRebooting(const btm::NodeConfig& nodeConfig)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    auto it = mResourceMap.find(nodeConfig);
+
+    if (it == mResourceMap.end())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   nodeConfig.nodeName);
+        return false;
+    }
+
+    return it->second.get()->IsRebooting();
+}
+
+void Api::RegisterNode(const btm::NodeConfig& nodeConfig)
+{
+    std::lock_guard<std::mutex> guard(mApiMutex);
+    auto it = mResourceMap.try_emplace(
+        nodeConfig, std::make_unique<resource::File>(nodeConfig.nodeName));
+
+    if (!it.second)
+    {
+        fmt::print(
+            "[{}] Failed to register `{}` due to the key duplicated. Skip it.\n",
+            __FUNCTION__, nodeConfig.nodeName);
+    }
+}
+} // namespace api
+} // namespace boot_time_monitor
diff --git a/src/bmc_monitor_app.cpp b/src/bmc_monitor_app.cpp
deleted file mode 100644
index 85371e3..0000000
--- a/src/bmc_monitor_app.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-#include "bmc_monitor_app.hpp"
-
-#include "boot_manager.hpp"
-#include "dbus_handler.hpp"
-#include "utils.hpp"
-
-#include <fmt/printf.h>
-
-#include <boost/asio/steady_timer.hpp>
-#include <sdbusplus/asio/connection.hpp>
-#include <sdbusplus/bus.hpp>
-
-#include <array>
-#include <memory>
-#include <regex>
-#include <string_view>
-
-namespace boot_time_monitor
-{
-
-constexpr std::string_view kSystemdService = "org.freedesktop.systemd1";
-constexpr std::string_view kSystemdPath = "/org/freedesktop/systemd1";
-constexpr std::string_view kSystemdIface = "org.freedesktop.DBus.Properties";
-constexpr std::string_view kSystemdFunc = "Get";
-constexpr std::string_view kSystemdParam1 = "org.freedesktop.systemd1.Manager";
-
-constexpr std::string_view kSystemdFirmwareTime = "FirmwareTimestampMonotonic";
-constexpr std::string_view kSystemdLoaderTime = "LoaderTimestampMonotonic";
-constexpr std::string_view kSystemdKernelTime = "KernelTimestampMonotonic";
-constexpr std::string_view kSystemdInitRDTime = "InitRDTimestampMonotonic";
-constexpr std::string_view kSystemdUserspaceTime =
-    "UserspaceTimestampMonotonic";
-constexpr std::string_view kSystemdFinishTime = "FinishTimestampMonotonic";
-
-struct SystemdTimestamp
-{
-    uint64_t firmware;
-    uint64_t loader;
-    uint64_t kernel;
-    uint64_t initRD;
-    uint64_t userspace;
-    uint64_t finish;
-};
-
-namespace
-{
-
-inline std::string convertName(std::string_view name)
-{
-    return std::regex_replace(name.data(), std::regex("TimestampMonotonic"),
-                              "");
-}
-
-} // namespace
-
-BMCMonitorApp::BMCMonitorApp(
-    sdbusplus::bus::bus& bus,
-    const std::shared_ptr<sdbusplus::asio::connection>& conn) :
-    objManager(bus, kObjPath.data()), util(std::make_shared<Util>()),
-    cpCSV(std::make_shared<FileUtil>(util->getCPPath(kNodeName, false))),
-    durCSV(std::make_shared<FileUtil>(util->getDurPath(kNodeName, false))),
-    bootManager(std::make_shared<BootManager>(util, cpCSV, durCSV)),
-    dbusHandler(
-        std::make_shared<DbusHandler>(bus, kObjPath.data(), bootManager, util))
-{
-    constexpr auto kCheckBmcFinishInterval = std::chrono::seconds(10);
-    static boost::asio::steady_timer BmcFinishTimer(conn->get_io_context());
-    static std::function<void(const boost::system::error_code&)>
-        checkBmcFinish = [this, conn, kCheckBmcFinishInterval](
-                             const boost::system::error_code& ec) {
-        if (ec == boost::asio::error::operation_aborted)
-        {
-            fmt::print(stderr, "[checkBmcFinish] BmcFinishTimer is being "
-                               "canceled\n");
-            // we're being canceled
-            return;
-        }
-        if (ec)
-        {
-            fmt::print(stderr, "[checkBmcFinish] Timer Error: {}\n",
-                       ec.message().c_str());
-            return;
-        }
-
-        constexpr uint64_t kMillisecondPerSecond = 1000;
-        // The raw pointer here is safe since it points to an existing instance
-        // instead of allocating a memory space.
-        // We can't use `std::share_ptr<uint64_t>(&times.firmware)` because the
-        // `times` will free its space while `share_ptr` will also free the same
-        // space again when this constructor completed. It will cause
-        // `free(): double free detected` or `free(): invalid pointer` error.
-        SystemdTimestamp times;
-        std::array<std::pair<std::string_view, uint64_t*>, 6> bootTimestamp = {
-            std::make_pair(kSystemdFirmwareTime.data(), &times.firmware),
-            std::make_pair(kSystemdLoaderTime.data(), &times.loader),
-            std::make_pair(kSystemdKernelTime.data(), &times.kernel),
-            std::make_pair(kSystemdInitRDTime.data(), &times.initRD),
-            std::make_pair(kSystemdUserspaceTime.data(), &times.userspace),
-            std::make_pair(kSystemdFinishTime.data(), &times.finish)};
-
-        for (const auto& timestamp : bootTimestamp)
-        {
-            auto method = conn->new_method_call(
-                kSystemdService.data(), kSystemdPath.data(),
-                kSystemdIface.data(), kSystemdFunc.data());
-            method.append(kSystemdParam1.data(), timestamp.first.data());
-            std::variant<uint64_t> result;
-            try
-            {
-                conn->call(method).read(result);
-            }
-            catch (const sdbusplus::exception::SdBusError& e)
-            {
-                fmt::print(
-                    stderr,
-                    "[checkBmcFinish] Can't get systemd property {}. ERROR={}\n",
-                    timestamp.first.data(), e.what());
-                // Restart the timer to keep polling the APML_ALERT status
-                BmcFinishTimer.expires_after(kCheckBmcFinishInterval);
-                BmcFinishTimer.async_wait(checkBmcFinish);
-                return;
-            }
-            *timestamp.second = std::get<uint64_t>(result);
-        }
-
-        // This daemon may start before the userspace is fully ready. So we need
-        // to confirm if userspace is fully ready by checking `times.finish`
-        // equals zero or not.
-        if (times.finish == 0)
-        {
-            fmt::print(
-                stderr,
-                "[checkBmcFinish] `FinishTimestampMonotonic` is not ready yet\n");
-            BmcFinishTimer.expires_after(kCheckBmcFinishInterval);
-            BmcFinishTimer.async_wait(checkBmcFinish);
-            return;
-        }
-
-        // How to calculate duration for each stage:
-        // https://github.com/systemd/systemd/blob/82b7bf8c1c8c6ded6f56b43998c803843a3b944b/src/analyze/analyze-time-data.c#L176-L186
-        // Special calculation for kernel duration:
-        // https://github.com/systemd/systemd/blob/82b7bf8c1c8c6ded6f56b43998c803843a3b944b/src/analyze/analyze-time-data.c#L84-L87
-        bootManager->setDuration(
-            convertName(kSystemdFirmwareTime),
-            times.firmware != 0
-                ? static_cast<int64_t>((times.firmware - times.loader) /
-                                       kMillisecondPerSecond)
-                : 0);
-        bootManager->setDuration(
-            convertName(kSystemdLoaderTime),
-            times.loader != 0
-                ? static_cast<int64_t>(times.loader / kMillisecondPerSecond)
-                : 0);
-        bootManager->setDuration(
-            convertName(kSystemdKernelTime),
-            static_cast<int64_t>(
-                (times.initRD > 0 ? times.initRD : times.userspace) /
-                kMillisecondPerSecond));
-        bootManager->setDuration(
-            convertName(kSystemdInitRDTime),
-            times.initRD != 0
-                ? static_cast<int64_t>((times.userspace - times.initRD) /
-                                       kMillisecondPerSecond)
-                : 0);
-        bootManager->setDuration(
-            convertName(kSystemdUserspaceTime),
-            times.userspace != 0
-                ? static_cast<int64_t>((times.finish - times.userspace) /
-                                       kMillisecondPerSecond)
-                : 0);
-
-#ifdef NPCM7XX_OR_NEWER
-        // NPCM7XX or newer Nuvoton BMC has a register that starts counting from
-        // SoC power on. Also uptime starts counting when kernel is up. Thus we
-        // can get (Firmware + Loader) time by `value[SEC_CNT_ADDR] - uptime`.
-        constexpr uint32_t SEC_CNT_ADDR = 0xF0801068;
-
-        auto powerOnSec = util->readMem4Bytes(SEC_CNT_ADDR);
-        auto upTimeMS = util->getUpTimeInMs();
-        if (powerOnSec != std::nullopt && upTimeMS != std::nullopt)
-        {
-            bootManager->setDuration("FirmwarePlusLoader",
-                                     powerOnSec.value() * 1000 -
-                                         upTimeMS.value());
-        }
-#endif
-
-// BMC marks the boot process as `completed` automatically if we do *NOT* have
-// external daemon to do so.
-#ifdef AUTO_BMC_COMPLETE
-        bootManager->setCheckpoint("UserspaceEnd", 0, 0);
-        bootManager->notifyComplete();
-#endif
-    };
-    BmcFinishTimer.expires_after(std::chrono::seconds(0));
-    BmcFinishTimer.async_wait(checkBmcFinish);
-}
-
-} // namespace boot_time_monitor
diff --git a/src/boot_manager.cpp b/src/boot_manager.cpp
deleted file mode 100644
index 4ead565..0000000
--- a/src/boot_manager.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-#include "boot_manager.hpp"
-
-#include "utils.hpp"
-
-#include <fmt/printf.h>
-
-#include <string_view>
-#include <vector>
-
-namespace boot_time_monitor
-{
-
-namespace
-{
-constexpr uint32_t kMaxCheckpointCnt = 100;
-constexpr uint32_t kMaxDurationCnt = 100;
-} // namespace
-
-/**
- * BootManager implementation class
- */
-BootManager::BootManager(std::shared_ptr<UtilIface> util,
-                         const std::shared_ptr<FileUtilIface>& cpCSV,
-                         const std::shared_ptr<FileUtilIface>& durCSV) :
-    util(std::move(util)), cpCSV(cpCSV), durCSV(durCSV)
-{
-    checkpoints = std::move(*cpCSV->loadCheckpoints(false));
-    durations = std::move(*durCSV->loadDurations(false));
-    preCheckpoints = std::move(*cpCSV->loadCheckpoints(true));
-    preDurations = std::move(*durCSV->loadDurations(true));
-}
-
-void BootManager::setCheckpoint(std::string_view cpName,
-                                int64_t externalWallTime, int64_t duration)
-{
-    auto wallTime = externalWallTime == 0 ? util->getWallTimeInMs()
-                                          : externalWallTime;
-    auto upTime = util->getUpTimeInMs();
-    if (upTime == std::nullopt)
-    {
-        fmt::print("[{}] Can't get up time. Skip this checkpoint.\n",
-                   __FUNCTION__);
-        return;
-    }
-    if (!util->isValidName(cpName))
-    {
-        fmt::print(
-            "[{}] Name is invalid. Only allows [0-9a-zA-Z_:] but get: {}\n",
-            __FUNCTION__, cpName);
-        return;
-    }
-
-    if (checkpoints.size() >= kMaxCheckpointCnt)
-    {
-        fmt::print("[{}] Drop incoming checkpoint due to hit the limit. "
-                   "name={}, externalWallTime={}, duration={}\n",
-                   __FUNCTION__, cpName, externalWallTime, duration);
-        return;
-    }
-    // `boot_manager` helps to calculate the transition time from one stage to
-    // next stage if the user can provide a self measured duration for the
-    // current stage.
-    // For example, if the interval between checkpoint A and B is 10 seconds and
-    // the user knows B stage only uses 8 seconds to boot up. Then that means 2
-    // seconds transition time was cost from A stage to B stage.
-    // `duration == 0` means "I don't have self measured duration, so no need to
-    // calculate transition time for me".
-    if (duration != 0)
-    {
-        checkpoints.emplace_back(cpName.data() + std::string{kBeginStageSuffix},
-                                 wallTime - duration,
-                                 upTime.value() - duration);
-        cpCSV->addCheckpoint(checkpoints.back().name,
-                             checkpoints.back().wallTime,
-                             checkpoints.back().monoTime);
-
-        if (checkpoints.size() >= kMaxCheckpointCnt)
-        {
-            fmt::print(
-                "[{}] Incoming checkpoint has `duration` so `To_{}` has been "
-                "created and added. Since the size is full so the incoming "
-                "checkpoint has been dropped\n",
-                __FUNCTION__, cpName);
-            return;
-        }
-    }
-
-    checkpoints.emplace_back(std::string{cpName}, wallTime, upTime.value());
-    cpCSV->addCheckpoint(checkpoints.back().name, checkpoints.back().wallTime,
-                         checkpoints.back().monoTime);
-}
-
-void BootManager::setDuration(std::string_view durName, int64_t duration)
-{
-    if (!util->isValidName(durName))
-    {
-        fmt::print(
-            "[{}] Name is invalid. Only allows [0-9a-zA-Z_:] but get: {}\n",
-            __FUNCTION__, durName);
-        return;
-    }
-
-    if (durations.size() >= kMaxDurationCnt)
-    {
-        fmt::print("[{}] Drop incoming duration due to hit the limit. "
-                   "name={}, duration={}\n",
-                   __FUNCTION__, durName, duration);
-        return;
-    }
-
-    durations.emplace_back(std::string(durName), duration);
-    durCSV->addDuration(durations.back().name, durations.back().duration);
-}
-
-void BootManager::notifyComplete()
-{
-    std::swap(preCheckpoints, checkpoints);
-    checkpoints.clear();
-    std::swap(preDurations, durations);
-    durations.clear();
-
-    cpCSV->completeCurrent();
-    durCSV->completeCurrent();
-}
-
-bool BootManager::isRebooting()
-{
-    return !(cpCSV->isEmpty() && durCSV->isEmpty());
-}
-
-const std::vector<Checkpoint>& BootManager::getCheckpoints() const
-{
-    return checkpoints;
-}
-
-const std::vector<Duration>& BootManager::getDurations() const
-{
-    return durations;
-}
-
-const std::vector<Checkpoint>& BootManager::getPreCheckpoints() const
-{
-    return preCheckpoints;
-}
-
-const std::vector<Duration>& BootManager::getPreDurations() const
-{
-    return preDurations;
-}
-
-} // namespace boot_time_monitor
diff --git a/src/dbus_handler.cpp b/src/dbus_handler.cpp
index fda63e3..9049b0e 100644
--- a/src/dbus_handler.cpp
+++ b/src/dbus_handler.cpp
@@ -1,58 +1,44 @@
 #include "dbus_handler.hpp"
 
-#include "boot_manager.hpp"
-
-#include <fmt/printf.h>
-
-#include <boost/container/flat_map.hpp>
-#include <sdbusplus/asio/object_server.hpp>
-#include <sdbusplus/bus/match.hpp>
-#include <xyz/openbmc_project/Common/error.hpp>
-#include <xyz/openbmc_project/State/Host/server.hpp>
-#include <xyz/openbmc_project/Time/Boot/Checkpoint/server.hpp>
-#include <xyz/openbmc_project/Time/Boot/Duration/server.hpp>
-#include <xyz/openbmc_project/Time/Boot/Statistic/server.hpp>
-
-#include <tuple>
-
 namespace boot_time_monitor
 {
+namespace dbus
+{
 
-DbusHandler::DbusHandler(sdbusplus::bus::bus& dbus, const std::string& objPath,
-                         std::shared_ptr<BootManagerIface> bootManager,
-                         std::shared_ptr<UtilIface> util) :
-    sdbusplus::server::object::object<Duration>(dbus, objPath.c_str()),
-    sdbusplus::server::object::object<Checkpoint>(dbus, objPath.c_str()),
-    sdbusplus::server::object::object<Statistic>(dbus, objPath.c_str()),
-    bootManager(std::move(bootManager)), util(std::move(util))
+namespace btm = boot_time_monitor;
+
+constexpr std::string_view kPSUCheckpointName = "TrayPowerCycle";
+constexpr std::string_view kPSUDurationName = "TrayPowerCycle";
+
+Handler::Handler(sdbusplus::bus::bus& dbus, const btm::NodeConfig& nodeConfig,
+                 const btm::dbus::DbusConfig& dbusConfig,
+                 std::shared_ptr<btm::api::IApi> api) :
+    sdbusplus::server::object::object<Duration>(dbus,
+                                                dbusConfig.dbusObjPath.c_str()),
+    sdbusplus::server::object::object<Checkpoint>(
+        dbus, dbusConfig.dbusObjPath.c_str()),
+    sdbusplus::server::object::object<Statistic>(
+        dbus, dbusConfig.dbusObjPath.c_str()),
+    mNodeConfig(nodeConfig), mApi(api)
 {}
 
-void DbusHandler::setCheckpoint(std::string checkpointName, int64_t wallTime,
-                                int64_t selfMeasuredDuration)
+void Handler::setCheckpoint(std::string checkpointName, int64_t wallTime,
+                            int64_t selfMeasuredDuration)
 {
-    if (!util->isValidName(checkpointName))
-    {
-        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
-    }
-
-    bootManager->setCheckpoint(checkpointName, wallTime, selfMeasuredDuration);
+    if (checkpointName == kPSUCheckpointName)
+        mApi->SetPSUCheckpoint(checkpointName, wallTime, selfMeasuredDuration);
+    else
+        mApi->SetNodeCheckpoint(mNodeConfig, checkpointName, wallTime,
+                                selfMeasuredDuration);
 }
 
 std::vector<std::tuple<std::string, int64_t, int64_t>>
-    DbusHandler::getCheckpointList()
+    Handler::getCheckpointList()
 {
-    std::vector<std::tuple<std::string, int64_t, int64_t>> result;
-
-    for (const auto& cp :
-         (bootManager->isRebooting() ? bootManager->getCheckpoints()
-                                     : bootManager->getPreCheckpoints()))
-    {
-        result.emplace_back(std::make_tuple(cp.name, cp.wallTime, cp.monoTime));
-    }
-    return result;
+    return mApi->GetNodeCheckpointList(mNodeConfig);
 }
 
-void DbusHandler::rebootComplete()
+void Handler::rebootComplete()
 {
     if (!isRebooting())
     {
@@ -62,36 +48,25 @@
         return;
     }
 
-    bootManager->notifyComplete();
+    mApi->NotifyNodeComplete(mNodeConfig);
 }
 
-void DbusHandler::setDuration(std::string durationName, int64_t duration)
+void Handler::setDuration(std::string durationName, int64_t duration)
 {
-    if (!util->isValidName(durationName))
-    {
-        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
-    }
-
-    bootManager->setDuration(durationName, duration);
+    if (durationName == kPSUDurationName)
+        mApi->SetPSUDuration(durationName, duration);
+    else
+        mApi->SetNodeDuration(mNodeConfig, durationName, duration);
 }
 
-std::vector<std::tuple<std::string, int64_t>>
-    DbusHandler::getAdditionalDurations()
+std::vector<std::tuple<std::string, int64_t>> Handler::getAdditionalDurations()
 {
-    std::vector<std::tuple<std::string, int64_t>> result;
-
-    for (const auto& dur :
-         (bootManager->isRebooting() ? bootManager->getDurations()
-                                     : bootManager->getPreDurations()))
-    {
-        result.emplace_back(std::make_tuple(dur.name, dur.duration));
-    }
-    return result;
+    return mApi->GetNodeAdditionalDurations(mNodeConfig);
 }
 
-bool DbusHandler::isRebooting() const
+bool Handler::isRebooting() const
 {
-    return bootManager->isRebooting();
+    return mApi->IsNodeRebooting(mNodeConfig);
 }
-
+} // namespace dbus
 } // namespace boot_time_monitor
diff --git a/src/host_monitor_app.cpp b/src/host_monitor_app.cpp
deleted file mode 100644
index 3c0dced..0000000
--- a/src/host_monitor_app.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-#include "host_monitor_app.hpp"
-
-#include "boot_manager.hpp"
-#include "dbus_handler.hpp"
-#include "utils.hpp"
-
-#include <fmt/printf.h>
-
-#include <boost/container/flat_map.hpp>
-#include <sdbusplus/bus.hpp>
-#include <sdbusplus/bus/match.hpp>
-#include <sdbusplus/message.hpp>
-
-#include <memory>
-#include <ranges>
-#include <string>
-#include <string_view>
-#include <variant>
-
-namespace boot_time_monitor
-{
-
-using BasicVariantType =
-    std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
-                 double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
-
-constexpr std::string_view kHostService = "xyz.openbmc_project.State.Host";
-constexpr std::string_view kHostPath = "/xyz/openbmc_project/state/host0";
-constexpr std::string_view kHostIface = "xyz.openbmc_project.State.Host";
-constexpr std::string_view kHostProperty = "CurrentHostState";
-
-[[maybe_unused]] constexpr std::string_view kHostStateRunning =
-    "xyz.openbmc_project.State.Host.HostState.Running";
-[[maybe_unused]] constexpr std::string_view kHostStateOff =
-    "xyz.openbmc_project.State.Host.HostState.Off";
-
-constexpr std::string_view kOSStatusService =
-    "xyz.openbmc_project.State.OperatingSystem";
-constexpr std::string_view kOSStatusPath = "/xyz/openbmc_project/state/os";
-constexpr std::string_view kOSStatusIface =
-    "xyz.openbmc_project.State.OperatingSystem.Status";
-constexpr std::string_view kOSStatusProperty = "OperatingSystemState";
-
-std::string inline translateHostStateName(std::string_view state)
-{
-    std::size_t found = state.find_last_of('.');
-    return std::string("HostState:") + std::string(state.substr(found + 1));
-}
-
-std::string inline translateOSStatus(std::string_view status)
-{
-    std::size_t found = status.find_last_of('.');
-    return std::string("OSStatus:") + std::string(status.substr(found + 1));
-}
-
-HostMonitorApp::HostMonitorApp(sdbusplus::bus::bus& bus, uint32_t hostNum) :
-    kNodeName(std::string{"host"} + std::to_string(hostNum)),
-    kObjPath(std::string{"/xyz/openbmc_project/time/boot/"} + kNodeName),
-    objManager(bus, kObjPath.c_str())
-{
-    util = std::make_shared<Util>();
-    cpCSV = std::make_shared<FileUtil>(util->getCPPath(kNodeName, false));
-    durCSV = std::make_shared<FileUtil>(util->getDurPath(kNodeName, false));
-    bootManager = std::make_shared<BootManager>(util, cpCSV, durCSV);
-    dbusHandler = std::make_shared<DbusHandler>(bus, kObjPath.data(),
-                                                bootManager, util);
-    // Initialize preHostState
-    static std::string preHostState;
-    auto method = bus.new_method_call(kHostService.data(), kHostPath.data(),
-                                      "org.freedesktop.DBus.Properties", "Get");
-    method.append(kHostIface.data(), kHostProperty.data());
-    BasicVariantType result;
-    try
-    {
-        bus.call(method).read(result);
-        preHostState = std::get<std::string>(result);
-    }
-    catch (const sdbusplus::exception::SdBusError& e)
-    {
-        fmt::print(stderr, "[{}] Failed to get `CurrentHostState`. ERROR={}\n",
-                   __FUNCTION__, e.what());
-        fmt::print(
-            stderr,
-            "[{}] Initialized `preHostState` to empty string as default value\n",
-            __FUNCTION__);
-    }
-
-    hostStateWatcher = std::make_unique<sdbusplus::bus::match::match>(
-        bus,
-        sdbusplus::bus::match::rules::propertiesChanged(kHostPath.data(),
-                                                        kHostIface.data()),
-        [this](sdbusplus::message::message& message) {
-        std::string objectName;
-        boost::container::flat_map<
-            std::string,
-            std::variant<std::string, bool, int64_t, uint64_t, double>>
-            values;
-        message.read(objectName, values);
-
-        auto findState = values.find(kHostProperty.data());
-        if (findState != values.end())
-        {
-            const std::string curHostState =
-                std::get<std::string>(findState->second);
-            fmt::print(
-                stderr,
-                "[hostStateWatcher] CurrentHostState has changed from {} to {}\n",
-                preHostState, curHostState);
-            bootManager->setCheckpoint(translateHostStateName(curHostState), 0,
-                                       0);
-            preHostState = curHostState;
-        }
-    });
-
-    // Initialize preOSStatus
-    static std::string preOSStatus;
-    method = bus.new_method_call(kOSStatusService.data(), kOSStatusPath.data(),
-                                 "org.freedesktop.DBus.Properties", "Get");
-    method.append(kOSStatusIface.data(), kOSStatusProperty.data());
-    try
-    {
-        bus.call(method).read(result);
-        preOSStatus = std::get<std::string>(result);
-    }
-    catch (const sdbusplus::exception::SdBusError& e)
-    {
-        fmt::print(stderr,
-                   "[{}] Failed to get `OperatingSystemState`. ERROR={}\n",
-                   __FUNCTION__, e.what());
-        fmt::print(
-            stderr,
-            "[{}] Initialized `preOSStatus` to empty string as default value\n",
-            __FUNCTION__);
-    }
-
-    osStatusWatcher = std::make_unique<sdbusplus::bus::match::match>(
-        bus,
-        sdbusplus::bus::match::rules::propertiesChanged(kOSStatusPath.data(),
-                                                        kOSStatusIface.data()),
-        [this](sdbusplus::message::message& message) {
-        std::string objectName;
-        boost::container::flat_map<
-            std::string,
-            std::variant<std::string, bool, int64_t, uint64_t, double>>
-            values;
-        message.read(objectName, values);
-
-        auto findState = values.find(kOSStatusProperty.data());
-        if (findState != values.end())
-        {
-            const std::string curOSStatus =
-                std::get<std::string>(findState->second);
-
-            fmt::print(
-                stderr,
-                "[osStatusWatcher] curOSStatus has changed from {} to {}\n",
-                preOSStatus, curOSStatus);
-            bootManager->setCheckpoint(translateOSStatus(curOSStatus), 0, 0);
-            preOSStatus = curOSStatus;
-        }
-    });
-}
-
-} // namespace boot_time_monitor
diff --git a/src/main.cpp b/src/main.cpp
index 7a9fc47..4a551a5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,11 @@
-#include "bmc_monitor_app.hpp"
-#include "host_monitor_app.hpp"
+#include "api.hpp"
+#include "dbus_handler.hpp"
+#include "gen.hpp"
+#include "psm_handler.hpp"
+#include "systemd_handler.hpp"
+
+#include <fmt/printf.h>
+#include <getopt.h> // For getopt_long
 
 #include <boost/asio/io_service.hpp>
 #include <sdbusplus/asio/connection.hpp>
@@ -7,19 +13,130 @@
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server.hpp>
 
-using boot_time_monitor::BMCMonitorApp;
-using boot_time_monitor::HostMonitorApp;
+namespace btm = boot_time_monitor;
 
-int main()
+/*
+ *  +-----------------+
+ *  |      main       |
+ *  +-----------------+
+ *        |
+ *        |
+ *        v
+ *  +-----------------+
+ *  |   data_sources  |
+ *  +-----------------+
+ *        |   |   |
+ *        |   |   psm_handler
+ *        |   dbus_handler
+ *        systemd_handler
+ *        |
+ *        | interface between data source and actual checkpoint/duration records
+ *        v
+ *  +-----------------+
+ *  |      api        |
+ *  +-----------------+
+ *        |
+ *        | store checkpoints/durations
+ *        v
+ *  +-----------------+
+ *  |     files       |
+ *  +-----------------+
+ *
+ */
+
+void print_usage(const char* prog_name)
 {
+    fmt::print(stderr, "Usage: {} [options]\n", prog_name);
+    fmt::print(stderr, "Options:\n");
+    fmt::print(stderr,
+               "  -h, --help             Show this help message and exit\n");
+    fmt::print(stderr,
+               "  --host_count <num>    Set number of hosts (default: 1)\n");
+    fmt::print(stderr,
+               "  --bmc_count <num>     Set number of BMCs (default: 1)\n");
+}
+
+int main(int argc, char* argv[])
+{
+    int hostCount = 1; // Default host count
+    int bmcCount = 1;  // Default BMC count
+
+    const struct option long_options[] = {
+        {"help", no_argument, nullptr, 'h'},
+        {"host_count", required_argument, nullptr, 'o'}, // 'o' for hOst
+        {"bmc_count", required_argument, nullptr, 'b'},  // 'b' for Bmc
+        {nullptr, 0, nullptr, 0}};
+
+    int opt;
+    int option_index = 0;
+    while ((opt = getopt_long(argc, argv, "h", long_options, &option_index)) !=
+           -1)
+    {
+        switch (opt)
+        {
+            case 'h':
+                print_usage(argv[0]);
+                return 0;
+            case 'o':
+                hostCount = std::atoi(optarg);
+                break;
+            case 'b':
+                bmcCount = std::atoi(optarg);
+                break;
+            default: /* '?' */
+                print_usage(argv[0]);
+                return 1;
+        }
+    }
+
     boost::asio::io_service io;
     auto conn = std::make_shared<sdbusplus::asio::connection>(io);
     conn->request_name("com.google.gbmc.boot_time_monitor");
     auto server = std::make_shared<sdbusplus::asio::object_server>(conn);
     auto& bus = static_cast<sdbusplus::bus::bus&>(*conn);
 
-    HostMonitorApp host(bus, 0);
-    BMCMonitorApp bmc(bus, conn);
+    std::vector<btm::NodeConfig> hostNodeConfigs;
+    std::vector<btm::NodeConfig> bmcNodeConfigs;
+
+    std::vector<std::unique_ptr<btm::dbus::Handler>> hostDbusHandlers;
+    std::vector<std::unique_ptr<btm::dbus::Handler>> bmcDbusHandlers;
+
+    std::vector<btm::psm::Handler> psmHandlers;
+
+    std::shared_ptr<btm::api::Api> api = std::make_shared<btm::api::Api>();
+
+    for (int i = 0; i < hostCount; i++)
+    {
+        hostNodeConfigs.emplace_back(btm::gen::GenHostNodeName(i),
+                                     std::string(btm::kBootTimeTagHost));
+    }
+
+    for (int i = 0; i < bmcCount; i++)
+    {
+        bmcNodeConfigs.emplace_back(btm::gen::GenBmcNodeName(bmcCount, i),
+                                    std::string(btm::kBootTimeTagBMC));
+    }
+
+    for (auto& nodeConfig : hostNodeConfigs)
+    {
+        api->RegisterNode(nodeConfig);
+
+        hostDbusHandlers.emplace_back(std::make_unique<btm::dbus::Handler>(
+            bus, nodeConfig, btm::gen::GenDbusConfig(nodeConfig), api));
+        psmHandlers.emplace_back(
+            bus, nodeConfig,
+            btm::gen::GenPSMConfig(hostNodeConfigs, nodeConfig), api);
+    }
+
+    for (auto& nodeConfig : bmcNodeConfigs)
+    {
+        api->RegisterNode(nodeConfig);
+
+        bmcDbusHandlers.emplace_back(std::make_unique<btm::dbus::Handler>(
+            bus, nodeConfig, btm::gen::GenDbusConfig(nodeConfig), api));
+    }
+
+    btm::systemd::Handler systemdHandler(conn, api);
 
     io.run();
 
diff --git a/src/meson.build b/src/meson.build
index 09fb8c7..17538c7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -25,11 +25,12 @@
 
 boot_time_monitor_lib = static_library(
   'boot_time_monitor',
-  'host_monitor_app.cpp',
-  'utils.cpp',
-  'boot_manager.cpp',
+  'api.cpp',
   'dbus_handler.cpp',
-  'bmc_monitor_app.cpp',
+  'psm_handler.cpp',
+  'resource.cpp',
+  'systemd_handler.cpp',
+  generated_sources,
   include_directories: boot_time_monitor_incs,
   implicit_include_directories: false,
   dependencies: boot_time_monitor_pre,
diff --git a/src/psm_handler.cpp b/src/psm_handler.cpp
new file mode 100644
index 0000000..285ddd6
--- /dev/null
+++ b/src/psm_handler.cpp
@@ -0,0 +1,144 @@
+#include "psm_handler.hpp"
+
+#include <fmt/printf.h>
+
+#include <variant>
+
+namespace boot_time_monitor
+{
+namespace psm
+{
+
+namespace btm = boot_time_monitor;
+
+using BasicVariantType =
+    std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
+                 double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
+
+std::string inline translateHostStateName(std::string_view state)
+{
+    std::size_t found = state.find_last_of('.');
+    return std::string("HostState:") + std::string(state.substr(found + 1));
+}
+
+std::string inline translateOSStatus(std::string_view status)
+{
+    std::size_t found = status.find_last_of('.');
+    return std::string("OSStatus:") + std::string(status.substr(found + 1));
+}
+
+Handler::Handler(sdbusplus::bus::bus& bus, const btm::NodeConfig& nodeConfig,
+                 const btm::psm::PSMConfig& psmConfig,
+                 std::shared_ptr<btm::api::IApi> api) :
+    mNodeConfig(nodeConfig), mApi(api)
+{
+    // Initialize mPreHostState
+    auto method =
+        bus.new_method_call(psmConfig.hostStateDbusService.c_str(), // Service
+                            psmConfig.hostStateDbusObjPath.c_str(), // Path
+                            "org.freedesktop.DBus.Properties",      // Iface
+                            "Get");                                 // Function
+    method.append("xyz.openbmc_project.State.Host", "CurrentHostState");
+    BasicVariantType result;
+    try
+    {
+        bus.call(method).read(result);
+        mPreHostState = std::get<std::string>(result);
+        fmt::print("[{}] Get `CurrentHostState` success. value={}\n",
+                   __FUNCTION__, mPreHostState);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        fmt::print(stderr, "[{}] Failed to get `CurrentHostState`. ERROR={}\n",
+                   __FUNCTION__, e.what());
+        fmt::print(
+            stderr,
+            "[{}] Initialized `mPreHostState` to empty string as default value\n",
+            __FUNCTION__);
+    }
+
+    hostStateWatcher = std::make_unique<sdbusplus::bus::match::match>(
+        bus,
+        sdbusplus::bus::match::rules::propertiesChanged(
+            psmConfig.hostStateDbusObjPath.c_str(),
+            "xyz.openbmc_project.State.Host"),
+        [this](sdbusplus::message::message& message) {
+        std::string objectName;
+        boost::container::flat_map<
+            std::string,
+            std::variant<std::string, bool, int64_t, uint64_t, double>>
+            values;
+        message.read(objectName, values);
+
+        auto findState = values.find("CurrentHostState");
+        if (findState != values.end())
+        {
+            const std::string curHostState =
+                std::get<std::string>(findState->second);
+            fmt::print(
+                stderr,
+                "[hostStateWatcher] CurrentHostState has changed from {} to {}\n",
+                mPreHostState, curHostState);
+            mApi->SetNodeCheckpoint(mNodeConfig,
+                                    translateHostStateName(curHostState), 0, 0);
+            mPreHostState = curHostState;
+        }
+    });
+
+    // Initialize mPreOSState
+    method =
+        bus.new_method_call(psmConfig.osStateDbusService.c_str(), // Service
+                            psmConfig.osStateDbusObjPath.c_str(), // Path
+                            "org.freedesktop.DBus.Properties",    // Iface
+                            "Get");                               // Function
+    method.append("xyz.openbmc_project.State.OperatingSystem.Status",
+                  "OperatingSystemState");
+    try
+    {
+        bus.call(method).read(result);
+        mPreOSState = std::get<std::string>(result);
+        fmt::print("[{}] Get `OperatingSystemState` success. value={}\n",
+                   __FUNCTION__, mPreOSState);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        fmt::print(stderr,
+                   "[{}] Failed to get `OperatingSystemState`. ERROR={}\n",
+                   __FUNCTION__, e.what());
+        fmt::print(
+            stderr,
+            "[{}] Initialized `mPreOSState` to empty string as default value\n",
+            __FUNCTION__);
+    }
+
+    osStatusWatcher = std::make_unique<sdbusplus::bus::match::match>(
+        bus,
+        sdbusplus::bus::match::rules::propertiesChanged(
+            psmConfig.osStateDbusObjPath.c_str(),
+            "xyz.openbmc_project.State.OperatingSystem.Status"),
+        [this](sdbusplus::message::message& message) {
+        std::string objectName;
+        boost::container::flat_map<
+            std::string,
+            std::variant<std::string, bool, int64_t, uint64_t, double>>
+            values;
+        message.read(objectName, values);
+
+        auto findState = values.find("OperatingSystemState");
+        if (findState != values.end())
+        {
+            const std::string curOSStatus =
+                std::get<std::string>(findState->second);
+
+            fmt::print(
+                stderr,
+                "[osStatusWatcher] curOSStatus has changed from {} to {}\n",
+                mPreOSState, curOSStatus);
+            mApi->SetNodeCheckpoint(mNodeConfig, translateOSStatus(curOSStatus),
+                                    0, 0);
+            mPreOSState = curOSStatus;
+        }
+    });
+}
+} // namespace psm
+} // namespace boot_time_monitor
diff --git a/src/resource.cpp b/src/resource.cpp
new file mode 100644
index 0000000..385053b
--- /dev/null
+++ b/src/resource.cpp
@@ -0,0 +1,309 @@
+
+#include "resource.hpp"
+
+#include <fmt/printf.h>
+
+#include <chrono>
+#include <filesystem>
+#include <regex>
+#include <string>
+
+namespace boot_time_monitor
+{
+
+namespace resource
+{
+
+namespace fs = std::filesystem;
+namespace btm = boot_time_monitor;
+
+// Definition of the global variable
+std::string BTMonitorDir = "/var/google/boot_time_monitor/";
+
+constexpr uint32_t kMaxCheckpointCnt = 100;
+constexpr uint32_t kMaxDurationCnt = 100;
+
+File::File(std::string_view nodeName) : mNodeName(nodeName)
+{
+    mCpOfs = StartFileInstance(GetCpFullFileName());
+    mDurOfs = StartFileInstance(GetDurFullFileName());
+
+    mCheckpointCache = LoadCheckpoints(false);
+    mDurationCache = LoadDurations(false);
+    mCheckpointCompletedCache = LoadCheckpoints(true);
+    mDurationCompletedCache = LoadDurations(true);
+}
+
+void File::SetCheckpoint(std::string_view name, int64_t epochTime,
+                         int64_t duration)
+{
+    auto wallTime = epochTime == 0 ? GetWallTimeInMs() : epochTime;
+    auto upTime = GetUpTimeInMs();
+    if (upTime == std::nullopt)
+    {
+        fmt::print("[{}] Can't get up time. Skip this checkpoint.\n",
+                   __FUNCTION__);
+        return;
+    }
+    if (!IsValidName(name))
+    {
+        fmt::print(
+            "[{}] Name is invalid. Only allows [0-9a-zA-Z_:] but get: {}\n",
+            __FUNCTION__, name);
+        return;
+    }
+
+    if (mCheckpointCache.size() >= kMaxCheckpointCnt)
+    {
+        fmt::print("[{}] Drop incoming checkpoint due to hit the limit. "
+                   "name={}, epochTime={}, duration={}\n",
+                   __FUNCTION__, name, epochTime, duration);
+        return;
+    }
+    // `boot_manager` helps to calculate the transition time from one stage to
+    // next stage if the user can provide a self measured duration for the
+    // current stage.
+    // For example, if the interval between checkpoint A and B is 10 seconds and
+    // the user knows B stage only uses 8 seconds to boot up. Then that means 2
+    // seconds transition time was cost from A stage to B stage.
+    // `duration == 0` means "I don't have self measured duration, so no need to
+    // calculate transition time for me".
+    if (duration != 0)
+    {
+        AppendCheckpointToFile(std::string(name) + kBeginStageSuffix,
+                               wallTime - duration, upTime.value() - duration);
+
+        if (mCheckpointCache.size() >= kMaxCheckpointCnt)
+        {
+            fmt::print(
+                "[{}] Incoming checkpoint has `duration` so `To_{}` has been "
+                "created and added. Since the size is full so the incoming "
+                "checkpoint has been dropped\n",
+                __FUNCTION__, name);
+            return;
+        }
+    }
+
+    AppendCheckpointToFile(std::string(name), wallTime, upTime.value());
+    // TODO(@huangweichieh): Check reboot complete.
+}
+
+void File::SetDuration(std::string_view name, int64_t duration)
+{
+    if (!IsValidName(name))
+    {
+        fmt::print(
+            "[{}] Name is invalid. Only allows [0-9a-zA-Z_:] but get: {}\n",
+            __FUNCTION__, name);
+        return;
+    }
+
+    if (mDurationCache.size() >= kMaxDurationCnt)
+    {
+        fmt::print("[{}] Drop incoming duration due to hit the limit. "
+                   "name={}, duration={}\n",
+                   __FUNCTION__, name, duration);
+        return;
+    }
+
+    AppendDurationToFile(std::string(name), duration);
+}
+
+std::vector<btm::resource::Checkpoint> File::GetCheckpointCache()
+{
+    return IsRebooting() ? mCheckpointCache : mCheckpointCompletedCache;
+}
+
+std::vector<btm::resource::Duration> File::GetDurationCache()
+{
+    return IsRebooting() ? mDurationCache : mDurationCompletedCache;
+}
+
+void File::MarkComplete()
+{
+    std::swap(mCheckpointCompletedCache, mCheckpointCache);
+    mCheckpointCache.clear();
+    std::swap(mDurationCompletedCache, mDurationCache);
+    mDurationCache.clear();
+
+    CompleteCurrentFile(mCpOfs, GetCpFullFileName());
+    CompleteCurrentFile(mDurOfs, GetDurFullFileName());
+}
+bool File::IsRebooting()
+{
+    return !(IsEmptyFile(mCpOfs) && IsEmptyFile(mDurOfs));
+}
+
+std::ofstream File::StartFileInstance(std::string_view filename)
+{
+    std::lock_guard<std::mutex> guard(mFileMutex);
+    fs::path dir = fs::path(filename).remove_filename();
+    if (!dir.empty() && !fs::exists(dir))
+    {
+        fs::create_directories(dir);
+    }
+    std::ofstream ofs = std::ofstream(std::string(filename),
+                                      std::fstream::out | std::fstream::app);
+    if (!ofs.good())
+    {
+        throw std::invalid_argument("ofstream is not available.");
+    }
+    return ofs;
+}
+
+bool File::IsEmptyFile(std::ofstream& ofs)
+{
+    std::lock_guard<std::mutex> guard(mFileMutex);
+    const auto result = ofs.tellp();
+    if (result < 0)
+    {
+        throw std::runtime_error("File can't be `tellp`");
+    }
+
+    return result == 0;
+}
+void File::CompleteCurrentFile(std::ofstream& ofs, std::string filename)
+{
+    std::lock_guard<std::mutex> guard(mFileMutex);
+    ofs.close();
+    fs::rename(filename, filename + kCompletedSuffix);
+
+    // Reopen for next reboot
+    ofs.open(filename.c_str(), std::fstream::out | std::fstream::app);
+    if (!ofs.good())
+    {
+        throw std::invalid_argument("ofstream is not available.");
+    }
+}
+
+void File::AppendCheckpointToFile(std::string name, int64_t wallTimeInMs,
+                                  int64_t monoTimeInMs)
+{
+    std::lock_guard<std::mutex> guard(mFileMutex);
+    mCheckpointCache.emplace_back(name, wallTimeInMs, monoTimeInMs);
+    mCpOfs << name << "," << wallTimeInMs << "," << monoTimeInMs << std::endl;
+    mCpOfs.flush();
+}
+
+void File::AppendDurationToFile(std::string name, int64_t durationInMs)
+{
+    std::lock_guard<std::mutex> guard(mFileMutex);
+    mDurationCache.emplace_back(name, durationInMs);
+    mDurOfs << name << "," << durationInMs << std::endl;
+    mDurOfs.flush();
+}
+
+std::vector<btm::resource::Checkpoint> File::LoadCheckpoints(bool loadCompleted)
+{
+    std::vector<btm::resource::Checkpoint> result =
+        std::vector<btm::resource::Checkpoint>();
+    std::string cpFilename = GetCpFullFileName() +
+                             (loadCompleted ? kCompletedSuffix : "");
+    std::ifstream ifs(cpFilename);
+    if (!ifs.good())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   cpFilename);
+        return result;
+    }
+
+    constexpr uint32_t kBufSize = 1024;
+    constexpr uint32_t kMatchCount = 4;
+
+    std::array<char, kBufSize> buf;
+    std::cmatch match;
+    const std::regex reg("^([0-9a-zA-Z_:]*),([0-9]+),([0-9]+)$");
+    while (ifs.getline(buf.data(), kBufSize))
+    {
+        if (regex_match(buf.data(), match, reg))
+        {
+            if (match.size() != kMatchCount)
+            {
+                // This line is problematic. Skip it.
+                continue;
+            }
+
+            result.emplace_back(match[1].str(), std::stoll(match[2].str()),
+                                std::stoll(match[3].str()));
+        }
+    }
+    return result;
+}
+
+std::vector<btm::resource::Duration> File::LoadDurations(bool loadComplete)
+{
+    std::vector<btm::resource::Duration> result =
+        std::vector<btm::resource::Duration>();
+    std::string durFilename = GetDurFullFileName() +
+                              (loadComplete ? kCompletedSuffix : "");
+    std::ifstream ifs(durFilename);
+    if (!ifs.good())
+    {
+        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
+                   durFilename);
+        return result;
+    }
+
+    constexpr uint32_t kBufSize = 1024;
+    constexpr uint32_t kMatchCount = 3;
+
+    std::array<char, kBufSize> buf;
+    std::cmatch match;
+    const std::regex reg("^([0-9a-zA-Z_:]*),([0-9]+)$");
+    while (ifs.getline(buf.data(), kBufSize))
+    {
+        if (regex_match(buf.data(), match, reg))
+        {
+            if (match.size() != kMatchCount)
+            {
+                // This line is problematic. Skip it.
+                continue;
+            }
+
+            result.emplace_back(match[1].str(), std::stoll(match[2].str()));
+        }
+    }
+    return result;
+}
+
+std::optional<int64_t> File::GetUpTimeInMs()
+{
+    constexpr int32_t kMillisecondPerSecond = 1000;
+    std::ifstream fin("/proc/uptime");
+    if (!fin.is_open())
+    {
+        fmt::print(stderr, "[{}]: Can not read \"/proc/uptime\"\n",
+                   __FUNCTION__);
+        return std::nullopt;
+    }
+    double uptimeSec;
+    fin >> uptimeSec;
+    fin.close();
+
+    return static_cast<int64_t>(uptimeSec * kMillisecondPerSecond);
+}
+
+int64_t File::GetWallTimeInMs()
+{
+    return std::chrono::system_clock::now().time_since_epoch() /
+           std::chrono::milliseconds(1);
+}
+
+bool File::IsValidName(std::string_view name)
+{
+    return !std::regex_search(std::string(name).c_str(),
+                              std::regex("[^0-9a-zA-Z_:]"));
+}
+
+inline std::string File::GetCpFullFileName()
+{
+    return std::string() + btm::resource::BTMonitorDir + mNodeName + "_" +
+           kCheckpointFile;
+}
+inline std::string File::GetDurFullFileName()
+{
+    return std::string() + btm::resource::BTMonitorDir + mNodeName + "_" +
+           kDurationFile;
+}
+} // namespace resource
+} // namespace boot_time_monitor
diff --git a/src/systemd_handler.cpp b/src/systemd_handler.cpp
new file mode 100644
index 0000000..859be37
--- /dev/null
+++ b/src/systemd_handler.cpp
@@ -0,0 +1,187 @@
+#include "systemd_handler.hpp"
+
+#include <fmt/printf.h>
+
+#include <regex>
+
+namespace boot_time_monitor
+{
+namespace systemd
+{
+
+namespace btm = boot_time_monitor;
+
+constexpr std::string_view kSystemdFirmwareTime = "FirmwareTimestampMonotonic";
+constexpr std::string_view kSystemdLoaderTime = "LoaderTimestampMonotonic";
+constexpr std::string_view kSystemdKernelTime = "KernelTimestampMonotonic";
+constexpr std::string_view kSystemdInitRDTime = "InitRDTimestampMonotonic";
+constexpr std::string_view kSystemdUserspaceTime =
+    "UserspaceTimestampMonotonic";
+constexpr std::string_view kSystemdFinishTime = "FinishTimestampMonotonic";
+
+struct SystemdTimestamp
+{
+    uint64_t firmware;
+    uint64_t loader;
+    uint64_t kernel;
+    uint64_t initRD;
+    uint64_t userspace;
+    uint64_t finish;
+};
+
+namespace
+{
+
+inline std::string convertName(std::string_view name)
+{
+    return std::regex_replace(std::string(name),
+                              std::regex("TimestampMonotonic"), "");
+}
+
+} // namespace
+
+Handler::Handler(std::shared_ptr<sdbusplus::asio::connection> conn,
+                 std::shared_ptr<btm::api::IApi> api) :
+    mApi(api), mBMCFinishTimer(conn->get_io_context())
+{
+    mBMCFinishTimer.expires_after(std::chrono::seconds(0));
+    mBMCFinishTimer.async_wait(std::bind(
+        &Handler::CheckBmcFinish, this, std::placeholders::_1, std::ref(conn)));
+}
+
+void Handler::CheckBmcFinish(const boost::system::error_code& ec,
+                             std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+    if (ec == boost::asio::error::operation_aborted)
+    {
+        fmt::print(stderr,
+                   "[CheckBmcFinish] BmcFinishTimer is being "
+                   "canceled, Error: {}\n",
+                   ec.message().c_str());
+        return;
+    }
+    else if (ec)
+    {
+        fmt::print(stderr, "[CheckBmcFinish] Timer Error: {}\n",
+                   ec.message().c_str());
+        return;
+    }
+
+    constexpr uint64_t kMillisecondPerSecond = 1000;
+    // The raw pointer here is safe since it points to an existing instance
+    // instead of allocating a memory space.
+    // We can't use `std::share_ptr<uint64_t>(&times.firmware)` because the
+    // `times` will free its space while `share_ptr` will also free the same
+    // space again when this constructor completed. It will cause
+    // `free(): double free detected` or `free(): invalid pointer` error.
+    SystemdTimestamp times;
+    std::array<std::pair<std::string, uint64_t*>, 6> bootTimestamp = {
+        std::make_pair<std::string, uint64_t*>( // Firmware
+            std::string(kSystemdFirmwareTime), &times.firmware),
+        std::make_pair<std::string, uint64_t*>( // Loader
+            std::string(kSystemdLoaderTime), &times.loader),
+        std::make_pair<std::string, uint64_t*>( // Kernel
+            std::string(kSystemdKernelTime), &times.kernel),
+        std::make_pair<std::string, uint64_t*>( // InitRD
+            std::string(kSystemdInitRDTime), &times.initRD),
+        std::make_pair<std::string, uint64_t*>( // Userspace
+            std::string(kSystemdUserspaceTime), &times.userspace),
+        std::make_pair<std::string, uint64_t*>( // Finish
+            std::string(kSystemdFinishTime), &times.finish)};
+
+    for (const auto& timestamp : bootTimestamp)
+    {
+        auto method = conn->new_method_call(
+            "org.freedesktop.systemd1",        // Systemd Service
+            "/org/freedesktop/systemd1",       // Systemd Path
+            "org.freedesktop.DBus.Properties", // Systemd Iface
+            "Get");                            // Systemd Func
+        method.append("org.freedesktop.systemd1.Manager",
+                      std::string(timestamp.first));
+        std::variant<uint64_t> result;
+        try
+        {
+            conn->call(method).read(result);
+        }
+        catch (const sdbusplus::exception::SdBusError& e)
+        {
+            fmt::print(
+                stderr,
+                "[CheckBmcFinish] Can't get systemd property {}. ERROR={}\n",
+                std::string(timestamp.first), e.what());
+            // Restart the timer to keep polling the APML_ALERT status
+            mBMCFinishTimer.expires_after(kCheckBmcFinishInterval);
+            mBMCFinishTimer.async_wait(std::bind(&Handler::CheckBmcFinish, this,
+                                                 std::placeholders::_1,
+                                                 std::ref(conn)));
+        }
+        *timestamp.second = std::get<uint64_t>(result);
+    }
+
+    // This daemon may start before the userspace is fully ready. So we need
+    // to confirm if userspace is fully ready by checking `times.finish`
+    // equals zero or not.
+    if (times.finish == 0)
+    {
+        fmt::print(
+            stderr,
+            "[CheckBmcFinish] `FinishTimestampMonotonic` is not ready yet\n");
+        mBMCFinishTimer.expires_after(kCheckBmcFinishInterval);
+        mBMCFinishTimer.async_wait(std::bind(&Handler::CheckBmcFinish, this,
+                                             std::placeholders::_1,
+                                             std::ref(conn)));
+    }
+
+    // How to calculate duration for each stage:
+    // https://github.com/systemd/systemd/blob/82b7bf8c1c8c6ded6f56b43998c803843a3b944b/src/analyze/analyze-time-data.c#L176-L186
+    // Special calculation for kernel duration:
+    // https://github.com/systemd/systemd/blob/82b7bf8c1c8c6ded6f56b43998c803843a3b944b/src/analyze/analyze-time-data.c#L84-L87
+    mApi->SetNodesDurationByTag(
+        btm::kBootTimeTagBMC, convertName(kSystemdFirmwareTime),
+        times.firmware != 0
+            ? (times.firmware - times.loader) / kMillisecondPerSecond
+            : 0);
+    mApi->SetNodesDurationByTag(
+        btm::kBootTimeTagBMC, convertName(kSystemdLoaderTime),
+        times.loader != 0 ? times.loader / kMillisecondPerSecond : 0);
+    mApi->SetNodesDurationByTag(
+        btm::kBootTimeTagBMC, convertName(kSystemdKernelTime),
+        (times.initRD > 0 ? times.initRD : times.userspace) /
+            kMillisecondPerSecond);
+    mApi->SetNodesDurationByTag(
+        btm::kBootTimeTagBMC, convertName(kSystemdInitRDTime),
+        times.initRD != 0
+            ? (times.userspace - times.initRD) / kMillisecondPerSecond
+            : 0);
+    mApi->SetNodesDurationByTag(
+        btm::kBootTimeTagBMC, convertName(kSystemdUserspaceTime),
+        times.userspace != 0
+            ? (times.finish - times.userspace) / kMillisecondPerSecond
+            : 0);
+
+#ifdef NPCM7XX_OR_NEWER
+    // NPCM7XX or newer Nuvoton BMC has a register that starts counting from
+    // SoC power on. Also uptime starts counting when kernel is up. Thus we
+    // can get (Firmware + Loader) time by `value[SEC_CNT_ADDR] - uptime`.
+    constexpr uint32_t SEC_CNT_ADDR = 0xF0801068;
+
+    auto powerOnSec = util->readMem4Bytes(SEC_CNT_ADDR);
+    auto upTimeMS = util->getUpTimeInMs();
+    if (powerOnSec != std::nullopt && upTimeMS != std::nullopt)
+    {
+        mApi->SetNodeDurationByTag(btm::kBootTimeTagBMC, "FirmwarePlusLoader",
+                                   powerOnSec.value() * 1000 -
+                                       upTimeMS.value());
+    }
+#endif
+
+// BMC marks the boot process as `completed` automatically if we do *NOT* have
+// external daemon to do so.
+#ifdef AUTO_BMC_COMPLETE
+    mApi->SetNodesCheckpointByTag(btm::kBootTimeTagBMC, "UserspaceEnd", 0, 0);
+    mApi->NotifyNodesCompleteByTag(btm::kBootTimeTagBMC);
+#endif
+    return;
+}
+} // namespace systemd
+} // namespace boot_time_monitor
diff --git a/src/utils.cpp b/src/utils.cpp
deleted file mode 100644
index b1afbef..0000000
--- a/src/utils.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-#include "utils.hpp"
-
-#include <fmt/printf.h>
-
-#include <boost/interprocess/file_mapping.hpp>
-#include <boost/interprocess/mapped_region.hpp>
-
-#include <array>
-#include <chrono>
-#include <filesystem>
-#include <fstream>
-#include <memory>
-#include <optional>
-#include <regex>
-#include <string>
-#include <string_view>
-#include <vector>
-
-namespace boot_time_monitor
-{
-
-namespace fs = std::filesystem;
-
-std::optional<int64_t> Util::getUpTimeInMs()
-{
-    constexpr int32_t kMillisecondPerSecond = 1000;
-    std::ifstream fin("/proc/uptime");
-    if (!fin.is_open())
-    {
-        fmt::print(stderr, "[{}]: Can not read \"/proc/uptime\"\n",
-                   __FUNCTION__);
-        return std::nullopt;
-    }
-    double uptimeSec;
-    fin >> uptimeSec;
-    fin.close();
-
-    return static_cast<int64_t>(uptimeSec * kMillisecondPerSecond);
-}
-
-inline int64_t Util::getWallTimeInMs()
-{
-    return std::chrono::system_clock::now().time_since_epoch() /
-           std::chrono::milliseconds(1);
-}
-
-inline bool Util::isValidName(std::string_view name)
-{
-    return !std::regex_search(name.data(), std::regex("[^0-9a-zA-Z_:]"));
-}
-
-inline std::string Util::getCPPath(std::string_view nodeName,
-                                   bool wantCompleted)
-{
-    return kBTMonitorDir + nodeName.data() + "_" + kCheckpointFile +
-           (wantCompleted ? kCompletedSuffix : "");
-}
-
-inline std::string Util::getDurPath(std::string_view nodeName,
-                                    bool wantCompleted)
-{
-    return kBTMonitorDir + nodeName.data() + "_" + kDurationFile +
-           (wantCompleted ? kCompletedSuffix : "");
-}
-
-std::optional<uint32_t> Util::readMem4Bytes(uint32_t target)
-{
-    // Typically the pageSize will be 4K/8K for 32 bit operating systems
-    uint32_t pageSize = boost::interprocess::mapped_region::get_page_size();
-    const uint32_t kPageMask = pageSize - 1;
-
-    uint32_t pageOffset = target & (~kPageMask);
-    uint32_t offsetInPage = target & kPageMask;
-
-    // Map `/dev/mem` to a region.
-    std::unique_ptr<boost::interprocess::file_mapping> fileMapping;
-    std::unique_ptr<boost::interprocess::mapped_region> MappedRegion;
-    try
-    {
-        fileMapping = std::make_unique<boost::interprocess::file_mapping>(
-            "/dev/mem", boost::interprocess::read_only);
-        // No need to unmap in the end.
-        MappedRegion = std::make_unique<boost::interprocess::mapped_region>(
-            *fileMapping, boost::interprocess::read_only, pageOffset,
-            pageSize * 2);
-
-        // MappedRegion->get_address() returns (void*) which needs to covert
-        // into (char*) to make `+ offsetInPage` work.
-        // Then converts it again into (uint32_t*) since we read 4 bytes.
-        return *(reinterpret_cast<uint32_t*>(
-            static_cast<char*>(MappedRegion->get_address()) + offsetInPage));
-    }
-    catch (const std::exception& e)
-    {
-        fmt::print(stderr, "[{}]: Throw exception: %s\n", __FUNCTION__,
-                   e.what());
-        return std::nullopt;
-    }
-
-    fmt::print(stderr, "[{}]: Shouldn't go to this line.\n", __FUNCTION__);
-    return std::nullopt;
-}
-
-FileUtil::FileUtil(std::string_view filename) : filename(filename)
-{
-    fs::path dir = fs::path(filename).remove_filename();
-    if (!dir.empty() && !fs::exists(dir))
-    {
-        fs::create_directories(dir);
-    }
-    ofs = std::make_unique<std::ofstream>(
-        filename.data(), std::fstream::out | std::fstream::app);
-    if (!ofs->good())
-    {
-        throw std::invalid_argument("ofstream is not available.");
-    }
-}
-
-void FileUtil::addCheckpoint(std::string_view name, int64_t wallTimeInMs,
-                             int64_t monoTimeInMs)
-{
-    (*ofs) << name << "," << wallTimeInMs << "," << monoTimeInMs << '\n';
-    ofs->flush();
-}
-
-void FileUtil::addDuration(std::string_view name, int64_t durationInMs)
-{
-    (*ofs) << name << "," << durationInMs << '\n';
-    ofs->flush();
-}
-
-void FileUtil::completeCurrent()
-{
-    ofs->close();
-    fs::rename(filename, filename + kCompletedSuffix);
-
-    // Reopen for next reboot
-    ofs->open(filename.c_str(), std::fstream::out | std::fstream::app);
-    if (!ofs->good())
-    {
-        throw std::invalid_argument("ofstream is not available.");
-    }
-}
-
-std::unique_ptr<std::vector<Checkpoint>>
-    FileUtil::loadCheckpoints(bool loadCompleted)
-{
-    std::unique_ptr<std::vector<Checkpoint>> result =
-        std::make_unique<std::vector<Checkpoint>>();
-    std::string cpFilename = filename + (loadCompleted ? kCompletedSuffix : "");
-    std::ifstream ifs(cpFilename);
-    if (!ifs.good())
-    {
-        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
-                   cpFilename);
-        return result;
-    }
-
-    constexpr uint32_t kBufSize = 1024;
-    constexpr uint32_t kMatchCount = 4;
-
-    std::array<char, kBufSize> buf;
-    std::cmatch match;
-    const std::regex reg("^([0-9a-zA-Z_:]*),([0-9]+),([0-9]+)$");
-    while (ifs.getline(buf.data(), kBufSize))
-    {
-        if (regex_match(buf.data(), match, reg))
-        {
-            if (match.size() != kMatchCount)
-            {
-                // This line is problematic. Skip it.
-                continue;
-            }
-
-            result->emplace_back(match[1].str(), std::stoll(match[2].str()),
-                                 std::stoll(match[3].str()));
-        }
-    }
-    return result;
-}
-
-std::unique_ptr<std::vector<Duration>>
-    FileUtil::loadDurations(bool loadCompleted)
-{
-    std::unique_ptr<std::vector<Duration>> result =
-        std::make_unique<std::vector<Duration>>();
-    std::string durFilename = filename +
-                              (loadCompleted ? kCompletedSuffix : "");
-    std::ifstream ifs(durFilename);
-    if (!ifs.good())
-    {
-        fmt::print("[{}] Failed to load `{}`. Skip it.\n", __FUNCTION__,
-                   durFilename);
-        return result;
-    }
-
-    constexpr uint32_t kBufSize = 1024;
-    constexpr uint32_t kMatchCount = 3;
-
-    std::array<char, kBufSize> buf;
-    std::cmatch match;
-    const std::regex reg("^([0-9a-zA-Z_:]*),([0-9]+)$");
-    while (ifs.getline(buf.data(), kBufSize))
-    {
-        if (regex_match(buf.data(), match, reg))
-        {
-            if (match.size() != kMatchCount)
-            {
-                // This line is problematic. Skip it.
-                continue;
-            }
-
-            result->emplace_back(match[1].str(), std::stoll(match[2].str()));
-        }
-    }
-    return result;
-}
-
-bool FileUtil::isEmpty()
-{
-    const auto result = ofs->tellp();
-    if (result < 0)
-    {
-        throw std::runtime_error("File can't be `tellp`");
-    }
-
-    return result == 0;
-}
-
-} // namespace boot_time_monitor
diff --git a/test/api_test.cpp b/test/api_test.cpp
new file mode 100644
index 0000000..c56d94d
--- /dev/null
+++ b/test/api_test.cpp
@@ -0,0 +1,323 @@
+#include "api.hpp"
+#include "resource.hpp"
+
+#include <fmt/format.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace boot_time_monitor
+{
+
+namespace btm = boot_time_monitor;
+
+namespace fs = std::filesystem;
+namespace
+{
+
+constexpr int kTestNodeCount = 6; // Number of nodes to create for testing
+
+class ApiTest : public ::testing::Test
+{
+  public:
+    // Wall time (second param) equals zero means BMC will get wall time by
+    // itself.
+    const std::vector<std::tuple<std::string_view, int64_t, int64_t>> inCP = {
+        {"reboot_start", 1000, 0},
+        {"Shutdown1", 0 /*will be 2000*/, 1000},
+        {"thisIs_2_shutdown", 0 /*will be 3000*/, 50},
+        {"000S0", 0 /*will be 4000*/, 0},
+        {"_PowerBack_", 0 /*will be 5000*/, 100},
+        {"start1", 0 /*will be 6000*/, 500},
+        {"start2", 0 /*will be 7000*/, 100},
+        {"reboot_end", 8000, 0},
+    };
+
+    const std::vector<std::tuple<std::string_view, int64_t>> inDur = {
+        {"nic1", 0},
+        {"A_nic", 1000},
+        {"_nextNIC", 4},
+        {"99NNN", 99},
+    };
+
+    ApiTest()
+    {
+        // Use a temporary directory for test files
+        // Prefer TMPDIR environment variable if set, otherwise use /tmp
+        const char* tmp_env = std::getenv("TMPDIR");
+        std::string base_tmp = (tmp_env != nullptr) ? tmp_env : "/tmp";
+        testDir = base_tmp + "/api_test_temp_" + std::to_string(getpid()) + "/";
+
+        // Clean up the temporary directory and its contents
+        // Check if the directory exists before removing
+        if (fs::exists(testDir))
+            fs::remove_all(testDir);
+        else
+            fs::create_directories(testDir); // Ensure the directory exists
+
+        // Use a temporary directory for test files
+        btm::resource::BTMonitorDir =
+            testDir; // Set the global monitor dir for this test scope
+    }
+
+  protected:
+    std::string testDir;
+};
+
+/*
+ * Verifies that setting multiple checkpoints, including those that generate
+ * an additional ":BEGIN" entry due to a non-zero duration, results in the
+ * correct total count when retrieved via GetNodeCheckpointList immediately
+ * after setting.
+ */
+TEST_F(ApiTest, checkpoint_set)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    btm::NodeConfig host0NodeConfig("host0",
+                                    std::string(btm::kBootTimeTagHost));
+    api->RegisterNode(host0NodeConfig);
+
+    // Calculate expected lines: checkpoints with duration != 0 add two lines
+    std::size_t expectedLines = 0;
+    for (const auto& cp : inCP)
+    {
+        expectedLines += (std::get<2>(cp) != 0) ? 2 : 1;
+    }
+
+    // Set checkpoints for host0
+    for (const auto& cp : inCP)
+    {
+        api->SetNodeCheckpoint(host0NodeConfig, std::get<0>(cp),
+                               std::get<1>(cp), std::get<2>(cp));
+    }
+
+    EXPECT_EQ(api->GetNodeCheckpointList(host0NodeConfig).size(),
+              expectedLines);
+}
+
+/*
+ * Verifies that calling SetNodeCheckpoint with a non-zero duration specifically
+ * creates both the original checkpoint entry and the associated ":BEGIN" entry.
+ */
+TEST_F(ApiTest, checkpoint_set_with_duration)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    btm::NodeConfig host1NodeConfig("host1",
+                                    std::string(btm::kBootTimeTagHost));
+    api->RegisterNode(host1NodeConfig);
+
+    // Test on host1
+    const std::string_view testName = "StageWithDuration";
+    const int64_t testDuration = 500;
+    const std::string expectedBeginName = std::string(testName) +
+                                          btm::resource::kBeginStageSuffix;
+
+    api->SetNodeCheckpoint(host1NodeConfig, testName, 0, testDuration);
+
+    auto checkpoints = api->GetNodeCheckpointList(host1NodeConfig);
+    EXPECT_EQ(checkpoints.size(), 2);
+
+    // Check if both the original and the :BEGIN checkpoints exist
+    bool foundOriginal = false;
+    bool foundBegin = false;
+    for (const auto& cp : checkpoints)
+    {
+        if (std::get<0>(cp) == testName)
+        {
+            foundOriginal = true;
+        }
+        else if (std::get<0>(cp) == expectedBeginName)
+        {
+            foundBegin = true;
+        }
+    }
+    EXPECT_TRUE(foundOriginal);
+    EXPECT_TRUE(foundBegin);
+}
+
+/*
+ * Verifies that setting multiple durations via SetNodeDuration results in the
+ * correct total count when retrieved via GetNodeAdditionalDurations
+ * immediately after setting.
+ */
+TEST_F(ApiTest, duration_set)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    btm::NodeConfig host2NodeConfig("host2",
+                                    std::string(btm::kBootTimeTagHost));
+    api->RegisterNode(host2NodeConfig);
+
+    // Test on host2
+    // Set durations for host2
+    for (const auto& dur : inDur)
+    {
+        api->SetNodeDuration(host2NodeConfig, std::get<0>(dur),
+                             std::get<1>(dur));
+    }
+    EXPECT_EQ(api->GetNodeAdditionalDurations(host2NodeConfig).size(),
+              inDur.size());
+}
+
+/*
+ * Verifies the state transitions of the IsNodeRebooting flag.
+ * It should be false initially, true after a checkpoint is set, false after
+ * NotifyNodeComplete is called, and true again after another checkpoint is set.
+ */
+TEST_F(ApiTest, rebootcomplete_behavior)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    btm::NodeConfig host3NodeConfig("host3",
+                                    std::string(btm::kBootTimeTagHost));
+    api->RegisterNode(host3NodeConfig);
+
+    // Test on host3
+    // Initially, no files exist or are empty, so not rebooting
+    EXPECT_FALSE(api->IsNodeRebooting(host3NodeConfig));
+
+    // Setting a checkpoint starts the "reboot" process for the node
+    api->SetNodeCheckpoint(host3NodeConfig, "FirstCheckpoint", 0, 0);
+    EXPECT_TRUE(api->IsNodeRebooting(host3NodeConfig));
+
+    // Marking complete finishes the reboot process
+    api->NotifyNodeComplete(host3NodeConfig);
+    EXPECT_FALSE(api->IsNodeRebooting(host3NodeConfig));
+
+    // Setting another checkpoint starts a new reboot cycle
+    api->SetNodeCheckpoint(host3NodeConfig, "SecondCheckpoint", 0, 0);
+    EXPECT_TRUE(api->IsNodeRebooting(host3NodeConfig));
+}
+
+/*
+ * Verifies that GetNodeCheckpointList retrieves the list of checkpoints
+ * belonging to the *current* boot cycle (i.e., since the last
+ * NotifyNodeComplete). Checkpoints from the previous completed cycle are not
+ * included.
+ */
+TEST_F(ApiTest, checkpoint_get)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    btm::NodeConfig host4NodeConfig("host4",
+                                    std::string(btm::kBootTimeTagHost));
+    api->RegisterNode(host4NodeConfig);
+
+    // Test on host4
+    // Set the initial checkpoints
+    for (const auto& cp : inCP)
+    {
+        api->SetNodeCheckpoint(host4NodeConfig, std::get<0>(cp),
+                               std::get<1>(cp), std::get<2>(cp));
+    }
+    // Calculate expected lines for the initial set
+    std::size_t expectedLines =
+        api->GetNodeCheckpointList(host4NodeConfig).size();
+
+    // Mark the initial set as complete
+    api->NotifyNodeComplete(host4NodeConfig);
+    // GetCheckpointList should return the *completed* list
+    EXPECT_EQ(api->GetNodeCheckpointList(host4NodeConfig).size(),
+              expectedLines);
+
+    // Set a new checkpoint (part of the *next* boot cycle)
+    api->SetNodeCheckpoint(host4NodeConfig, "NewCycleCheckpoint", 0, 0);
+
+    // GetCheckpointList should return the checkpoint amount after previous
+    // RebootComplete
+    EXPECT_EQ(api->GetNodeCheckpointList(host4NodeConfig).size(), 1);
+}
+
+/*
+ * Verifies that when SetNodeCheckpoint is called with wallTime=0, the system
+ * automatically assigns non-zero values for both the wall time (epoch) and
+ * monotonic time in the recorded checkpoint data.
+ */
+TEST_F(ApiTest, checkpoint_monotory)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    btm::NodeConfig host5NodeConfig("host5",
+                                    std::string(btm::kBootTimeTagHost));
+    api->RegisterNode(host5NodeConfig);
+
+    // Test on host5
+    api->SetNodeCheckpoint(host5NodeConfig, "AutoTimestamp", 0, 0);
+
+    auto checkpoints = api->GetNodeCheckpointList(host5NodeConfig);
+    ASSERT_EQ(checkpoints.size(), 1);
+
+    // Wall time (index 1) and monotonic time (index 2) should be non-zero
+    EXPECT_NE(std::get<1>(checkpoints[0]), 0);
+    EXPECT_NE(std::get<2>(checkpoints[0]), 0);
+}
+
+/*
+ * Verifies that calls to SetPSUCheckpoint and SetPSUDuration are broadcast
+ * and correctly recorded for *all* registered nodes.
+ */
+TEST_F(ApiTest, powercycle_behavior)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    std::vector<btm::NodeConfig> ncs;
+    for (int i = 6; i < 8; i++)
+    {
+        ncs.emplace_back("host6", std::string(btm::kBootTimeTagHost));
+        api->RegisterNode(ncs.back());
+    }
+
+    // This test inherently checks all nodes
+    const std::string_view psuCpName = "PSU_PowerOn";
+    const std::string_view psuDurName = "PSU_Stabilize";
+
+    api->SetPSUCheckpoint(psuCpName, 0, 0);
+    api->SetPSUDuration(psuDurName, 1234);
+
+    for (const auto& nc : ncs) // Iterate through all created nodes
+    {
+        auto checkpoints = api->GetNodeCheckpointList(nc);
+        EXPECT_EQ(checkpoints.size(), 1);
+        EXPECT_EQ(std::get<0>(checkpoints.back()), psuCpName);
+
+        auto durations = api->GetNodeAdditionalDurations(nc);
+        EXPECT_EQ(durations.size(), 1);
+        EXPECT_EQ(std::get<0>(durations.back()), psuDurName);
+        EXPECT_EQ(std::get<1>(durations.back()), 1234);
+    }
+}
+
+/*
+ * Verifies that the NotifyNodeComplete(RebootComplete) should be individule,
+ * not boardcast to all of the nodes.
+ */
+TEST_F(ApiTest, notify_complete_is_individual)
+{
+    std::unique_ptr<btm::api::Api> api = std::make_unique<btm::api::Api>();
+    btm::NodeConfig host9NodeConfig("host9",
+                                    std::string(btm::kBootTimeTagHost));
+    btm::NodeConfig host10NodeConfig("host10",
+                                     std::string(btm::kBootTimeTagHost));
+    api->RegisterNode(host9NodeConfig);
+    api->RegisterNode(host10NodeConfig);
+
+    // Set checkpoints on two different nodes to mark them as rebooting
+    api->SetNodeCheckpoint(host9NodeConfig, "Node0_BootStart", 0, 0);
+    api->SetNodeCheckpoint(host10NodeConfig, "Node1_BootStart", 0, 0);
+
+    EXPECT_TRUE(api->IsNodeRebooting(host9NodeConfig));
+    EXPECT_TRUE(api->IsNodeRebooting(host10NodeConfig));
+
+    // Notify only the first node as complete
+    api->NotifyNodeComplete(host9NodeConfig);
+
+    // Verify the first node is no longer rebooting, but the second one still is
+    EXPECT_FALSE(api->IsNodeRebooting(host9NodeConfig));
+    EXPECT_TRUE(api->IsNodeRebooting(host10NodeConfig));
+}
+} // namespace
+} // namespace boot_time_monitor
diff --git a/test/boot_manager_test.cpp b/test/boot_manager_test.cpp
deleted file mode 100644
index bcbfd32..0000000
--- a/test/boot_manager_test.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-#include "boot_manager.hpp"
-#include "mockups.hpp"
-#include "utils.hpp"
-
-#include <string>
-#include <string_view>
-#include <tuple>
-
-#include <gtest/gtest.h>
-
-namespace boot_time_monitor
-{
-namespace
-{
-
-using ::testing::_;
-using ::testing::Return;
-
-class BootManagerTest : public ::testing::Test
-{
-  public:
-    // Wall time (second param) equals zero means BMC will get wall time by
-    // itself.
-    const std::vector<std::tuple<std::string_view, int64_t, int64_t>> inCP = {
-        {"reboot_start", 1000, 0},
-        {"Shutdown1", 0 /*will be 2000*/, 1000},
-        {"thisIs_2_shutdown", 0 /*will be 3000*/, 50},
-        {"000S0", 0 /*will be 4000*/, 0},
-        {"_PowerBack_", 0 /*will be 5000*/, 100},
-        {"start1", 0 /*will be 6000*/, 500},
-        {"start2", 0 /*will be 7000*/, 100},
-        {"reboot_end", 8000, 0},
-    };
-
-    const std::vector<std::tuple<std::string_view, int64_t>> inDur = {
-        {"nic1", 0},
-        {"A_nic", 1000},
-        {"_nextNIC", 4},
-        {"99NNN", 99},
-    };
-
-    BootManagerTest() :
-        util(std::make_shared<MockUtil>()),
-        cpFile(std::make_shared<MockFileUtil>()),
-        durFile(std::make_shared<MockFileUtil>())
-    {
-        utilPtr = util.get();
-        cpFilePtr = cpFile.get();
-        durFilePtr = durFile.get();
-
-        EXPECT_CALL(*cpFile, loadCheckpoints(false))
-            .WillOnce(Return(std::make_unique<std::vector<Checkpoint>>()));
-        EXPECT_CALL(*durFile, loadDurations(false))
-            .WillOnce(Return(std::make_unique<std::vector<Duration>>()));
-        EXPECT_CALL(*cpFile, loadCheckpoints(true))
-            .WillOnce(Return(std::make_unique<std::vector<Checkpoint>>()));
-        EXPECT_CALL(*durFile, loadDurations(true))
-            .WillOnce(Return(std::make_unique<std::vector<Duration>>()));
-
-        stateMachine = std::make_unique<BootManager>(
-            std::move(util), std::move(cpFile), std::move(durFile));
-    }
-
-  protected:
-    std::shared_ptr<MockUtil> util;
-    MockUtil* utilPtr;
-    std::shared_ptr<MockFileUtil> cpFile;
-    MockFileUtil* cpFilePtr;
-    std::shared_ptr<MockFileUtil> durFile;
-    MockFileUtil* durFilePtr;
-
-    std::unique_ptr<BootManager> stateMachine;
-};
-
-TEST_F(BootManagerTest, AddCheckpointTest)
-{
-    EXPECT_CALL(*utilPtr, getUpTimeInMs()).WillOnce(Return(10000));
-    EXPECT_CALL(*utilPtr, isValidName(std::get<0>(inCP[0])))
-        .WillOnce(Return(true));
-    EXPECT_CALL(*cpFilePtr, addCheckpoint(std::get<0>(inCP[0]), _, _));
-    stateMachine->setCheckpoint(std::get<0>(inCP[0]), std::get<1>(inCP[0]),
-                                std::get<2>(inCP[0]));
-
-    for (uint32_t i = 1; i <= 6; i++)
-    {
-        EXPECT_CALL(*utilPtr, getWallTimeInMs())
-            .WillOnce(Return(1000 + 1000 * i));
-        EXPECT_CALL(*utilPtr, getUpTimeInMs())
-            .WillOnce(Return(10000 + 1000 * i));
-        EXPECT_CALL(*utilPtr, isValidName(std::get<0>(inCP[i])))
-            .WillOnce(Return(true));
-        // Won't enter `addCheckpoint` twice if duration is 0.
-        if (std::get<2>(inCP[i]) != 0)
-        {
-            EXPECT_CALL(
-                *cpFilePtr,
-                addCheckpoint(std::get<0>(inCP[i]).data() +
-                                  std::string{BootManager::kBeginStageSuffix},
-                              _, _));
-        }
-        EXPECT_CALL(*cpFilePtr, addCheckpoint(std::get<0>(inCP[i]), _, _));
-        stateMachine->setCheckpoint(std::get<0>(inCP[i]), std::get<1>(inCP[i]),
-                                    std::get<2>(inCP[i]));
-    }
-
-    EXPECT_CALL(*utilPtr, getUpTimeInMs()).WillOnce(Return(17000));
-    EXPECT_CALL(*utilPtr, isValidName(std::get<0>(inCP[7])))
-        .WillOnce(Return(true));
-    EXPECT_CALL(*cpFilePtr, addCheckpoint(std::get<0>(inCP[7]), _, _));
-    stateMachine->setCheckpoint(std::get<0>(inCP[7]), std::get<1>(inCP[7]),
-                                std::get<2>(inCP[7]));
-
-    auto cps = stateMachine->getCheckpoints();
-    int32_t count = 0;
-    for (uint32_t i = 0; i < inCP.size(); i++)
-    {
-        auto name = std::get<0>(inCP[i]);
-        auto duration = std::get<2>(inCP[i]);
-
-        if (i != 0 && duration != 0)
-        {
-            EXPECT_EQ(cps[count].name,
-                      name.data() +
-                          std::string{BootManager::kBeginStageSuffix});
-            EXPECT_EQ(cps[count].wallTime, 1000 * (i + 1) - duration);
-            EXPECT_EQ(cps[count].monoTime, 10000 + 1000 * i - duration);
-            count++;
-        }
-        EXPECT_EQ(cps[count].name, name);
-        EXPECT_EQ(cps[count].wallTime, 1000 * (i + 1));
-        EXPECT_EQ(cps[count].monoTime, 10000 + 1000 * i);
-        count++;
-    }
-    EXPECT_EQ(count, cps.size());
-}
-
-TEST_F(BootManagerTest, AddDurationTest)
-{
-    for (auto dur : inDur)
-    {
-        EXPECT_CALL(*durFilePtr, addDuration(_, _));
-        EXPECT_CALL(*utilPtr, isValidName(std::get<0>(dur)))
-            .WillOnce(Return(true));
-        stateMachine->setDuration(std::get<0>(dur), std::get<1>(dur));
-    }
-
-    auto durs = stateMachine->getDurations();
-    EXPECT_EQ(inDur.size(), durs.size());
-
-    for (uint32_t i = 0; i < inDur.size(); i++)
-    {
-        auto name = std::get<0>(inDur[i]);
-        auto duration = std::get<1>(inDur[i]);
-
-        EXPECT_EQ(durs[i].name, name);
-        EXPECT_EQ(durs[i].duration, duration);
-    }
-}
-
-TEST_F(BootManagerTest, CompleteTest)
-{
-    EXPECT_CALL(*utilPtr, getUpTimeInMs()).WillOnce(Return(10000));
-    EXPECT_CALL(*utilPtr, isValidName(std::get<0>(inCP[0])))
-        .WillOnce(Return(true));
-    EXPECT_CALL(*cpFilePtr, addCheckpoint(std::get<0>(inCP[0]), _, _));
-    stateMachine->setCheckpoint(std::get<0>(inCP[0]), std::get<1>(inCP[0]),
-                                std::get<2>(inCP[0]));
-    EXPECT_CALL(*durFilePtr, addDuration(std::get<0>(inDur[0]), _));
-    EXPECT_CALL(*utilPtr, isValidName(std::get<0>(inDur[0])))
-        .WillOnce(Return(true));
-    stateMachine->setDuration(std::get<0>(inDur[0]), std::get<1>(inDur[0]));
-
-    // Originally these 2 vectors should be empty.
-    EXPECT_TRUE(stateMachine->getPreCheckpoints().empty());
-    EXPECT_TRUE(stateMachine->getPreDurations().empty());
-
-    EXPECT_CALL(*cpFilePtr, completeCurrent());
-    EXPECT_CALL(*durFilePtr, completeCurrent());
-    stateMachine->notifyComplete();
-
-    auto cps = stateMachine->getPreCheckpoints();
-    auto durs = stateMachine->getPreDurations();
-    EXPECT_EQ(cps.size(), 1);
-    EXPECT_EQ(durs.size(), 1);
-    EXPECT_TRUE(stateMachine->getCheckpoints().empty());
-    EXPECT_TRUE(stateMachine->getDurations().empty());
-
-    EXPECT_EQ(cps[0].name, std::get<0>(inCP[0]));
-    EXPECT_EQ(cps[0].wallTime, std::get<1>(inCP[0]));
-    EXPECT_EQ(cps[0].monoTime, 10000);
-
-    EXPECT_EQ(durs[0].name, std::get<0>(inDur[0]));
-    EXPECT_EQ(durs[0].duration, std::get<1>(inDur[0]));
-}
-
-} // namespace
-} // namespace boot_time_monitor
diff --git a/test/include/mockups.hpp b/test/include/mockups.hpp
deleted file mode 100644
index 387e006..0000000
--- a/test/include/mockups.hpp
+++ /dev/null
@@ -1,64 +0,0 @@
-#pragma once
-
-#include "boot_manager.hpp"
-#include "utils.hpp"
-
-#include <cstdint>
-#include <memory>
-#include <optional>
-#include <string_view>
-
-#include <gmock/gmock.h>
-
-namespace boot_time_monitor
-{
-
-class MockUtil : public UtilIface
-{
-  public:
-    MOCK_METHOD(std::optional<int64_t>, getUpTimeInMs, (), (override));
-    MOCK_METHOD(int64_t, getWallTimeInMs, (), (override));
-    MOCK_METHOD(bool, isValidName, (std::string_view), (override));
-    MOCK_METHOD(std::string, getCPPath, (std::string_view, bool), (override));
-    MOCK_METHOD(std::string, getDurPath, (std::string_view, bool), (override));
-    MOCK_METHOD(std::optional<uint32_t>, readMem4Bytes, (uint32_t), (override));
-};
-
-class MockFileUtil : public FileUtilIface
-{
-  public:
-    MOCK_METHOD(void, addCheckpoint,
-                (std::string_view name, int64_t wallTimeInMs,
-                 int64_t monoTimeInMs),
-                (override));
-    MOCK_METHOD(void, addDuration,
-                (std::string_view name, int64_t durationInMs), (override));
-    MOCK_METHOD(void, completeCurrent, (), (override));
-    MOCK_METHOD(std::unique_ptr<std::vector<Checkpoint>>, loadCheckpoints,
-                (bool), (override));
-    MOCK_METHOD(std::unique_ptr<std::vector<Duration>>, loadDurations, (bool),
-                (override));
-    MOCK_METHOD(bool, isEmpty, (), (override));
-};
-
-class MockBootManager : public BootManagerIface
-{
-    MOCK_METHOD(void, setCheckpoint,
-                (std::string_view cpName, int64_t externalWallTime,
-                 int64_t duration),
-                (override));
-    MOCK_METHOD(void, setDuration, (std::string_view durName, int64_t duration),
-                (override));
-    MOCK_METHOD(void, notifyComplete, (), (override));
-    MOCK_METHOD(bool, isRebooting, (), (override));
-    MOCK_METHOD((const std::vector<Checkpoint>&), getCheckpoints, (),
-                (const, override));
-    MOCK_METHOD((const std::vector<Duration>&), getDurations, (),
-                (const, override));
-    MOCK_METHOD((const std::vector<Checkpoint>&), getPreCheckpoints, (),
-                (const, override));
-    MOCK_METHOD((const std::vector<Duration>&), getPreDurations, (),
-                (const, override));
-};
-
-} // namespace boot_time_monitor
diff --git a/test/meson.build b/test/meson.build
index 61279cf..ed84efc 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -20,17 +20,13 @@
   endif
 endif
 
-boot_time_monitor_test_incs = include_directories('include')
-
 tests = [
-  'utils_test',
-  'boot_manager_test',
+  'api_test',
 ]
 
 foreach t : tests
   test(t, executable(t.underscorify(), t + '.cpp',
                      build_by_default: false,
-                     include_directories : boot_time_monitor_test_incs,
                      implicit_include_directories: false,
                      dependencies: [boot_time_monitor_dep, gtest, gmock]))
 endforeach
diff --git a/test/utils_test.cpp b/test/utils_test.cpp
deleted file mode 100644
index 9326069..0000000
--- a/test/utils_test.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-#include "config.h"
-
-#include "mockups.hpp"
-#include "utils.hpp"
-
-#include <chrono>
-#include <filesystem>
-#include <fstream>
-#include <memory>
-#include <optional>
-#include <string>
-#include <string_view>
-#include <thread>
-
-#include <gtest/gtest.h>
-
-namespace boot_time_monitor
-{
-namespace
-{
-
-namespace fs = std::filesystem;
-
-TEST(DoesUtilWorkTest, Works)
-{
-    Util u;
-    auto curUpTime = u.getUpTimeInMs();
-    auto curWallTime = u.getWallTimeInMs();
-    std::this_thread::sleep_for(std::chrono::seconds(2));
-
-    ASSERT_NE(curUpTime, std::nullopt);
-    ASSERT_GT(curUpTime.value(), 0);
-
-    EXPECT_GT(u.getUpTimeInMs().value_or(-1), curUpTime);
-    EXPECT_GT(u.getWallTimeInMs(), curWallTime);
-}
-
-TEST(ValidNameTest, Valid)
-{
-    Util u;
-    EXPECT_TRUE(u.isValidName("0129"));
-    EXPECT_TRUE(u.isValidName("ABCZ"));
-    EXPECT_TRUE(u.isValidName("abcz"));
-    EXPECT_TRUE(u.isValidName("____"));
-    EXPECT_TRUE(u.isValidName("A_a_1_"));
-    EXPECT_FALSE(u.isValidName("A-_a_1_"));
-    EXPECT_FALSE(u.isValidName("A_a*_1_"));
-    EXPECT_FALSE(u.isValidName("A_a_1@_"));
-    EXPECT_FALSE(u.isValidName("A#_a_1_"));
-    EXPECT_FALSE(u.isValidName("A_a _1_"));
-}
-
-TEST(getPathTest, Works)
-{
-    Util u;
-    EXPECT_EQ(u.getCPPath("host0", false),
-              std::string(bootTimeDataDir) + "host0_checkpoints.csv");
-    EXPECT_EQ(u.getCPPath("bmc", false),
-              std::string(bootTimeDataDir) + "bmc_checkpoints.csv");
-    EXPECT_EQ(u.getCPPath("_N_o_d_e_", false),
-              std::string(bootTimeDataDir) + "_N_o_d_e__checkpoints.csv");
-
-    EXPECT_EQ(u.getCPPath("host0", true),
-              std::string(bootTimeDataDir) + "host0_checkpoints.csv.completed");
-    EXPECT_EQ(u.getCPPath("bmc", true),
-              std::string(bootTimeDataDir) + "bmc_checkpoints.csv.completed");
-    EXPECT_EQ(u.getCPPath("_N_o_d_e_", true),
-              std::string(bootTimeDataDir) +
-                  "_N_o_d_e__checkpoints.csv.completed");
-
-    EXPECT_EQ(u.getDurPath("host0", false),
-              std::string(bootTimeDataDir) + "host0_durations.csv");
-    EXPECT_EQ(u.getDurPath("bmc", false),
-              std::string(bootTimeDataDir) + "bmc_durations.csv");
-    EXPECT_EQ(u.getDurPath("_N_o_d_e_", false),
-              std::string(bootTimeDataDir) + "_N_o_d_e__durations.csv");
-
-    EXPECT_EQ(u.getDurPath("host0", true),
-              std::string(bootTimeDataDir) + "host0_durations.csv.completed");
-    EXPECT_EQ(u.getDurPath("bmc", true),
-              std::string(bootTimeDataDir) + "bmc_durations.csv.completed");
-    EXPECT_EQ(u.getDurPath("_N_o_d_e_", true),
-              std::string(bootTimeDataDir) +
-                  "_N_o_d_e__durations.csv.completed");
-}
-
-TEST(DoesFileUtilWork, CheckpointWorks)
-{
-    constexpr std::string_view cpName = "temp_cp";
-    const std::string cpCompleted = std::string{cpName} + kCompletedSuffix;
-
-    // Clean previous test result
-    fs::remove(cpName);
-    fs::remove(cpCompleted);
-
-    FileUtil cpUtil(cpName);
-    EXPECT_TRUE(cpUtil.isEmpty());
-    cpUtil.addCheckpoint("checkpoint_10", 10, 20);
-    cpUtil.addCheckpoint("checkpoint_30", 30, 50);
-    EXPECT_FALSE(cpUtil.isEmpty());
-
-    // Completed data should be empty
-    EXPECT_TRUE(cpUtil.loadCheckpoints(true)->empty());
-    auto vec = cpUtil.loadCheckpoints(false);
-    EXPECT_EQ(vec->size(), 2);
-    EXPECT_EQ((*vec)[0].name, "checkpoint_10");
-    EXPECT_EQ((*vec)[0].wallTime, 10);
-    EXPECT_EQ((*vec)[0].monoTime, 20);
-    EXPECT_EQ((*vec)[1].name, "checkpoint_30");
-    EXPECT_EQ((*vec)[1].wallTime, 30);
-    EXPECT_EQ((*vec)[1].monoTime, 50);
-
-    cpUtil.completeCurrent();
-    // Current data should be empty
-    EXPECT_TRUE(cpUtil.loadCheckpoints(false)->empty());
-    EXPECT_TRUE(cpUtil.isEmpty());
-
-    // Check completed data
-    vec = cpUtil.loadCheckpoints(true);
-    EXPECT_EQ(vec->size(), 2);
-    EXPECT_EQ((*vec)[0].name, "checkpoint_10");
-    EXPECT_EQ((*vec)[0].wallTime, 10);
-    EXPECT_EQ((*vec)[0].monoTime, 20);
-    EXPECT_EQ((*vec)[1].name, "checkpoint_30");
-    EXPECT_EQ((*vec)[1].wallTime, 30);
-    EXPECT_EQ((*vec)[1].monoTime, 50);
-
-    // Check file content
-    std::string input;
-    std::ifstream ifs(cpCompleted);
-    ASSERT_TRUE(ifs.good());
-    ifs >> input;
-    EXPECT_EQ(input, "checkpoint_10,10,20");
-    ifs >> input;
-    EXPECT_EQ(input, "checkpoint_30,30,50");
-    EXPECT_FALSE(ifs >> input);
-    ifs.close();
-}
-
-TEST(DoesFileUtilWork, DurationWorks)
-{
-    constexpr std::string_view durName = "temp_dur";
-    const std::string durCompleted = std::string{durName} + kCompletedSuffix;
-
-    // Clean previous test result
-    fs::remove(durName);
-    fs::remove(durCompleted);
-
-    FileUtil durUtil(durName);
-    EXPECT_TRUE(durUtil.isEmpty());
-    durUtil.addDuration("duration_1000", 1000);
-    durUtil.addDuration("duration_3000", 3000);
-    EXPECT_FALSE(durUtil.isEmpty());
-
-    // Completed data should be empty
-    EXPECT_TRUE(durUtil.loadDurations(true)->empty());
-    auto vec = durUtil.loadDurations(false);
-    EXPECT_EQ(vec->size(), 2);
-    EXPECT_EQ((*vec)[0].name, "duration_1000");
-    EXPECT_EQ((*vec)[0].duration, 1000);
-    EXPECT_EQ((*vec)[1].name, "duration_3000");
-    EXPECT_EQ((*vec)[1].duration, 3000);
-
-    durUtil.completeCurrent();
-    // Current data should be empty
-    EXPECT_TRUE(durUtil.loadDurations(false)->empty());
-    EXPECT_TRUE(durUtil.isEmpty());
-
-    // Check completed data
-    vec = durUtil.loadDurations(true);
-    EXPECT_EQ(vec->size(), 2);
-    EXPECT_EQ((*vec)[0].name, "duration_1000");
-    EXPECT_EQ((*vec)[0].duration, 1000);
-    EXPECT_EQ((*vec)[1].name, "duration_3000");
-    EXPECT_EQ((*vec)[1].duration, 3000);
-
-    // Check file content
-    std::string input;
-    std::ifstream ifs(durCompleted);
-    ASSERT_TRUE(ifs.good());
-    ifs >> input;
-    EXPECT_EQ(input, "duration_1000,1000");
-    ifs >> input;
-    EXPECT_EQ(input, "duration_3000,3000");
-    EXPECT_FALSE(ifs >> input);
-    ifs.close();
-}
-
-} // namespace
-} // namespace boot_time_monitor