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>(×.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(),
+ ×.firmware),
+ std::make_pair<std::string_view, uint64_t*>(kSystemdLoaderTime.data(),
+ ×.loader),
+ std::make_pair<std::string_view, uint64_t*>(kSystemdKernelTime.data(),
+ ×.kernel),
+ std::make_pair<std::string_view, uint64_t*>(kSystemdInitRDTime.data(),
+ ×.initRD),
+ std::make_pair<std::string_view, uint64_t*>(
+ kSystemdUserspaceTime.data(), ×.userspace),
+ std::make_pair<std::string_view, uint64_t*>(kSystemdFinishTime.data(),
+ ×.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();