| #ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME |
| #pragma once |
| |
| #include "absl/time/time.h" |
| #include "boottime_api/boottime_api.h" |
| #include "boottime_api/boottime_metrics.h" |
| #include "boottime_api/resource.h" |
| |
| #include "async_resp.hpp" |
| #include "error_messages.hpp" |
| #include "generated/enums/google_boot_time.hpp" |
| #include "boottime.hpp" |
| #include "time_utils.hpp" |
| |
| #include <absl/status/status.h> |
| #include <absl/status/statusor.h> |
| #include <absl/strings/str_cat.h> |
| |
| #include <nlohmann/json.hpp> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <cstring> |
| #include <map> |
| #include <span> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <vector> |
| |
| namespace redfish::boottime |
| { |
| |
| 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. |
| std::string_view kLockFile = boot_time_monitor::def::kLockFile; |
| 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."); |
| } |
| |
| 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. |
| */ |
| 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()) |
| { |
| return absl::NotFoundError("Checkpoints is empty."); |
| } |
| |
| 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)); |
| } |
| |
| 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"] = oDataId; |
| asyncResp->res.jsonValue["Breakdown"] = ConvertToJson(breakdown); |
| asyncResp->res.jsonValue["Durations"] = ConvertToJson(durations); |
| asyncResp->res.jsonValue["RebootFlow"] = ConvertToJson(checkpoints); |
| // Total is in seconds for Redfish. |
| asyncResp->res.jsonValue["Total"] = totalMs / 1000.0; |
| asyncResp->res.jsonValue["Stat"] = { |
| {"PowerSource", GetPowerType(checkpoints)}, |
| {"IsRebooting", isRebooting}, |
| {"UpTime", nodeUpTime.has_value() ? nlohmann::json(nodeUpTime.value()) |
| : nlohmann::json()}, |
| {"UpTimestamp", nodeUpTimestamp.has_value() |
| ? nlohmann::json(nodeUpTimestamp.value()) |
| : nlohmann::json()}, |
| {"LoaderReadyTimestamp", loaderTimestamp.has_value() |
| ? nlohmann::json(loaderTimestamp.value()) |
| : nlohmann::json()}}; |
| |
| 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. |
| */ |
| 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); |
| |
| if (!idx.ok() || *idx >= objects.size()) |
| { |
| return absl::NotFoundError( |
| absl::StrCat("Invalid systemName index: ", systemName, |
| ". Error: ", idx.status().ToString())); |
| } |
| |
| boot_time_monitor::NodeConfig node = { |
| .node_name = "host" + std::to_string(*idx), |
| .tag = std::string(boot_time_monitor::kBootTimeTagHost), |
| }; |
| std::string oDataId = absl::StrCat("/redfish/v1/Systems/", systemName, |
| "/Oem/Google/BootTime"); |
| 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. |
| */ |
| 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), |
| }; |
| |
| std::string oDataId = "/redfish/v1/Managers/bmc/Oem/Google/BootTime"; |
| return PopulateBootTimeData(asyncResp, oDataId, node); |
| } |
| |
| } // namespace redfish::boottime |
| |
| #endif // End of BMCWEB_ENABLE_REDFISH_BOOT_TIME |