blob: 859be376564c9a7ebe69d5410025444faf040bcb [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";
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>(&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<std::string, uint64_t*>( // Firmware
std::string(kSystemdFirmwareTime), &times.firmware),
std::make_pair<std::string, uint64_t*>( // Loader
std::string(kSystemdLoaderTime), &times.loader),
std::make_pair<std::string, uint64_t*>( // Kernel
std::string(kSystemdKernelTime), &times.kernel),
std::make_pair<std::string, uint64_t*>( // InitRD
std::string(kSystemdInitRDTime), &times.initRD),
std::make_pair<std::string, uint64_t*>( // Userspace
std::string(kSystemdUserspaceTime), &times.userspace),
std::make_pair<std::string, uint64_t*>( // 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(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