blob: 185d1e069b0bcf0506da81ce2fd8320154f112f2 [file] [log] [blame]
#include "utils/boot_time_utils.hpp"
#include "boottime_api/boottime_api.h"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "generated/enums/google_boot_time.hpp"
#include "managed_store.hpp"
#include "utils/dbus_utils.hpp"
#include "utils/time_utils.hpp"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <map>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish::boot_time_utils
{
// Logics to calculate the boot time are from
// google3/platforms/mosys/modules/bmc/gbmcipmi/boottime_parsing.cc
inline void RecordDuration(std::map<std::string, int64_t>& rebootStages,
std::string_view name, const Checkpoint& begin,
const Checkpoint& end)
{
int64_t duration = end.monoTimeMs - begin.monoTimeMs;
if (duration < 0)
{
duration = 0;
}
rebootStages[std::string(name)] += duration;
}
inline bool IsBeginningOfBootEvent(std::string_view checkpointName)
{
return checkpointName == kHostOff || checkpointName == kOSInactive;
}
int64_t PowerCycleCount(std::span<const Checkpoint> checkpoints)
{
int64_t cnt = 0;
if (checkpoints.empty())
{
return 0;
}
if (IsBeginningOfBootEvent(checkpoints.begin()->name))
{
cnt++;
}
for (std::span<const Checkpoint>::iterator it = checkpoints.begin() + 1;
it != checkpoints.end(); ++it)
{
if ((it - 1)->monoTimeMs > it->monoTimeMs)
{
cnt++;
}
}
return cnt;
}
void FormatCheckpoints(std::vector<Checkpoint>& checkpoints)
{
if (checkpoints.empty())
{
return;
}
// Remove HostState and OSState right after BMC power on so that these won't
// be treated as a checkpoint while they are actually just initial value,
// i.e. state is updated from NULL.
bool flag_remove = false;
for (auto it = checkpoints.begin() + 1; it != checkpoints.end();)
{
if ((it - 1)->monoTimeMs > it->monoTimeMs)
{
flag_remove = true;
}
if (flag_remove && (it->name == kHostOff || it->name == kOSInactive))
{
checkpoints.erase(it);
continue;
}
// Reset the flag once we hit any other events.
flag_remove = false;
++it;
}
}
void ParseTransitionEventDurations(
std::map<std::string, int64_t>& rebootStages, std::string_view name,
std::span<const Checkpoint> checkpoints,
const std::vector<std::string_view>& begWords,
const std::vector<std::string_view>& endWords)
{
if (checkpoints.empty())
{
return;
}
for (std::span<const Checkpoint>::iterator it = checkpoints.begin() + 1;
it != checkpoints.end(); ++it)
{
if (std::find(begWords.begin(), begWords.end(), (it - 1)->name) !=
begWords.end() &&
std::find(endWords.begin(), endWords.end(), it->name) !=
endWords.end())
{
RecordDuration(rebootStages, name, *(it - 1), *it);
}
}
}
void ParseEventDurations(std::map<std::string, int64_t>& rebootStages,
std::string_view name,
std::span<const Checkpoint> checkpoints,
const std::vector<std::string_view>& begWords,
const std::vector<std::string_view>& endWords)
{
std::span<const Checkpoint>::iterator lastMatchedWord = checkpoints.end();
for (std::span<const Checkpoint>::iterator it = checkpoints.begin();
it != checkpoints.end(); ++it)
{
// Keep tracking the last matched checkpoint.
if (std::find(begWords.begin(), begWords.end(), it->name) !=
begWords.end())
{
lastMatchedWord = it;
}
if (lastMatchedWord != checkpoints.end() &&
std::find(endWords.begin(), endWords.end(), it->name) !=
endWords.end())
{
RecordDuration(rebootStages, name, *lastMatchedWord, *it);
lastMatchedWord = checkpoints.end();
}
}
}
void ParseOffDuration(std::map<std::string, int64_t>& rebootStages,
std::span<const Checkpoint> checkpoints)
{
if (checkpoints.empty())
{
return;
}
ParseEventDurations(rebootStages, kOff, checkpoints,
{kHostOff, kHostTransitioningToOff}, {kHostRunning});
ParseTransitionEventDurations(
rebootStages, kOff, checkpoints,
{kHostOff, kHostTransitioningToOff, kOSInactive}, {kTrayPowerCycle});
// Handle unclean power cycle conditions. This assumes that the monoTimeMs
// represent monotonic time from BMC power on. However, this may not be true
// on some platforms.
for (auto it = checkpoints.begin() + 1; it != checkpoints.end(); ++it)
{
if (it->name == kHostRunning && (it - 1)->monoTimeMs > it->monoTimeMs)
{
rebootStages[std::string(kOff)] += it->monoTimeMs;
}
}
}
// Parses the duration from host off to power cycle. This duration means how
// long the time spent on BMC shutdown + IMC shutdown + S4 operation time.
// The returned value is duration in milliseconds.
static void
ParseOffToPowerCycleDuration(std::map<std::string, int64_t>& reboot_stages,
std::span<const Checkpoint> checkpoints)
{
if (checkpoints.empty())
{
return;
}
auto prev = checkpoints.begin();
for (auto it = checkpoints.begin() + 1; it != checkpoints.end(); ++it)
{
// Ignore kOSInactive because it may be shown after kHostOff but the
// real event happens in a different order.
if (it->name == kOSInactive)
{
continue;
}
if (prev->name == kHostOff && it->name == kTrayPowerCycle)
{
RecordDuration(reboot_stages, "OffToPowerCycle", *prev, *it);
}
prev = it;
}
}
void ParseBootToHostDuration(std::map<std::string, int64_t>& rebootStages,
std::span<const Checkpoint> checkpoints)
{
if (checkpoints.empty())
{
return;
}
for (const auto& checkpoint : checkpoints)
{
if (checkpoint.name == "HostReleased")
{
rebootStages[std::string(kBMCBootToHost)] += checkpoint.monoTimeMs;
}
}
}
void ParseStageDurations(std::map<std::string, int64_t>& rebootStages,
std::span<const Checkpoint> checkpoints)
{
if (checkpoints.empty())
{
return;
}
for (auto it = checkpoints.begin() + 1; it != checkpoints.end(); ++it)
{
std::string name = it->name;
// Skip HW events.
if (name.starts_with(kHostState) || name.starts_with(kOSStatus) ||
name.starts_with(kTrayPowerCycle))
{
continue;
}
if (name.ends_with(":BEGIN"))
{
name = "TransitioningTo" +
name.substr(0, name.size() - std::strlen(":BEGIN"));
}
RecordDuration(rebootStages, name, *(it - 1), *it);
}
}
BreakdownStagesInMs
ParseHostRebootStages(std::span<const Checkpoint> origCheckpoints)
{
BreakdownStagesInMs rebootStages;
std::vector<Checkpoint> checkpoints(origCheckpoints.begin(),
origCheckpoints.end());
if (checkpoints.empty())
{
return {};
}
FormatCheckpoints(checkpoints);
ParseOffDuration(rebootStages, checkpoints);
ParseOffToPowerCycleDuration(rebootStages, checkpoints);
ParseEventDurations(rebootStages, "Firmware", checkpoints,
{kHostRunning, kOSInactive}, {kOSStandby});
ParseTransitionEventDurations(rebootStages, "TransitioningToOff",
checkpoints, {"Shutdown", kOSInactive},
{kHostTransitioningToOff, kHostOff});
ParseTransitionEventDurations(rebootStages, "TransitioningToOff",
checkpoints, {"Shutdown"}, {kOSInactive});
ParseTransitionEventDurations(rebootStages, "TransitioningToOff",
checkpoints, {kHostTransitioningToOff},
{kHostOff});
ParseStageDurations(rebootStages, checkpoints);
return rebootStages;
}
BreakdownStagesInMs
ParseBMCRebootStages(std::span<const Checkpoint> checkpoints)
{
BreakdownStagesInMs rebootStages;
if (checkpoints.empty())
{
return {};
}
ParseEventDurations(rebootStages, "Shutdown", checkpoints, {"RebootStart"},
{kTrayPowerCycle});
ParseBootToHostDuration(rebootStages, checkpoints);
return rebootStages;
}
void UpdateKernelStageDuration(std::map<std::string, int64_t>& stages,
std::span<const Duration> duration_list)
{
// Do nothing if it's already set.
if (stages.find("Kernel") != stages.end())
{
return;
}
std::span<const Duration>::iterator kernel_duration = std::find_if(
duration_list.begin(), duration_list.end(),
[](const Duration& duration) { return duration.name == "Kernel"; });
if (kernel_duration != duration_list.end())
{
// Kernel stage should always be assigned even without
// TransitioningToUserspace stage.
stages["Kernel"] = kernel_duration->durationMs;
if (stages.find("TransitioningToUserspace") != stages.end())
{
// TransitioningToUserspace = TransitioningToKernel + Kernel.
stages["TransitioningToKernel"] =
stages["TransitioningToUserspace"] - stages["Kernel"];
stages.erase("TransitioningToUserspace");
if (stages["TransitioningToKernel"] < 0)
{
stages["TransitioningToKernel"] = 0;
}
}
}
}
double GetTimeOffsetDuration(std::span<const Duration> durations)
{
auto timeOffset = std::find_if(
durations.begin(), durations.end(),
[](const Duration& duration) { return duration.name == "TimeOffset"; });
if (timeOffset != durations.end())
{
return static_cast<double>(timeOffset->durationMs);
}
return 0.0;
}
double GetTotalRebootTime(const Checkpoint& start, const Checkpoint& end,
std::span<const Duration> durations)
{
double timeOffsetMs = GetTimeOffsetDuration(durations);
double totalMs = static_cast<double>(end.wallTimeMs - start.wallTimeMs);
return totalMs - timeOffsetMs;
}
void UpdateHostOffStageDuration(std::map<std::string, int64_t>& stages,
double totalTimeMs)
{
if (!stages.contains("Off"))
{
return;
}
int64_t totalMs = 0;
for (auto const& [stage, durationMs] : stages)
{
totalMs += durationMs;
}
int64_t& offStage = stages["Off"];
offStage += static_cast<int64_t>(totalTimeMs) - totalMs;
// NOTE: In case of a warm reboot and incorrect TimeOffset, the Off stage
// will be a minus number after calculation. Set to zero in this case.
if (offStage < 0)
{
offStage = 0;
}
}
// End of logics from
// google3/platforms/mosys/modules/bmc/gbmcipmi/boottime_parsing.cc
void PopulateDefaultBootTimeProperties(
const std::shared_ptr<bmcweb::AsyncResp>& aResp)
{
aResp->res.jsonValue["@odata.type"] =
"#GoogleBootTime.v1_0_0.GoogleBootTime";
aResp->res.jsonValue["Name"] = "Boot time data";
aResp->res.jsonValue["Id"] = "BootTimeData";
aResp->res.jsonValue["Breakdown"] = nlohmann::json::array();
aResp->res.jsonValue["Durations"] = nlohmann::json::array();
aResp->res.jsonValue["RebootFlow"] = nlohmann::json::array();
aResp->res.jsonValue["Stat"]["IsRebooting"] = false;
aResp->res.jsonValue["Total"] = 0;
}
// Logics to calculate Boot time in metrics are from
// google3/platforms/mosys/modules/bmc/gbmcipmi/bmc_context.cc
void PopulateBreakdownInResponse(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const BreakdownStagesInMs& stages)
{
for (auto const& [name, duration] : stages)
{
nlohmann::json::object_t stage;
stage["Stage"] = name;
stage["Duration"] = static_cast<double>(duration) / 1000.0;
aResp->res.jsonValue["Breakdown"].push_back(stage);
}
}
void PopulateDurationInResponse(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<std::vector<Duration>>& durations)
{
for (auto const& duration : *durations)
{
nlohmann::json::object_t stage;
stage["Name"] = duration.name;
stage["Duration"] = static_cast<double>(duration.durationMs) / 1000.0;
aResp->res.jsonValue["Durations"].push_back(stage);
}
}
void PopulateStatIsRebootingInResponse(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<bool>& isRebooting)
{
aResp->res.jsonValue["Stat"]["IsRebooting"] = *isRebooting;
}
void PopulateTotalTimeInResponse(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<std::vector<Checkpoint>>& checkpoints,
const std::shared_ptr<std::vector<Duration>>& durations)
{
double total = 0;
if (!checkpoints->empty())
{
total = GetTotalRebootTime(checkpoints->front(), checkpoints->back(),
*durations);
total /= 1000.0;
}
aResp->res.jsonValue["Total"] = total;
}
void GetHostBreakdownStages(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<std::vector<Checkpoint>>& hostCheckpoints,
const std::shared_ptr<std::vector<Duration>>& hostDurations)
{
BreakdownStagesInMs stages = ParseHostRebootStages(*hostCheckpoints);
UpdateKernelStageDuration(stages, *hostDurations);
// Update Off stage duration by using total time.
double totalMs = 0.0;
if (!hostCheckpoints->empty())
{
totalMs = GetTotalRebootTime(hostCheckpoints->front(),
hostCheckpoints->back(), *hostDurations);
}
UpdateHostOffStageDuration(stages, totalMs);
PopulateBreakdownInResponse(aResp, stages);
}
void GetBmcBreakdownStages(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<std::vector<Checkpoint>>& bmcCheckpoints)
{
BreakdownStagesInMs stages = ParseBMCRebootStages(*bmcCheckpoints);
PopulateBreakdownInResponse(aResp, stages);
}
void CalibrateWallTimeAndPopulate(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::vector<Checkpoint>& checkpoints)
{
// Add entries with calibrated wall_time.
int64_t curWallTime = 0;
for (auto it = checkpoints.begin(); it != checkpoints.end(); ++it)
{
if (it == checkpoints.begin())
{
curWallTime = checkpoints.begin()->wallTimeMs;
}
else if ((it - 1)->monoTimeMs > it->monoTimeMs)
{
// Increment by 0.1s for correct timestamp ordering.
curWallTime = curWallTime + 100;
}
else
{
curWallTime += it->monoTimeMs - (it - 1)->monoTimeMs;
}
nlohmann::json::object_t stage;
stage["Stage"] = it->name;
if (curWallTime >= 0)
{
stage["StartTime"] = time_utils::getDateTimeUintMs(
static_cast<uint64_t>(curWallTime));
}
stage["MonotonicStartTime"] = it->monoTimeMs;
aResp->res.jsonValue["RebootFlow"].push_back(stage);
}
}
void GetHostRebootFlow(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<std::vector<Checkpoint>>& hostCheckpoints)
{
CalibrateWallTimeAndPopulate(aResp, *hostCheckpoints);
}
void GetBmcRebootFlow(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<std::vector<Checkpoint>>& bmcCheckpoints)
{
CalibrateWallTimeAndPopulate(aResp, *bmcCheckpoints);
}
void PopulateStatPowerSourceInResponse(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::shared_ptr<std::vector<Checkpoint>>& checkpoints)
{
google_boot_time::PowerSourceTypes powerSource =
google_boot_time::PowerSourceTypes::Unknown;
if (!checkpoints->empty())
{
if (PowerCycleCount(*checkpoints) > 0)
{
powerSource = google_boot_time::PowerSourceTypes::AC;
}
else
{
powerSource = google_boot_time::PowerSourceTypes::DC;
}
}
aResp->res.jsonValue["Stat"]["PowerSource"] = powerSource;
}
// End of logic from
// google3/platforms/mosys/modules/bmc/gbmcipmi/bmc_context.cc
void GetBootTimeCheckpoints(
const std::shared_ptr<boot_time_monitor::api::IBoottimeApi>& api,
const boot_time_monitor::NodeConfig& node,
const std::shared_ptr<std::vector<Checkpoint>>& checkpoints,
const std::function<void()>& successCb)
{
std::vector<CheckpointTuple> checkpoints_source =
api->GetNodeCheckpointList(node);
checkpoints->reserve(checkpoints_source.size());
for (auto& cp : checkpoints_source)
{
checkpoints->emplace_back(std::get<0>(cp), std::get<1>(cp),
std::get<2>(cp));
}
successCb();
}
void GetBootTimeDurations(
const std::shared_ptr<boot_time_monitor::api::IBoottimeApi>& api,
const boot_time_monitor::NodeConfig& node,
const std::shared_ptr<std::vector<Duration>>& durations,
const std::function<void()>& successCb)
{
std::vector<DurationTuple> durations_source =
api->GetNodeAdditionalDurations(node);
durations->reserve(durations_source.size());
for (auto& dur : durations_source)
{
durations->emplace_back(std::get<0>(dur), std::get<1>(dur));
}
successCb();
}
void GetIsRebooting(const std::shared_ptr<boot_time_monitor::api::IBoottimeApi>& api,
const boot_time_monitor::NodeConfig& node,
const std::shared_ptr<bool>& isRebooting,
const std::function<void()>& successCb)
{
bool rebooting = api->IsNodeRebooting(node);
*isRebooting = rebooting;
successCb();
}
} // namespace redfish::boot_time_utils