| #pragma once |
| |
| #include "dbus_utility.hpp" |
| #include "human_sort.hpp" |
| #include "managed_store.hpp" |
| #include "managed_store_types.hpp" |
| #include "storage_utils.hpp" |
| |
| #include <boost/asio/posix/stream_descriptor.hpp> |
| #include <boost/endian.hpp> |
| #include <boost/system/error_code.hpp> |
| #include <sdbusplus/asio/property.hpp> |
| |
| #include <array> |
| #include <cstdlib> |
| #include <vector> |
| |
| namespace redfish |
| { |
| |
| using MetricStoreCb = |
| std::function<void(const std::shared_ptr<bmcweb::AsyncResp>& resp, |
| const std::optional<std::string>& data)>; |
| // Define the header struct |
| |
| struct NVMeMetricHeader |
| { |
| boost::endian::little_uint32_t lens = 0; // Header length |
| boost::endian::little_uint16_t version = 0; // Header version |
| uint8_t dataFormat = 0; // the data format. 0x00 = Raw NVMe format |
| uint8_t reserved = 0; |
| boost::endian::little_uint64_t startTime = |
| 0; // time stamp when the Metric started the retrival from the |
| // device |
| boost::endian::little_uint64_t finishTime = |
| 0; // time stamp when the Metric finished the retrival from |
| // the device |
| }; |
| |
| inline std::optional<NVMeMetricHeader> extractHeader(const std::string& buffer) |
| { |
| if (buffer.size() < sizeof(NVMeMetricHeader)) |
| { |
| return std::nullopt; |
| } |
| |
| NVMeMetricHeader header; |
| auto it = buffer.begin(); |
| |
| std::copy(it, it + sizeof(header.lens), |
| reinterpret_cast<char*>(&header.lens)); // NOLINT |
| std::advance(it, sizeof(header.lens)); |
| |
| std::copy(it, it + sizeof(header.version), |
| reinterpret_cast<char*>(&header.version)); // NOLINT |
| std::advance(it, sizeof(header.version)); |
| |
| std::copy(it, it + sizeof(header.dataFormat), |
| reinterpret_cast<char*>(&header.dataFormat)); // NOLINT |
| std::advance(it, sizeof(header.dataFormat)); |
| |
| std::copy(it, it + sizeof(header.reserved), |
| reinterpret_cast<char*>(&header.reserved)); // NOLINT |
| std::advance(it, sizeof(header.reserved)); |
| |
| std::copy(it, it + sizeof(header.startTime), |
| reinterpret_cast<char*>(&header.startTime)); // NOLINT |
| std::advance(it, sizeof(header.startTime)); |
| |
| std::copy(it, it + sizeof(header.finishTime), |
| reinterpret_cast<char*>(&header.finishTime)); // NOLINT |
| std::advance(it, sizeof(header.finishTime)); |
| |
| return header; |
| } |
| |
| inline void fetchFileUtil( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::shared_ptr<boost::asio::posix::stream_descriptor>& fileConn, |
| MetricStoreCb&& cb, |
| const std::shared_ptr<std::array<char, 1024>>& readBuffer = |
| std::make_shared<std::array<char, 1024>>(), |
| const std::shared_ptr<std::string>& output = |
| std::make_shared<std::string>()) |
| { |
| if (!fileConn) |
| { |
| BMCWEB_LOG_ERROR << "Fetch File Context is not setup properly"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| fileConn->async_read_some( |
| boost::asio::buffer(*readBuffer), |
| [asyncResp, fileConn, readBuffer, output, |
| cb{std::move(cb)}](const boost::system::error_code& ec, |
| const std::size_t& bytesTransferred) mutable { |
| if (ec == boost::asio::error::eof) |
| { |
| cb(asyncResp, *output); |
| fileConn->close(); |
| return; |
| } |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "Failed to async_read_some" << ec.message(); |
| redfish::messages::internalError(asyncResp->res); |
| fileConn->close(); |
| return; |
| } |
| *output += std::string(readBuffer->begin(), |
| readBuffer->begin() + bytesTransferred); |
| fetchFileUtil(asyncResp, fileConn, std::move(cb), readBuffer, output); |
| }); |
| } |
| |
| constexpr const char nvmeMetricInfc[] = "xyz.openbmc_project.NVMe.MetricStore"; |
| |
| template <const char* INFC = nvmeMetricInfc> |
| inline void tryPopulateMetricCollection( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& path, const dbus::utility::MapperServiceMap& ifaces, |
| const boost::urls::url& parentUrl) |
| { |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| auto service = storage_utils::matchServiceName(ifaces, INFC); |
| if (!service) |
| { |
| return; |
| } |
| redfish::storage_utils::getProperty<std::vector<std::string>>( |
| *service, path, INFC, "MetricCollection", context, |
| [asyncResp, |
| parentUrl](const boost::system::error_code& ec, |
| const std::vector<std::string>& metricCollection) { |
| if (ec) |
| { |
| return; |
| } |
| |
| for (const std::string& metric : metricCollection) |
| { |
| boost::urls::url dataUri(parentUrl); |
| crow::utility::appendUrlPieces(dataUri, "Oem", |
| "Google", "Metrics", metric); |
| asyncResp->res.jsonValue["Oem"]["Google"][metric]["DataUri"] = dataUri; |
| } |
| }); |
| } |
| |
| /** Fetch the Metric for given {systemName, storageId, controllerId, metricId} |
| * File the resp fields in terms of the data and time stamps |
| */ |
| |
| inline void |
| defaultMetricCallback(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::optional<std::string>& output) |
| { |
| // if the output is nullopt then the cache is invalid |
| if (!output.has_value()) |
| { |
| BMCWEB_LOG_ERROR << "Failed to read metric: Cache invalid"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| const std::string& data = output.value(); |
| std::optional<NVMeMetricHeader> header = extractHeader(data); |
| if (!header.has_value()) |
| { |
| BMCWEB_LOG_ERROR << "Failed to read metric header"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| asyncResp->res.jsonValue["StartTime"] = |
| std::to_string(header.value().startTime); |
| asyncResp->res.jsonValue["FinishTime"] = |
| std::to_string(header.value().finishTime); |
| |
| asyncResp->res.jsonValue["Metric"] = std::move( |
| ::crow::utility::base64encode(data.substr(sizeof(NVMeMetricHeader)))); |
| return; |
| } |
| |
| /** |
| * @details fetch the metric in the given resource dbus path from metric store |
| * and trigger the cb with the data if succeed. |
| * |
| * Exception Handling: |
| * If the expected service or the required interfaces or the metric itself is |
| * not available, it will set resource not found in the provided asyncResp and |
| * will not trigger cb. If while fetching metric, the service throws dbus |
| * exception Unavailable, then that means the service could not cache the metric |
| * yet. In that case the callback cb will be called with data as nullopt. The |
| * consumer can either set internal error or treat that as success case. For all |
| * other exceptions, it will set internal error in asyncResp and will not |
| * trigger cb. |
| */ |
| template <const char* INFC = nvmeMetricInfc> |
| inline void |
| nvmeMetricFetcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const sdbusplus::message::object_path& resourcePath, |
| const dbus::utility::MapperServiceMap& resourceIfaces, |
| const std::string& metricId, |
| std::unordered_set<std::string> requiredIfaces, |
| MetricStoreCb&& cb = defaultMetricCallback) |
| { |
| // Find the controller and then create a dbus call to the metric name |
| // Before you do check for the presence of the interface using association |
| // get sub tree |
| auto foundService = std::find_if( |
| resourceIfaces.begin(), resourceIfaces.end(), |
| [](const std::pair<std::string, |
| dbus::utility::MapperGetSubTreePathsResponse>& |
| pair) { return pair.first == "xyz.openbmc_project.NVMe"; }); |
| |
| if (foundService == resourceIfaces.end()) |
| { |
| redfish::messages::resourceNotFound( |
| asyncResp->res, "xyz.openbmc_project.NVMe", resourcePath.str); |
| return; |
| } |
| const auto& service = foundService->first; |
| auto ifaceNames = foundService->second; |
| |
| // examine if the controller resource has all required interfaces for |
| // metric |
| |
| std::unordered_set<std::string> checklist = { {INFC}}; |
| checklist.insert(requiredIfaces.begin(), requiredIfaces.end()); |
| |
| for (const auto& iface : ifaceNames) |
| { |
| auto find = checklist.find(iface); |
| if (find != checklist.end()) |
| { |
| checklist.erase(*find); |
| } |
| if (checklist.empty()) |
| { |
| break; |
| } |
| } |
| if (!checklist.empty()) |
| { |
| redfish::messages::resourceNotFound( |
| asyncResp->res, *(checklist.begin()), resourcePath.str); |
| return; |
| } |
| |
| // query the metric from MetricStore |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| |
| redfish::dbus_utils::getProperty<std::vector<std::string>>( |
| service, resourcePath, INFC, "MetricCollection", requestContext, |
| [asyncResp, service, resourcePath, metricId, cb{std::move(cb)}]( |
| const boost::system::error_code ec, |
| const std::vector<std::string>& metricCollection) mutable { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << std::format( |
| "[{}, {}]Failed to enumerate Metric", resourcePath.str, metricId); |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| auto res = std::find(metricCollection.begin(), |
| metricCollection.end(), metricId); |
| if (res == metricCollection.end()) |
| { |
| redfish::messages::resourceNotFound(asyncResp->res, INFC, |
| metricId); |
| return; |
| } |
| |
| // read metric |
| managedStore::GetManagedObjectStore() |
| ->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [asyncResp, service, resourcePath, metricId, cb{std::move(cb)}]( |
| const boost::system::error_code ec, |
| const sdbusplus::message_t& msg, |
| const sdbusplus::message::unix_fd& fd) mutable { |
| if (ec) |
| { |
| const sd_bus_error* dbusError = msg.get_error(); |
| if (std::string_view( |
| "xyz.openbmc_project.Common.Error.Unavailable") == |
| dbusError->name) |
| { |
| // if cache is invalid then the service throws unavailble |
| cb(asyncResp, std::nullopt); |
| return; |
| } |
| BMCWEB_LOG_ERROR << std::format( |
| "[{}, {}]Failed to read Metric", resourcePath.str, metricId); |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| int dupFd = dup(fd.fd); |
| fetchFileUtil( |
| asyncResp, |
| std::make_shared<boost::asio::posix::stream_descriptor>( |
| managedStore::GetManagedObjectStore()->GetIoContext(), |
| dupFd), |
| std::move(cb)); |
| }, |
| service, resourcePath, INFC, "GetMetric", metricId); |
| }); |
| } |
| |
| template <const char* INFC = nvmeMetricInfc> |
| inline void nvmeControllerMetricFetcher( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& storageId, const std::string& controllerId, |
| const std::string& metricId, MetricStoreCb&& cb = defaultMetricCallback) |
| { |
| redfish::storage_utils::findStorageAndController( |
| asyncResp, storageId, controllerId, |
| [asyncResp, metricId, cb{std::move(cb)}]( |
| const sdbusplus::message::object_path& path, |
| const dbus::utility::MapperServiceMap& ifaces) mutable { |
| nvmeMetricFetcher<INFC>(asyncResp, path, ifaces, metricId, |
| {"xyz.openbmc_project.NVMe.NVMeAdmin"}, |
| std::move(cb)); |
| }); |
| } |
| } // namespace redfish |