From 32e557279450226ed9c06312649d90b802f3d4c5 Mon Sep 17 00:00:00 2001 From: Krzysztof Grobelny Date: Tue, 13 Apr 2021 13:00:18 +0000 Subject: [PATCH] Add support for MetricDefinition scheme Added MetricDefinition node to Redfish code. Now user is able to list all available metrics in OpenBMC that are supported by Telemetry service. Metrics are grouped by reading type. MetricDefinitions contains all physical sensors supported by redfish, algorithm iterates through all chassis and collects results for each node available in that chassis (Power, Thermal, Sensors). When BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM will be enabled by default (meson option redfish-new-powersubsystem-thermalsubsystem) it will be possible to optimize this algorithm to only get sensors from Sensors node. Currently Sensors node doesn't contain all available sensors. Tested: - MetricDefinitions response is filled with existing sensors, it works with and without Telemetry service - Validated a presence of MetricDefinition members and its attributes - Successfully passed RedfishServiceValidator.py using witherspoon image on QEMU - Tested using following GET,POST requests GET /redfish/v1/TelemetryService/MetricDefinitions { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions", "@odata.type": "#MetricDefinitionCollection.MetricDefinitionCollection", "Members": [ { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Fan_Pwm" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Fan_Tach" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/HostCpuUtilization" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/HostMemoryBandwidthUtilization" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/HostPciBandwidthUtilization" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Inlet_BRD_Temp" }, { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Left_Rear_Board_Temp" } ], "Members@odata.count": 7, "Name": "Metric Definition Collection" } GET /redfish/v1/TelemetryService/MetricDefinitions/Fan_Tach { "@odata.id": "/redfish/v1/TelemetryService/MetricDefinitions/Fan_Tach", "@odata.type": "#MetricDefinition.v1_0_3.MetricDefinition", "Id": "Fan_Tach", "IsLinear": true, "MaxReadingRange": 25000.0, "MetricDataType": "Decimal", "MetricProperties": [ "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/0/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/1/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/2/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/3/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/4/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/5/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/6/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/7/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/8/Reading", "/redfish/v1/Chassis/AC_Baseboard/Thermal#/Fans/9/Reading" ], "MetricType": "Gauge", "MinReadingRange": 0.0, "Name": "Fan_Tach", "Units": "RPM" } POST redfish/v1/TelemetryService/MetricReportDefinitions, body: { "Id": "TestReport", "Metrics": [ { "MetricId": "TestMetric", "MetricProperties": [ "/redfish/v1/Chassis/Chassis0/Thermal#/Fans/3/Reading", ] } ], "MetricReportDefinitionType": "OnRequest", "ReportActions": [ "RedfishEvent", "LogToMetricReportsCollection" ] } { "@Message.ExtendedInfo": [ { "@odata.type": "#Message.v1_1_1.Message", "Message": "The resource has been created successfully", "MessageArgs": [], "MessageId": "Base.1.8.1.Created", "MessageSeverity": "OK", "Resolution": "None" } ] } Signed-off-by: Wludzik, Jozef Signed-off-by: Krzysztof Grobelny Change-Id: I3086e1302e1ba2e5442d1367939fd5507a0cbc00 --- redfish-core/include/redfish.hpp | 3 + .../include/utils/get_chassis_names.hpp | 58 +++ .../include/utils/telemetry_utils.hpp | 2 + redfish-core/lib/metric_definition.hpp | 368 ++++++++++++++++++ redfish-core/lib/telemetry_service.hpp | 3 +- 5 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 redfish-core/include/utils/get_chassis_names.hpp create mode 100644 redfish-core/lib/metric_definition.hpp diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp index 0a97150..67c5af2 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -26,6 +26,7 @@ #include "../lib/managers.hpp" #include "../lib/memory.hpp" #include "../lib/message_registries.hpp" +#include "../lib/metric_definition.hpp" #include "../lib/metric_report.hpp" #include "../lib/metric_report_definition.hpp" #include "../lib/network_protocol.hpp" @@ -200,6 +201,8 @@ class RedfishService requestRoutesMetricReportDefinition(app); requestRoutesMetricReportCollection(app); requestRoutesMetricReport(app); + requestRoutesMetricDefinitionCollection(app); + requestRoutesMetricDefinition(app); } }; diff --git a/redfish-core/include/utils/get_chassis_names.hpp b/redfish-core/include/utils/get_chassis_names.hpp new file mode 100644 index 0000000..0276b6f --- /dev/null +++ b/redfish-core/include/utils/get_chassis_names.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include +#include +#include + +namespace redfish +{ + +namespace utils +{ + +template +inline void getChassisNames(F&& cb) +{ + const std::array interfaces = { + "xyz.openbmc_project.Inventory.Item.Board", + "xyz.openbmc_project.Inventory.Item.Chassis"}; + + crow::connections::systemBus->async_method_call( + [callback = std::move(cb)](const boost::system::error_code ec, + const std::vector& chassis) { + std::vector chassisNames; + + if (ec) + { + callback(ec, chassisNames); + return; + } + + chassisNames.reserve(chassis.size()); + for (const std::string& path : chassis) + { + sdbusplus::message::object_path dbusPath = path; + std::string name = dbusPath.filename(); + if (name.empty()) + { + callback(boost::system::errc::make_error_code( + boost::system::errc::invalid_argument), + chassisNames); + return; + } + chassisNames.emplace_back(std::move(name)); + } + + callback(ec, chassisNames); + }, + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", + "/xyz/openbmc_project/inventory", 0, interfaces); +} + +} // namespace utils + +} // namespace redfish diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp index 5872350..1b4f75d 100644 --- a/redfish-core/include/utils/telemetry_utils.hpp +++ b/redfish-core/include/utils/telemetry_utils.hpp @@ -10,6 +10,8 @@ namespace telemetry constexpr const char* service = "xyz.openbmc_project.Telemetry"; constexpr const char* reportInterface = "xyz.openbmc_project.Telemetry.Report"; +constexpr const char* metricDefinitionUri = + "/redfish/v1/TelemetryService/MetricDefinitions/"; constexpr const char* metricReportDefinitionUri = "/redfish/v1/TelemetryService/MetricReportDefinitions/"; constexpr const char* metricReportUri = diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp new file mode 100644 index 0000000..347c297 --- /dev/null +++ b/redfish-core/lib/metric_definition.hpp @@ -0,0 +1,368 @@ +#pragma once + +#include "async_resp.hpp" +#include "sensors.hpp" +#include "utils/get_chassis_names.hpp" +#include "utils/telemetry_utils.hpp" + +#include + +namespace redfish +{ + +namespace telemetry +{ + +struct ValueVisitor +{ + ValueVisitor(boost::system::error_code& ec) : ec(ec) + {} + + template + double operator()(T value) const + { + return static_cast(value); + } + + double operator()(std::monostate) const + { + ec = boost::system::errc::make_error_code( + boost::system::errc::invalid_argument); + return double{}; + } + + boost::system::error_code& ec; +}; + +inline void getReadingRange( + const std::string& service, const std::string& path, + const std::string& property, + std::function callback) +{ + crow::connections::systemBus->async_method_call( + [callback = std::move(callback)]( + boost::system::error_code ec, + const std::variant& + valueVariant) { + if (ec) + { + callback(ec, double{}); + return; + } + + const double value = std::visit(ValueVisitor(ec), valueVariant); + + callback(ec, value); + }, + service, path, "org.freedesktop.DBus.Properties", "Get", + "xyz.openbmc_project.Sensor.Value", property); +} + +inline void + fillMinMaxReadingRange(const std::shared_ptr& asyncResp, + const std::string& serviceName, + const std::string& sensorPath) +{ + asyncResp->res.jsonValue["MetricType"] = "Numeric"; + + telemetry::getReadingRange( + serviceName, sensorPath, "MinValue", + [asyncResp](boost::system::error_code ec, double readingRange) { + if (ec) + { + messages::internalError(asyncResp->res); + return; + } + + if (std::isfinite(readingRange)) + { + asyncResp->res.jsonValue["MetricType"] = "Gauge"; + + asyncResp->res.jsonValue["MinReadingRange"] = readingRange; + } + }); + + telemetry::getReadingRange( + serviceName, sensorPath, "MaxValue", + [asyncResp](boost::system::error_code ec, double readingRange) { + if (ec) + { + messages::internalError(asyncResp->res); + return; + } + + if (std::isfinite(readingRange)) + { + asyncResp->res.jsonValue["MetricType"] = "Gauge"; + + asyncResp->res.jsonValue["MaxReadingRange"] = readingRange; + } + }); +} + +inline void getSensorService( + const std::string& sensorPath, + std::function callback) +{ + using ResultType = std::pair< + std::string, + std::vector>>>; + + crow::connections::systemBus->async_method_call( + [sensorPath, callback = std::move(callback)]( + boost::system::error_code ec, + const std::vector& result) { + if (ec) + { + callback(ec, std::string{}); + return; + } + + for (const auto& [path, serviceToInterfaces] : result) + { + if (path == sensorPath) + { + for (const auto& [service, interfaces] : + serviceToInterfaces) + { + callback(boost::system::errc::make_error_code( + boost::system::errc::success), + service); + return; + } + } + } + + callback(boost::system::errc::make_error_code( + boost::system::errc::no_such_file_or_directory), + std::string{}); + }, + "xyz.openbmc_project.ObjectMapper", + "/xyz/openbmc_project/object_mapper", + "xyz.openbmc_project.ObjectMapper", "GetSubTree", + "/xyz/openbmc_project/sensors", 2, + std::array{"xyz.openbmc_project.Sensor.Value"}); +} + +constexpr auto metricDefinitionMapping = std::array{ + std::pair{"fan_pwm", "Fan_Pwm"}, std::pair{"fan_tach", "Fan_Tach"}}; + +std::string mapSensorToMetricDefinition(const std::string& sensorPath) +{ + sdbusplus::message::object_path sensorObjectPath{sensorPath}; + + const auto it = std::find_if( + metricDefinitionMapping.begin(), metricDefinitionMapping.end(), + [&sensorObjectPath](const auto& item) { + return item.first == sensorObjectPath.parent_path().filename(); + }); + + const char* metricDefinitionPath = + "/redfish/v1/TelemetryService/MetricDefinitions/"; + + if (it != metricDefinitionMapping.end()) + { + return std::string{metricDefinitionPath} + it->second; + } + + return metricDefinitionPath + sensorObjectPath.filename(); +} + +template +inline void mapRedfishUriToDbusPath(Callback&& callback) +{ + utils::getChassisNames([callback = std::move(callback)]( + boost::system::error_code ec, + const std::vector& chassisNames) { + if (ec) + { + BMCWEB_LOG_ERROR << "getChassisNames error: " << ec.value(); + callback(ec, {}); + return; + } + + auto counter = std::make_shared, size_t>>(); + + auto handleRetrieveUriToDbusMap = + [counter, callback = std::move(callback)]( + const boost::beast::http::status status, + const boost::container::flat_map& + uriToDbus) { + if (status != boost::beast::http::status::ok) + { + BMCWEB_LOG_ERROR << "Failed to retrieve URI to dbus " + "sensors map with err " + << static_cast(status); + counter->second = 0u; + callback(boost::system::errc::make_error_code( + boost::system::errc::io_error), + {}); + return; + } + + for (const auto& [key, value] : uriToDbus) + { + counter->first[key] = value; + } + + if (--counter->second == 0u) + { + callback(boost::system::errc::make_error_code( + boost::system::errc::success), + counter->first); + } + }; + + for (const std::string& chassisName : chassisNames) + { + for (const auto& [sensorNode, dbusPaths] : sensors::dbus::paths) + { + ++counter->second; + retrieveUriToDbusMap(chassisName, sensorNode.data(), + handleRetrieveUriToDbusMap); + } + } + }); +} + +} // namespace telemetry + +inline void requestRoutesMetricDefinitionCollection(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricDefinitions/") + .privileges(privileges::getTelemetryService) + .methods(boost::beast::http::verb::get)( + [](const crow::Request&, + const std::shared_ptr& asyncResp) { + telemetry::mapRedfishUriToDbusPath( + [asyncResp](boost::system::error_code ec, + const boost::container::flat_map< + std::string, std::string>& uriToDbus) { + if (ec) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR + << "mapRedfishUriToDbusPath error: " + << ec.value(); + return; + } + + std::set members; + + for (const auto& [uri, dbusPath] : uriToDbus) + { + members.insert( + telemetry::mapSensorToMetricDefinition( + dbusPath)); + } + + for (const std::string& odataId : members) + { + asyncResp->res.jsonValue["Members"].push_back( + {{"@odata.id", odataId}}); + } + + asyncResp->res.jsonValue["Members@odata.count"] = + asyncResp->res.jsonValue["Members"].size(); + }); + + asyncResp->res.jsonValue["@odata.type"] = + "#MetricDefinitionCollection." + "MetricDefinitionCollection"; + asyncResp->res.jsonValue["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; + asyncResp->res.jsonValue["Name"] = + "Metric Definition Collection"; + asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); + asyncResp->res.jsonValue["Members@odata.count"] = 0; + }); +} + +inline void requestRoutesMetricDefinition(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricDefinitions//") + .privileges(privileges::getTelemetryService) + .methods( + boost::beast::http::verb::get)([](const crow::Request&, + const std::shared_ptr< + bmcweb::AsyncResp>& asyncResp, + const std::string& name) { + telemetry::mapRedfishUriToDbusPath( + [asyncResp, name]( + boost::system::error_code ec, + const boost::container::flat_map& + uriToDbus) { + if (ec) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR << "mapRedfishUriToDbusPath error: " + << ec.value(); + return; + } + + std::string odataId = telemetry::metricDefinitionUri + name; + boost::container::flat_map + matchingUris; + + for (const auto& [uri, dbusPath] : uriToDbus) + { + if (telemetry::mapSensorToMetricDefinition(dbusPath) == + odataId) + { + matchingUris.emplace(uri, dbusPath); + } + } + + if (matchingUris.empty()) + { + messages::resourceNotFound(asyncResp->res, + "MetricDefinition", name); + return; + } + + std::string sensorPath = matchingUris.begin()->second; + + telemetry::getSensorService( + sensorPath, + [asyncResp, name, odataId = std::move(odataId), + sensorPath, matchingUris = std::move(matchingUris)]( + boost::system::error_code ec, + const std::string& serviceName) { + if (ec) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR << "getServiceSensorFailed: " + << ec.value(); + return; + } + + asyncResp->res.jsonValue["Id"] = name; + asyncResp->res.jsonValue["Name"] = name; + asyncResp->res.jsonValue["@odata.id"] = odataId; + asyncResp->res.jsonValue["@odata.type"] = + "#MetricDefinition.v1_0_3.MetricDefinition"; + asyncResp->res.jsonValue["MetricDataType"] = + "Decimal"; + asyncResp->res.jsonValue["IsLinear"] = true; + asyncResp->res.jsonValue["Units"] = + sensors::toReadingUnits( + sdbusplus::message::object_path{sensorPath} + .parent_path() + .filename()); + + for (const auto& [uri, dbusPath] : matchingUris) + { + asyncResp->res.jsonValue["MetricProperties"] + .push_back(uri); + } + + telemetry::fillMinMaxReadingRange( + asyncResp, serviceName, sensorPath); + }); + }); + }); +} + +} // namespace redfish diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp index 8ecc591..027b51b 100644 --- a/redfish-core/lib/telemetry_service.hpp +++ b/redfish-core/lib/telemetry_service.hpp @@ -18,11 +18,12 @@ inline void handleTelemetryServiceGet( asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/TelemetryService"; asyncResp->res.jsonValue["Id"] = "TelemetryService"; asyncResp->res.jsonValue["Name"] = "Telemetry Service"; - asyncResp->res.jsonValue["MetricReportDefinitions"]["@odata.id"] = "/redfish/v1/TelemetryService/MetricReportDefinitions"; asyncResp->res.jsonValue["MetricReports"]["@odata.id"] = "/redfish/v1/TelemetryService/MetricReports"; + asyncResp->res.jsonValue["MetricDefinitions"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, -- 2.25.1