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