mirror of https://github.com/grpc/grpc.git
[chttp2] Bound write sizes based on observed write performance (#34665)
Instead of fixing a target size for writes, try to adapt it a little to observed bandwidth. The initial algorithm tries to get large writes within 100-1000ms maximum delay - this range probably wants to be tuned, but let's see. The hope here is that on slow connections we can not back buffer so much and so when we need to send a ping-ack it's possible without great delay.pull/34680/head
parent
716333135d
commit
7c59c09f43
31 changed files with 575 additions and 7 deletions
@ -0,0 +1,26 @@ |
||||
# Copyright 2023 gRPC authors. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
load("//fuzztest:grpc_fuzz_test.bzl", "grpc_fuzz_test") |
||||
|
||||
grpc_fuzz_test( |
||||
name = "write_size_policy_test", |
||||
srcs = ["write_size_policy_test.cc"], |
||||
external_deps = [ |
||||
"fuzztest", |
||||
"fuzztest_main", |
||||
"gtest", |
||||
], |
||||
deps = ["//src/core:write_size_policy"], |
||||
) |
@ -0,0 +1,64 @@ |
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Test to verify Fuzztest integration
|
||||
|
||||
#include "src/core/ext/transport/chttp2/transport/write_size_policy.h" |
||||
|
||||
#include <vector> |
||||
|
||||
#include "fuzztest/fuzztest.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
struct OneWrite { |
||||
uint16_t delay_start; |
||||
uint32_t size; |
||||
uint16_t write_time; |
||||
bool success; |
||||
}; |
||||
|
||||
void WriteSizePolicyStaysWithinBounds(std::vector<OneWrite> ops) { |
||||
ScopedTimeCache time_cache; |
||||
uint32_t now = 100; |
||||
Chttp2WriteSizePolicy policy; |
||||
for (const OneWrite op : ops) { |
||||
const auto start_target = policy.WriteTargetSize(); |
||||
now += op.delay_start; |
||||
time_cache.TestOnlySetNow(Timestamp::ProcessEpoch() + |
||||
Duration::Milliseconds(now)); |
||||
policy.BeginWrite(op.size); |
||||
now += op.write_time; |
||||
time_cache.TestOnlySetNow(Timestamp::ProcessEpoch() + |
||||
Duration::Milliseconds(now)); |
||||
policy.EndWrite(op.success); |
||||
if (op.size >= start_target * 7 / 10) { |
||||
if (op.write_time < Chttp2WriteSizePolicy::FastWrite().millis()) { |
||||
EXPECT_GE(policy.WriteTargetSize(), start_target); |
||||
EXPECT_LE(policy.WriteTargetSize(), start_target * 3 / 2); |
||||
} else if (op.write_time > Chttp2WriteSizePolicy::SlowWrite().millis()) { |
||||
EXPECT_LE(policy.WriteTargetSize(), start_target); |
||||
EXPECT_GE(policy.WriteTargetSize(), start_target / 3); |
||||
} |
||||
} else { |
||||
EXPECT_EQ(policy.WriteTargetSize(), start_target); |
||||
} |
||||
EXPECT_GE(policy.WriteTargetSize(), Chttp2WriteSizePolicy::MinTarget()); |
||||
EXPECT_LE(policy.WriteTargetSize(), Chttp2WriteSizePolicy::MaxTarget()); |
||||
} |
||||
} |
||||
FUZZ_TEST(MyTestSuite, WriteSizePolicyStaysWithinBounds); |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,60 @@ |
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include "src/core/ext/transport/chttp2/transport/write_size_policy.h" |
||||
|
||||
#include <algorithm> |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
namespace grpc_core { |
||||
|
||||
size_t Chttp2WriteSizePolicy::WriteTargetSize() { return current_target_; } |
||||
|
||||
void Chttp2WriteSizePolicy::BeginWrite(size_t size) { |
||||
GPR_ASSERT(experiment_start_time_ == Timestamp::InfFuture()); |
||||
if (size < current_target_ * 7 / 10) { |
||||
// If we were trending fast but stopped getting enough data to verify, then
|
||||
// reset back to the default state.
|
||||
if (state_ < 0) state_ = 0; |
||||
return; |
||||
} |
||||
experiment_start_time_ = Timestamp::Now(); |
||||
} |
||||
|
||||
void Chttp2WriteSizePolicy::EndWrite(bool success) { |
||||
if (experiment_start_time_ == Timestamp::InfFuture()) return; |
||||
const auto elapsed = Timestamp::Now() - experiment_start_time_; |
||||
experiment_start_time_ = Timestamp::InfFuture(); |
||||
if (!success) return; |
||||
if (elapsed < FastWrite()) { |
||||
--state_; |
||||
if (state_ == -2) { |
||||
state_ = 0; |
||||
current_target_ = std::min(current_target_ * 3 / 2, MaxTarget()); |
||||
} |
||||
} else if (elapsed > SlowWrite()) { |
||||
++state_; |
||||
if (state_ == 2) { |
||||
state_ = 0; |
||||
current_target_ = std::max(current_target_ / 3, MinTarget()); |
||||
} |
||||
} else { |
||||
state_ = 0; |
||||
} |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,66 @@ |
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_WRITE_SIZE_POLICY_H |
||||
#define GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_WRITE_SIZE_POLICY_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
#include "src/core/lib/gprpp/time.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class Chttp2WriteSizePolicy { |
||||
public: |
||||
// Smallest possible WriteTargetSize
|
||||
static constexpr size_t MinTarget() { return 32 * 1024; } |
||||
// Largest possible WriteTargetSize
|
||||
static constexpr size_t MaxTarget() { return 16 * 1024 * 1024; } |
||||
// How long should a write take to be considered "fast"
|
||||
static constexpr Duration FastWrite() { return Duration::Milliseconds(100); } |
||||
// How long should a write take to be considered "slow"
|
||||
static constexpr Duration SlowWrite() { return Duration::Seconds(1); } |
||||
// If a read is slow, what target time should we use to try and adjust back
|
||||
// to?
|
||||
static constexpr Duration TargetWriteTime() { |
||||
return Duration::Milliseconds(300); |
||||
} |
||||
|
||||
// What size should be targetted for the next write.
|
||||
size_t WriteTargetSize(); |
||||
// Notify the policy that a write of some size has begun.
|
||||
// EndWrite must be called when the write completes.
|
||||
void BeginWrite(size_t size); |
||||
// Notify the policy that a write of some size has ended.
|
||||
void EndWrite(bool success); |
||||
|
||||
private: |
||||
size_t current_target_ = 128 * 1024; |
||||
Timestamp experiment_start_time_ = Timestamp::InfFuture(); |
||||
// State varies from -2...2
|
||||
// Every time we do a write faster than kFastWrite, we decrement
|
||||
// Every time we do a write slower than kSlowWrite, we increment
|
||||
// If we hit -2, we increase the target size and reset state to 0
|
||||
// If we hit 2, we decrease the target size and reset state to 0
|
||||
// In this way, we need two consecutive fast/slow operations to adjust,
|
||||
// denoising the signal significantly
|
||||
int8_t state_ = 0; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_WRITE_SIZE_POLICY_H
|
@ -0,0 +1,125 @@ |
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/core/ext/transport/chttp2/transport/write_size_policy.h" |
||||
|
||||
#include <memory> |
||||
|
||||
#include "gtest/gtest.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace { |
||||
|
||||
TEST(WriteSizePolicyTest, InitialValue) { |
||||
Chttp2WriteSizePolicy policy; |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
} |
||||
|
||||
TEST(WriteSizePolicyTest, FastWritesOpenThingsUp) { |
||||
ScopedTimeCache time_cache; |
||||
auto timestamp = [&time_cache](int i) { |
||||
time_cache.TestOnlySetNow(Timestamp::ProcessEpoch() + |
||||
Duration::Milliseconds(i)); |
||||
}; |
||||
Chttp2WriteSizePolicy policy; |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(10); |
||||
policy.BeginWrite(131072); |
||||
timestamp(20); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(30); |
||||
policy.BeginWrite(131072); |
||||
timestamp(40); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 196608); |
||||
timestamp(50); |
||||
policy.BeginWrite(196608); |
||||
timestamp(60); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 196608); |
||||
timestamp(70); |
||||
policy.BeginWrite(196608); |
||||
timestamp(80); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 294912); |
||||
} |
||||
|
||||
TEST(WriteSizePolicyTest, SlowWritesCloseThingsUp) { |
||||
ScopedTimeCache time_cache; |
||||
auto timestamp = [&time_cache](int i) { |
||||
time_cache.TestOnlySetNow(Timestamp::ProcessEpoch() + |
||||
Duration::Milliseconds(i)); |
||||
}; |
||||
Chttp2WriteSizePolicy policy; |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(10000); |
||||
policy.BeginWrite(131072); |
||||
timestamp(20000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(30000); |
||||
policy.BeginWrite(131072); |
||||
timestamp(40000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 43690); |
||||
timestamp(50000); |
||||
policy.BeginWrite(43690); |
||||
timestamp(60000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 43690); |
||||
timestamp(70000); |
||||
policy.BeginWrite(43690); |
||||
timestamp(80000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 32768); |
||||
} |
||||
|
||||
TEST(WriteSizePolicyTest, MediumWritesJustHangOut) { |
||||
ScopedTimeCache time_cache; |
||||
auto timestamp = [&time_cache](int i) { |
||||
time_cache.TestOnlySetNow(Timestamp::ProcessEpoch() + |
||||
Duration::Milliseconds(i)); |
||||
}; |
||||
Chttp2WriteSizePolicy policy; |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(500); |
||||
policy.BeginWrite(131072); |
||||
timestamp(1000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(1500); |
||||
policy.BeginWrite(131072); |
||||
timestamp(2000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(2500); |
||||
policy.BeginWrite(131072); |
||||
timestamp(3000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
timestamp(3500); |
||||
policy.BeginWrite(131072); |
||||
timestamp(4000); |
||||
policy.EndWrite(true); |
||||
EXPECT_EQ(policy.WriteTargetSize(), 131072); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue