blob: a997a1cb8181494f89953b7071ef8e323a82c7d2 [file] [log] [blame]
#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