| #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 |