From b5b852320fa6732935500eea78279f68040f565b Mon Sep 17 00:00:00 2001 From: gaoweiwen Date: Fri, 28 May 2021 16:37:43 +0800 Subject: [PATCH 1/7] add rps_threshold & max_rejection_probability Signed-off-by: gaoweiwen --- .../v3alpha/admission_control.proto | 11 ++- .../http_filters/admission_control_filter.rst | 17 +++- .../v3alpha/admission_control.proto | 11 ++- .../admission_control/admission_control.cc | 24 ++++++ .../admission_control/admission_control.h | 4 + .../thread_local_controller.h | 11 +++ .../admission_control_filter_test.cc | 82 +++++++++++++++++++ .../admission_control_integration_test.cc | 12 +-- .../http/admission_control/config_test.cc | 31 +++++++ .../http/admission_control/controller_test.cc | 17 ++++ 10 files changed, 211 insertions(+), 9 deletions(-) diff --git a/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto b/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto index 33d415ab4391..c526afbc80b5 100644 --- a/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto +++ b/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Admission Control] // [#extension: envoy.filters.http.admission_control] -// [#next-free-field: 6] +// [#next-free-field: 8] message AdmissionControl { // Default method of specifying what constitutes a successful request. All status codes that // indicate a successful request must be explicitly specified if not relying on the default @@ -91,4 +91,13 @@ message AdmissionControl { // below this threshold, rejection probability will increase. Any success rate above the threshold // results in a rejection probability of 0. Defaults to 95%. config.core.v3.RuntimePercent sr_threshold = 5; + + // If the number of requests in the last second is less than this threshold, the request + // will not be rejected, even if the success rate is lower than sr_threshold. + // Defaults to 0. + config.core.v3.RuntimeUInt32 rps_threshold = 6; + + // The probability of rejection will never exceed this value, even if the failure rate is rising. + // Defaults to 80%. + config.core.v3.RuntimePercent max_rejection_probability = 7; } diff --git a/docs/root/configuration/http/http_filters/admission_control_filter.rst b/docs/root/configuration/http/http_filters/admission_control_filter.rst index 146b50dc31c3..f81142dcc8e5 100644 --- a/docs/root/configuration/http/http_filters/admission_control_filter.rst +++ b/docs/root/configuration/http/http_filters/admission_control_filter.rst @@ -24,7 +24,12 @@ The probability that the filter will reject a request is as follows: .. math:: - P_{reject} = {(\frac{n_{total} - s}{n_{total} + 1})}^\frac{1}{aggression} + P_{reject} = \left\{ + \begin{array}{cl} + 0 & \ (rps < rps\_threshold) \\ + min({(\frac{n_{total} - s}{n_{total} + 1})}^\frac{1}{aggression}\ ,\ max\_reject\_probability) & \ (rps \geq rps\_threshold) + \end{array} + \right. where, @@ -33,6 +38,7 @@ where, s = \frac{n_{success}}{threshold} +- *rps_threshold* is a configurable value that when RPS is lower than it, requests will pass through the filter. - *n* refers to a request count gathered in the sliding window. - *threshold* is a configurable value that dictates the lowest request success rate at which the filter will **not reject** requests. The value is normalized to [0,1] for the calculation. @@ -40,6 +46,7 @@ where, rejection probability as the success rate decreases. As the **aggression** increases, the rejection probability will be higher for higher success rates. See `Aggression`_ for a more detailed explanation. +- *max_reject_probability* represents the upper limit of the rejection probability. .. note:: The success rate calculations are performed on a per-thread basis for increased performance. In @@ -91,6 +98,12 @@ fields can be overridden via runtime settings. aggression: default_value: 1.5 runtime_key: "admission_control.aggression" + rps_threshold: + default_value: 5 + runtime_key: "admission_control.rps_threshold" + max_rejection_probability: + default_value: 80.0 + runtime_key: "admission_control.max_rejection_probability" success_criteria: http_criteria: http_success_status: @@ -110,6 +123,8 @@ The above configuration can be understood as follows: window. * HTTP requests are considered successful if they are 1xx, 2xx, 3xx, or a 404. * gRPC requests are considered successful if they are OK or CANCELLED. +* Requests will never be rejeted from this filter if the RPS is lower than 5. +* Rejection probability will never exceed 80% even if the failure rate is 100%. Statistics ---------- diff --git a/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto b/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto index 33d415ab4391..c526afbc80b5 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Admission Control] // [#extension: envoy.filters.http.admission_control] -// [#next-free-field: 6] +// [#next-free-field: 8] message AdmissionControl { // Default method of specifying what constitutes a successful request. All status codes that // indicate a successful request must be explicitly specified if not relying on the default @@ -91,4 +91,13 @@ message AdmissionControl { // below this threshold, rejection probability will increase. Any success rate above the threshold // results in a rejection probability of 0. Defaults to 95%. config.core.v3.RuntimePercent sr_threshold = 5; + + // If the number of requests in the last second is less than this threshold, the request + // will not be rejected, even if the success rate is lower than sr_threshold. + // Defaults to 0. + config.core.v3.RuntimeUInt32 rps_threshold = 6; + + // The probability of rejection will never exceed this value, even if the failure rate is rising. + // Defaults to 80%. + config.core.v3.RuntimePercent max_rejection_probability = 7; } diff --git a/source/extensions/filters/http/admission_control/admission_control.cc b/source/extensions/filters/http/admission_control/admission_control.cc index 9329e10ab351..000f3064dc89 100644 --- a/source/extensions/filters/http/admission_control/admission_control.cc +++ b/source/extensions/filters/http/admission_control/admission_control.cc @@ -31,6 +31,8 @@ using GrpcStatus = Grpc::Status::GrpcStatus; static constexpr double defaultAggression = 1.0; static constexpr double defaultSuccessRateThreshold = 95.0; +static constexpr uint32_t defaultRpsThreshold = 0; +static constexpr double defaultMaxRejectionProbability = 80.0; AdmissionControlFilterConfig::AdmissionControlFilterConfig( const AdmissionControlProto& proto_config, Runtime::Loader& runtime, @@ -45,6 +47,13 @@ AdmissionControlFilterConfig::AdmissionControlFilterConfig( sr_threshold_(proto_config.has_sr_threshold() ? std::make_unique( proto_config.sr_threshold(), runtime) : nullptr), + rps_threshold_(proto_config.has_rps_threshold() + ? std::make_unique(proto_config.rps_threshold(), runtime) + : nullptr), + max_rejection_probability_(proto_config.has_max_rejection_probability() + ? std::make_unique( + proto_config.max_rejection_probability(), runtime) + : nullptr), response_evaluator_(std::move(response_evaluator)) {} double AdmissionControlFilterConfig::aggression() const { @@ -56,6 +65,16 @@ double AdmissionControlFilterConfig::successRateThreshold() const { return std::min(pct, 100.0) / 100.0; } +uint32_t AdmissionControlFilterConfig::rpsThreshold() const { + return rps_threshold_ ? rps_threshold_->value() : defaultRpsThreshold; +} + +double AdmissionControlFilterConfig::maxRejectionProbability() const { + const double ret = max_rejection_probability_ ? max_rejection_probability_->value() + : defaultMaxRejectionProbability; + return ret / 100.0; +} + AdmissionControlFilter::AdmissionControlFilter(AdmissionControlFilterConfigSharedPtr config, const std::string& stats_prefix) : config_(std::move(config)), stats_(generateStats(config_->scope(), stats_prefix)), @@ -68,6 +87,10 @@ Http::FilterHeadersStatus AdmissionControlFilter::decodeHeaders(Http::RequestHea return Http::FilterHeadersStatus::Continue; } + if (config_->getController().lastSampleRequestCounts() < config_->rpsThreshold()) { + return Http::FilterHeadersStatus::Continue; + } + if (shouldRejectRequest()) { // We do not want to sample requests that we are rejecting, since this taints the measurements // that should be describing the upstreams. In addition, if we were to record the requests @@ -148,6 +171,7 @@ bool AdmissionControlFilter::shouldRejectRequest() const { if (aggression != 1.0) { probability = std::pow(probability, 1.0 / aggression); } + probability = std::min(probability, config_->maxRejectionProbability()); // Choosing an accuracy of 4 significant figures for the probability. static constexpr uint64_t accuracy = 1e4; diff --git a/source/extensions/filters/http/admission_control/admission_control.h b/source/extensions/filters/http/admission_control/admission_control.h index 7b4e83de80c7..e012b1038db6 100644 --- a/source/extensions/filters/http/admission_control/admission_control.h +++ b/source/extensions/filters/http/admission_control/admission_control.h @@ -66,6 +66,8 @@ class AdmissionControlFilterConfig { Stats::Scope& scope() const { return scope_; } double aggression() const; double successRateThreshold() const; + uint32_t rpsThreshold() const; + double maxRejectionProbability() const; ResponseEvaluator& responseEvaluator() const { return *response_evaluator_; } private: @@ -75,6 +77,8 @@ class AdmissionControlFilterConfig { Runtime::FeatureFlag admission_control_feature_; std::unique_ptr aggression_; std::unique_ptr sr_threshold_; + std::unique_ptr rps_threshold_; + std::unique_ptr max_rejection_probability_; std::shared_ptr response_evaluator_; }; diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.h b/source/extensions/filters/http/admission_control/thread_local_controller.h index fde56131ecd8..0664f50b0ba2 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.h +++ b/source/extensions/filters/http/admission_control/thread_local_controller.h @@ -37,6 +37,9 @@ class ThreadLocalController { // Returns the current number of requests and how many of them are successful. virtual RequestData requestCounts() PURE; + + // return the last sampled request counts + virtual uint32_t lastSampleRequestCounts() PURE; }; /** @@ -63,6 +66,14 @@ class ThreadLocalControllerImpl : public ThreadLocalController, return global_data_; } + // TODO (WeavingGao): We should calculate an average RPS when the granularity can be configured in + // the future. + uint32_t lastSampleRequestCounts() override { + maybeUpdateHistoricalData(); + size_t sample_number = historical_data_.size(); + return sample_number < 2 ? 0 : historical_data_[sample_number - 2].second.requests; + } + private: void recordRequest(bool success); diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index 847c0c0bb8a9..b575572820c6 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -37,6 +37,7 @@ class MockThreadLocalController : public ThreadLocal::ThreadLocalObject, MOCK_METHOD(RequestData, requestCounts, ()); MOCK_METHOD(void, recordSuccess, ()); MOCK_METHOD(void, recordFailure, ()); + MOCK_METHOD(uint32_t, lastSampleRequestCounts, ()); }; class MockResponseEvaluator : public ResponseEvaluator { @@ -142,6 +143,10 @@ sampling_window: 10s aggression: default_value: 1.0 runtime_key: "foo.aggression" +max_rejection_probability: + default_value: + value: 100.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -171,6 +176,7 @@ sampling_window: 10s // The filter is bypassed via runtime. EXPECT_CALL(controller_, requestCounts()).Times(0); + EXPECT_CALL(controller_, lastSampleRequestCounts()).Times(0); // We expect no rejections. Http::TestRequestHeaderMapImpl request_headers; @@ -189,6 +195,7 @@ TEST_F(AdmissionControlTest, DisregardHealthChecks) { // We do not make admission decisions for health checks, so we expect no lookup of request success // counts. EXPECT_CALL(controller_, requestCounts()).Times(0); + EXPECT_CALL(controller_, lastSampleRequestCounts()).Times(0); Http::TestRequestHeaderMapImpl request_headers; Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; @@ -206,6 +213,7 @@ TEST_F(AdmissionControlTest, HttpFailureBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isHttpSuccess(500)).WillRepeatedly(Return(false)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -225,6 +233,7 @@ TEST_F(AdmissionControlTest, HttpSuccessBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isHttpSuccess(200)).WillRepeatedly(Return(true)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -242,6 +251,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -261,6 +271,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehaviorTrailer) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -279,6 +290,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehaviorTrailer) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -298,6 +310,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -321,6 +334,10 @@ sampling_window: 10s aggression: default_value: 1.0 runtime_key: "foo.aggression" +max_rejection_probability: + default_value: + value: 80.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -336,6 +353,8 @@ sampling_window: 10s // Increase aggression and expect higher rejection probabilities for the same values. EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(2.0)); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(100.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(80.0)); verifyProbabilities(100, 0.0); verifyProbabilities(95, 0.22); verifyProbabilities(75, 0.5); @@ -344,12 +363,75 @@ sampling_window: 10s // from there. EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(1.0)); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(95.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(80.0)); verifyProbabilities(100, 0.0); verifyProbabilities(98, 0.0); verifyProbabilities(95, 0.0); verifyProbabilities(90, 0.05); verifyProbabilities(75, 0.20); verifyProbabilities(50, 0.46); + + // Validate max rejection probability + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(1.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(100.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(10.0)); + + verifyProbabilities(100, 0.0); + verifyProbabilities(95, 0.05); + verifyProbabilities(80, 0.1); + verifyProbabilities(0, 0.1); +} + +// Validate RPS threshold. +TEST_F(AdmissionControlTest, RpsThreshold) { + std::string yaml = R"EOF( +enabled: + default_value: true + runtime_key: "foo.enabled" +sampling_window: 10s +aggression: + default_value: 1.0 + runtime_key: "foo.aggression" +rps_threshold: + default_value: 0 + runtime_key: "foo.rps_threshold" +max_rejection_probability: + default_value: + value: 100.0 + runtime_key: "foo.max_rejection_probability" +success_criteria: + http_criteria: + grpc_criteria: +)EOF"; + + auto config = makeConfig(yaml); + setupFilter(config); + + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 0)).WillRepeatedly(Return(10)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(1)); + EXPECT_CALL(controller_, requestCounts()).Times(0); + + // Continue expected. + Http::TestRequestHeaderMapImpl request_headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 0)).WillRepeatedly(Return(10)); + EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(100)); + + // We expect rejection counter to increment upon failure. + TestUtility::waitForCounterEq(scope_, "test_prefix.rq_rejected", 0, time_system_); + + EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); + EXPECT_CALL(*evaluator_, isHttpSuccess(500)).WillRepeatedly(Return(false)); + + // StopIteration expected. + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + sampleHttpRequest("500"); + + TestUtility::waitForCounterEq(scope_, "test_prefix.rq_rejected", 1, time_system_); } } // namespace diff --git a/test/extensions/filters/http/admission_control/admission_control_integration_test.cc b/test/extensions/filters/http/admission_control/admission_control_integration_test.cc index a7e8d83f97cf..d3c2e68ca058 100644 --- a/test/extensions/filters/http/admission_control/admission_control_integration_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_integration_test.cc @@ -117,9 +117,9 @@ TEST_P(AdmissionControlIntegrationTest, HttpTest) { ++request_count; } - // Given the current throttling rate formula with an aggression of 1, it should result in a ~98% - // throttling rate. Allowing an error of 5%. - EXPECT_NEAR(throttle_count / request_count, 0.98, 0.05); + // Given the current throttling rate formula with an aggression of 1, it should result in a ~80% + // throttling rate (default max_rejection_probability). Allowing an error of 5%. + EXPECT_NEAR(throttle_count / request_count, 0.80, 0.05); // We now wait for the history to become stale. timeSystem().advanceTimeWait(std::chrono::seconds(120)); @@ -157,9 +157,9 @@ TEST_P(AdmissionControlIntegrationTest, GrpcTest) { ++request_count; } - // Given the current throttling rate formula with an aggression of 1, it should result in a ~98% - // throttling rate. Allowing an error of 5%. - EXPECT_NEAR(throttle_count / request_count, 0.98, 0.05); + // Given the current throttling rate formula with an aggression of 1, it should result in a ~80% + // throttling rate (default max_rejection_probability). Allowing an error of 5%. + EXPECT_NEAR(throttle_count / request_count, 0.80, 0.05); // We now wait for the history to become stale. timeSystem().advanceTimeWait(std::chrono::seconds(120)); diff --git a/test/extensions/filters/http/admission_control/config_test.cc b/test/extensions/filters/http/admission_control/config_test.cc index 13d73551462e..08caaeb1d502 100644 --- a/test/extensions/filters/http/admission_control/config_test.cc +++ b/test/extensions/filters/http/admission_control/config_test.cc @@ -119,6 +119,13 @@ sampling_window: 1337s aggression: default_value: 4.2 runtime_key: "foo.aggression" +rps_threshold: + default_value: 5 + runtime_key: "foo.rps_threshold" +max_rejection_probability: + default_value: + value: 70.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -129,6 +136,8 @@ sampling_window: 1337s EXPECT_FALSE(config->filterEnabled()); EXPECT_EQ(4.2, config->aggression()); EXPECT_EQ(0.92, config->successRateThreshold()); + EXPECT_EQ(5, config->rpsThreshold()); + EXPECT_EQ(0.7, config->maxRejectionProbability()); } // Verify the config defaults when not specified. @@ -146,6 +155,8 @@ TEST_F(AdmissionControlConfigTest, BasicTestMinimumConfigured) { EXPECT_TRUE(config->filterEnabled()); EXPECT_EQ(1.0, config->aggression()); EXPECT_EQ(0.95, config->successRateThreshold()); + EXPECT_EQ(0, config->rpsThreshold()); + EXPECT_EQ(0.8, config->maxRejectionProbability()); } // Ensure runtime fields are honored. @@ -162,6 +173,13 @@ sampling_window: 1337s aggression: default_value: 4.2 runtime_key: "foo.aggression" +rps_threshold: + default_value: 5 + runtime_key: "foo.rps_threshold" +max_rejection_probability: + default_value: + value: 70.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -175,12 +193,25 @@ sampling_window: 1337s EXPECT_EQ(1.3, config->aggression()); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.sr_threshold", 92)).WillOnce(Return(24.0)); EXPECT_EQ(0.24, config->successRateThreshold()); + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 5)).WillOnce(Return(12)); + EXPECT_EQ(12, config->rpsThreshold()); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 70.0)) + .WillOnce(Return(32.0)); + EXPECT_EQ(0.32, config->maxRejectionProbability()); // Verify bogus runtime thresholds revert to the default value. EXPECT_CALL(runtime_.snapshot_, getDouble("foo.sr_threshold", 92)).WillOnce(Return(250.0)); EXPECT_EQ(0.92, config->successRateThreshold()); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.sr_threshold", 92)).WillOnce(Return(-1.0)); EXPECT_EQ(0.92, config->successRateThreshold()); + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 5)).WillOnce(Return(99ull << 40)); + EXPECT_EQ(5, config->rpsThreshold()); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 70.0)) + .WillOnce(Return(-2.0)); + EXPECT_EQ(0.7, config->maxRejectionProbability()); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 70.0)) + .WillOnce(Return(300.0)); + EXPECT_EQ(0.7, config->maxRejectionProbability()); } } // namespace diff --git a/test/extensions/filters/http/admission_control/controller_test.cc b/test/extensions/filters/http/admission_control/controller_test.cc index bf88a7037431..87bbec4883f1 100644 --- a/test/extensions/filters/http/admission_control/controller_test.cc +++ b/test/extensions/filters/http/admission_control/controller_test.cc @@ -100,6 +100,23 @@ TEST_F(ThreadLocalControllerTest, VerifyMemoryUsage) { EXPECT_EQ(RequestData(3, 3), tlc_.requestCounts()); } +// Test for function: lastSampleRequestCounts +TEST_F(ThreadLocalControllerTest, LastSampleRequestCounts) { + EXPECT_EQ(0, tlc_.lastSampleRequestCounts()); + + tlc_.recordSuccess(); + EXPECT_EQ(0, tlc_.lastSampleRequestCounts()); + + tlc_.recordFailure(); + time_system_.advanceTimeWait(std::chrono::seconds(1)); + tlc_.recordSuccess(); + EXPECT_EQ(2, tlc_.lastSampleRequestCounts()); + + // make all samples to be stale + time_system_.advanceTimeWait(std::chrono::seconds(10)); + EXPECT_EQ(0, tlc_.lastSampleRequestCounts()); +} + } // namespace } // namespace AdmissionControl } // namespace HttpFilters From 3ee18a31294b1953d93b1ad2acde9440587620ca Mon Sep 17 00:00:00 2001 From: gaoweiwen Date: Tue, 1 Jun 2021 16:41:15 +0800 Subject: [PATCH 2/7] fix format Signed-off-by: gaoweiwen --- .../http/http_filters/admission_control_filter.rst | 2 +- .../http/admission_control/admission_control_filter_test.cc | 6 +++--- .../filters/http/admission_control/config_test.cc | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/root/configuration/http/http_filters/admission_control_filter.rst b/docs/root/configuration/http/http_filters/admission_control_filter.rst index f81142dcc8e5..1300f5e38368 100644 --- a/docs/root/configuration/http/http_filters/admission_control_filter.rst +++ b/docs/root/configuration/http/http_filters/admission_control_filter.rst @@ -28,7 +28,7 @@ The probability that the filter will reject a request is as follows: \begin{array}{cl} 0 & \ (rps < rps\_threshold) \\ min({(\frac{n_{total} - s}{n_{total} + 1})}^\frac{1}{aggression}\ ,\ max\_reject\_probability) & \ (rps \geq rps\_threshold) - \end{array} + \end{array} \right. where, diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index b575572820c6..9f5e058f50cd 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -144,7 +144,7 @@ sampling_window: 10s default_value: 1.0 runtime_key: "foo.aggression" max_rejection_probability: - default_value: + default_value: value: 100.0 runtime_key: "foo.max_rejection_probability" success_criteria: @@ -335,7 +335,7 @@ sampling_window: 10s default_value: 1.0 runtime_key: "foo.aggression" max_rejection_probability: - default_value: + default_value: value: 80.0 runtime_key: "foo.max_rejection_probability" success_criteria: @@ -398,7 +398,7 @@ sampling_window: 10s default_value: 0 runtime_key: "foo.rps_threshold" max_rejection_probability: - default_value: + default_value: value: 100.0 runtime_key: "foo.max_rejection_probability" success_criteria: diff --git a/test/extensions/filters/http/admission_control/config_test.cc b/test/extensions/filters/http/admission_control/config_test.cc index 08caaeb1d502..cf89ab810ecb 100644 --- a/test/extensions/filters/http/admission_control/config_test.cc +++ b/test/extensions/filters/http/admission_control/config_test.cc @@ -123,7 +123,7 @@ sampling_window: 1337s default_value: 5 runtime_key: "foo.rps_threshold" max_rejection_probability: - default_value: + default_value: value: 70.0 runtime_key: "foo.max_rejection_probability" success_criteria: @@ -177,7 +177,7 @@ sampling_window: 1337s default_value: 5 runtime_key: "foo.rps_threshold" max_rejection_probability: - default_value: + default_value: value: 70.0 runtime_key: "foo.max_rejection_probability" success_criteria: From 543d3abac6f7f0d7224e94b1bf2b68021554eab2 Mon Sep 17 00:00:00 2001 From: gaoweiwen Date: Wed, 2 Jun 2021 18:45:36 +0800 Subject: [PATCH 3/7] add averageRps() Signed-off-by: gaoweiwen --- .../v3alpha/admission_control.proto | 2 +- .../http_filters/admission_control_filter.rst | 12 ++++---- .../v3alpha/admission_control.proto | 2 +- source/common/runtime/runtime_protos.h | 1 - .../admission_control/admission_control.cc | 2 +- .../thread_local_controller.cc | 10 +++++++ .../thread_local_controller.h | 12 ++------ .../admission_control_filter_test.cc | 22 +++++++------- .../http/admission_control/controller_test.cc | 29 +++++++++++++------ 9 files changed, 52 insertions(+), 40 deletions(-) diff --git a/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto b/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto index c526afbc80b5..9bb3603f9ebd 100644 --- a/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto +++ b/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto @@ -92,7 +92,7 @@ message AdmissionControl { // results in a rejection probability of 0. Defaults to 95%. config.core.v3.RuntimePercent sr_threshold = 5; - // If the number of requests in the last second is less than this threshold, the request + // If the average RPS of the sampling window is below this threshold, the request // will not be rejected, even if the success rate is lower than sr_threshold. // Defaults to 0. config.core.v3.RuntimeUInt32 rps_threshold = 6; diff --git a/docs/root/configuration/http/http_filters/admission_control_filter.rst b/docs/root/configuration/http/http_filters/admission_control_filter.rst index 1300f5e38368..d5820fd07802 100644 --- a/docs/root/configuration/http/http_filters/admission_control_filter.rst +++ b/docs/root/configuration/http/http_filters/admission_control_filter.rst @@ -24,12 +24,7 @@ The probability that the filter will reject a request is as follows: .. math:: - P_{reject} = \left\{ - \begin{array}{cl} - 0 & \ (rps < rps\_threshold) \\ - min({(\frac{n_{total} - s}{n_{total} + 1})}^\frac{1}{aggression}\ ,\ max\_reject\_probability) & \ (rps \geq rps\_threshold) - \end{array} - \right. + P_{reject} = {(\frac{n_{total} - s}{n_{total} + 1})}^\frac{1}{aggression} where, @@ -38,7 +33,6 @@ where, s = \frac{n_{success}}{threshold} -- *rps_threshold* is a configurable value that when RPS is lower than it, requests will pass through the filter. - *n* refers to a request count gathered in the sliding window. - *threshold* is a configurable value that dictates the lowest request success rate at which the filter will **not reject** requests. The value is normalized to [0,1] for the calculation. @@ -46,6 +40,10 @@ where, rejection probability as the success rate decreases. As the **aggression** increases, the rejection probability will be higher for higher success rates. See `Aggression`_ for a more detailed explanation. + +In addition, there are two more configuration item: + +- *rps_threshold* is a configurable value that when RPS is lower than it, requests will pass through the filter. - *max_reject_probability* represents the upper limit of the rejection probability. .. note:: diff --git a/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto b/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto index c526afbc80b5..9bb3603f9ebd 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto @@ -92,7 +92,7 @@ message AdmissionControl { // results in a rejection probability of 0. Defaults to 95%. config.core.v3.RuntimePercent sr_threshold = 5; - // If the number of requests in the last second is less than this threshold, the request + // If the average RPS of the sampling window is below this threshold, the request // will not be rejected, even if the success rate is lower than sr_threshold. // Defaults to 0. config.core.v3.RuntimeUInt32 rps_threshold = 6; diff --git a/source/common/runtime/runtime_protos.h b/source/common/runtime/runtime_protos.h index b9d7d2550e9f..96e123412cbb 100644 --- a/source/common/runtime/runtime_protos.h +++ b/source/common/runtime/runtime_protos.h @@ -11,7 +11,6 @@ namespace Envoy { namespace Runtime { -// TODO(WeavingGao): use for #16392 // Helper class for runtime-derived uint32. class UInt32 : Logger::Loggable { public: diff --git a/source/extensions/filters/http/admission_control/admission_control.cc b/source/extensions/filters/http/admission_control/admission_control.cc index 000f3064dc89..922b068094f5 100644 --- a/source/extensions/filters/http/admission_control/admission_control.cc +++ b/source/extensions/filters/http/admission_control/admission_control.cc @@ -87,7 +87,7 @@ Http::FilterHeadersStatus AdmissionControlFilter::decodeHeaders(Http::RequestHea return Http::FilterHeadersStatus::Continue; } - if (config_->getController().lastSampleRequestCounts() < config_->rpsThreshold()) { + if (config_->getController().averageRps() < config_->rpsThreshold()) { return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.cc b/source/extensions/filters/http/admission_control/thread_local_controller.cc index 30f0aac40061..559609f7f7a2 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.cc +++ b/source/extensions/filters/http/admission_control/thread_local_controller.cc @@ -17,6 +17,16 @@ ThreadLocalControllerImpl::ThreadLocalControllerImpl(TimeSource& time_source, std::chrono::seconds sampling_window) : time_source_(time_source), sampling_window_(sampling_window) {} +uint32_t ThreadLocalControllerImpl::averageRps() const { + // In the very beginning of sampling, the average rps might be very big, so just skipping from it. + if (historical_data_.size() < 2 || global_data_.requests == 0) { + return 0; + } + std::chrono::duration period_second = ageOfOldestSample(); + + return static_cast(global_data_.requests / period_second.count()); +} + void ThreadLocalControllerImpl::maybeUpdateHistoricalData() { // Purge stale samples. while (!historical_data_.empty() && ageOfOldestSample() >= sampling_window_) { diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.h b/source/extensions/filters/http/admission_control/thread_local_controller.h index 0664f50b0ba2..4c536ba1dd55 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.h +++ b/source/extensions/filters/http/admission_control/thread_local_controller.h @@ -38,8 +38,8 @@ class ThreadLocalController { // Returns the current number of requests and how many of them are successful. virtual RequestData requestCounts() PURE; - // return the last sampled request counts - virtual uint32_t lastSampleRequestCounts() PURE; + // return the average RPS across the sampling window + virtual uint32_t averageRps() const PURE; }; /** @@ -66,13 +66,7 @@ class ThreadLocalControllerImpl : public ThreadLocalController, return global_data_; } - // TODO (WeavingGao): We should calculate an average RPS when the granularity can be configured in - // the future. - uint32_t lastSampleRequestCounts() override { - maybeUpdateHistoricalData(); - size_t sample_number = historical_data_.size(); - return sample_number < 2 ? 0 : historical_data_[sample_number - 2].second.requests; - } + uint32_t averageRps() const override; private: void recordRequest(bool success); diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index 9f5e058f50cd..a32c3a0f51d9 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -37,7 +37,7 @@ class MockThreadLocalController : public ThreadLocal::ThreadLocalObject, MOCK_METHOD(RequestData, requestCounts, ()); MOCK_METHOD(void, recordSuccess, ()); MOCK_METHOD(void, recordFailure, ()); - MOCK_METHOD(uint32_t, lastSampleRequestCounts, ()); + MOCK_METHOD(uint32_t, averageRps, (), (const)); }; class MockResponseEvaluator : public ResponseEvaluator { @@ -176,7 +176,7 @@ sampling_window: 10s // The filter is bypassed via runtime. EXPECT_CALL(controller_, requestCounts()).Times(0); - EXPECT_CALL(controller_, lastSampleRequestCounts()).Times(0); + EXPECT_CALL(controller_, averageRps()).Times(0); // We expect no rejections. Http::TestRequestHeaderMapImpl request_headers; @@ -195,7 +195,7 @@ TEST_F(AdmissionControlTest, DisregardHealthChecks) { // We do not make admission decisions for health checks, so we expect no lookup of request success // counts. EXPECT_CALL(controller_, requestCounts()).Times(0); - EXPECT_CALL(controller_, lastSampleRequestCounts()).Times(0); + EXPECT_CALL(controller_, averageRps()).Times(0); Http::TestRequestHeaderMapImpl request_headers; Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; @@ -213,7 +213,7 @@ TEST_F(AdmissionControlTest, HttpFailureBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isHttpSuccess(500)).WillRepeatedly(Return(false)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -233,7 +233,7 @@ TEST_F(AdmissionControlTest, HttpSuccessBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isHttpSuccess(200)).WillRepeatedly(Return(true)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -251,7 +251,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -271,7 +271,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehaviorTrailer) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -290,7 +290,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehaviorTrailer) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -310,7 +310,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(99)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -410,7 +410,7 @@ sampling_window: 10s setupFilter(config); EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 0)).WillRepeatedly(Return(10)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(1)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(1)); EXPECT_CALL(controller_, requestCounts()).Times(0); // Continue expected. @@ -418,7 +418,7 @@ sampling_window: 10s EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 0)).WillRepeatedly(Return(10)); - EXPECT_CALL(controller_, lastSampleRequestCounts()).WillRepeatedly(Return(100)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(100)); // We expect rejection counter to increment upon failure. TestUtility::waitForCounterEq(scope_, "test_prefix.rq_rejected", 0, time_system_); diff --git a/test/extensions/filters/http/admission_control/controller_test.cc b/test/extensions/filters/http/admission_control/controller_test.cc index 87bbec4883f1..6ad6e16512f9 100644 --- a/test/extensions/filters/http/admission_control/controller_test.cc +++ b/test/extensions/filters/http/admission_control/controller_test.cc @@ -100,21 +100,32 @@ TEST_F(ThreadLocalControllerTest, VerifyMemoryUsage) { EXPECT_EQ(RequestData(3, 3), tlc_.requestCounts()); } -// Test for function: lastSampleRequestCounts -TEST_F(ThreadLocalControllerTest, LastSampleRequestCounts) { - EXPECT_EQ(0, tlc_.lastSampleRequestCounts()); +// Test for function: averageRps +TEST_F(ThreadLocalControllerTest, AverageRps) { + // Validate global_data_.requests == 0 + tlc_.requestCounts(); + EXPECT_EQ(0, tlc_.averageRps()); + // Validate historical_data_.size() < 2 tlc_.recordSuccess(); - EXPECT_EQ(0, tlc_.lastSampleRequestCounts()); - tlc_.recordFailure(); + EXPECT_EQ(0, tlc_.averageRps()); + + // Validate that the sampling window is not full time_system_.advanceTimeWait(std::chrono::seconds(1)); - tlc_.recordSuccess(); - EXPECT_EQ(2, tlc_.lastSampleRequestCounts()); + tlc_.requestCounts(); + EXPECT_EQ(2, tlc_.averageRps()); - // make all samples to be stale + // Now clean up the sampling window to validate that the sampling window is full time_system_.advanceTimeWait(std::chrono::seconds(10)); - EXPECT_EQ(0, tlc_.lastSampleRequestCounts()); + for (int tick = 0; tick < window_.count(); ++tick) { + // 1 + 3 + 5 + 7 + 9 + for (int record_count = 0; record_count <= tick * 2; ++record_count) { + tlc_.recordSuccess(); + } + time_system_.advanceTimeWait(std::chrono::seconds(1)); + } + EXPECT_EQ(5, tlc_.averageRps()); } } // namespace From 80668e7bc241f8d9973f56e4ccea096b2d802751 Mon Sep 17 00:00:00 2001 From: gaoweiwen Date: Fri, 4 Jun 2021 14:43:14 +0800 Subject: [PATCH 4/7] some minor changes Signed-off-by: gaoweiwen --- .../http_filters/admission_control_filter.rst | 2 +- .../admission_control/admission_control.cc | 1 + .../admission_control_filter_test.cc | 38 +++++++++++++++++++ .../http/admission_control/controller_test.cc | 2 +- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/root/configuration/http/http_filters/admission_control_filter.rst b/docs/root/configuration/http/http_filters/admission_control_filter.rst index d5820fd07802..ac974b2f4067 100644 --- a/docs/root/configuration/http/http_filters/admission_control_filter.rst +++ b/docs/root/configuration/http/http_filters/admission_control_filter.rst @@ -41,7 +41,7 @@ where, rejection probability will be higher for higher success rates. See `Aggression`_ for a more detailed explanation. -In addition, there are two more configuration item: +Note that there are additional parameters that affect the rejection probability: - *rps_threshold* is a configurable value that when RPS is lower than it, requests will pass through the filter. - *max_reject_probability* represents the upper limit of the rejection probability. diff --git a/source/extensions/filters/http/admission_control/admission_control.cc b/source/extensions/filters/http/admission_control/admission_control.cc index 922b068094f5..6104693bd0f9 100644 --- a/source/extensions/filters/http/admission_control/admission_control.cc +++ b/source/extensions/filters/http/admission_control/admission_control.cc @@ -88,6 +88,7 @@ Http::FilterHeadersStatus AdmissionControlFilter::decodeHeaders(Http::RequestHea } if (config_->getController().averageRps() < config_->rpsThreshold()) { + ENVOY_LOG(debug, "Current rps: {} is below rps_threshold: {}, continue"); return Http::FilterHeadersStatus::Continue; } diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index a32c3a0f51d9..5754cbc89828 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -434,6 +434,44 @@ sampling_window: 10s TestUtility::waitForCounterEq(scope_, "test_prefix.rq_rejected", 1, time_system_); } +// Validate max rejection probability. +TEST_F(AdmissionControlTest, MaxRejectionProbability) { + std::string yaml = R"EOF( +enabled: + default_value: true + runtime_key: "foo.enabled" +sampling_window: 10s +sr_threshold: + default_value: + value: 100.0 + runtime_key: "foo.threshold" +aggression: + default_value: 1.0 + runtime_key: "foo.aggression" +max_rejection_probability: + default_value: + value: 80.0 + runtime_key: "foo.max_rejection_probability" +success_criteria: + http_criteria: + grpc_criteria: +)EOF"; + + auto config = makeConfig(yaml); + setupFilter(config); + + // Validate max rejection probability + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(1.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(100.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(10.0)); + + verifyProbabilities(100, 0.0); + verifyProbabilities(95, 0.05); + verifyProbabilities(80, 0.1); + verifyProbabilities(0, 0.1); +} + } // namespace } // namespace AdmissionControl } // namespace HttpFilters diff --git a/test/extensions/filters/http/admission_control/controller_test.cc b/test/extensions/filters/http/admission_control/controller_test.cc index 6ad6e16512f9..e46a919a6c7d 100644 --- a/test/extensions/filters/http/admission_control/controller_test.cc +++ b/test/extensions/filters/http/admission_control/controller_test.cc @@ -100,7 +100,7 @@ TEST_F(ThreadLocalControllerTest, VerifyMemoryUsage) { EXPECT_EQ(RequestData(3, 3), tlc_.requestCounts()); } -// Test for function: averageRps +// Test for function: averageRps. TEST_F(ThreadLocalControllerTest, AverageRps) { // Validate global_data_.requests == 0 tlc_.requestCounts(); From 68e35355b4b458d5e6115af9dd1f2859960138c0 Mon Sep 17 00:00:00 2001 From: gaoweiwen Date: Mon, 7 Jun 2021 11:44:17 +0800 Subject: [PATCH 5/7] Modify the calculation method of avergeRps Signed-off-by: gaoweiwen --- .../http/admission_control/thread_local_controller.cc | 8 ++++---- .../filters/http/admission_control/controller_test.cc | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.cc b/source/extensions/filters/http/admission_control/thread_local_controller.cc index 559609f7f7a2..dc3ada92d48b 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.cc +++ b/source/extensions/filters/http/admission_control/thread_local_controller.cc @@ -18,13 +18,13 @@ ThreadLocalControllerImpl::ThreadLocalControllerImpl(TimeSource& time_source, : time_source_(time_source), sampling_window_(sampling_window) {} uint32_t ThreadLocalControllerImpl::averageRps() const { - // In the very beginning of sampling, the average rps might be very big, so just skipping from it. - if (historical_data_.size() < 2 || global_data_.requests == 0) { + if (historical_data_.empty() || global_data_.requests == 0) { return 0; } - std::chrono::duration period_second = ageOfOldestSample(); + using namespace std::chrono; + auto count_of_seconds = duration_cast(ageOfOldestSample()).count(); - return static_cast(global_data_.requests / period_second.count()); + return count_of_seconds == 0 ? 0 : global_data_.requests / count_of_seconds; } void ThreadLocalControllerImpl::maybeUpdateHistoricalData() { diff --git a/test/extensions/filters/http/admission_control/controller_test.cc b/test/extensions/filters/http/admission_control/controller_test.cc index e46a919a6c7d..34e8f3f7b64a 100644 --- a/test/extensions/filters/http/admission_control/controller_test.cc +++ b/test/extensions/filters/http/admission_control/controller_test.cc @@ -102,6 +102,9 @@ TEST_F(ThreadLocalControllerTest, VerifyMemoryUsage) { // Test for function: averageRps. TEST_F(ThreadLocalControllerTest, AverageRps) { + // Validate historical_data_.empty() + EXPECT_EQ(0, tlc_.averageRps()); + // Validate global_data_.requests == 0 tlc_.requestCounts(); EXPECT_EQ(0, tlc_.averageRps()); From 7c508c1f659719339eaecfaec8ad6fb98e1cf3ea Mon Sep 17 00:00:00 2001 From: gaoweiwen Date: Tue, 8 Jun 2021 14:28:30 +0800 Subject: [PATCH 6/7] add some feature description Signed-off-by: gaoweiwen --- docs/root/version_history/current.rst | 3 ++- .../admission_control/thread_local_controller.cc | 7 +++---- .../http/admission_control/controller_test.cc | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 27a50d16838c..6df687253ce5 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -13,7 +13,7 @@ Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* -* access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. +* admission control: added :ref:`admission control ` whose default value is 80%, which means that the upper limit of the default rejection probability of the filter is changed from 100% to 80%. * aws_request_signing: requests are now buffered by default to compute signatures which include the payload hash, making the filter compatible with most AWS services. Previously, requests were never buffered, which only produced correct signatures for requests without a body, or for @@ -67,6 +67,7 @@ Removed Config or Runtime New Features ------------ +* admission control: added :ref:`admission control ` option that when average RPS of the sampling window is below this threshold, the filter will not throttle requests. Added :ref:`admission control ` option to set an upper limit on the probability of rejection. * bandwidth_limit: added new :ref:`HTTP bandwidth limit filter `. * crash support: restore crash context when continuing to processing requests or responses as a result of an asynchronous callback that invokes a filter directly. This is unlike the call stacks that go through the various network layers, to eventually reach the filter. For a concrete example see: ``Envoy::Extensions::HttpFilters::Cache::CacheFilter::getHeaders`` which posts a callback on the dispatcher that will invoke the filter directly. * dynamic_forward_proxy: added :ref:`dns_resolver` option to the DNS cache config in order use custom DNS resolvers instead of the system default resolvers. diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.cc b/source/extensions/filters/http/admission_control/thread_local_controller.cc index dc3ada92d48b..40281927b121 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.cc +++ b/source/extensions/filters/http/admission_control/thread_local_controller.cc @@ -21,10 +21,9 @@ uint32_t ThreadLocalControllerImpl::averageRps() const { if (historical_data_.empty() || global_data_.requests == 0) { return 0; } - using namespace std::chrono; - auto count_of_seconds = duration_cast(ageOfOldestSample()).count(); - - return count_of_seconds == 0 ? 0 : global_data_.requests / count_of_seconds; + using std::chrono::seconds; + seconds secs = std::max(seconds(1), std::chrono::duration_cast(ageOfOldestSample())); + return global_data_.requests / secs.count(); } void ThreadLocalControllerImpl::maybeUpdateHistoricalData() { diff --git a/test/extensions/filters/http/admission_control/controller_test.cc b/test/extensions/filters/http/admission_control/controller_test.cc index 34e8f3f7b64a..08d03dc30cff 100644 --- a/test/extensions/filters/http/admission_control/controller_test.cc +++ b/test/extensions/filters/http/admission_control/controller_test.cc @@ -102,24 +102,24 @@ TEST_F(ThreadLocalControllerTest, VerifyMemoryUsage) { // Test for function: averageRps. TEST_F(ThreadLocalControllerTest, AverageRps) { - // Validate historical_data_.empty() + // Validate that historical_data_ is empty. EXPECT_EQ(0, tlc_.averageRps()); - // Validate global_data_.requests == 0 + // Validate that global_data_.requests equals to zero. tlc_.requestCounts(); EXPECT_EQ(0, tlc_.averageRps()); - // Validate historical_data_.size() < 2 + // Validate the value in one second. tlc_.recordSuccess(); tlc_.recordFailure(); - EXPECT_EQ(0, tlc_.averageRps()); + EXPECT_EQ(2, tlc_.averageRps()); - // Validate that the sampling window is not full + // Validate that the sampling window is not full. time_system_.advanceTimeWait(std::chrono::seconds(1)); - tlc_.requestCounts(); - EXPECT_EQ(2, tlc_.averageRps()); + tlc_.recordSuccess(); + EXPECT_EQ(3, tlc_.averageRps()); - // Now clean up the sampling window to validate that the sampling window is full + // Now clean up the sampling window to validate that the sampling window is full. time_system_.advanceTimeWait(std::chrono::seconds(10)); for (int tick = 0; tick < window_.count(); ++tick) { // 1 + 3 + 5 + 7 + 9 From 363a38790030029873d0ade5989850c550053685 Mon Sep 17 00:00:00 2001 From: gaoweiwen Date: Tue, 8 Jun 2021 16:03:25 +0800 Subject: [PATCH 7/7] fix format Signed-off-by: gaoweiwen --- docs/root/version_history/current.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f7acdc49f62b..5ad5f338cb91 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -13,9 +13,9 @@ Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* -* admission control: added :ref:`admission control ` whose default value is 80%, which means that the upper limit of the default rejection probability of the filter is changed from 100% to 80%. * access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. * access_log: remove extra quotes on metadata string values. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.unquote_log_string_values`` to false. +* admission control: added :ref:`admission control ` whose default value is 80%, which means that the upper limit of the default rejection probability of the filter is changed from 100% to 80%. * aws_request_signing: requests are now buffered by default to compute signatures which include the payload hash, making the filter compatible with most AWS services. Previously, requests were never buffered, which only produced correct signatures for requests without a body, or for @@ -69,7 +69,7 @@ Removed Config or Runtime New Features ------------ -* admission control: added :ref:`admission control ` option that when average RPS of the sampling window is below this threshold, the filter will not throttle requests. Added :ref:`admission control ` option to set an upper limit on the probability of rejection. +* admission control: added :ref:`admission control ` option that when average RPS of the sampling window is below this threshold, the filter will not throttle requests. Added :ref:`admission control ` option to set an upper limit on the probability of rejection. * bandwidth_limit: added new :ref:`HTTP bandwidth limit filter `. * bootstrap: added :ref:`dns_resolution_config ` to aggregate all of the DNS resolver configuration in a single message. By setting one such configuration option *no_default_search_domain* as true the DNS resolver will not use the default search domains. And by setting the configuration *resolvers* we can specify the external DNS servers to be used for external DNS query. * cluster: added :ref:`dns_resolution_config ` to aggregate all of the DNS resolver configuration in a single message. By setting one such configuration option *no_default_search_domain* as true the DNS resolver will not use the default search domains.