blob: 1148a0c0b2ce5484e91d10b105e2481679c663fd [file] [log] [blame]
#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