bmc_monitor_app: Adding systemd boot process duration

Expose BMC bootup process durations.
This bootup process is defined by systemd[1] and has been included in
Redfish schema[2].
Basically the implementation is copied from systemd-analyze[3].

Ref:
[1] https://www.freedesktop.org/software/systemd/man/bootup.html
[2] https://www.dmtf.org/sites/default/files/standards/documents/DSP0268_2023.1.pdf
    (6.65 ManagerDiagnosticData 1.2.0)
[3] https://github.com/systemd/systemd/blob/82b7bf8c1c8c6ded6f56b43998c803843a3b944b/src/analyze/analyze-time-data.c

Tested:
```
\# Check the duration calculated by boot-time-monitor
bmc:~# cat /usr/share/boot_time_monitor/bmc_durations.csv.completed
Firmware,0
Loader,0
Kernel,6125
InitRD,0
Userspace,371105

\# Manual install systemd-analyze to ensure the result is correct.
bmc:~# systemd-analyze
Startup finished in 6.125s (kernel) + 6min 11.105s (userspace) = 6min 17.231s
multi-user.target reached after 2min 48.715s in userspace.
```

Google-Bug-Id: 296530445
Change-Id: Ifa632a920e4d23085f25e6a38e00475218e907c8
Signed-off-by: Michael Shen <gpgpgp@google.com>
diff --git a/include/bmc_monitor_app.hpp b/include/bmc_monitor_app.hpp
index 17fbe43..3a0dc09 100644
--- a/include/bmc_monitor_app.hpp
+++ b/include/bmc_monitor_app.hpp
@@ -4,6 +4,7 @@
 #include "dbus_handler.hpp"
 #include "utils.hpp"
 
+#include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/bus.hpp>
 
 #include <memory>
@@ -19,7 +20,8 @@
     constexpr static std::string_view kObjPath =
         "/xyz/openbmc_project/time/boot/bmc";
 
-    explicit BMCMonitorApp(sdbusplus::bus::bus& bus);
+    BMCMonitorApp(sdbusplus::bus::bus& bus,
+                  std::shared_ptr<sdbusplus::asio::connection> conn);
 
   private:
     sdbusplus::server::manager::manager objManager;
diff --git a/src/bmc_monitor_app.cpp b/src/bmc_monitor_app.cpp
index bf1eb9e..96e0bb0 100644
--- a/src/bmc_monitor_app.cpp
+++ b/src/bmc_monitor_app.cpp
@@ -4,21 +4,132 @@
 #include "dbus_handler.hpp"
 #include "utils.hpp"
 
+#include <fmt/printf.h>
+
+#include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/bus.hpp>
 
+#include <array>
 #include <memory>
+#include <regex>
 #include <string_view>
 
 namespace boot_time_monitor
 {
 
-BMCMonitorApp::BMCMonitorApp(sdbusplus::bus::bus& bus) :
-    objManager(bus, kObjPath.data()), util(std::make_shared<Util>()),
+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,
+    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 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<std::string_view, uint64_t*>(kSystemdFirmwareTime.data(),
+                                                    &times.firmware),
+        std::make_pair<std::string_view, uint64_t*>(kSystemdLoaderTime.data(),
+                                                    &times.loader),
+        std::make_pair<std::string_view, uint64_t*>(kSystemdKernelTime.data(),
+                                                    &times.kernel),
+        std::make_pair<std::string_view, uint64_t*>(kSystemdInitRDTime.data(),
+                                                    &times.initRD),
+        std::make_pair<std::string_view, uint64_t*>(
+            kSystemdUserspaceTime.data(), &times.userspace),
+        std::make_pair<std::string_view, uint64_t*>(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, "[{}] Can't get systemd property {}. ERROR={}\n",
+                       __FUNCTION__, timestamp.first.data(), e.what());
+        }
+        *timestamp.second = std::get<uint64_t>(result);
+    }
+
+    // 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
+                                 ? (times.firmware - times.loader) /
+                                       kMillisecondPerSecond
+                                 : 0);
+    bootManager->setDuration(
+        convertName(kSystemdLoaderTime),
+        times.loader != 0 ? times.loader / kMillisecondPerSecond : 0);
+    bootManager->setDuration(
+        convertName(kSystemdKernelTime),
+        (times.initRD > 0 ? times.initRD : times.userspace) /
+            kMillisecondPerSecond);
+    bootManager->setDuration(convertName(kSystemdInitRDTime),
+                             times.initRD != 0
+                                 ? (times.userspace - times.initRD) /
+                                       kMillisecondPerSecond
+                                 : 0);
+    bootManager->setDuration(convertName(kSystemdUserspaceTime),
+                             times.userspace != 0
+                                 ? (times.finish - times.userspace) /
+                                       kMillisecondPerSecond
+                                 : 0);
+}
 
 } // namespace boot_time_monitor
diff --git a/src/main.cpp b/src/main.cpp
index 1f2e8eb..63021d9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -18,7 +18,7 @@
     auto& bus = static_cast<sdbusplus::bus::bus&>(*conn);
 
     HostMonitorApp host(bus, 0);
-    BMCMonitorApp bmc(bus);
+    BMCMonitorApp bmc(bus, conn);
 
     io.run();