From a33cde6cea0cf4bfb700a9002175e74553dc52a4 Mon Sep 17 00:00:00 2001 From: StarryNight Date: Tue, 7 Nov 2023 13:32:05 +0800 Subject: [PATCH] ratelimit support stat prefix (#30677) Risk Level: low Testing: ut Signed-off-by: wangkai19 --- .../http/ratelimit/v3/rate_limit.proto | 6 +- changelogs/current.yaml | 5 ++ .../http/http_filters/rate_limit_filter.rst | 2 +- .../filters/common/ratelimit/stat_names.h | 18 ++++-- .../filters/http/ratelimit/ratelimit.h | 2 +- .../filters/http/ratelimit/ratelimit_test.cc | 57 +++++++++++++++++++ 6 files changed, 83 insertions(+), 7 deletions(-) diff --git a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto index bd5eb47eb70c..3e33536b228a 100644 --- a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto +++ b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto @@ -25,7 +25,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.ratelimit] -// [#next-free-field: 13] +// [#next-free-field: 14] message RateLimit { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.rate_limit.v2.RateLimit"; @@ -130,6 +130,10 @@ message RateLimit { // Sets the HTTP status that is returned to the client when the ratelimit server returns an error // or cannot be reached. The default status is 500. type.v3.HttpStatus status_on_error = 12; + + // Optional additional prefix to use when emitting statistics. This allows to distinguish + // emitted statistics between configured ``ratelimit`` filters in an HTTP filter chain. + string stat_prefix = 13; } // Global rate limiting :ref:`architecture overview `. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 71727829240f..2e952dea71eb 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -156,5 +156,10 @@ new_features: added :ref:`with_request_body ` to optionally override the default behavior of sending the request body to the authorization server from the per-route filter. +- area: ratelimit + change: | + Ratelimit supports optional additional prefix to use when emitting statistics with :ref:`stat_prefix + ` + configuration flag. deprecated: diff --git a/docs/root/configuration/http/http_filters/rate_limit_filter.rst b/docs/root/configuration/http/http_filters/rate_limit_filter.rst index 5e62c2b49b6d..5aca3d31473d 100644 --- a/docs/root/configuration/http/http_filters/rate_limit_filter.rst +++ b/docs/root/configuration/http/http_filters/rate_limit_filter.rst @@ -158,7 +158,7 @@ no entry is added. Statistics ---------- -The rate limit filter outputs statistics in the ``cluster..ratelimit.`` namespace. +The rate limit filter outputs statistics in the ``cluster..ratelimit..`` namespace. 429 responses or the configured :ref:`rate_limited_status ` are emitted to the normal cluster :ref:`dynamic HTTP statistics `. diff --git a/source/extensions/filters/common/ratelimit/stat_names.h b/source/extensions/filters/common/ratelimit/stat_names.h index 65a8b0614d5d..4af10617f0ac 100644 --- a/source/extensions/filters/common/ratelimit/stat_names.h +++ b/source/extensions/filters/common/ratelimit/stat_names.h @@ -1,5 +1,6 @@ #pragma once +#include "source/common/common/empty_string.h" #include "source/common/stats/symbol_table.h" namespace Envoy { @@ -12,10 +13,19 @@ namespace RateLimit { // filters. These should generally be initialized once per process, and // not per-request, to avoid lock contention. struct StatNames { - explicit StatNames(Stats::SymbolTable& symbol_table) - : pool_(symbol_table), ok_(pool_.add("ratelimit.ok")), error_(pool_.add("ratelimit.error")), - failure_mode_allowed_(pool_.add("ratelimit.failure_mode_allowed")), - over_limit_(pool_.add("ratelimit.over_limit")) {} + explicit StatNames(Stats::SymbolTable& symbol_table, + const std::string& stat_prefix = EMPTY_STRING) + : pool_(symbol_table), ok_(pool_.add(createPoolStatName(stat_prefix, "ok"))), + error_(pool_.add(createPoolStatName(stat_prefix, "error"))), + failure_mode_allowed_(pool_.add(createPoolStatName(stat_prefix, "failure_mode_allowed"))), + over_limit_(pool_.add(createPoolStatName(stat_prefix, "over_limit"))) {} + + // This generates ratelimit..name + const std::string createPoolStatName(const std::string& stat_prefix, const std::string& name) { + return absl::StrCat("ratelimit", + stat_prefix.empty() ? EMPTY_STRING : absl::StrCat(".", stat_prefix), ".", + name); + } Stats::StatNamePool pool_; Stats::StatName ok_; Stats::StatName error_; diff --git a/source/extensions/filters/http/ratelimit/ratelimit.h b/source/extensions/filters/http/ratelimit/ratelimit.h index 782812204ea8..fb35a2380241 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.h +++ b/source/extensions/filters/http/ratelimit/ratelimit.h @@ -56,7 +56,7 @@ class FilterConfig { config.rate_limited_as_resource_exhausted() ? absl::make_optional(Grpc::Status::WellKnownGrpcStatus::ResourceExhausted) : absl::nullopt), - http_context_(http_context), stat_names_(scope.symbolTable()), + http_context_(http_context), stat_names_(scope.symbolTable(), config.stat_prefix()), rate_limited_status_(toErrorCode(config.rate_limited_status().code())), response_headers_parser_( Envoy::Router::HeaderParser::configure(config.response_headers_to_add())), diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 3465a44e6bb5..93da09c9f600 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -106,6 +106,11 @@ class HttpRateLimitFilterTest : public testing::Test { code: 200 )EOF"; + const std::string stat_prefix_config_ = R"EOF( + domain: foo + stat_prefix: with_stat_prefix + )EOF"; + Filters::Common::RateLimit::MockClient* client_; NiceMock filter_callbacks_; Stats::StatNamePool pool_{filter_callbacks_.clusterInfo()->statsScope().symbolTable()}; @@ -1615,6 +1620,58 @@ TEST_F(HttpRateLimitFilterTest, DefaultConfigValueTest) { EXPECT_EQ(FilterRequestType::Both, config_->requestType()); } +// Test that defining stat_prefix appends an additional prefix to the emitted statistics names. +TEST_F(HttpRateLimitFilterTest, StatsWithPrefix) { + const std::string stat_prefix = "with_stat_prefix"; + const std::string over_limit_counter_name_with_prefix = + absl::StrCat("ratelimit.", stat_prefix, ".over_limit"); + const std::string over_limit_counter_name_without_prefix = "ratelimit.over_limit"; + + setUpTest(stat_prefix_config_); + InSequence s; + + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _)) + .WillOnce(SetArgReferee<0>(descriptor_)); + EXPECT_CALL(*client_, limit(_, _, _, _, _, 0)) + .WillOnce( + WithArgs<0>(Invoke([&](Filters::Common::RateLimit::RequestCallbacks& callbacks) -> void { + request_callbacks_ = &callbacks; + }))); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_, false)); + + EXPECT_CALL(filter_callbacks_.stream_info_, + setResponseFlag(StreamInfo::ResponseFlag::RateLimited)); + + Http::ResponseHeaderMapPtr h{new Http::TestResponseHeaderMapImpl()}; + Http::TestResponseHeaderMapImpl response_headers{ + {":status", "429"}, + {"x-envoy-ratelimited", Http::Headers::get().EnvoyRateLimitedValues.True}}; + EXPECT_CALL(filter_callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); + EXPECT_CALL(filter_callbacks_, continueDecoding()).Times(0); + + request_callbacks_->complete(Filters::Common::RateLimit::LimitStatus::OverLimit, nullptr, + std::move(h), nullptr, "", nullptr); + + EXPECT_EQ(1U, filter_callbacks_.clusterInfo() + ->statsScope() + .counterFromString(over_limit_counter_name_with_prefix) + .value()); + + EXPECT_EQ(0U, filter_callbacks_.clusterInfo() + ->statsScope() + .counterFromString(over_limit_counter_name_without_prefix) + .value()); + EXPECT_EQ( + 1U, + filter_callbacks_.clusterInfo()->statsScope().counterFromStatName(upstream_rq_4xx_).value()); + EXPECT_EQ( + 1U, + filter_callbacks_.clusterInfo()->statsScope().counterFromStatName(upstream_rq_429_).value()); + EXPECT_EQ("request_rate_limited", filter_callbacks_.details()); +} + } // namespace } // namespace RateLimitFilter } // namespace HttpFilters