blob: dc005ece8f65471a70c7e6b8f791beb834c45581 [file] [log] [blame]
#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";
constexpr int64_t kMillisecondPerSecond = 1000;
struct SystemdTimestamp
{
uint64_t firmware;
uint64_t loader;
uint64_t kernel;
uint64_t initRD;
uint64_t userspace;
uint64_t finish;
};
struct SystemdDuration
{
int64_t firmware;
int64_t loader;
int64_t kernel;
int64_t initRD;
int64_t userspace;
};
namespace
{
// 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
inline SystemdDuration CalculateSystemdDuration(SystemdTimestamp times)
{
int64_t firmware = static_cast<int64_t>(times.firmware);
int64_t loader = static_cast<int64_t>(times.loader);
int64_t initRD = static_cast<int64_t>(times.initRD);
int64_t userspace = static_cast<int64_t>(times.userspace);
int64_t finish = static_cast<int64_t>(times.finish);
SystemdDuration durations;
durations.kernel = userspace;
if (firmware != 0)
{
durations.firmware = firmware - loader;
}
if (loader != 0)
{
durations.loader = loader;
}
if (initRD > 0)
{
durations.kernel = initRD;
}
if (initRD != 0)
{
durations.initRD = userspace - initRD;
}
if (userspace != 0)
{
durations.userspace = finish - userspace;
}
return durations;
}
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(std::move(api)), mBMCFinishTimer(conn->get_io_context())
{
mBMCFinishTimer.expires_after(std::chrono::seconds(0));
mBMCFinishTimer.async_wait(
[this, &conn](const boost::system::error_code& ec) {
CheckBmcFinish(ec, 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;
}
if (ec)
{
fmt::print(stderr, "[CheckBmcFinish] Timer Error: {}\n",
ec.message().c_str());
return;
}
// 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, uint64_t*>, 6> bootTimestamp = {
std::make_pair( // Firmware
std::string(kSystemdFirmwareTime), &times.firmware),
std::make_pair( // Loader
std::string(kSystemdLoaderTime), &times.loader),
std::make_pair( // Kernel
std::string(kSystemdKernelTime), &times.kernel),
std::make_pair( // InitRD
std::string(kSystemdInitRDTime), &times.initRD),
std::make_pair( // Userspace
std::string(kSystemdUserspaceTime), &times.userspace),
std::make_pair( // Finish
std::string(kSystemdFinishTime), &times.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(
[this, &conn](const boost::system::error_code& ec) {
CheckBmcFinish(ec, 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(
[this, &conn](const boost::system::error_code& ec) {
CheckBmcFinish(ec, conn);
});
}
SystemdDuration durations = CalculateSystemdDuration(times);
mApi->SetNodesDurationByTag(btm::kBootTimeTagBMC,
convertName(kSystemdFirmwareTime),
durations.firmware / kMillisecondPerSecond);
mApi->SetNodesDurationByTag(btm::kBootTimeTagBMC,
convertName(kSystemdLoaderTime),
durations.loader / kMillisecondPerSecond);
mApi->SetNodesDurationByTag(btm::kBootTimeTagBMC,
convertName(kSystemdKernelTime),
durations.kernel / kMillisecondPerSecond);
mApi->SetNodesDurationByTag(btm::kBootTimeTagBMC,
convertName(kSystemdInitRDTime),
durations.initRD / kMillisecondPerSecond);
mApi->SetNodesDurationByTag(btm::kBootTimeTagBMC,
convertName(kSystemdUserspaceTime),
durations.userspace / kMillisecondPerSecond);
#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
}
} // namespace systemd
} // namespace boot_time_monitor