| #include "request_stats.hpp" | |
| #include <chrono> // NOLINT | |
| #include <cstdint> | |
| #include <sstream> | |
| #include <string> | |
| #include "logging.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store_clock.hpp" | |
| namespace managedStore { | |
| /** the linear constants below are a good start, we can revisit them as we find | |
| * value in this histogram */ | |
| /** num of buckets */ | |
| constexpr static int64_t kHistNumBuckets = 50; | |
| /** size of each bucket in milliseconds */ | |
| constexpr static int64_t kHistBucketSizeMillisec = 100; | |
| /** global stats store config */ | |
| RequestStatsStoreConfig statsStoreConfigDefault; // NOLINT | |
| /** global stats store */ | |
| RequestStatsStore statsStore(statsStoreConfigDefault); // NOLINT | |
| RequestStatsContext::RequestStatsContext( | |
| const std::string& key, const std::chrono::steady_clock::time_point& now) | |
| : queryURLKey(key), timepointStart(now), rdeStats() { | |
| BMCWEB_LOG_DEBUG << "RequestStatsContext: start: " << this->toJson(); | |
| } | |
| RequestStatsContext::~RequestStatsContext() { | |
| BMCWEB_LOG_DEBUG << "RequestStatsContext: done: " << this->toJson(); | |
| } | |
| std::string RequestStatsContext::queryURLKeyFor( | |
| const boost::beast::http::verb& method, const boost::urls::url_view& qURL) { | |
| std::stringstream key; | |
| key << method << kSep << qURL; | |
| return key.str(); | |
| } | |
| nlohmann::json RequestStatsContext::toJson() const { | |
| nlohmann::json obj; | |
| obj["queryURLKey"] = this->queryURLKey; | |
| if (managedStore::RequestStatsStore::isRdeRateLimitEnabled()) { | |
| obj["rdeStats"] = nlohmann::json::object(); | |
| obj["rdeStats"]["rdeReqs"] = getRdeReqs(); | |
| obj["rdeStats"]["rdeDbusErrors"] = getRdeDbusErrors(); | |
| obj["rdeStats"]["rdeSkips"] = getRdeSkips(); | |
| obj["rdeStats"]["rdeRateLimitToggles"] = getRdeRateLimitToggles(); | |
| } | |
| obj["ageMillisec"] = | |
| managedStore::clockSinceMilliseconds(this->timepointStart); | |
| return obj; | |
| } | |
| std::string RequestStatsContext::getQueryURLKey() const { | |
| return this->queryURLKey; | |
| }; | |
| std::chrono::steady_clock::time_point RequestStatsContext::getTimepointStart() | |
| const { | |
| return this->timepointStart; | |
| }; | |
| void RequestStatsContext::setOK(bool ok) { this->isOK = ok; } | |
| bool RequestStatsContext::ok() const { return this->isOK; } | |
| void RequestStatsContext::updateRdeReqs() { this->rdeStats.totalReqs += 1; } | |
| void RequestStatsContext::updateRdeDbusErrors() { | |
| this->rdeStats.totalDbusErrors += 1; | |
| } | |
| void RequestStatsContext::updateRdeSkips() { this->rdeStats.totalSkips += 1; } | |
| void RequestStatsContext::updateRdeRateLimitToggles() { | |
| this->rdeStats.totalRateLimitToggles += 1; | |
| } | |
| int RequestStatsContext::getRdeReqs() const { return this->rdeStats.totalReqs; } | |
| int RequestStatsContext::getRdeDbusErrors() const { | |
| return this->rdeStats.totalDbusErrors; | |
| } | |
| int RequestStatsContext::getRdeSkips() const { | |
| return this->rdeStats.totalSkips; | |
| } | |
| int RequestStatsContext::getRdeRateLimitToggles() const { | |
| return this->rdeStats.totalRateLimitToggles; | |
| } | |
| nlohmann::json RequestStatsStoreConfig::toJson() const { | |
| nlohmann::json obj; | |
| obj["enable"] = this->enable; | |
| obj["enableRdeRateLimit"] = this->enableRdeRateLimit; | |
| obj["rdeDbusErrorThreshold"] = this->rdeDbusErrorThreshold; | |
| obj["rdeMaxSkips"] = this->rdeMaxSkips; | |
| obj["bucket_num"] = kHistNumBuckets; | |
| obj["bucket_millisec"] = kHistBucketSizeMillisec; | |
| return obj; | |
| } | |
| void RequestStatsStore::initialize(const RequestStatsStoreConfig& config) { | |
| BMCWEB_LOG_INFO << "RequestStatsStore::initialize: config: " | |
| << config.toJson(); | |
| statsStore.config = config; | |
| } | |
| RequestStatsStore& RequestStatsStore::instance() { return statsStore; } | |
| RequestStatsStore::RequestStatsStore(const RequestStatsStoreConfig& conf) | |
| : config(conf) {} | |
| RequestStatsStore::~RequestStatsStore() = default; | |
| nlohmann::json RequestStatsStore::toJson() const { | |
| nlohmann::json obj; | |
| obj["config"] = this->config.toJson(); | |
| if (isRdeRateLimitEnabled()) { | |
| obj["rdeStats"] = nlohmann::json::object(); | |
| obj["rdeStats"]["totalReqs"] = this->rdeStats.totalReqs; | |
| obj["rdeStats"]["totalDbusErrors"] = this->rdeStats.totalDbusErrors; | |
| obj["rdeStats"]["totalSkips"] = this->rdeStats.totalSkips; | |
| obj["rdeStats"]["totalRateLimitToggles"] = | |
| this->rdeStats.totalRateLimitToggles; | |
| obj["rdeStats"]["CurrentRdeDbusErrorCount"] = | |
| this->rdeStats.rdeDbusErrorCount; | |
| obj["rdeStats"]["CurrentRdeRequestSkips"] = this->rdeStats.rdeRequestSkips; | |
| } | |
| // TODO: pick on of these: | |
| // obj["statsList"] = nlohmann::json::array(); | |
| obj["statsMap"] = nlohmann::json::object(); | |
| for (const auto& query_stats : this->requestURLStats) { | |
| auto query_stats_json = query_stats.second.toJson(); | |
| query_stats_json["target"] = query_stats.first; | |
| // obj["statsList"].push_back(query_stats_json); | |
| obj["statsMap"][query_stats.first] = query_stats_json; | |
| } | |
| return obj; | |
| } | |
| void RequestStatsStore::clear() { | |
| this->requestURLStats.clear(); | |
| this->rdeStats.clear(); | |
| } | |
| void RequestStatsStore::logRequestStats( | |
| std::chrono::steady_clock::time_point now, | |
| const RequestStatsContext& requestStatsContext) { | |
| BMCWEB_LOG_DEBUG << "logRequestStats: " << requestStatsContext.toJson(); | |
| // auto iter = | |
| // this->requestURLStats.find(requestStatsContext.getQueryURLKey()); | |
| // if (iter == this->requestURLStats.end()) | |
| // { | |
| // this->requestURLStats[requestStatsContext.getQueryURLKey()] = | |
| // RequestStats(); | |
| // iter = | |
| // this->requestURLStats.find(requestStatsContext.getQueryURLKey()); | |
| // } | |
| auto insert_ret = this->requestURLStats.insert( | |
| {requestStatsContext.getQueryURLKey(), RequestStats()}); | |
| auto insert_ret_iter = insert_ret.first; | |
| // check again, just in case | |
| if (insert_ret_iter == this->requestURLStats.end()) { | |
| BMCWEB_LOG_ERROR << "failed to allocate stats for: " | |
| << requestStatsContext.getQueryURLKey(); | |
| return; | |
| } | |
| RequestStats& request_stats = insert_ret_iter->second; | |
| // log returned status code: | |
| if (requestStatsContext.ok()) { | |
| request_stats.countOK += 1; | |
| } else { | |
| request_stats.countError += 1; | |
| } | |
| // calculate and export latency: | |
| const int64_t latency_milliseconds = managedStore::clockSinceMilliseconds( | |
| requestStatsContext.getTimepointStart(), now); | |
| if (latency_milliseconds >= 0) { | |
| int64_t bucket_index = latency_milliseconds / kHistBucketSizeMillisec; | |
| // last bucket is "catch all": | |
| if (bucket_index >= kHistNumBuckets) { | |
| bucket_index = kHistNumBuckets; | |
| } | |
| // increment the right histogram bucket: | |
| request_stats.latencyHistogramMilliseconds[kHistBucketSizeMillisec * | |
| (1 + bucket_index)] += 1; | |
| } else { | |
| BMCWEB_LOG_ERROR << "logRequestStats:" | |
| << " context: " << requestStatsContext.toJson() | |
| << " latency_milliseconds: " << latency_milliseconds | |
| << " negative"; | |
| } | |
| BMCWEB_LOG_INFO << "logRequestStats: iter: " | |
| << insert_ret_iter->second.toJson() | |
| << " requestStatsContext: " << requestStatsContext.toJson(); | |
| } | |
| void RequestStatsStore::logRdeRequestStats( | |
| const RequestStatsContext& requestStatsContext) { | |
| BMCWEB_LOG_DEBUG << "logRdeRequestStats: " << requestStatsContext.toJson(); | |
| this->rdeStats.totalReqs += requestStatsContext.getRdeReqs(); | |
| this->rdeStats.totalDbusErrors += requestStatsContext.getRdeDbusErrors(); | |
| this->rdeStats.totalSkips += requestStatsContext.getRdeSkips(); | |
| this->rdeStats.totalRateLimitToggles += | |
| requestStatsContext.getRdeRateLimitToggles(); | |
| } | |
| void RequestStatsStore::updateCurrentRdeDbusErrors() { | |
| this->rdeStats.rdeDbusErrorCount += 1; | |
| } | |
| int RequestStatsStore::getCurrentRdeDbusErrors() const { | |
| return this->rdeStats.rdeDbusErrorCount; | |
| } | |
| void RequestStatsStore::setCurrentRdeDbusErrors(int errCount) { | |
| this->rdeStats.rdeDbusErrorCount = errCount; | |
| } | |
| void RequestStatsStore::updateCurrentRdeRequestSkips() { | |
| if (this->rdeStats.rdeRequestSkips > 0) { | |
| this->rdeStats.rdeRequestSkips -= 1; | |
| } | |
| } | |
| int RequestStatsStore::getCurrentRdeRequestSkips() const { | |
| return this->rdeStats.rdeRequestSkips; | |
| } | |
| void RequestStatsStore::setCurrentRdeRequestSkips(int skipCount) { | |
| this->rdeStats.rdeRequestSkips = skipCount; | |
| } | |
| } // namespace managedStore |