blob: 74a19a66e4fcd5f79d3a9cbfb61686870eca29d3 [file] [log] [blame]
#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_