| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_BOOTTIME_H_ |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_BOOTTIME_H_ |
| |
| #include <string_view> |
| |
| #ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME |
| #include <charconv> |
| #include <cstdint> |
| #include <cstring> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <span> // NOLINT |
| #include <string> |
| #include <system_error> // NOLINT |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/time/time.h" |
| #include "boottime_api/boottime_api.h" |
| #include "boottime_api/boottime_api_common.h" |
| #include "boottime_api/boottime_metrics.h" |
| #include "boottime_api/node_config.h" |
| #include "boottime_api/resource.h" |
| #include "time_utils.hpp" |
| #endif // BMCWEB_ENABLE_REDFISH_BOOT_TIME |
| |
| #include "async_resp.hpp" |
| #include "generated/enums/google_boot_time.hpp" |
| #include <nlohmann/json.hpp> |
| |
| namespace redfish::boottime { |
| |
| struct BootTimeResponseData { |
| std::string_view oDataId; |
| nlohmann::json::array_t breakdown; |
| nlohmann::json::array_t durations; |
| nlohmann::json::array_t checkpoints; |
| double totalMs; |
| bool isRebooting; |
| google_boot_time::PowerSourceTypes powerType; |
| nlohmann::json nodeUpTime; |
| nlohmann::json nodeUpTimestamp; |
| nlohmann::json loaderTimestamp; |
| bool disabled; |
| nlohmann::json warningMessage; |
| }; |
| |
| // A common API for populating BootTime data with a consistent schema. |
| inline void PopulateResponse( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const BootTimeResponseData& data) { |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#GoogleBootTime.v1_0_0.GoogleBootTime"; |
| asyncResp->res.jsonValue["Name"] = "Boot time data"; |
| asyncResp->res.jsonValue["Id"] = "BootTimeData"; |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#GoogleBootTime.v1_0_0.GoogleBootTime"; |
| asyncResp->res.jsonValue["@odata.id"] = data.oDataId; |
| asyncResp->res.jsonValue["Breakdown"] = data.breakdown; |
| asyncResp->res.jsonValue["Durations"] = data.durations; |
| asyncResp->res.jsonValue["RebootFlow"] = data.checkpoints; |
| // Total is in seconds for Redfish. |
| asyncResp->res.jsonValue["Total"] = data.totalMs / 1000.0; |
| asyncResp->res.jsonValue["Stat"] = { |
| {"PowerSource", data.powerType}, |
| {"IsRebooting", data.isRebooting}, |
| {"UpTime", data.nodeUpTime}, |
| {"UpTimestamp", data.nodeUpTimestamp}, |
| {"LoaderReadyTimestamp", data.loaderTimestamp}, |
| }; |
| asyncResp->res.jsonValue["Status"] = { |
| {"State", data.disabled ? "Disabled" : "Enabled"}, |
| {"Health", "Ok"}, |
| }; |
| if (!data.warningMessage.is_null()) { |
| asyncResp->res.jsonValue["Status"]["Health"] = "Warning"; |
| asyncResp->res.jsonValue["Status"]["Conditions"] = nlohmann::json::array(); |
| asyncResp->res.jsonValue["Status"]["Conditions"].push_back( |
| data.warningMessage); |
| } |
| } |
| |
| // Populates the response with data indicating the BootTime feature is disabled. |
| inline void PopulateDisabledResponse( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| std::string_view oDataId) { |
| BootTimeResponseData data = {}; |
| data.oDataId = oDataId; |
| data.disabled = true; |
| data.isRebooting = true; |
| PopulateResponse(asyncResp, data); |
| } |
| |
| inline nlohmann::json newWarningMessage(std::string_view message, |
| std::string_view resolution) { |
| return { |
| {"MessageId", "Base.1.13.0.ResourceNotFound"}, |
| {"Message", message}, |
| {"Severity", "Warning"}, |
| {"Resolution", resolution}, |
| }; |
| } |
| |
| #ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME |
| |
| using boot_time_monitor::resource::FileResource; |
| using boot_time_monitor::type::Checkpoint; |
| using boot_time_monitor::type::CheckpointEntry; |
| using boot_time_monitor::type::Duration; |
| using boot_time_monitor::type::DurationEntry; |
| using boot_time_monitor::type::NamedDurationsMs; |
| |
| // Import default values here for mocking. |
| inline std::string_view kLockFile = boot_time_monitor::def::kLockFile; |
| inline std::string_view kDataDir = boot_time_monitor::def::kDataDir; |
| |
| inline std::vector<CheckpointEntry> ToCheckpointEntry( |
| std::span<const Checkpoint> inputTuples) { |
| std::vector<CheckpointEntry> checkpoints; |
| checkpoints.reserve(inputTuples.size()); |
| for (const auto& t : inputTuples) { |
| checkpoints.push_back(CheckpointEntry(t)); |
| } |
| |
| return checkpoints; |
| } |
| |
| inline std::vector<DurationEntry> ToDurationEntry( |
| std::span<const Duration> inputTuples) { |
| std::vector<DurationEntry> durations; |
| durations.reserve(inputTuples.size()); |
| for (const auto& t : inputTuples) { |
| durations.push_back(DurationEntry(t)); |
| } |
| return durations; |
| } |
| |
| inline nlohmann::json::array_t ConvertToJson(const NamedDurationsMs& stages) { |
| nlohmann::json::array_t ret = nlohmann::json::array(); |
| for (const auto& [name, duration] : stages) { |
| nlohmann::json::object_t breakdownEntry; |
| breakdownEntry["Stage"] = name; |
| breakdownEntry["Duration"] = static_cast<double>(duration) / 1000.0; |
| ret.push_back(breakdownEntry); |
| } |
| return ret; |
| } |
| |
| inline nlohmann::json::array_t ConvertToJson( |
| std::span<const CheckpointEntry> checkpoints) { |
| nlohmann::json::array_t ret = nlohmann::json::array(); |
| for (const auto& checkpoint : checkpoints) { |
| nlohmann::json::object_t checkpointEntry; |
| checkpointEntry["Stage"] = checkpoint.name; |
| checkpointEntry["StartTime"] = time_utils::getDateTimeUintMs( |
| static_cast<uint64_t>(checkpoint.wall_time_ms)); |
| checkpointEntry["MonotonicStartTime"] = checkpoint.monotonic_time_ms; |
| ret.push_back(checkpointEntry); |
| } |
| return ret; |
| } |
| |
| inline nlohmann::json::array_t ConvertToJson( |
| std::span<const DurationEntry> durations) { |
| nlohmann::json::array_t ret = nlohmann::json::array(); |
| for (const auto& duration : durations) { |
| nlohmann::json::object_t durationEntry; |
| durationEntry["Name"] = duration.name; |
| durationEntry["Duration"] = static_cast<double>(duration.duration_ms) / |
| 1000.0; // Convert ms to seconds |
| ret.push_back(durationEntry); |
| } |
| return ret; |
| } |
| |
| /** |
| * @brief Parses a system name to determine its corresponding host index. |
| * |
| * This function validates and extracts a host index from a system name string, |
| * adjusting its behavior based on whether the system is in a multihost |
| * configuration. |
| * |
| * In a multihost configuration (`multihost` is `true`), this function expects |
| * the system name to be in the format "system<N>", where <N> is a 1-based |
| * integer (e.g., "system1", "system2"). On success, the `index` parameter |
| * is set to the corresponding 0-based index (N-1). |
| * |
| * In a single-host configuration (`multihost` is `false`), this function |
| * expects the system name to be exactly "system". On success, the `index` |
| * parameter is set to `0`. |
| * |
| * @param systemName The system name string to parse (e.g., "system1"). |
| * @param multihost A flag indicating if the system is in a |
| * multihost environment. |
| * @return An `absl::StatusOr<size_t>` containing the parsed 0-based host index |
| * if successful, or an `absl::Status` with an error otherwise. |
| */ |
| inline absl::StatusOr<size_t> parseSystemNameToIndex( |
| const std::string_view systemName, const bool multihost) { |
| if (!multihost) { |
| // The system name should just be "system" in single host. |
| if (systemName == "system") { |
| return 0; |
| } |
| return absl::NotFoundError( |
| absl::StrCat("Invalid single node machine system name: ", systemName)); |
| } |
| |
| constexpr std::string_view prefix = "system"; |
| |
| if (systemName.size() < prefix.size() || |
| systemName.substr(0, prefix.size()) != prefix) { |
| return absl::NotFoundError( |
| "system name must start with 'system' but got: " + |
| std::string(systemName)); |
| } |
| |
| std::string_view number_part = systemName.substr(prefix.length()); |
| |
| if (number_part.empty()) { |
| return absl::NotFoundError( |
| "system name must has index on multi-node machines."); |
| } |
| |
| size_t number = 0; |
| const auto result = std::from_chars( |
| number_part.data(), number_part.data() + number_part.size(), number); |
| |
| if (result.ec == std::errc() && |
| result.ptr == number_part.data() + number_part.size()) { |
| if (number > 0) { |
| return number - 1; // System names are 1-based, index is 0-based |
| } |
| } |
| |
| return absl::NotFoundError( |
| "system index parsing failed or number was not a positive integer."); |
| } |
| |
| inline google_boot_time::PowerSourceTypes GetPowerType( |
| std::span<const CheckpointEntry> checkpoints) { |
| if (checkpoints.empty()) { |
| return google_boot_time::PowerSourceTypes::Unknown; |
| } |
| if (boot_time_monitor::metrics::PowerCycleCount(checkpoints) > 0) { |
| return google_boot_time::PowerSourceTypes::AC; |
| } |
| return google_boot_time::PowerSourceTypes::DC; |
| } |
| |
| /** |
| * @brief Populates Redfish BootTime data for a given node. |
| * |
| * This function retrieves boot time data (checkpoints, durations) from |
| * the boot time monitor API, processes it based on the node's tag (Host or |
| * BMC), and populates the asynchronous Redfish response with the structured |
| * data. |
| * |
| * @param asyncResp A shared pointer to the bmcweb::AsyncResp object for |
| * populating the Redfish response. |
| * @param oDataId The OData ID string for the Redfish resource. |
| * @param node The boot_time_monitor::NodeConfig specifying the target |
| * node. |
| * @return An `absl::Status` indicating success or failure. |
| */ |
| inline absl::Status PopulateBootTimeData( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| std::string_view oDataId, boot_time_monitor::NodeConfig node) { |
| boot_time_monitor::api::BoottimeApi api(kLockFile, absl::Seconds(5)); |
| auto file = std::make_unique<FileResource>(node.node_name, kDataDir); |
| absl::Status status = api.RegisterNode(node, std::move(file)); |
| if (status.code() != absl::StatusCode::kOk && |
| status.code() != absl::StatusCode::kAlreadyExists) { |
| return absl::InternalError( |
| absl::StrCat("Failed to register ", node.node_name, |
| " node due to internal error: ", status.ToString())); |
| } |
| |
| NamedDurationsMs breakdown; |
| auto checkpoints = ToCheckpointEntry(api.GetNodeCheckpointList(node)); |
| auto durations = ToDurationEntry(api.GetNodeAdditionalDurations(node)); |
| bool isRebooting = api.IsNodeRebooting(node); |
| std::optional<int64_t> nodeUpTime = |
| boot_time_monitor::metrics::ParseNodeUpTimeMs(checkpoints); |
| std::optional<int64_t> nodeUpTimestamp = |
| boot_time_monitor::metrics::ParseNodeUpTimestamp(checkpoints); |
| std::optional<int64_t> loaderTimestamp = |
| boot_time_monitor::metrics::ParseLastLoaderTimestamp(checkpoints); |
| double totalMs = 0.0; |
| |
| if (checkpoints.empty()) { |
| BootTimeResponseData data = {}; |
| data.oDataId = oDataId; |
| data.warningMessage = newWarningMessage( |
| "Checkpoints is empty.", |
| "Please check if host/bmc sends checkpoints correctly."); |
| data.isRebooting = true; |
| PopulateResponse(asyncResp, data); |
| return absl::OkStatus(); |
| } |
| |
| if (node.tag == boot_time_monitor::kBootTimeTagHost) { |
| breakdown = boot_time_monitor::metrics::ParseHostRebootStages(checkpoints); |
| // Make sure kernel stage always present. |
| boot_time_monitor::metrics::UpdateKernelStageDuration(breakdown, durations); |
| totalMs = boot_time_monitor::metrics::GetTotalRebootTime( |
| checkpoints.front(), checkpoints.back(), durations); |
| // Update Off stage duration by using total time. |
| boot_time_monitor::metrics::UpdateHostOffStageDuration(breakdown, totalMs); |
| } else if (node.tag == boot_time_monitor::kBootTimeTagBMC) { |
| breakdown = boot_time_monitor::metrics::ParseBMCRebootStages(checkpoints); |
| totalMs = boot_time_monitor::metrics::GetTotalRebootTime( |
| checkpoints.front(), checkpoints.back(), durations); |
| } else { |
| return absl::NotFoundError( |
| absl::StrCat("Invalid node tag(type): ", node.tag)); |
| } |
| |
| BootTimeResponseData data = {}; |
| data.oDataId = oDataId; |
| data.breakdown = ConvertToJson(breakdown); |
| data.durations = ConvertToJson(durations); |
| data.checkpoints = ConvertToJson(checkpoints); |
| data.totalMs = totalMs; |
| data.powerType = GetPowerType(checkpoints); |
| data.isRebooting = isRebooting; |
| data.nodeUpTime = nodeUpTime.has_value() ? nlohmann::json(nodeUpTime.value()) |
| : nlohmann::json(); |
| data.nodeUpTimestamp = nodeUpTimestamp.has_value() |
| ? nlohmann::json(nodeUpTimestamp.value()) |
| : nlohmann::json(); |
| data.loaderTimestamp = loaderTimestamp.has_value() |
| ? nlohmann::json(loaderTimestamp.value()) |
| : nlohmann::json(); |
| |
| PopulateResponse(asyncResp, data); |
| return absl::OkStatus(); |
| } |
| |
| /** |
| * @brief Populates Redfish BootTime data for a specific host system. |
| * |
| * This function determines the host index from the system name, constructs |
| * the appropriate node configuration for the host, and then calls |
| * PopulateBootTimeData to retrieve and populate the Redfish response. |
| * |
| * @param asyncResp A shared pointer to the bmcweb::AsyncResp object. |
| * @param systemName The name of the system (e.g., "system", "system1"). |
| * @param objects A vector of strings, representing dbus objects under |
| * the system. |
| * @return An `absl::Status` indicating success or failure. |
| */ |
| inline absl::Status PopulateHostData( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, const std::vector<std::string>& objects) { |
| bool isMultiHost = (objects.size() != 1); |
| absl::StatusOr<size_t> idx = parseSystemNameToIndex(systemName, isMultiHost); |
| std::string oDataId = |
| absl::StrCat("/redfish/v1/Systems/", systemName, "/Oem/Google/BootTime"); |
| |
| if (!idx.ok() || *idx >= objects.size()) { |
| BootTimeResponseData data = {}; |
| data.oDataId = oDataId; |
| data.warningMessage = |
| newWarningMessage(absl::StrCat("Invalid systemName index: ", systemName, |
| ". Error: ", idx.status().ToString()), |
| "Please assign correct system index."); |
| data.isRebooting = true; |
| PopulateResponse(asyncResp, data); |
| return absl::OkStatus(); |
| } |
| |
| boot_time_monitor::NodeConfig node = { |
| .node_name = "host" + std::to_string(*idx), |
| .tag = std::string(boot_time_monitor::kBootTimeTagHost), |
| }; |
| return PopulateBootTimeData(asyncResp, oDataId, node); |
| } |
| |
| /** |
| * @brief Populates Redfish BootTime data for the BMC. |
| * |
| * This function constructs the node configuration for the BMC and then calls |
| * PopulateBootTimeData to retrieve and populate the Redfish response. |
| * |
| * @param asyncResp A shared pointer to the bmcweb::AsyncResp object. |
| * @return An `absl::Status` indicating success or failure. |
| */ |
| inline absl::Status PopulateBmcData( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { |
| boot_time_monitor::NodeConfig node = { |
| .node_name = "bmc", |
| .tag = std::string(boot_time_monitor::kBootTimeTagBMC), |
| }; |
| |
| return PopulateBootTimeData( |
| asyncResp, "/redfish/v1/Managers/bmc/Oem/Google/BootTime", node); |
| } |
| |
| #endif // BMCWEB_ENABLE_REDFISH_BOOT_TIME |
| } // namespace redfish::boottime |
| |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_BOOTTIME_H_ |