xref: /aosp_15_r20/external/openscreen/cast/streaming/bandwidth_estimator_unittest.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "cast/streaming/bandwidth_estimator.h"
6 
7 #include <chrono>
8 #include <limits>
9 #include <random>
10 
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
13 #include "platform/api/time.h"
14 #include "util/chrono_helpers.h"
15 
16 namespace openscreen {
17 namespace cast {
18 namespace {
19 
20 using openscreen::operator<<;  // For std::chrono::duration gtest pretty-print.
21 
22 // BandwidthEstimator configuration common to all tests.
23 constexpr int kMaxPacketsPerTimeslice = 10;
24 constexpr Clock::duration kTimesliceDuration = milliseconds(10);
25 constexpr int kTimeslicesPerSecond = seconds(1) / kTimesliceDuration;
26 
27 // Use a fake, fixed start time.
28 constexpr Clock::time_point kStartTime =
29     Clock::time_point() + Clock::duration(1234567890);
30 
31 // Range of "fuzz" to add to timestamps in BandwidthEstimatorTest::AddFuzz().
32 constexpr Clock::duration kMaxFuzzOffset = milliseconds(15);
33 constexpr int kFuzzLowerBoundClockTicks = (-kMaxFuzzOffset).count();
34 constexpr int kFuzzUpperBoundClockTicks = kMaxFuzzOffset.count();
35 
36 class BandwidthEstimatorTest : public testing::Test {
37  public:
BandwidthEstimatorTest()38   BandwidthEstimatorTest()
39       : estimator_(kMaxPacketsPerTimeslice, kTimesliceDuration, kStartTime) {}
40 
estimator()41   BandwidthEstimator* estimator() { return &estimator_; }
42 
43   // Returns |t| plus or minus |kMaxFuzzOffset|.
AddFuzz(Clock::time_point t)44   Clock::time_point AddFuzz(Clock::time_point t) {
45     return t + Clock::duration(distribution_(rand_));
46   }
47 
48  private:
49   BandwidthEstimator estimator_;
50 
51   // These are used to generate random values for AddFuzz().
52   static constexpr std::minstd_rand::result_type kRandSeed =
53       kStartTime.time_since_epoch().count();
54   std::minstd_rand rand_{kRandSeed};
55   std::uniform_int_distribution<int> distribution_{kFuzzLowerBoundClockTicks,
56                                                    kFuzzUpperBoundClockTicks};
57 };
58 
59 // Tests that, without any data, there won't be any estimates.
TEST_F(BandwidthEstimatorTest,DoesNotEstimateWithoutAnyInput)60 TEST_F(BandwidthEstimatorTest, DoesNotEstimateWithoutAnyInput) {
61   EXPECT_EQ(0, estimator()->ComputeNetworkBandwidth());
62 }
63 
64 // Tests the case where packets are being sent, but the Receiver hasn't provided
65 // feedback (e.g., due to a network blackout).
TEST_F(BandwidthEstimatorTest,DoesNotEstimateWithoutFeedback)66 TEST_F(BandwidthEstimatorTest, DoesNotEstimateWithoutFeedback) {
67   Clock::time_point now = kStartTime;
68   for (int i = 0; i < 3; ++i) {
69     const Clock::time_point end = now + estimator()->history_window();
70     for (; now < end; now += kTimesliceDuration) {
71       estimator()->OnBurstComplete(i, now);
72       EXPECT_EQ(0, estimator()->ComputeNetworkBandwidth());
73     }
74     now = end;
75   }
76 }
77 
78 // Tests the case where packets are being sent, and a connection to the Receiver
79 // has been confirmed (because RTCP packets are coming in), but the Receiver has
80 // not successfully received anything.
TEST_F(BandwidthEstimatorTest,DoesNotEstimateIfNothingSuccessfullyReceived)81 TEST_F(BandwidthEstimatorTest, DoesNotEstimateIfNothingSuccessfullyReceived) {
82   const Clock::duration kRoundTripTime = milliseconds(1);
83 
84   Clock::time_point now = kStartTime;
85   for (int i = 0; i < 3; ++i) {
86     const Clock::time_point end = now + estimator()->history_window();
87     for (; now < end; now += kTimesliceDuration) {
88       estimator()->OnBurstComplete(i, now);
89       estimator()->OnRtcpReceived(now + kRoundTripTime, kRoundTripTime);
90       EXPECT_EQ(0, estimator()->ComputeNetworkBandwidth());
91     }
92     now = end;
93   }
94 }
95 
96 // Tests that, when the Receiver successfully receives the payload bytes at a
97 // fixed rate, the network bandwidth estimates are based on the amount of time
98 // the Sender spent transmitting.
TEST_F(BandwidthEstimatorTest,EstimatesAtVariousBurstSaturations)99 TEST_F(BandwidthEstimatorTest, EstimatesAtVariousBurstSaturations) {
100   // These determine how many packets to burst in the simulation below.
101   constexpr int kDivisors[] = {
102       1,  // Burst 100% of max possible packets.
103       2,  // Burst 50% of max possible packets.
104       5,  // Burst 20% of max possible packets.
105   };
106 
107   const Clock::duration kRoundTripTime = milliseconds(1);
108 
109   constexpr int kReceivedBytesPerSecond = 256000;
110   constexpr int kReceivedBytesPerTimeslice =
111       kReceivedBytesPerSecond / kTimeslicesPerSecond;
112   static_assert(kReceivedBytesPerSecond % kTimeslicesPerSecond == 0,
113                 "Test expectations won't account for rounding errors.");
114 
115   ASSERT_EQ(0, estimator()->ComputeNetworkBandwidth());
116 
117   // Simulate bursting at various rates, and confirm the bandwidth estimate is
118   // increasing for each burst rate. The estimate should be increasing because
119   // the total time spent transmitting is decreasing (while the same number of
120   // bytes are being received).
121   Clock::time_point now = kStartTime;
122   for (int divisor : kDivisors) {
123     SCOPED_TRACE(testing::Message() << "divisor=" << divisor);
124 
125     const Clock::time_point end = now + estimator()->history_window();
126     for (; now < end; now += kTimesliceDuration) {
127       estimator()->OnBurstComplete(kMaxPacketsPerTimeslice / divisor, now);
128       const Clock::time_point rtcp_arrival_time = now + kRoundTripTime;
129       estimator()->OnPayloadReceived(kReceivedBytesPerTimeslice,
130                                      rtcp_arrival_time, kRoundTripTime);
131       estimator()->OnRtcpReceived(rtcp_arrival_time, kRoundTripTime);
132     }
133     now = end;
134 
135     const int estimate = estimator()->ComputeNetworkBandwidth();
136     EXPECT_EQ(divisor * kReceivedBytesPerSecond * CHAR_BIT, estimate);
137   }
138 }
139 
140 // Tests that magnitude of the network round trip times, as well as random
141 // variance in packet arrival times, do not have a significant effect on the
142 // bandwidth estimates.
TEST_F(BandwidthEstimatorTest,EstimatesIndependentOfFeedbackDelays)143 TEST_F(BandwidthEstimatorTest, EstimatesIndependentOfFeedbackDelays) {
144   constexpr int kFactor = 2;
145   constexpr int kPacketsPerBurst = kMaxPacketsPerTimeslice / kFactor;
146   static_assert(kMaxPacketsPerTimeslice % kFactor == 0, "wanted exactly half");
147 
148   constexpr milliseconds kRoundTripTimes[3] = {milliseconds(1), milliseconds(9),
149                                                milliseconds(42)};
150 
151   constexpr int kReceivedBytesPerSecond = 2000000;
152   constexpr int kReceivedBytesPerTimeslice =
153       kReceivedBytesPerSecond / kTimeslicesPerSecond;
154 
155   // An arbitrary threshold. Sources of error include anything that would place
156   // byte flows outside the history window (e.g., AddFuzz(), or the 42ms round
157   // trip time).
158   constexpr int kMaxErrorPercent = 3;
159 
160   Clock::time_point now = kStartTime;
161   for (Clock::duration round_trip_time : kRoundTripTimes) {
162     SCOPED_TRACE(testing::Message()
163                  << "round_trip_time=" << round_trip_time.count());
164 
165     const Clock::time_point end = now + estimator()->history_window();
166     for (; now < end; now += kTimesliceDuration) {
167       estimator()->OnBurstComplete(kPacketsPerBurst, now);
168       const Clock::time_point rtcp_arrival_time =
169           AddFuzz(now + round_trip_time);
170       estimator()->OnPayloadReceived(kReceivedBytesPerTimeslice,
171                                      rtcp_arrival_time, round_trip_time);
172       estimator()->OnRtcpReceived(rtcp_arrival_time, round_trip_time);
173     }
174     now = end;
175 
176     constexpr int kExpectedEstimate =
177         kFactor * kReceivedBytesPerSecond * CHAR_BIT;
178     constexpr int kMaxError = kExpectedEstimate * kMaxErrorPercent / 100;
179     EXPECT_NEAR(kExpectedEstimate, estimator()->ComputeNetworkBandwidth(),
180                 kMaxError);
181   }
182 }
183 
184 // Tests that both the history tracking internal to BandwidthEstimator, as well
185 // as its computation of the bandwidth estimate, are both resistant to possible
186 // integer overflow cases. The internal implementation always clamps to the
187 // valid range of int.
TEST_F(BandwidthEstimatorTest,ClampsEstimateToMaxInt)188 TEST_F(BandwidthEstimatorTest, ClampsEstimateToMaxInt) {
189   constexpr int kPacketsPerBurst = kMaxPacketsPerTimeslice / 5;
190   static_assert(kMaxPacketsPerTimeslice % 5 == 0, "wanted exactly 20%");
191   const Clock::duration kRoundTripTime = milliseconds(1);
192 
193   int last_estimate = estimator()->ComputeNetworkBandwidth();
194   ASSERT_EQ(last_estimate, 0);
195 
196   // Simulate increasing numbers of bytes received per timeslice until it
197   // reaches values near INT_MAX. Along the way, the bandwidth estimates
198   // themselves should start clamping and, because of the fuzz added to RTCP
199   // arrival times, individual buckets in BandwidthEstimator::FlowTracker will
200   // occassionally be clamped too.
201   Clock::time_point now = kStartTime;
202   for (unsigned int bytes_received_per_timeslice = 1;
203        // Not overflowed past INT_MAX.
204        static_cast<int>(bytes_received_per_timeslice) > 0;
205        bytes_received_per_timeslice *= 2) {
206     int int_bytes = static_cast<int>(bytes_received_per_timeslice);
207     SCOPED_TRACE(testing::Message()
208                  << "bytes_received_per_timeslice=" << int_bytes);
209 
210     const Clock::time_point end = now + estimator()->history_window() / 4;
211     for (; now < end; now += kTimesliceDuration) {
212       estimator()->OnBurstComplete(kPacketsPerBurst, now);
213       const Clock::time_point rtcp_arrival_time = AddFuzz(now + kRoundTripTime);
214       estimator()->OnPayloadReceived(int_bytes, rtcp_arrival_time,
215                                      kRoundTripTime);
216       estimator()->OnRtcpReceived(rtcp_arrival_time, kRoundTripTime);
217     }
218     now = end;
219 
220     const int estimate = estimator()->ComputeNetworkBandwidth();
221     EXPECT_LE(last_estimate, estimate);
222     last_estimate = estimate;
223   }
224 
225   // Confirm there was a loop iteration at which the estimate reached INT_MAX
226   // and then stayed there for successive loop iterations.
227   EXPECT_EQ(std::numeric_limits<int>::max(), last_estimate);
228 }
229 
230 }  // namespace
231 }  // namespace cast
232 }  // namespace openscreen
233