| #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, |
| 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; |
| } |
| 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_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, |
| "[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 |
| ? (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); |
| |
| #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 |