| #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 |