• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "modules/congestion_controller/goog_cc/trendline_estimator.h"
12 
13 #include <math.h>
14 
15 #include <algorithm>
16 #include <string>
17 
18 #include "absl/strings/match.h"
19 #include "absl/types/optional.h"
20 #include "api/network_state_predictor.h"
21 #include "modules/remote_bitrate_estimator/test/bwe_test_logging.h"
22 #include "rtc_base/checks.h"
23 #include "rtc_base/experiments/struct_parameters_parser.h"
24 #include "rtc_base/logging.h"
25 #include "rtc_base/numerics/safe_minmax.h"
26 
27 namespace webrtc {
28 
29 namespace {
30 
31 // Parameters for linear least squares fit of regression line to noisy data.
32 constexpr double kDefaultTrendlineSmoothingCoeff = 0.9;
33 constexpr double kDefaultTrendlineThresholdGain = 4.0;
34 const char kBweWindowSizeInPacketsExperiment[] =
35     "WebRTC-BweWindowSizeInPackets";
36 
ReadTrendlineFilterWindowSize(const FieldTrialsView * key_value_config)37 size_t ReadTrendlineFilterWindowSize(const FieldTrialsView* key_value_config) {
38   std::string experiment_string =
39       key_value_config->Lookup(kBweWindowSizeInPacketsExperiment);
40   size_t window_size;
41   int parsed_values =
42       sscanf(experiment_string.c_str(), "Enabled-%zu", &window_size);
43   if (parsed_values == 1) {
44     if (window_size > 1)
45       return window_size;
46     RTC_LOG(LS_WARNING) << "Window size must be greater than 1.";
47   }
48   RTC_LOG(LS_WARNING) << "Failed to parse parameters for BweWindowSizeInPackets"
49                          " experiment from field trial string. Using default.";
50   return TrendlineEstimatorSettings::kDefaultTrendlineWindowSize;
51 }
52 
LinearFitSlope(const std::deque<TrendlineEstimator::PacketTiming> & packets)53 absl::optional<double> LinearFitSlope(
54     const std::deque<TrendlineEstimator::PacketTiming>& packets) {
55   RTC_DCHECK(packets.size() >= 2);
56   // Compute the "center of mass".
57   double sum_x = 0;
58   double sum_y = 0;
59   for (const auto& packet : packets) {
60     sum_x += packet.arrival_time_ms;
61     sum_y += packet.smoothed_delay_ms;
62   }
63   double x_avg = sum_x / packets.size();
64   double y_avg = sum_y / packets.size();
65   // Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2
66   double numerator = 0;
67   double denominator = 0;
68   for (const auto& packet : packets) {
69     double x = packet.arrival_time_ms;
70     double y = packet.smoothed_delay_ms;
71     numerator += (x - x_avg) * (y - y_avg);
72     denominator += (x - x_avg) * (x - x_avg);
73   }
74   if (denominator == 0)
75     return absl::nullopt;
76   return numerator / denominator;
77 }
78 
ComputeSlopeCap(const std::deque<TrendlineEstimator::PacketTiming> & packets,const TrendlineEstimatorSettings & settings)79 absl::optional<double> ComputeSlopeCap(
80     const std::deque<TrendlineEstimator::PacketTiming>& packets,
81     const TrendlineEstimatorSettings& settings) {
82   RTC_DCHECK(1 <= settings.beginning_packets &&
83              settings.beginning_packets < packets.size());
84   RTC_DCHECK(1 <= settings.end_packets &&
85              settings.end_packets < packets.size());
86   RTC_DCHECK(settings.beginning_packets + settings.end_packets <=
87              packets.size());
88   TrendlineEstimator::PacketTiming early = packets[0];
89   for (size_t i = 1; i < settings.beginning_packets; ++i) {
90     if (packets[i].raw_delay_ms < early.raw_delay_ms)
91       early = packets[i];
92   }
93   size_t late_start = packets.size() - settings.end_packets;
94   TrendlineEstimator::PacketTiming late = packets[late_start];
95   for (size_t i = late_start + 1; i < packets.size(); ++i) {
96     if (packets[i].raw_delay_ms < late.raw_delay_ms)
97       late = packets[i];
98   }
99   if (late.arrival_time_ms - early.arrival_time_ms < 1) {
100     return absl::nullopt;
101   }
102   return (late.raw_delay_ms - early.raw_delay_ms) /
103              (late.arrival_time_ms - early.arrival_time_ms) +
104          settings.cap_uncertainty;
105 }
106 
107 constexpr double kMaxAdaptOffsetMs = 15.0;
108 constexpr double kOverUsingTimeThreshold = 10;
109 constexpr int kMinNumDeltas = 60;
110 constexpr int kDeltaCounterMax = 1000;
111 
112 }  // namespace
113 
114 constexpr char TrendlineEstimatorSettings::kKey[];
115 
TrendlineEstimatorSettings(const FieldTrialsView * key_value_config)116 TrendlineEstimatorSettings::TrendlineEstimatorSettings(
117     const FieldTrialsView* key_value_config) {
118   if (absl::StartsWith(
119           key_value_config->Lookup(kBweWindowSizeInPacketsExperiment),
120           "Enabled")) {
121     window_size = ReadTrendlineFilterWindowSize(key_value_config);
122   }
123   Parser()->Parse(key_value_config->Lookup(TrendlineEstimatorSettings::kKey));
124   if (window_size < 10 || 200 < window_size) {
125     RTC_LOG(LS_WARNING) << "Window size must be between 10 and 200 packets";
126     window_size = kDefaultTrendlineWindowSize;
127   }
128   if (enable_cap) {
129     if (beginning_packets < 1 || end_packets < 1 ||
130         beginning_packets > window_size || end_packets > window_size) {
131       RTC_LOG(LS_WARNING) << "Size of beginning and end must be between 1 and "
132                           << window_size;
133       enable_cap = false;
134       beginning_packets = end_packets = 0;
135       cap_uncertainty = 0.0;
136     }
137     if (beginning_packets + end_packets > window_size) {
138       RTC_LOG(LS_WARNING)
139           << "Size of beginning plus end can't exceed the window size";
140       enable_cap = false;
141       beginning_packets = end_packets = 0;
142       cap_uncertainty = 0.0;
143     }
144     if (cap_uncertainty < 0.0 || 0.025 < cap_uncertainty) {
145       RTC_LOG(LS_WARNING) << "Cap uncertainty must be between 0 and 0.025";
146       cap_uncertainty = 0.0;
147     }
148   }
149 }
150 
Parser()151 std::unique_ptr<StructParametersParser> TrendlineEstimatorSettings::Parser() {
152   return StructParametersParser::Create("sort", &enable_sort,  //
153                                         "cap", &enable_cap,    //
154                                         "beginning_packets",
155                                         &beginning_packets,                   //
156                                         "end_packets", &end_packets,          //
157                                         "cap_uncertainty", &cap_uncertainty,  //
158                                         "window_size", &window_size);
159 }
160 
TrendlineEstimator(const FieldTrialsView * key_value_config,NetworkStatePredictor * network_state_predictor)161 TrendlineEstimator::TrendlineEstimator(
162     const FieldTrialsView* key_value_config,
163     NetworkStatePredictor* network_state_predictor)
164     : settings_(key_value_config),
165       smoothing_coef_(kDefaultTrendlineSmoothingCoeff),
166       threshold_gain_(kDefaultTrendlineThresholdGain),
167       num_of_deltas_(0),
168       first_arrival_time_ms_(-1),
169       accumulated_delay_(0),
170       smoothed_delay_(0),
171       delay_hist_(),
172       k_up_(0.0087),
173       k_down_(0.039),
174       overusing_time_threshold_(kOverUsingTimeThreshold),
175       threshold_(12.5),
176       prev_modified_trend_(NAN),
177       last_update_ms_(-1),
178       prev_trend_(0.0),
179       time_over_using_(-1),
180       overuse_counter_(0),
181       hypothesis_(BandwidthUsage::kBwNormal),
182       hypothesis_predicted_(BandwidthUsage::kBwNormal),
183       network_state_predictor_(network_state_predictor) {
184   RTC_LOG(LS_INFO)
185       << "Using Trendline filter for delay change estimation with settings "
186       << settings_.Parser()->Encode() << " and "
187       << (network_state_predictor_ ? "injected" : "no")
188       << " network state predictor";
189 }
190 
~TrendlineEstimator()191 TrendlineEstimator::~TrendlineEstimator() {}
192 
UpdateTrendline(double recv_delta_ms,double send_delta_ms,int64_t send_time_ms,int64_t arrival_time_ms,size_t packet_size)193 void TrendlineEstimator::UpdateTrendline(double recv_delta_ms,
194                                          double send_delta_ms,
195                                          int64_t send_time_ms,
196                                          int64_t arrival_time_ms,
197                                          size_t packet_size) {
198   const double delta_ms = recv_delta_ms - send_delta_ms;
199   ++num_of_deltas_;
200   num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax);
201   if (first_arrival_time_ms_ == -1)
202     first_arrival_time_ms_ = arrival_time_ms;
203 
204   // Exponential backoff filter.
205   accumulated_delay_ += delta_ms;
206   BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms,
207                         accumulated_delay_);
208   smoothed_delay_ = smoothing_coef_ * smoothed_delay_ +
209                     (1 - smoothing_coef_) * accumulated_delay_;
210   BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms,
211                         smoothed_delay_);
212 
213   // Maintain packet window
214   delay_hist_.emplace_back(
215       static_cast<double>(arrival_time_ms - first_arrival_time_ms_),
216       smoothed_delay_, accumulated_delay_);
217   if (settings_.enable_sort) {
218     for (size_t i = delay_hist_.size() - 1;
219          i > 0 &&
220          delay_hist_[i].arrival_time_ms < delay_hist_[i - 1].arrival_time_ms;
221          --i) {
222       std::swap(delay_hist_[i], delay_hist_[i - 1]);
223     }
224   }
225   if (delay_hist_.size() > settings_.window_size)
226     delay_hist_.pop_front();
227 
228   // Simple linear regression.
229   double trend = prev_trend_;
230   if (delay_hist_.size() == settings_.window_size) {
231     // Update trend_ if it is possible to fit a line to the data. The delay
232     // trend can be seen as an estimate of (send_rate - capacity)/capacity.
233     // 0 < trend < 1   ->  the delay increases, queues are filling up
234     //   trend == 0    ->  the delay does not change
235     //   trend < 0     ->  the delay decreases, queues are being emptied
236     trend = LinearFitSlope(delay_hist_).value_or(trend);
237     if (settings_.enable_cap) {
238       absl::optional<double> cap = ComputeSlopeCap(delay_hist_, settings_);
239       // We only use the cap to filter out overuse detections, not
240       // to detect additional underuses.
241       if (trend >= 0 && cap.has_value() && trend > cap.value()) {
242         trend = cap.value();
243       }
244     }
245   }
246   BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend);
247 
248   Detect(trend, send_delta_ms, arrival_time_ms);
249 }
250 
Update(double recv_delta_ms,double send_delta_ms,int64_t send_time_ms,int64_t arrival_time_ms,size_t packet_size,bool calculated_deltas)251 void TrendlineEstimator::Update(double recv_delta_ms,
252                                 double send_delta_ms,
253                                 int64_t send_time_ms,
254                                 int64_t arrival_time_ms,
255                                 size_t packet_size,
256                                 bool calculated_deltas) {
257   if (calculated_deltas) {
258     UpdateTrendline(recv_delta_ms, send_delta_ms, send_time_ms, arrival_time_ms,
259                     packet_size);
260   }
261   if (network_state_predictor_) {
262     hypothesis_predicted_ = network_state_predictor_->Update(
263         send_time_ms, arrival_time_ms, hypothesis_);
264   }
265 }
266 
State() const267 BandwidthUsage TrendlineEstimator::State() const {
268   return network_state_predictor_ ? hypothesis_predicted_ : hypothesis_;
269 }
270 
Detect(double trend,double ts_delta,int64_t now_ms)271 void TrendlineEstimator::Detect(double trend, double ts_delta, int64_t now_ms) {
272   if (num_of_deltas_ < 2) {
273     hypothesis_ = BandwidthUsage::kBwNormal;
274     return;
275   }
276   const double modified_trend =
277       std::min(num_of_deltas_, kMinNumDeltas) * trend * threshold_gain_;
278   prev_modified_trend_ = modified_trend;
279   BWE_TEST_LOGGING_PLOT(1, "T", now_ms, modified_trend);
280   BWE_TEST_LOGGING_PLOT(1, "threshold", now_ms, threshold_);
281   if (modified_trend > threshold_) {
282     if (time_over_using_ == -1) {
283       // Initialize the timer. Assume that we've been
284       // over-using half of the time since the previous
285       // sample.
286       time_over_using_ = ts_delta / 2;
287     } else {
288       // Increment timer
289       time_over_using_ += ts_delta;
290     }
291     overuse_counter_++;
292     if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) {
293       if (trend >= prev_trend_) {
294         time_over_using_ = 0;
295         overuse_counter_ = 0;
296         hypothesis_ = BandwidthUsage::kBwOverusing;
297       }
298     }
299   } else if (modified_trend < -threshold_) {
300     time_over_using_ = -1;
301     overuse_counter_ = 0;
302     hypothesis_ = BandwidthUsage::kBwUnderusing;
303   } else {
304     time_over_using_ = -1;
305     overuse_counter_ = 0;
306     hypothesis_ = BandwidthUsage::kBwNormal;
307   }
308   prev_trend_ = trend;
309   UpdateThreshold(modified_trend, now_ms);
310 }
311 
UpdateThreshold(double modified_trend,int64_t now_ms)312 void TrendlineEstimator::UpdateThreshold(double modified_trend,
313                                          int64_t now_ms) {
314   if (last_update_ms_ == -1)
315     last_update_ms_ = now_ms;
316 
317   if (fabs(modified_trend) > threshold_ + kMaxAdaptOffsetMs) {
318     // Avoid adapting the threshold to big latency spikes, caused e.g.,
319     // by a sudden capacity drop.
320     last_update_ms_ = now_ms;
321     return;
322   }
323 
324   const double k = fabs(modified_trend) < threshold_ ? k_down_ : k_up_;
325   const int64_t kMaxTimeDeltaMs = 100;
326   int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs);
327   threshold_ += k * (fabs(modified_trend) - threshold_) * time_delta_ms;
328   threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f);
329   last_update_ms_ = now_ms;
330 }
331 
332 }  // namespace webrtc
333