blob: 13fd315d981af3c69f4121fc416aeb6ac96d4b2a [file]
#include "NVMeMetricStore.hpp"
#include <phosphor-logging/lg2.hpp>
#include <xyz/openbmc_project/Common/File/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
sdbusplus::message::unix_fd
MetricStore::writeToFd(std::span<const uint8_t> header,
std::span<const uint8_t> data)
{
std::array<int, 2> pipe{};
if (::pipe(pipe.data()) < 0)
{
lg2::error("Metric Fetcher failed due to pipe creation: {ERROR}",
"ERROR", strerror(errno));
throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open{};
}
auto asyncPipe =
std::make_shared<boost::asio::posix::stream_descriptor>(io, pipe[1]);
std::weak_ptr<boost::asio::posix::stream_descriptor> weakPipe = asyncPipe;
auto asyncBuffer = std::make_shared<std::vector<uint8_t>>(header.begin(),
header.end());
asyncBuffer->insert(asyncBuffer->end(), data.begin(), data.end());
boost::asio::async_write(
*asyncPipe, boost::asio::buffer(*asyncBuffer),
[asyncPipe, asyncBuffer](const boost::system::error_code& error,
std::size_t /* transferredSize */) {
if (error)
{
lg2::error("Metric Fetcher fails to write fd: {ERROR}", "ERROR",
error.message());
}
});
// close the read end of the pipe after waiting for 5 seconds
// It is expected that the client has duplicated the fd in this time
auto timer = std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(std::chrono::seconds(5));
timer->async_wait([timer, fd{pipe[0]},
weakPipe](const boost::system::error_code& /* ec */) {
auto pipe = weakPipe.lock();
if (pipe)
{
pipe->cancel();
}
close(fd);
});
// Return the read end wrapped in a stream descriptor
return sdbusplus::message::unix_fd{pipe[0]};
}
sdbusplus::message::unix_fd MetricStore::getMetric(std::string name)
{
auto it = metricStore.find(name);
if (it == metricStore.end() || !it->second)
{
lg2::error("Cannot find the target Metric", "PATH", this->path, "NAME",
name);
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument{};
}
if (!it->second->isCacheValid())
{
lg2::error("The Metric cache is invalid", "PATH", this->path, "NAME",
name);
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable{};
}
auto data = it->second->getCache();
// Send the data and header to be written to pipe
MetricHeader header{};
header.lens = boost::endian::little_uint32_t(sizeof(header));
header.version = 0;
header.dataFormat = 0;
header.startTime = boost::endian::little_uint64_t(
std::get<0>(data).time_since_epoch().count());
header.finishTime = boost::endian::little_uint64_t(
std::get<1>(data).time_since_epoch().count());
return writeToFd(
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
std::span<uint8_t>(reinterpret_cast<std::uint8_t*>(&header),
sizeof(header)),
std::get<2>(data));
}
static void ensureFresh(
const std::shared_ptr<MetricBase<std::chrono::steady_clock>>& metric,
std::chrono::steady_clock::time_point requestTime,
std::function<void(std::error_code ec)>&& cb)
{
metric->refresh(
[metric, requestTime, cb = std::move(cb)](std::error_code ec) mutable {
if (ec)
{
cb(ec);
return;
}
if (std::get<0>(metric->getCache()) < requestTime)
{
ensureFresh(metric, requestTime, std::move(cb));
}
else
{
cb(ec);
}
});
}
sdbusplus::message::unix_fd MetricStore::refreshMetric(std::string name)
{
if (name.empty())
{
lg2::error("Metric name is empty");
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument{};
}
auto it = metricStore.find(name);
if (it == metricStore.end() || !it->second)
{
lg2::error("Cannot find the target Metric", "PATH", this->path, "NAME",
name);
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument{};
}
std::array<int, 2> pipe{};
if (::pipe(pipe.data()) < 0)
{
lg2::error("Metric Fetcher failed due to pipe creation: {ERROR}",
"ERROR", strerror(errno));
throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open{};
}
auto asyncPipe =
std::make_shared<boost::asio::posix::stream_descriptor>(io, pipe[1]);
std::weak_ptr<boost::asio::posix::stream_descriptor> weakPipe = asyncPipe;
auto requestTime = std::chrono::steady_clock::now();
ensureFresh(it->second, requestTime,
[asyncPipe, metric = it->second](std::error_code ec) {
if (ec)
{
lg2::error("Metric Refresh fails: {ERROR}", "ERROR", ec.message());
return;
}
auto data = metric->getCache();
// Send the data and header to be written to pipe
MetricHeader header{};
header.lens = boost::endian::little_uint32_t(sizeof(header));
header.version = 0;
header.dataFormat = 0;
header.startTime = boost::endian::little_uint64_t(
std::get<0>(data).time_since_epoch().count());
header.finishTime = boost::endian::little_uint64_t(
std::get<1>(data).time_since_epoch().count());
auto asyncBuffer =
std::make_shared<std::vector<uint8_t>>(sizeof(header));
std::memcpy(asyncBuffer->data(), &header, sizeof(header));
asyncBuffer->insert(asyncBuffer->end(), std::get<2>(data).begin(),
std::get<2>(data).end());
boost::asio::async_write(
*asyncPipe, boost::asio::buffer(*asyncBuffer),
[asyncPipe, asyncBuffer](const boost::system::error_code& error,
std::size_t /* transferredSize */) {
if (error)
{
lg2::error("Metric Fetcher fails to write fd: {ERROR}", "ERROR",
error.message());
}
});
});
// close the read end of the pipe after waiting for 5 seconds
// It is expected that the client has duplicated the fd in this time
auto timer = std::make_shared<boost::asio::steady_timer>(io);
timer->expires_after(std::chrono::seconds(5));
timer->async_wait([timer, fd{pipe[0]},
weakPipe](const boost::system::error_code& /* ec */) {
auto pipe = weakPipe.lock();
if (pipe)
{
pipe->cancel();
}
close(fd);
});
return sdbusplus::message::unix_fd{pipe[0]};
}
void MetricStore::signalHelper(
std::string_view name,
const std::shared_ptr<MetricBase<std::chrono::steady_clock>>&
metric) noexcept
{
if (!metric || !metric->isCacheValid())
{
// broadcast invalid metric signal
metricUpdated(std::string{name}, {});
return;
}
auto data = metric->getCache();
std::vector<uint8_t> headerBuf;
headerBuf.resize(sizeof(MetricHeader));
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
MetricHeader& header = *reinterpret_cast<MetricHeader*>(headerBuf.data());
header.lens = boost::endian::little_uint32_t(sizeof(header));
header.version = 0;
header.dataFormat = 0;
header.startTime = boost::endian::little_uint64_t(
std::get<0>(data).time_since_epoch().count());
header.finishTime = boost::endian::little_uint64_t(
std::get<1>(data).time_since_epoch().count());
metricUpdated(std::string{name}, headerBuf);
};