| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MANAGER_DIAGNOSTIC_DATA_H_ | |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MANAGER_DIAGNOSTIC_DATA_H_ | |
| #include <algorithm> | |
| #include <array> | |
| #include <cctype> | |
| #include <chrono> // NOLINT | |
| #include <cstdint> | |
| #include <fstream> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <ratio> // NOLINT | |
| #include <string> | |
| #include <string_view> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "boost/algorithm/string/predicate.hpp" // NOLINT | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "routing.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "str_utility.hpp" | |
| #include "error_messages.hpp" | |
| #include "query.hpp" | |
| #include "registries/privilege_registry.hpp" | |
| #include "dbus_utils.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/unpack_properties.hpp" | |
| 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"}; | |
| constexpr const char 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; | |
| }); | |
| } | |
| // On-demand read of BootId | |
| // See https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#random | |
| std::ifstream bootIdFile("/proc/sys/kernel/random/boot_id"); | |
| if (bootIdFile.is_open()) { | |
| std::string line; | |
| if (std::getline(bootIdFile, line)) { | |
| // Remove potential trailing newline or spaces | |
| line.erase( | |
| std::remove_if(line.begin(), line.end(), | |
| [](unsigned char c) { return std::isspace(c); }), | |
| line.end()); | |
| asyncResp->res.jsonValue["BootInfo"]["BootId"] = line; | |
| } | |
| } else { | |
| BMCWEB_LOG_ERROR << "Failed to open /proc/sys/kernel/random/boot_id"; | |
| } | |
| } | |
| 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 | |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_MANAGER_DIAGNOSTIC_DATA_H_ |