blob: 613f348bead3823027dc614f2f2158904057f00d [file] [log] [blame]
#include "managed_store_http.hpp"
#include "logging.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include <nlohmann/json.hpp>
#include <algorithm>
#include <charconv>
#include <string_view>
namespace managedStore
{
// Max number of PQ Entries that should be exported for debug.
constexpr size_t kPQExportLimit = 20000;
class ManagedObjectStoreHttp
{
public:
// static only class, extension/friend to ManagedObjectStore
ManagedObjectStoreHttp() = delete;
~ManagedObjectStoreHttp() = delete;
static void requestRoutesManagedStoreDebug(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
(void)app;
// playing with the header:
auto maxAgeSec = 0;
const auto maxAgeSecStr =
req.getHeaderValue(bmcweb::BMCWEB_HINT_MAX_AGE_SEC);
if (!maxAgeSecStr.empty())
{
std::from_chars(maxAgeSecStr.data(),
maxAgeSecStr.data() + maxAgeSecStr.size(),
maxAgeSec);
}
BMCWEB_LOG_STATEFUL_DEBUG
<< "=> /debug/managed_store: target: " << req.target()
<< " method: " << req.method() << " body: " << req.body()
<< " maxAgeSecStr: " << maxAgeSecStr << " maxAgeSec: " << maxAgeSec;
// ManagedObjectStore instance:
auto store = managedStore::managedObjectStore;
if (!store)
{
asyncResp->res.jsonValue["err"] = "managed object store is null";
BMCWEB_LOG_STATEFUL_ERROR << "=> ERROR: "
<< asyncResp->res.jsonValue.dump();
return;
}
// config:
asyncResp->res.jsonValue["config"] = store->getConfig().toJson();
// objects:
const auto& objects = store->getManagedObjects();
auto jsonObjKeysArray = nlohmann::json::array();
auto jsonObjects = nlohmann::json();
for (const auto& iter : objects)
{
jsonObjKeysArray.push_back(iter.first);
jsonObjects[iter.first] = iter.second.toJson();
}
asyncResp->res.jsonValue["managed_objects"] = jsonObjects;
asyncResp->res.jsonValue["managed_objects_keys"] = jsonObjKeysArray;
// Export pq entries:
auto jsonObjPqArray = nlohmann::json::array();
ManagedStorePriorityQueue pq = store->getPriorityQueue();
size_t entries = 0;
while (!pq.empty() && entries++ < kPQExportLimit)
{
const auto& entry = pq.top();
jsonObjPqArray.push_back(entry.managedStoreKey.toString());
pq.pop();
}
asyncResp->res.jsonValue["managed_objects_pq_entries"] = jsonObjPqArray;
// Export sizes for all containers in managedStore:
asyncResp->res.jsonValue["managed_objects_keys_size"] = objects.size();
asyncResp->res.jsonValue["managed_objects_pq_size"] =
store->getPriorityQueue().size();
// ManagedStore counters from managedStoreTracker
const ManagedStoreTracker& storeTracker = store->getStoreTracker();
asyncResp->res.jsonValue["tracker"] = storeTracker.toJson();
// always keep this one as the last item:
asyncResp->res.jsonValue["ok"] = true;
}
static void requestRoutesManagedStorePostFlush(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
(void)app;
BMCWEB_LOG_STATEFUL_DEBUG
<< "=> /debug/managed_store: target: " << req.target()
<< " method: " << req.method() << " body: " << req.body();
// ManagedObjectStore instance:
auto store = managedStore::managedObjectStore;
if (!store)
{
asyncResp->res.jsonValue["err"] = "managed object store is null";
BMCWEB_LOG_STATEFUL_ERROR << "=> ERROR: "
<< asyncResp->res.jsonValue.dump();
return;
}
asyncResp->res.jsonValue["config"] = store->getConfig().toJson();
store->clearAllObjects();
asyncResp->res.jsonValue["managed_objects_keys_size"] =
store->getManagedObjects().size();
asyncResp->res.jsonValue["managed_objects_pq_size"] =
store->getPriorityQueue().size();
// always keep this one as the last item:
asyncResp->res.jsonValue["ok"] = true;
}
static void requestGetGoogleManagedObjectStoreMetrics(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
(void)app;
BMCWEB_LOG_STATEFUL_DEBUG << "=> GoogleManagedObjectStoreMetrics:"
<< " method: " << req.method()
<< " target: " << req.target()
<< " body: " << req.body();
// ManagedObjectStore instance:
auto store = managedStore::managedObjectStore;
if (!store)
{
asyncResp->res.jsonValue["err"] = "managed object store is null";
BMCWEB_LOG_STATEFUL_ERROR << "=> ERROR: "
<< asyncResp->res.jsonValue.dump();
return;
}
const int64_t kNumBuckets = 20;
const int64_t kBucketSize = 1000;
nlohmann::json managedObjectsAgeHistograms = nlohmann::json::array();
for (size_t i = 0; i < kNumBuckets; i++)
{
nlohmann::json bucket;
bucket["Start"] = i * kBucketSize;
bucket["Count"] = 0;
managedObjectsAgeHistograms.push_back(bucket);
}
const auto now = clockNow();
nlohmann::json managedObjects = nlohmann::json::array();
int64_t maxObjectAgeMs = 0;
int64_t maxLastServedAgeMs = 0;
int64_t countLastServed = 0;
uint64_t sumTotalObjectRefreshes = 0;
uint64_t sumTotalObjectRefreshScheduled = 0;
uint64_t sumTotalObjectUsed = 0;
int keyIndex = 0;
for (const auto& iter : store->getManagedObjects())
{
const auto& key = iter.first;
const auto& value = iter.second;
nlohmann::json obj;
obj["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/GoogleManagedObjectStoreMetrics#/ManagedObjects/" +
std::to_string(keyIndex);
obj["@odata.type"] =
"#GoogleManagedObjectMetrics.v1_0_0.GoogleManagedObjectMetrics";
// Split the key:
std::string serviceName, objectPath, interface, property;
KeyType::fromKey(key, serviceName, objectPath, interface, property);
obj["Id"] = keyIndex;
obj["ServiceName"] = serviceName;
obj["ObjectPath"] = objectPath;
obj["Interface"] = interface;
obj["Property"] = property;
obj["Age"] = clockSinceMilliseconds(value.lastRefreshAt, now);
obj["RefreshCountDone"] = value.numRefreshesDone;
obj["RefreshCountScheduled"] = value.numRefreshesScheduled;
obj["NumTimesUsed"] = value.numTimesUsed;
keyIndex++;
// sum up the counts:
sumTotalObjectRefreshes += value.numRefreshesDone;
sumTotalObjectRefreshScheduled += value.numRefreshesScheduled;
sumTotalObjectUsed += value.numTimesUsed;
nlohmann::json lastUsedAgesObj = nlohmann::json::array();
if (value.lastUsedAges)
{
std::for_each(
value.lastUsedAges->begin(), value.lastUsedAges->end(),
[&](const std::chrono::steady_clock::duration& dur) {
const int64_t lastUsedDurationMs =
clockDurationMilliseconds(dur);
// report the last served ages:
lastUsedAgesObj.push_back(lastUsedDurationMs);
// total count:
countLastServed += 1;
// track the max:
maxLastServedAgeMs =
std::max(maxLastServedAgeMs, lastUsedDurationMs);
});
}
obj["AgeLastServed"] = lastUsedAgesObj;
// object age histogram:
const int64_t objectAgeMs =
clockSinceMilliseconds(value.lastRefreshAt, now);
if (objectAgeMs >= 0)
{
int64_t bucketIndex =
std::min(objectAgeMs / kBucketSize, kNumBuckets);
if (bucketIndex >= 0)
{
// last buckets catches all:
if (bucketIndex >= kNumBuckets)
bucketIndex = (kNumBuckets - 1);
const auto i = static_cast<size_t>(bucketIndex);
auto count =
managedObjectsAgeHistograms[i]["Count"]
.get_ref<nlohmann::json::number_integer_t&>();
managedObjectsAgeHistograms[i]["Count"] = count + 1;
}
// max age:
maxObjectAgeMs = std::max(maxObjectAgeMs, objectAgeMs);
}
managedObjects.push_back(obj);
}
// complying with:
// https://critique.corp.google.com/cl/546939035
nlohmann::json google;
google["@odata.id"] =
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/GoogleManagedObjectStoreMetrics";
google["@odata.type"] =
"#GoogleManagedObjectStoreMetrics.v1_0_0.GoogleManagedObjectStoreMetrics";
// Age Buckets:
google["ManagedObjectsAgeHistograms"]["AgeBuckets"] =
managedObjectsAgeHistograms;
// Age Max:
google["ManagedObjectsAgeHistograms"]["AgeMax"] = maxObjectAgeMs;
// Last Served Count:
google["ManagedObjectsAgeHistograms"]["LastServedCount"] =
countLastServed;
google["ManagedObjectsAgeHistograms"]["LastServedMax"] =
maxLastServedAgeMs;
// Total num of refreshes:
google["NumRefreshesDone"] = sumTotalObjectRefreshes;
google["NumRefreshesScheduled"] = sumTotalObjectRefreshScheduled;
// Total num used:
google["NumTimesUsed"] = sumTotalObjectUsed;
// ManagedStore counters from managedStoreTracker
const ManagedStoreTracker& storeTracker = store->getStoreTracker();
google["NumGetManagedObjects"] = storeTracker.countGetManagedObjects;
google["NumGetManagedObjectsCacheMiss"] =
storeTracker.countGetManagedObjectsCacheMiss;
google["NumGetManagedObjectsCacheHit"] =
storeTracker.countGetManagedObjectsCacheHit;
google["NumGetManagedObjectsCacheMissMaxAge"] =
storeTracker.countGetManagedObjectsCacheMissMaxAge;
// Export max pending and the threshold:
google["PendingResponsesMax"] =
storeTracker.countMaxPendingDbusResponses;
google["PendingResponsesThreshold"] =
storeTracker.countThresholdPendingResponses;
// objects: (will probably remove from none debug builds)
google["ManagedObjects"] = managedObjects;
// final object:
asyncResp->res.jsonValue = google;
}
};
void requestRoutesManagedStore(App& app)
{
// TODO: manage who gets to trigger these (permissions):
// TODO: perhaps only for debug builds? or runtime flag enabled?
// TODO: at least integrate with the existing redfish permissions scheme:
// `.privileges({{"Login"}})`
// a few exports for debugging:
BMCWEB_ROUTE(app, "/debug/managed_store")
// .privileges(redfish::privileges::postManager)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
ManagedObjectStoreHttp::requestRoutesManagedStoreDebug(app, req,
asyncResp);
});
// flush the managed objects:
BMCWEB_ROUTE(app, "/debug/managed_store/clear")
.methods(boost::beast::http::verb::post)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
ManagedObjectStoreHttp::requestRoutesManagedStorePostFlush(app, req,
asyncResp);
});
// GoogleManagedObjectStoreMetrics:
BMCWEB_ROUTE(
app,
"/redfish/v1/Managers/bmc/ManagerDiagnosticData/Oem/Google/GoogleManagedObjectStoreMetrics")
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
ManagedObjectStoreHttp::requestGetGoogleManagedObjectStoreMetrics(
app, req, asyncResp);
});
}
} // namespace managedStore