| |
| #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 |