diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn index b914cf4ba2..9120541041 100644 --- a/webrtc/modules/BUILD.gn +++ b/webrtc/modules/BUILD.gn @@ -399,6 +399,7 @@ if (rtc_include_tests) { "congestion_controller/probe_controller_unittest.cc", "congestion_controller/probing_interval_estimator_unittest.cc", "congestion_controller/transport_feedback_adapter_unittest.cc", + "congestion_controller/trendline_estimator_unittest.cc", "media_file/media_file_unittest.cc", "module_common_types_unittest.cc", "pacing/alr_detector_unittest.cc", diff --git a/webrtc/modules/congestion_controller/BUILD.gn b/webrtc/modules/congestion_controller/BUILD.gn index e25c992a78..09b33881c8 100644 --- a/webrtc/modules/congestion_controller/BUILD.gn +++ b/webrtc/modules/congestion_controller/BUILD.gn @@ -22,8 +22,16 @@ rtc_static_library("congestion_controller") { "probing_interval_estimator.h", "transport_feedback_adapter.cc", "transport_feedback_adapter.h", + "trendline_estimator.cc", + "trendline_estimator.h", ] + if (rtc_enable_bwe_test_logging) { + defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1" ] + } else { + defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0" ] + } + # TODO(jschuh): Bug 1348: fix this warning. configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] diff --git a/webrtc/modules/congestion_controller/delay_based_bwe.cc b/webrtc/modules/congestion_controller/delay_based_bwe.cc index af27505268..78bc7f0e6e 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe.cc +++ b/webrtc/modules/congestion_controller/delay_based_bwe.cc @@ -12,6 +12,7 @@ #include #include +#include #include "webrtc/base/checks.h" #include "webrtc/base/constructormagic.h" @@ -38,15 +39,52 @@ constexpr uint32_t kFixedSsrc = 0; constexpr int kInitialRateWindowMs = 500; constexpr int kRateWindowMs = 150; +constexpr size_t kDefaultTrendlineWindowSize = 15; +constexpr double kDefaultTrendlineSmoothingCoeff = 0.9; +constexpr double kDefaultTrendlineThresholdGain = 4.0; + const char kBitrateEstimateExperiment[] = "WebRTC-ImprovedBitrateEstimate"; +const char kBweTrendlineFilterExperiment[] = "WebRTC-BweTrendlineFilter"; bool BitrateEstimateExperimentIsEnabled() { return webrtc::field_trial::FindFullName(kBitrateEstimateExperiment) == "Enabled"; } + +bool TrendlineFilterExperimentIsEnabled() { + std::string experiment_string = + webrtc::field_trial::FindFullName(kBweTrendlineFilterExperiment); + // The experiment is enabled iff the field trial string begins with "Enabled". + return experiment_string.find("Enabled") == 0; +} + +bool ReadTrendlineFilterExperimentParameters(size_t* window_points, + double* smoothing_coef, + double* threshold_gain) { + RTC_DCHECK(TrendlineFilterExperimentIsEnabled()); + std::string experiment_string = + webrtc::field_trial::FindFullName(kBweTrendlineFilterExperiment); + int parsed_values = sscanf(experiment_string.c_str(), "Enabled-%zu,%lf,%lf", + window_points, smoothing_coef, threshold_gain); + if (parsed_values == 3) { + RTC_CHECK_GT(*window_points, 1) << "Need at least 2 points to fit a line."; + RTC_CHECK(0 <= *smoothing_coef && *smoothing_coef <= 1) + << "Coefficient needs to be between 0 and 1 for weighted average."; + RTC_CHECK_GT(*threshold_gain, 0) << "Threshold gain needs to be positive."; + return true; + } + LOG(LS_WARNING) << "Failed to parse parameters for BweTrendlineFilter " + "experiment from field trial string. Using default."; + *window_points = kDefaultTrendlineWindowSize; + *smoothing_coef = kDefaultTrendlineSmoothingCoeff; + *threshold_gain = kDefaultTrendlineThresholdGain; + return false; +} + } // namespace namespace webrtc { + DelayBasedBwe::BitrateEstimator::BitrateEstimator() : sum_(0), current_win_ms_(0), @@ -132,12 +170,22 @@ rtc::Optional DelayBasedBwe::BitrateEstimator::bitrate_bps() const { DelayBasedBwe::DelayBasedBwe(Clock* clock) : clock_(clock), inter_arrival_(), - estimator_(), + kalman_estimator_(), + trendline_estimator_(), detector_(OverUseDetectorOptions()), receiver_incoming_bitrate_(), last_update_ms_(-1), last_seen_packet_ms_(-1), - uma_recorded_(false) { + uma_recorded_(false), + trendline_window_size_(kDefaultTrendlineWindowSize), + trendline_smoothing_coeff_(kDefaultTrendlineSmoothingCoeff), + trendline_threshold_gain_(kDefaultTrendlineThresholdGain), + in_trendline_experiment_(TrendlineFilterExperimentIsEnabled()) { + if (in_trendline_experiment_) { + ReadTrendlineFilterExperimentParameters(&trendline_window_size_, + &trendline_smoothing_coeff_, + &trendline_threshold_gain_); + } network_thread_.DetachFromThread(); } @@ -171,7 +219,10 @@ DelayBasedBwe::Result DelayBasedBwe::IncomingPacketInfo( inter_arrival_.reset( new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000, kTimestampToMs, true)); - estimator_.reset(new OveruseEstimator(OverUseDetectorOptions())); + kalman_estimator_.reset(new OveruseEstimator(OverUseDetectorOptions())); + trendline_estimator_.reset(new TrendlineEstimator( + trendline_window_size_, trendline_smoothing_coeff_, + trendline_threshold_gain_)); } last_seen_packet_ms_ = now_ms; @@ -192,10 +243,19 @@ DelayBasedBwe::Result DelayBasedBwe::IncomingPacketInfo( info.payload_size, &ts_delta, &t_delta, &size_delta)) { double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift); - estimator_->Update(t_delta, ts_delta_ms, size_delta, detector_.State(), + if (in_trendline_experiment_) { + trendline_estimator_->Update(t_delta, ts_delta_ms, info.arrival_time_ms); + detector_.Detect(trendline_estimator_->trendline_slope(), ts_delta_ms, + trendline_estimator_->num_of_deltas(), info.arrival_time_ms); - detector_.Detect(estimator_->offset(), ts_delta_ms, - estimator_->num_of_deltas(), info.arrival_time_ms); + + } else { + kalman_estimator_->Update(t_delta, ts_delta_ms, size_delta, + detector_.State(), info.arrival_time_ms); + detector_.Detect(kalman_estimator_->offset(), ts_delta_ms, + kalman_estimator_->num_of_deltas(), + info.arrival_time_ms); + } } int probing_bps = 0; @@ -237,8 +297,9 @@ bool DelayBasedBwe::UpdateEstimate(int64_t arrival_time_ms, int64_t now_ms, rtc::Optional acked_bitrate_bps, uint32_t* target_bitrate_bps) { - const RateControlInput input(detector_.State(), acked_bitrate_bps, - estimator_->var_noise()); + // TODO(terelius): RateControlInput::noise_var is deprecated and will be + // removed. In the meantime, we set it to zero. + const RateControlInput input(detector_.State(), acked_bitrate_bps, 0); rate_control_.Update(&input, now_ms); *target_bitrate_bps = rate_control_.UpdateBandwidthEstimate(now_ms); return rate_control_.ValidEstimate(); diff --git a/webrtc/modules/congestion_controller/delay_based_bwe.h b/webrtc/modules/congestion_controller/delay_based_bwe.h index 6aab54918c..0f1472e672 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe.h +++ b/webrtc/modules/congestion_controller/delay_based_bwe.h @@ -20,6 +20,7 @@ #include "webrtc/base/rate_statistics.h" #include "webrtc/base/thread_checker.h" #include "webrtc/modules/congestion_controller/probe_bitrate_estimator.h" +#include "webrtc/modules/congestion_controller/trendline_estimator.h" #include "webrtc/modules/remote_bitrate_estimator/aimd_rate_control.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "webrtc/modules/remote_bitrate_estimator/inter_arrival.h" @@ -85,7 +86,8 @@ class DelayBasedBwe { rtc::ThreadChecker network_thread_; Clock* const clock_; std::unique_ptr inter_arrival_; - std::unique_ptr estimator_; + std::unique_ptr kalman_estimator_; + std::unique_ptr trendline_estimator_; OveruseDetector detector_; BitrateEstimator receiver_incoming_bitrate_; int64_t last_update_ms_; @@ -93,6 +95,10 @@ class DelayBasedBwe { bool uma_recorded_; AimdRateControl rate_control_; ProbeBitrateEstimator probe_bitrate_estimator_; + size_t trendline_window_size_; + double trendline_smoothing_coeff_; + double trendline_threshold_gain_; + const bool in_trendline_experiment_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayBasedBwe); }; diff --git a/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc b/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc index d439a2b9cb..3f26e592ae 100644 --- a/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc +++ b/webrtc/modules/congestion_controller/delay_based_bwe_unittest.cc @@ -195,4 +195,38 @@ TEST_F(DelayBasedBweExperimentTest, CapacityDropNegOffsetChange) { TEST_F(DelayBasedBweExperimentTest, CapacityDropOneStreamWrap) { CapacityDropTestHelper(1, true, 333, 0); } + +class DelayBasedBweTrendlineExperimentTest : public DelayBasedBweTest { + public: + DelayBasedBweTrendlineExperimentTest() + : override_field_trials_("WebRTC-BweTrendlineFilter/Enabled-15,0.9,4/") {} + + protected: + void SetUp() override { + bitrate_estimator_.reset(new DelayBasedBwe(&clock_)); + } + + test::ScopedFieldTrials override_field_trials_; +}; + +TEST_F(DelayBasedBweTrendlineExperimentTest, RateIncreaseRtpTimestamps) { + RateIncreaseRtpTimestampsTestHelper(1240); +} + +TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropOneStream) { + CapacityDropTestHelper(1, false, 600, 0); +} + +TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropPosOffsetChange) { + CapacityDropTestHelper(1, false, 600, 30000); +} + +TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropNegOffsetChange) { + CapacityDropTestHelper(1, false, 1267, -30000); +} + +TEST_F(DelayBasedBweTrendlineExperimentTest, CapacityDropOneStreamWrap) { + CapacityDropTestHelper(1, true, 600, 0); +} + } // namespace webrtc diff --git a/webrtc/modules/congestion_controller/trendline_estimator.cc b/webrtc/modules/congestion_controller/trendline_estimator.cc new file mode 100644 index 0000000000..a086f6aaa1 --- /dev/null +++ b/webrtc/modules/congestion_controller/trendline_estimator.cc @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/congestion_controller/trendline_estimator.h" + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/modules/remote_bitrate_estimator/test/bwe_test_logging.h" + +namespace webrtc { + +namespace { +double LinearFitSlope(const std::list> points) { + RTC_DCHECK(points.size() >= 2); + // Compute the "center of mass". + double sum_x = 0; + double sum_y = 0; + for (const auto& point : points) { + sum_x += point.first; + sum_y += point.second; + } + double x_avg = sum_x / points.size(); + double y_avg = sum_y / points.size(); + // Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2 + double numerator = 0; + double denominator = 0; + for (const auto& point : points) { + numerator += (point.first - x_avg) * (point.second - y_avg); + denominator += (point.first - x_avg) * (point.first - x_avg); + } + return numerator / denominator; +} +} // namespace + +enum { kDeltaCounterMax = 1000 }; + +TrendlineEstimator::TrendlineEstimator(size_t window_size, + double smoothing_coef, + double threshold_gain) + : window_size_(window_size), + smoothing_coef_(smoothing_coef), + threshold_gain_(threshold_gain), + num_of_deltas_(0), + accumulated_delay_(0), + smoothed_delay_(0), + delay_hist_(), + trendline_(0) {} + +TrendlineEstimator::~TrendlineEstimator() {} + +void TrendlineEstimator::Update(double recv_delta_ms, + double send_delta_ms, + double now_ms) { + const double delta_ms = recv_delta_ms - send_delta_ms; + ++num_of_deltas_; + if (num_of_deltas_ > kDeltaCounterMax) { + num_of_deltas_ = kDeltaCounterMax; + } + + // Exponential backoff filter. + accumulated_delay_ += delta_ms; + BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", now_ms, accumulated_delay_); + smoothed_delay_ = smoothing_coef_ * smoothed_delay_ + + (1 - smoothing_coef_) * accumulated_delay_; + BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", now_ms, smoothed_delay_); + + // Simple linear regression. + delay_hist_.push_back(std::make_pair(now_ms, smoothed_delay_)); + if (delay_hist_.size() > window_size_) { + delay_hist_.pop_front(); + } + if (delay_hist_.size() == window_size_) { + trendline_ = LinearFitSlope(delay_hist_); + } + + BWE_TEST_LOGGING_PLOT(1, "trendline_slope", now_ms, trendline_); +} + +} // namespace webrtc diff --git a/webrtc/modules/congestion_controller/trendline_estimator.h b/webrtc/modules/congestion_controller/trendline_estimator.h new file mode 100644 index 0000000000..0bf9bf27c7 --- /dev/null +++ b/webrtc/modules/congestion_controller/trendline_estimator.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef WEBRTC_MODULES_CONGESTION_CONTROLLER_TRENDLINE_ESTIMATOR_H_ +#define WEBRTC_MODULES_CONGESTION_CONTROLLER_TRENDLINE_ESTIMATOR_H_ + +#include +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/common_types.h" + +namespace webrtc { + +class TrendlineEstimator { + public: + // |window_size| is the number of points required to compute a trend line. + // |smoothing_coef| controls how much we smooth out the delay before fitting + // the trend line. |threshold_gain| is used to scale the trendline slope for + // comparison to the old threshold. Once the old estimator has been removed + // (or the thresholds been merged into the estimators), we can just set the + // threshold instead of setting a gain. + TrendlineEstimator(size_t window_size, + double smoothing_coef, + double threshold_gain); + ~TrendlineEstimator(); + + // Update the estimator with a new sample. The deltas should represent deltas + // between timestamp groups as defined by the InterArrival class. + void Update(double recv_delta_ms, double send_delta_ms, double now_ms); + + // Returns the estimated trend k multiplied by some gain. + // 0 < k < 1 -> the delay increases, queues are filling up + // k == 0 -> the delay does not change + // k < 0 -> the delay decreases, queues are being emptied + double trendline_slope() const { return trendline_ * threshold_gain_; } + + // Returns the number of deltas which the current estimator state is based on. + unsigned int num_of_deltas() const { return num_of_deltas_; } + + private: + // Parameters. + const size_t window_size_; + const double smoothing_coef_; + const double threshold_gain_; + // Used by the existing threshold. + unsigned int num_of_deltas_; + // Exponential backoff filtering. + double accumulated_delay_; + double smoothed_delay_; + // Linear least squares regression. + std::list> delay_hist_; + double trendline_; + + RTC_DISALLOW_COPY_AND_ASSIGN(TrendlineEstimator); +}; +} // namespace webrtc + +#endif // WEBRTC_MODULES_CONGESTION_CONTROLLER_TRENDLINE_ESTIMATOR_H_ diff --git a/webrtc/modules/congestion_controller/trendline_estimator_unittest.cc b/webrtc/modules/congestion_controller/trendline_estimator_unittest.cc new file mode 100644 index 0000000000..51778e6cf3 --- /dev/null +++ b/webrtc/modules/congestion_controller/trendline_estimator_unittest.cc @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/test/gtest.h" +#include "webrtc/base/random.h" +#include "webrtc/modules/congestion_controller/trendline_estimator.h" + +namespace webrtc { + +namespace { +constexpr size_t kWindowSize = 15; +constexpr double kSmoothing = 0.0; +constexpr double kGain = 1; +constexpr int64_t kAvgTimeBetweenPackets = 10; +} // namespace + +TEST(TrendlineEstimator, PerfectLineSlopeOneHalf) { + TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 2 * send_delta; + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), 0.5, 0.001); + } +} + +TEST(TrendlineEstimator, PerfectLineSlopeMinusOne) { + TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 0.5 * send_delta; + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), -1, 0.001); + } +} + +TEST(TrendlineEstimator, PerfectLineSlopeZero) { + TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = send_delta; + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + } +} + +TEST(TrendlineEstimator, JitteryLineSlopeOneHalf) { + TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 2 * send_delta + rand.Gaussian(0, send_delta / 3); + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), 0.5, 0.1); + } +} + +TEST(TrendlineEstimator, JitteryLineSlopeMinusOne) { + TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = 0.5 * send_delta + rand.Gaussian(0, send_delta / 25); + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + if (i < kWindowSize) + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.001); + else + EXPECT_NEAR(estimator.trendline_slope(), -1, 0.1); + } +} + +TEST(TrendlineEstimator, JitteryLineSlopeZero) { + TrendlineEstimator estimator(kWindowSize, kSmoothing, kGain); + Random rand(0x1234567); + double now_ms = rand.Rand() * 10000; + for (size_t i = 1; i < 2 * kWindowSize; i++) { + double send_delta = rand.Rand() * 2 * kAvgTimeBetweenPackets; + double recv_delta = send_delta + rand.Gaussian(0, send_delta / 8); + now_ms += recv_delta; + estimator.Update(recv_delta, send_delta, now_ms); + EXPECT_NEAR(estimator.trendline_slope(), 0, 0.1); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc index f13a1d5a18..b671a13dbd 100644 --- a/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc +++ b/webrtc/modules/remote_bitrate_estimator/bwe_simulations.cc @@ -242,6 +242,19 @@ TEST_P(BweSimulation, PacerGoogleWifiTrace3Mbps) { RunFor(300 * 1000); } +TEST_P(BweSimulation, PacerGoogleWifiTrace3MbpsLowFramerate) { + PeriodicKeyFrameSource source(0, 5, 300, 0, 0, 1000); + PacedVideoSender sender(&uplink_, &source, GetParam()); + RateCounterFilter counter1(&uplink_, 0, "sender_output", + bwe_names[GetParam()]); + TraceBasedDeliveryFilter filter(&uplink_, 0, "link_capacity"); + filter.set_max_delay_ms(500); + RateCounterFilter counter2(&uplink_, 0, "Receiver", bwe_names[GetParam()]); + PacketReceiver receiver(&uplink_, 0, GetParam(), true, true); + ASSERT_TRUE(filter.Init(test::ResourcePath("google-wifi-3mbps", "rx"))); + RunFor(300 * 1000); +} + TEST_P(BweSimulation, SelfFairnessTest) { Random prng(Clock::GetRealTimeClock()->TimeInMicroseconds()); const int kAllFlowIds[] = {0, 1, 2, 3}; diff --git a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py index dac47626de..5fdac51d09 100755 --- a/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py +++ b/webrtc/modules/remote_bitrate_estimator/test/plot_dynamics.py @@ -140,8 +140,13 @@ def main(): detector_state.addSubplot(['offset_ms'], "Time (s)", "Offset") detector_state.addSubplot(['gamma_ms'], "Time (s)", "Gamma") + trendline_state = Figure("TrendlineState") + trendline_state.addSubplot(["accumulated_delay_ms", "smoothed_delay_ms"], + "Time (s)", "Delay (ms)") + trendline_state.addSubplot(["trendline_slope"], "Time (s)", "Slope") + # Select which figures to plot here. - figures = [receiver, detector_state] + figures = [receiver, detector_state, trendline_state] # Add samples to the figures. for line in sys.stdin: