blob: 00fd0a1568f417eb6a192268c360238bcbce922e [file] [log] [blame]
#pragma once
#include "app.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "http_request.hpp"
#include "managed_store.hpp"
#include "openbmc_dbus_rest.hpp"
#include "privileges.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "routing.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <nlohmann/json.hpp>
#include <sdbusplus/asio/property.hpp>
#include <string>
#include <string_view>
#include <unordered_set>
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish
{
using SensorVariantType = dbus::utility::DbusVariantType;
const std::unordered_set<std::string> daemonDoublePropertiesSet{
"KernelTimeSeconds", "UserTimeSeconds", "UptimeSeconds"};
const std::unordered_set<std::string> daemonU32TPropertiesSet{
"ResidentSetSizeBytes", "NFileDescriptors", "RestartCount"};
constexpr std::array bootupTimeProperties = {
"FirmwareTimestampMonotonic", "LoaderTimestampMonotonic",
"KernelTimestampMonotonic", "InitRDTimestampMonotonic",
"UserspaceTimestampMonotonic", "FinishTimestampMonotonic"};
constexpr std::array bootupTimeMetricName = {
"FirmwareTimeSeconds", "LoaderTimeSeconds", "KernelTimeSeconds",
"InitrdTimeSeconds", "UserSpaceTimeSeconds"};
const std::string healthMon = "xyz.openbmc_project.HealthMon";
inline std::optional<std::pair<std::string, std::string>>
checkAndGetSoleGetSubTreeResponse(
const dbus::utility::MapperGetSubTreeResponse& subtree)
{
if (subtree.empty())
{
BMCWEB_LOG_DEBUG << "Can't find bmc D-Bus object!";
return std::nullopt;
}
// TODO: Consider Multi-BMC cases
// Assume only 1 bmc D-Bus object
// Throw an error if there is more than 1
if (subtree.size() > 1)
{
BMCWEB_LOG_DEBUG << "Found more than 1 bmc D-Bus object!";
return std::nullopt;
}
if (subtree[0].first.empty() || subtree[0].second.size() != 1)
{
BMCWEB_LOG_DEBUG << "Error getting bmc D-Bus object!";
return std::nullopt;
}
return std::make_pair(subtree[0].first, subtree[0].second[0].first);
}
// Returns the sole non-Object Mapper service
inline std::optional<std::string> checkAndGetSoleNonMapperGetObjectResponse(
const dbus::utility::MapperGetObject& objects)
{
if (objects.empty())
{
return std::nullopt;
}
for (const std::pair<std::string, std::vector<std::string>>&
serviceAndIfaces : objects)
{
if (serviceAndIfaces.first != "xyz.openbmc_project.ObjectMapper")
{
return serviceAndIfaces.first;
}
}
return std::nullopt;
}
inline void populateProcessorStatistics(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
const std::array<std::pair<std::string, std::string>, 2>
processorStatisticMapping{
{{"/xyz/openbmc_project/metric/bmc/cpu/kernel", "KernelPercent"},
{"/xyz/openbmc_project/metric/bmc/cpu/user", "UserPercent"}}};
for (const auto& mapping : processorStatisticMapping)
{
dbus_utils::getProperty<double>(
healthMon, mapping.first, "xyz.openbmc_project.Metric.Value",
"Value", context,
[asyncResp, property(mapping.second)](
const boost::system::error_code ec2, double value) {
if (ec2)
{
BMCWEB_LOG_ERROR << "DBus response error on getProperty "
<< ec2;
return;
}
asyncResp->res.jsonValue["ProcessorStatistics"][property] = value;
});
}
}
inline void populateMemoryStatistics(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<double>(
healthMon, "/xyz/openbmc_project/metric/bmc/memory/available",
"xyz.openbmc_project.Metric.Value", "Value", context,
[asyncResp](const boost::system::error_code ec2, double value) {
if (ec2)
{
BMCWEB_LOG_ERROR << "DBus response error on getProperty " << ec2;
return;
}
asyncResp->res.jsonValue["MemoryStatistics"]["AvailableBytes"] =
static_cast<int64_t>(value);
});
}
inline void
populateRWFSStatistics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<double>(
healthMon, "/xyz/openbmc_project/metric/bmc/storage/rw",
"xyz.openbmc_project.Metric.Value", "Value", context,
[asyncResp](const boost::system::error_code ec, double value) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus response error on getProperty " << ec;
return;
}
asyncResp->res.jsonValue["Oem"]["Google"]["RWStorageSpaceUsageBytes"] =
static_cast<int64_t>(value);
});
}
inline void
populateTMPFSStatistics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<double>(
healthMon, "/xyz/openbmc_project/metric/bmc/storage/tmp",
"xyz.openbmc_project.Metric.Value", "Value", context,
[asyncResp](const boost::system::error_code ec, double value) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus response error on getProperty " << ec;
return;
}
asyncResp->res.jsonValue["Oem"]["Google"]["TMPStorageSpaceUsageBytes"] =
static_cast<int64_t>(value);
});
}
/**
* @brief Find the endpoints for an Association definition.
*
* @param[i,o] asyncResp - Async response object
*
* @return void
*/
inline void
findBmcInventory(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
populateProcessorStatistics(asyncResp);
populateMemoryStatistics(asyncResp);
populateRWFSStatistics(asyncResp);
populateTMPFSStatistics(asyncResp);
}
inline void getBootInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
const std::array<std::string, 2> bootInfoProperties = {
{"BootCount", "CrashCount"}};
for (const std::string& propertyName : bootInfoProperties)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<uint32_t>(
"xyz.openbmc_project.Metric", "/xyz/openbmc_project/metric/bmc0",
"xyz.openbmc_project.Metric.BMC", propertyName, context,
[asyncResp, propertyName](const boost::system::error_code ec,
uint32_t property) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus error getting boot count";
return;
}
asyncResp->res.jsonValue["BootInfo"][propertyName] = property;
});
}
}
inline void calculateBootTimeResp(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
properties)
{
static_assert(bootupTimeProperties.size() ==
bootupTimeMetricName.size() + 1);
const uint32_t kernelIdx =
std::find(bootupTimeProperties.begin(), bootupTimeProperties.end(),
"KernelTimestampMonotonic") -
bootupTimeProperties.begin();
uint64_t lastTime = 0;
uint32_t metricIdx = 0;
for (const auto& s : bootupTimeMetricName)
{
aResp->res.jsonValue["BootTimeStatistics"][s] = 0.0;
}
// each metric should be calculated as next monotime - curr monotime
// some timestamps maybe 0 if the hardware doesn't support it. In this
// case we simply set that metrics to 0, and accumulate the diff until
// an available time is seen
// e.g. kernelmono initrdmono usermono finishmono is 0, 4, 0, 10
// The metric values kerneltime initrdtime usertime should be 4, 0, 6
try
{
for (uint32_t i = 0; i < bootupTimeProperties.size(); ++i)
{
const uint64_t* currTime = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), properties,
static_cast<std::string>(bootupTimeProperties[i]), currTime);
if (!success || currTime == nullptr)
{
messages::internalError(aResp->res);
return;
}
if (i == 0)
{
lastTime = *currTime;
continue;
}
uint64_t timeDiff = 0;
// KernelTime is 0 and timestamps before kernelTime are negative,
// timestamps after kernel are positive.
if (*currTime == 0 && i != kernelIdx)
{
continue;
}
if (i <= kernelIdx)
{
if (lastTime != 0)
{
timeDiff = lastTime - *currTime;
}
}
else
{
timeDiff = *currTime - lastTime;
}
lastTime = *currTime;
aResp->res.jsonValue["BootTimeStatistics"]
[bootupTimeMetricName[metricIdx]] =
static_cast<double>(timeDiff) / 1000000.0;
metricIdx = i;
}
}
catch (...)
{
messages::internalError(aResp->res);
}
}
inline void getBootTimeInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
"org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", context,
[asyncResp](const boost::system::error_code ec2,
const dbus::utility::DBusPropertiesMap& properties) {
if (ec2)
{
BMCWEB_LOG_ERROR << "DBUS error getting boot time";
return;
}
calculateBootTimeResp(asyncResp, properties);
});
}
inline void
populateDaemonEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& objectPath,
const std::string& filterServiceName)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
"xyz.openbmc_project.Metric", objectPath,
"xyz.openbmc_project.Metric.Daemon", context,
[asyncResp, filterServiceName](const boost::system::error_code ec,
const dbus::utility::DBusPropertiesMap& propertyMap) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus error getting daemon property";
return;
}
nlohmann::json daemon;
for (const auto& propertyPair : propertyMap)
{
if (propertyPair.first == "CommandLine")
{
std::string commandLine =
std::get<std::string>(propertyPair.second);
if (!filterServiceName.empty() &&
commandLine.find(filterServiceName) == std::string::npos)
{
BMCWEB_LOG_DEBUG << "Filter param exsits,skipping service:"
<< commandLine << ". Fitler service name is "
<< filterServiceName;
return;
}
daemon[propertyPair.first] = commandLine;
continue;
}
if (daemonDoublePropertiesSet.contains(propertyPair.first))
{
daemon[propertyPair.first] =
std::get<double>(propertyPair.second);
continue;
}
if (daemonU32TPropertiesSet.contains(propertyPair.first))
{
daemon[propertyPair.first] =
std::get<uint32_t>(propertyPair.second);
continue;
}
}
asyncResp->res.jsonValue["TopProcesses"].emplace_back(daemon);
});
}
inline void getDaemonInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const query_param::Query& delegatedQuery)
{
asyncResp->res.jsonValue["TopProcesses"] = nlohmann::json::array();
std::string filterServiceName;
if (!delegatedQuery.filter.empty())
{
std::vector<std::string> filterParams;
bmcweb::split(filterParams, delegatedQuery.filter, ' ');
if (filterParams.size() != 3 ||
filterParams[0] != "TopProcesses.CommandLine" ||
filterParams[1] != "eq")
{
redfish::messages::queryParameterValueFormatError(
asyncResp->res, "$filter", delegatedQuery.filter);
asyncResp->res.result(boost::beast::http::status::not_implemented);
return;
}
filterServiceName = filterParams[2];
}
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<std::vector<std::string>>(
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/metric/bmc0/daemon",
"xyz.openbmc_project.Association", "endpoints", context,
[asyncResp, filterServiceName](const boost::system::error_code ec,
const std::vector<std::string>& endpoints) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "DBus error getting daemon association endpoints";
return;
}
for (const auto& endpoint : endpoints)
{
populateDaemonEntry(asyncResp, endpoint, filterServiceName);
}
});
}
inline void getLatencyInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<double>(
"xyz.openbmc_project.Metric", "/xyz/openbmc_project/metric/bmc0",
"xyz.openbmc_project.Metric.BMC", "Latency", context,
[asyncResp](const boost::system::error_code ec, const double property) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "DBus error getting daemon association endpoints";
return;
}
asyncResp->res.jsonValue["Oem"]["Google"]["Latency"] = property;
});
}
inline void
afterGetManagerStartTime(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const boost::system::error_code& ec,
uint64_t bmcwebResetTime)
{
if (ec)
{
// Not all servers will be running in systemd, so ignore the error.
return;
}
using std::chrono::steady_clock;
std::chrono::duration<steady_clock::rep, std::micro> usReset{
bmcwebResetTime};
steady_clock::time_point resetTime{usReset};
steady_clock::time_point now = steady_clock::now();
steady_clock::duration runTime = now - resetTime;
if (runTime < steady_clock::duration::zero())
{
BMCWEB_LOG_CRITICAL << "Uptime was negative????";
messages::internalError(aResp->res);
return;
}
// Floor to the closest millisecond
using Milli = std::chrono::duration<steady_clock::rep, std::milli>;
Milli milli = std::chrono::floor<Milli>(runTime);
using SecondsFloat = std::chrono::duration<double>;
SecondsFloat sec = std::chrono::duration_cast<SecondsFloat>(milli);
aResp->res.jsonValue["ServiceRootUptimeSeconds"] = sec.count();
}
inline void
managerGetServiceRootUptime(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
{
managedStore::ManagedObjectStoreContext context(aResp);
dbus_utils::getProperty<uint64_t>(
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1/unit/bmcweb_2eservice",
"org.freedesktop.systemd1.Unit", "ActiveEnterTimestampMonotonic",
context, std::bind_front(afterGetManagerStartTime, aResp));
}
/**
* handleManagerDiagnosticData supports ManagerDiagnosticData.
* It retrieves BMC health information from various DBus resources and returns
* the information through the response.
*/
inline void handleManagerDiagnosticDataGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
query_param::QueryCapabilities capabilities = {
.canDelegateFilter = true,
};
query_param::Query delegatedQuery;
if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
delegatedQuery, capabilities))
{
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#ManagerDiagnosticData.v1_2_0.ManagerDiagnosticData";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData";
asyncResp->res.jsonValue["Id"] = "ManagerDiagnosticData";
asyncResp->res.jsonValue["Name"] = "Manager Diagnostic Data";
#ifdef BMCWEB_ENABLE_STATEFUL_BMCWEB
asyncResp->res.jsonValue["Oem"]["Google"]["GoogleManagedObjectStoreMetrics"]
["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/GoogleManagedObjectStoreMetrics";
#endif
managerGetServiceRootUptime(asyncResp);
// Start the first step of the 4-step process to populate
// the BMC diagnostic data
findBmcInventory(asyncResp);
getBootInfo(asyncResp);
getBootTimeInfo(asyncResp);
getDaemonInfo(asyncResp, delegatedQuery);
getLatencyInfo(asyncResp);
}
inline void requestRoutesManagerDiagnosticData(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/ManagerDiagnosticData")
.privileges(redfish::privileges::getManagerDiagnosticData)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleManagerDiagnosticDataGet, std::ref(app)));
}
} // namespace redfish