blob: aa19c81c3cf9bf1f80c6d473b945120dfb11d315 [file] [log] [blame]
#include "bmc_monitor_app.hpp"
#include "boot_manager.hpp"
#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
{
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);
#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->complete();
#endif
}
} // namespace boot_time_monitor