| #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>(×.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), ×.firmware), |
| std::make_pair<std::string, uint64_t*>( // Loader |
| std::string(kSystemdLoaderTime), ×.loader), |
| std::make_pair<std::string, uint64_t*>( // Kernel |
| std::string(kSystemdKernelTime), ×.kernel), |
| std::make_pair<std::string, uint64_t*>( // InitRD |
| std::string(kSystemdInitRDTime), ×.initRD), |
| std::make_pair<std::string, uint64_t*>( // Userspace |
| std::string(kSystemdUserspaceTime), ×.userspace), |
| std::make_pair<std::string, uint64_t*>( // Finish |
| std::string(kSystemdFinishTime), ×.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 |