blob: b3d86d1260204278fa8fe13d4765673e67594d0e [file] [log] [blame]
#pragma once
#include "app.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#ifdef BMCWEB_ENABLE_GRPC
#include "grpc_statistics.h"
#endif
#include "http_request.hpp"
#include "openbmc_dbus_rest.hpp"
#include "privileges.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "routing.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <nlohmann/json.hpp>
#include <sdbusplus/asio/property.hpp>
#include <string>
#include <string_view>
#include <unordered_set>
namespace redfish
{
using SensorVariantType = dbus::utility::DbusVariantType;
const std::unordered_set<std::string> daemonDoublePropertiesSet{
"KernelTimeSeconds", "UserTimeSeconds", "UptimeSeconds"};
const std::unordered_set<std::string> daemonSizeTPropertiesSet{
"ResidentSetSizeBytes", "NFileDescriptors", "RestartCount"};
constexpr std::array bootupTimeProperties = {
"FirmwareTimestampMonotonic", "LoaderTimestampMonotonic",
"KernelTimestampMonotonic", "InitRDTimestampMonotonic",
"UserspaceTimestampMonotonic", "FinishTimestampMonotonic"};
constexpr std::array bootupTimeMetricName = {
"FirmwareTimeSeconds", "LoaderTimeSeconds", "KernelTimeSeconds",
"InitrdTimeSeconds", "UserSpaceTimeSeconds"};
std::optional<std::pair<std::string, std::string>>
checkAndGetSoleGetSubTreeResponse(
const dbus::utility::MapperGetSubTreeResponse& subtree)
{
if (subtree.empty())
{
BMCWEB_LOG_DEBUG << "Can't find bmc D-Bus object!";
return std::nullopt;
}
// TODO: Consider Multi-BMC cases
// Assume only 1 bmc D-Bus object
// Throw an error if there is more than 1
if (subtree.size() > 1)
{
BMCWEB_LOG_DEBUG << "Found more than 1 bmc D-Bus object!";
return std::nullopt;
}
if (subtree[0].first.empty() || subtree[0].second.size() != 1)
{
BMCWEB_LOG_DEBUG << "Error getting bmc D-Bus object!";
return std::nullopt;
}
return std::make_pair(subtree[0].first, subtree[0].second[0].first);
}
// Returns the sole non-Object Mapper service
std::optional<std::string> checkAndGetSoleNonMapperGetObjectResponse(
const dbus::utility::MapperGetObject& objects)
{
if (objects.empty())
{
return std::nullopt;
}
for (const std::pair<std::string, std::vector<std::string>>&
serviceAndIfaces : objects)
{
if (serviceAndIfaces.first != "xyz.openbmc_project.ObjectMapper")
{
return serviceAndIfaces.first;
}
}
return std::nullopt;
}
/**
* @brief Checks whether the last segment of a DBus object is a specific value.
*
* @param[i] objectPath - the DBus object path to check
* @param[i] value - the value to test against
*
* @return bool
*/
bool dbusObjectFilenameEquals(const std::string& objectName,
const std::string& value)
{
sdbusplus::message::object_path objectPath(objectName);
return (objectPath.filename() == value);
}
/**
* @brief Populates on entry in ManagerDiagnosticData with one utility sensor
* DBus object.
*
* @param[i,o] asyncResp - Async response object
* @param[i] diagnosticDataObjectPath - the DBus object path of the utility
* sensor.
*
* @return void
*/
inline void populateOneManagerDiagnosticDataEntry(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& diagnosticDataObjectPath)
{
crow::connections::systemBus->async_method_call(
[asyncResp, diagnosticDataObjectPath](
const boost::system::error_code ec,
const dbus::utility::MapperGetObject& objects) {
if (ec)
{
BMCWEB_LOG_ERROR << "Error occurred GetObject";
return;
}
std::optional<std::string> serviceName =
checkAndGetSoleNonMapperGetObjectResponse(objects);
if (!serviceName)
{
BMCWEB_LOG_ERROR << "Cannot get service from object name"
<< diagnosticDataObjectPath;
return;
}
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<double>(
*serviceName, diagnosticDataObjectPath,
"xyz.openbmc_project.Sensor.Value", "Value", context,
[asyncResp, diagnosticDataObjectPath](
const boost::system::error_code ec2, const double value) {
if (ec2)
{
BMCWEB_LOG_DEBUG << "DBus response error on getProperty "
<< ec2;
return;
}
if (dbusObjectFilenameEquals(diagnosticDataObjectPath,
"CPU_Kernel"))
{
asyncResp->res
.jsonValue["ProcessorStatistics"]["KernelPercent"] = value;
}
else if (dbusObjectFilenameEquals(diagnosticDataObjectPath,
"CPU_User"))
{
asyncResp->res.jsonValue["ProcessorStatistics"]["UserPercent"] =
value;
}
else if (dbusObjectFilenameEquals(diagnosticDataObjectPath,
"Memory_AvailableBytes"))
{
asyncResp->res.jsonValue["MemoryStatistics"]["AvailableBytes"] =
static_cast<int64_t>(value);
}
});
},
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetObject",
diagnosticDataObjectPath, std::array<const char*, 0>{});
}
/**
* @brief Find the endpoints for an Association definition.
*
* @param[i,o] asyncResp - Async response object
* @param[i] bmcAssociationObjectPath - the DBus object path of the Association
* definition.
* @param[i] bmcAssociationService - the DBus service that contains the
* Association definition.
*
* @return void
*/
inline void findBmcAssociationEndpoints(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& bmcAssociationObjectPath,
const std::string& bmcAssociationService)
{
// No need to use std::variant when using getProperty, just use the
// contained type in the template parameter
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<std::vector<std::string>>(
bmcAssociationService, bmcAssociationObjectPath,
"xyz.openbmc_project.Association", "endpoints", context,
[asyncResp](const boost::system::error_code ec,
const std::vector<std::string>& v) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus error getting association endpoints";
return;
}
for (const std::string& endpoint : v)
{
populateOneManagerDiagnosticDataEntry(asyncResp, endpoint);
}
});
}
/**
* @brief Find Associations related to one BMC inventory item.
*
* @param[i,o] asyncResp - Async response object
* @param[i] bmcObjectPath - the DBus object path of the BMC inventory item.
*
* @return void
*/
inline void
findBmcAssociation(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& bmcObjectPath)
{
std::array<std::string_view, 1> interfaces{
"xyz.openbmc_project.Association"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::managedObjectStore->getSubTree(
bmcObjectPath, 0, interfaces, requestContext,
[asyncResp, bmcObjectPath](
const boost::system::error_code ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus error getting associations for BMC "
"inventory object "
<< ec;
return;
}
// The expected values may be as follows, depending on whether the
// static/dynamic stack is used. subtree[0].first:
// "/xyz/openbmc_project/inventory/system/bmc/bmc/bmc_diagnostic_data"
// subtree[0].second[0]: "xyz.openbmc_project.ObjectMapper"
std::optional<std::pair<std::string, std::string>> pathAndService =
checkAndGetSoleGetSubTreeResponse(subtree);
if (!pathAndService)
{
BMCWEB_LOG_ERROR << "Couldn't get sole Subtree response";
return;
}
findBmcAssociationEndpoints(asyncResp, pathAndService->first,
pathAndService->second);
});
}
/**
* @brief Find BMC inventories in the system and iterate through them.
*
* @param[i,o] asyncResp - Async response object
*
* @return void
*/
inline void
findBmcInventory(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
std::array<std::string_view, 1> interfaces{
"xyz.openbmc_project.Inventory.Item.Bmc"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::managedObjectStore->getSubTree(
"/xyz/openbmc_project/inventory", int32_t(0), interfaces,
requestContext,
[asyncResp](const boost::system::error_code ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus response error on GetSubTree " << ec;
return;
}
// For Entity-Manager based systems ("dynamic inventory stack"):
// subtree[0].first may be
// "/xyz/openbmc_project/inventory/system/bmc/bmc".
// subtree[0].second[0].first may be {
// "xyz.openbmc_project.EntityManager",
// {"xyz.openbmc_project.AddObject"
// "xyz.openbmc_project.Inventory.Item.Bmc"} }.
// For Inventory-Manager based systems ("static inventory stack"):
// subtree[0].first may be
// "/xyz/openbmc_project/inventory/bmc", depending on compile-time
// configuration subtree[0].second[0].first may be {
// ""xyz.openbmc_project.Inventory.Manager",
// {"org.freedesktop.DBus.Introspectable",
// "org.freedesktop.DBus.Peer",
// "org.freedesktop.DBus.Properties",
// "xyz.openbmc_project.Inventory.Item.Bmc"} }
std::optional<std::pair<std::string, std::string>> pathAndService =
checkAndGetSoleGetSubTreeResponse(subtree);
if (!pathAndService)
{
BMCWEB_LOG_ERROR << "Couldn't get sole Subtree response";
return;
}
// Found BMC object path. Ask ObjectMapper about the reverse
// Association edges.
findBmcAssociation(asyncResp, pathAndService->first);
});
}
void getBootInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
const std::array<std::string, 2> bootInfoProperties = {
{"BootCount", "CrashCount"}};
for (const std::string& propertyName : bootInfoProperties)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<size_t>(
"xyz.openbmc_project.Metric", "/xyz/openbmc_project/metric/bmc0",
"xyz.openbmc_project.Metric.BMC", propertyName, context,
[asyncResp, propertyName](const boost::system::error_code ec,
const size_t property) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus error getting boot count";
return;
}
asyncResp->res.jsonValue["BootInfo"][propertyName] = property;
});
}
}
inline void calculateBootTimeResp(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>&
properties)
{
static_assert(bootupTimeProperties.size() ==
bootupTimeMetricName.size() + 1);
const size_t kernelIdx =
std::find(bootupTimeProperties.begin(), bootupTimeProperties.end(),
"KernelTimestampMonotonic") -
bootupTimeProperties.begin();
uint64_t lastTime = 0;
size_t metricIdx = 0;
for (auto& s : bootupTimeMetricName)
{
aResp->res.jsonValue["BootTimeStatistics"][s] = 0.0;
}
// each metric should be calculated as next monotime - curr monotime
// some timestamps maybe 0 if the hardware doesn't support it. In this
// case we simply set that metrics to 0, and accumulate the diff until
// an available time is seen
// e.g. kernelmono initrdmono usermono finishmono is 0, 4, 0, 10
// The metric values kerneltime initrdtime usertime should be 4, 0, 6
try
{
for (size_t i = 0; i < bootupTimeProperties.size(); ++i)
{
const uint64_t* currTime = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), properties,
static_cast<std::string>(bootupTimeProperties[i]), currTime);
if (!success || !currTime)
{
messages::internalError(aResp->res);
return;
}
if (i == 0)
{
lastTime = *currTime;
continue;
}
uint64_t timeDiff = 0;
// KernelTime is 0 and timestamps before kernelTime are negative,
// timestamps after kernel are positive.
if (*currTime == 0 && i != kernelIdx)
{
continue;
}
if (i <= kernelIdx)
{
if (lastTime != 0)
{
timeDiff = lastTime - *currTime;
}
}
else
{
timeDiff = *currTime - lastTime;
}
lastTime = *currTime;
aResp->res.jsonValue["BootTimeStatistics"]
[bootupTimeMetricName[metricIdx]] =
static_cast<double>(timeDiff) / 1000000.0;
metricIdx = i;
}
}
catch (...)
{
messages::internalError(aResp->res);
}
}
void getBootTimeInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::managedObjectStore->getAllProperties(
"org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", context,
[asyncResp](const boost::system::error_code ec2,
const dbus::utility::DBusPropertiesMap& properties) {
if (ec2)
{
BMCWEB_LOG_ERROR << "DBUS error getting boot time";
return;
}
calculateBootTimeResp(asyncResp, properties);
});
}
void populateDaemonEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string objectPath)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::managedObjectStore->getAllProperties(
"xyz.openbmc_project.Metric", objectPath,
"xyz.openbmc_project.Metric.Daemon", context,
[asyncResp](const boost::system::error_code ec,
const dbus::utility::DBusPropertiesMap& propertyMap) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBus error getting daemon property";
return;
}
nlohmann::json daemon;
for (const auto& propertyPair : propertyMap)
{
if (propertyPair.first == "CommandLine")
{
daemon[propertyPair.first] =
std::get<std::string>(propertyPair.second);
continue;
}
if (daemonDoublePropertiesSet.contains(propertyPair.first))
{
daemon[propertyPair.first] =
std::get<double>(propertyPair.second);
continue;
}
if (daemonSizeTPropertiesSet.contains(propertyPair.first))
{
daemon[propertyPair.first] =
std::get<size_t>(propertyPair.second);
continue;
}
}
asyncResp->res.jsonValue["TopProcesses"].emplace_back(daemon);
});
}
void getDaemonInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
asyncResp->res.jsonValue["TopProcesses"] = nlohmann::json::array();
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<std::vector<std::string>>(
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/metric/bmc0/daemon",
"xyz.openbmc_project.Association", "endpoints", context,
[asyncResp](const boost::system::error_code ec,
const std::vector<std::string>& endpoints) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "DBus error getting daemon association endpoints";
return;
}
for (size_t i = 0; i < endpoints.size(); ++i)
{
populateDaemonEntry(asyncResp, endpoints[i]);
}
});
}
void getLatencyInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
std::array<std::string, 3> latencyPropertynames{
{"LatencyMin", "LatencyMax", "LatencyAvg"}};
for (const std::string& propertyName : latencyPropertynames)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
dbus_utils::getProperty<double>(
"xyz.openbmc_project.Metric", "/xyz/openbmc_project/metric/bmc0",
"xyz.openbmc_project.Metric.BMC", propertyName, context,
[asyncResp, propertyName](const boost::system::error_code ec,
const double property) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "DBus error getting daemon association endpoints";
return;
}
asyncResp->res.jsonValue["Oem"]["Google"]["Latency"][propertyName] =
property;
});
}
}
inline void
afterGetManagerStartTime(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const boost::system::error_code& ec,
uint64_t bmcwebResetTime)
{
if (ec)
{
// Not all servers will be running in systemd, so ignore the error.
return;
}
using std::chrono::steady_clock;
std::chrono::duration<steady_clock::rep, std::micro> usReset{
bmcwebResetTime};
steady_clock::time_point resetTime{usReset};
steady_clock::time_point now = steady_clock::now();
steady_clock::duration runTime = now - resetTime;
if (runTime < steady_clock::duration::zero())
{
BMCWEB_LOG_CRITICAL << "Uptime was negative????";
messages::internalError(aResp->res);
return;
}
// Floor to the closest millisecond
using Milli = std::chrono::duration<steady_clock::rep, std::milli>;
Milli milli = std::chrono::floor<Milli>(runTime);
using SecondsFloat = std::chrono::duration<double>;
SecondsFloat sec = std::chrono::duration_cast<SecondsFloat>(milli);
aResp->res.jsonValue["ServiceRootUptimeSeconds"] = sec.count();
}
inline void
managerGetServiceRootUptime(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
{
managedStore::ManagedObjectStoreContext context(aResp);
dbus_utils::getProperty<uint64_t>(
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1/unit/bmcweb_2eservice",
"org.freedesktop.systemd1.Unit", "ActiveEnterTimestampMonotonic",
context, std::bind_front(afterGetManagerStartTime, aResp));
}
/**
* handleManagerDiagnosticData supports ManagerDiagnosticData.
* It retrieves BMC health information from various DBus resources and returns
* the information through the response.
*/
inline void handleManagerDiagnosticDataGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#ManagerDiagnosticData.v1_2_0.ManagerDiagnosticData";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData";
asyncResp->res.jsonValue["Id"] = "ManagerDiagnosticData";
asyncResp->res.jsonValue["Name"] = "Manager Diagnostic Data";
#ifdef BMCWEB_ENABLE_REDFISH_OEM_GRPC_STATS
asyncResp->res.jsonValue["Oem"]["Google"]["@odata.type"] =
"#GoogleManagerDiagnosticData.v1_0_0.GoogleManagerDiagnosticData";
asyncResp->res.jsonValue["Oem"]["Google"]["gRPCStatistics"]["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/"
"GooglegRPCStatistics";
#endif
#ifdef BMCWEB_ENABLE_STATEFUL_BMCWEB
asyncResp->res.jsonValue["Oem"]["Google"]["GoogleManagedObjectStoreMetrics"]
["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/GoogleManagedObjectStoreMetrics";
#endif
managerGetServiceRootUptime(asyncResp);
// Start the first step of the 4-step process to populate
// the BMC diagnostic data
findBmcInventory(asyncResp);
getBootInfo(asyncResp);
getBootTimeInfo(asyncResp);
getDaemonInfo(asyncResp);
getLatencyInfo(asyncResp);
}
#ifdef BMCWEB_ENABLE_REDFISH_OEM_GRPC_STATS
inline void handleManagerDiagnosticDataGooglegRPCStatsHead(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.addHeader(
boost::beast::http::field::link,
"</redfish/v1/JsonSchemas/GoogleManagerDiagnosticData/"
"GooglegRPCStatistics.json>; rel=describedby");
}
inline void handleManagerDiagnosticDataGooglegRPCStatsGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
nlohmann::json& j = asyncResp->res.jsonValue;
j["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/"
"GooglegRPCStatistics";
j["@odata.type"] = "#GooglegRPCStatistics.v1_0_0.GooglegRPCStatistics";
j["Id"] = "GooglegRPCStatistics";
j["Name"] = "Google gRPC Statistics";
grpc_redfish::gRPCStatisticsShared shared_stats;
grpc_redfish::gRPCStatistics stats = shared_stats.GetgRPCStatistics();
j["gRPCInitLatencyMs"] = stats.grpc_init_latency_ms.count();
j["AuthenticationLatencyMs"] = stats.authn_latency_total_ms.count();
j["QueueLatencyMs"] = stats.queue_latency_total_ms.count();
j["RequestLatencyMs"] = stats.request_latency_total_ms.count();
j["AuthorizationLatencyMs"] = stats.authz_latency_total_ms.count();
j["ProcessingLatencyMs"] = stats.processing_latency_total_ms.count();
j["ResponseLatencyMs"] = stats.response_latency_total_ms.count();
j["AuthorizedCount"] = stats.total_authorized_count;
j["AuthorizedFailCount"] = stats.total_authorized_fail_count;
j["AuthenticatedCount"] = stats.total_authenticated_count;
j["AuthenticatedFailCount"] = stats.total_authenticated_fail_count;
nlohmann::json::array_t httpMethodCounts;
for (auto const& [http, count] : stats.request_count)
{
nlohmann::json::object_t countObj;
countObj["Method"] = static_cast<unsigned>(http);
countObj["Count"] = static_cast<unsigned>(count);
httpMethodCounts.emplace_back(std::move(countObj));
}
nlohmann::json::array_t httpResponseCounts;
for (auto const& [response, count] : stats.http_response_count)
{
nlohmann::json::object_t countObj;
countObj["StatusCode"] = static_cast<unsigned>(response);
countObj["Count"] = static_cast<unsigned>(count);
httpResponseCounts.emplace_back(std::move(countObj));
}
nlohmann::json::array_t gRPCStatusCounts;
for (auto const& [status, count] : stats.grpc_response_count)
{
nlohmann::json::object_t countObj;
countObj["StatusCode"] = static_cast<unsigned>(status);
countObj["Count"] = static_cast<unsigned>(count);
gRPCStatusCounts.emplace_back(std::move(countObj));
}
j["HTTPMethods"] = std::move(httpMethodCounts);
j["HTTPResponseCodes"] = std::move(httpResponseCounts);
j["gRPCResponseCodes"] = std::move(gRPCStatusCounts);
}
#endif
inline void requestRoutesManagerDiagnosticData(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/ManagerDiagnosticData")
.privileges(redfish::privileges::getManagerDiagnosticData)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleManagerDiagnosticDataGet, std::ref(app)));
#ifdef BMCWEB_ENABLE_REDFISH_OEM_GRPC_STATS
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/"
"GooglegRPCStatistics")
.privileges(redfish::privileges::headManagerDiagnosticData)
.methods(boost::beast::http::verb::head)(std::bind_front(
handleManagerDiagnosticDataGooglegRPCStatsHead, std::ref(app)));
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/"
"GooglegRPCStatistics")
.privileges(redfish::privileges::getManagerDiagnosticData)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleManagerDiagnosticDataGooglegRPCStatsGet, std::ref(app)));
#endif
}
} // namespace redfish