blob: 984daf2c7e92ba17410ce0728283331a854ba46e [file] [log] [blame]
#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