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();