blob: dced71ff5a077468bc62f56244c9aea3dbf79cc6 [file] [log] [blame]
#ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_NVME_METRIC_UTILS_H_
#define THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_NVME_METRIC_UTILS_H_
#include <systemd/sd-bus.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <format> // NOLINT
#include <functional>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
#include "boost/asio/posix/stream_descriptor.hpp" // NOLINT
#include "boost/endian.hpp" // NOLINT
#include "boost/system/error_code.hpp" // NOLINT
#include "boost/url/url.hpp" // NOLINT
#include "logging.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "storage_utils.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "sdbusplus/message.hpp"
#include "sdbusplus/message/native_types.hpp"
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"] =
::crow::utility::base64encode(data.substr(sizeof(NVMeMetricHeader)));
}
/**
* @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
#endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_NVME_METRIC_UTILS_H_