Skip to content

Commit

Permalink
ratelimit support stat prefix (envoyproxy#30677)
Browse files Browse the repository at this point in the history
Risk Level: low
Testing: ut

Signed-off-by: wangkai19 <[email protected]>
  • Loading branch information
StarryVae authored Nov 7, 2023
1 parent dd05ef6 commit a33cde6
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// Rate limit :ref:`configuration overview <config_http_filters_rate_limit>`.
// [#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";
Expand Down Expand Up @@ -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 <arch_overview_global_rate_limit>`.
Expand Down
5 changes: 5 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,10 @@ new_features:
added :ref:`with_request_body
<envoy_v3_api_field_extensions.filters.http.ext_authz.v3.CheckSettings.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
<envoy_v3_api_field_extensions.filters.http.ratelimit.v3.RateLimit.stat_prefix>`
configuration flag.
deprecated:
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ no entry is added.
Statistics
----------

The rate limit filter outputs statistics in the ``cluster.<route target cluster>.ratelimit.`` namespace.
The rate limit filter outputs statistics in the ``cluster.<route target cluster>.ratelimit.<optional stat prefix>.`` namespace.
429 responses or the configured :ref:`rate_limited_status <envoy_v3_api_field_extensions.filters.http.ratelimit.v3.RateLimit.rate_limited_status>` are emitted to the normal cluster :ref:`dynamic HTTP statistics
<config_cluster_manager_cluster_stats_dynamic_http>`.

Expand Down
18 changes: 14 additions & 4 deletions source/extensions/filters/common/ratelimit/stat_names.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "source/common/common/empty_string.h"
#include "source/common/stats/symbol_table.h"

namespace Envoy {
Expand All @@ -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.<optional stat_prefix>.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_;
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/filters/http/ratelimit/ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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())),
Expand Down
57 changes: 57 additions & 0 deletions test/extensions/filters/http/ratelimit/ratelimit_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<Http::MockStreamDecoderFilterCallbacks> filter_callbacks_;
Stats::StatNamePool pool_{filter_callbacks_.clusterInfo()->statsScope().symbolTable()};
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit a33cde6

Please sign in to comment.