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 000000000..96bd753a3 --- /dev/null +++ b/redfish-core/include/utils/get_chassis_names.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "dbus_singleton.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +namespace redfish +{ + +namespace utils +{ + +template +inline void getChassisNames(F&& cb) +{ + const std::array interfaces = { + "xyz.openbmc_project.Inventory.Item.Chassis"}; + + crow::connections::systemBus->async_method_call( + [callback = std::forward( + 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 df71027b8..94aeb11ea 100644 --- a/redfish-core/include/utils/telemetry_utils.hpp +++ b/redfish-core/include/utils/telemetry_utils.hpp @@ -30,6 +30,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/"; inline std::string getDbusReportPath(std::string_view id) { sdbusplus::message::object_path reportsPath( diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp new file mode 100644 index 000000000..a7e894c7b --- /dev/null +++ b/redfish-core/lib/metric_definition.hpp @@ -0,0 +1,414 @@ +#pragma once + +#include "app.hpp" +#include "async_resp.hpp" +#include "dbus_singleton.hpp" +#include "error_messages.hpp" +#include "generated/enums/metric_definition.hpp" +#include "http_request.hpp" +#include "logging.hpp" +#include "sensors.hpp" +#include "utils/get_chassis_names.hpp" +#include "utils/sensor_utils.hpp" +#include "utils/telemetry_utils.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace redfish +{ + +namespace telemetry +{ + +struct ValueVisitor +{ + explicit ValueVisitor(boost::system::error_code& ec) : ec3(ec) {} + + template + double operator()(T value) const + { + return static_cast(value); + } + + double operator()(std::monostate /*unused*/) const + { + ec3 = boost::system::errc::make_error_code( + boost::system::errc::invalid_argument); + return double{}; + } + + boost::system::error_code& ec3; +}; + +inline void getReadingRange( + const std::string& service2, 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); + }, + service2, 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"] = + metric_definition::MetricType::Numeric; + + telemetry::getReadingRange( + serviceName, sensorPath, "MinValue", + [asyncResp](boost::system::error_code ec, double readingRange) { + if (ec) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR( + "fillMinMaxReadingRange: getReadingRange error {}", ec); + return; + } + + if (std::isfinite(readingRange)) + { + asyncResp->res.jsonValue["MetricType"] = + metric_definition::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); + BMCWEB_LOG_ERROR( + "fillMinMaxReadingRange: getReadingRange error {}", ec); + return; + } + + if (std::isfinite(readingRange)) + { + asyncResp->res.jsonValue["MetricType"] = + metric_definition::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& [service2, interfaces] : + serviceToInterfaces) + { + callback(boost::system::errc::make_error_code( + boost::system::errc::success), + service2); + 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"}}; + +static std::string mapSensorToMetricDefinition(const std::string& sensorPath) +{ + sdbusplus::message::object_path sensorObjectPath{sensorPath}; + + const auto* const it = std::find_if( + metricDefinitionMapping.begin(), metricDefinitionMapping.end(), + [&sensorObjectPath](const auto& item) { + return item.first == sensorObjectPath.parent_path().filename(); + }); + + std::string metricDefinitionPath = + "/redfish/v1/TelemetryService/MetricDefinitions/"; + + if (it != metricDefinitionMapping.end()) + { + return metricDefinitionPath + it->second; + } + + return metricDefinitionPath + sensorObjectPath.filename(); +} + +static std::string& stringViewToStringRef(const std::string_view& sv) +{ + static std::string s; + s = std::string(sv); + return s; +} + +template +inline void mapRedfishUriToDbusPath(Callback&& callback) +{ + utils::getChassisNames([callback = std::forward(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 std::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) + { + ++counter->second; + retrieveUriToDbusMap(chassisName, + stringViewToStringRef(sensors::sensorsNodeStr), + handleRetrieveUriToDbusMap); + } + }); +} + +} // namespace telemetry + +inline void requestRoutesMetricDefinitionCollection(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricDefinitions/") + .privileges(privileges::getMetricDefinitionCollection) + .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 handleMetricDefinitionsGet( + App& app, const crow::Request& req, + const std::shared_ptr& asyncResp, + const std::string& name) +{ + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + + telemetry::mapRedfishUriToDbusPath( + [asyncResp, name]( + boost::system::error_code ec2, + const boost::container::flat_map& + uriToDbus) { + if (ec2) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR("mapRedfishUriToDbusPath error: {}", + ec2.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 ec3, + const std::string& serviceName) { + if (ec3) + { + messages::internalError(asyncResp->res); + BMCWEB_LOG_ERROR("getServiceSensorFailed: {}", + ec3.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"] = + metric_definition::MetricDataType::Decimal; + asyncResp->res.jsonValue["IsLinear"] = true; + asyncResp->res.jsonValue["Units"] = + sensor_utils::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); + }); + }); + +} + +inline void requestRoutesMetricDefinition(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricDefinitions//") + .privileges(privileges::getMetricDefinition) + .methods(boost::beast::http::verb::get)( + std::bind_front(handleMetricDefinitionsGet, std::ref(app))); + +} + +} // namespace redfish diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp index 49d467335..479b8b606 100644 --- a/redfish-core/lib/telemetry_service.hpp +++ b/redfish-core/lib/telemetry_service.hpp @@ -47,6 +47,8 @@ inline void handleTelemetryServiceGet( "/redfish/v1/TelemetryService/MetricReportDefinitions"; asyncResp->res.jsonValue["MetricReports"]["@odata.id"] = "/redfish/v1/TelemetryService/MetricReports"; + asyncResp->res.jsonValue["MetricDefinitions"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; asyncResp->res.jsonValue["Triggers"]["@odata.id"] = "/redfish/v1/TelemetryService/Triggers"; diff --git a/redfish-core/src/redfish.cpp b/redfish-core/src/redfish.cpp index 34d455ab5..cdc8645bf 100644 --- a/redfish-core/src/redfish.cpp +++ b/redfish-core/src/redfish.cpp @@ -27,6 +27,7 @@ #include "memory.hpp" #include "message_registries.hpp" #include "metadata.hpp" +#include "metric_definition.hpp" #include "metric_report.hpp" #include "metric_report_definition.hpp" #include "network_protocol.hpp" @@ -251,6 +252,8 @@ RedfishService::RedfishService(App& app) requestRoutesMetricReportDefinition(app); requestRoutesMetricReportCollection(app); requestRoutesMetricReport(app); + requestRoutesMetricDefinitionCollection(app); + requestRoutesMetricDefinition(app); requestRoutesTriggerCollection(app); requestRoutesTrigger(app);