Add a rate tracker that tracks rate over a given interval split up into buckets that accumulate unit counts for their portion of said interval and use this instead of the standard rate tracker so that the values of retrieved frame rate stats are completely independent of the polling rate.

BUG=
R=asapersson@webrtc.org, noahric@chromium.org, pbos@webrtc.org, pthatcher@webrtc.org

Review URL: https://codereview.webrtc.org/1279433006 .

Cr-Commit-Position: refs/heads/master@{#9933}
diff --git a/webrtc/base/ratetracker.cc b/webrtc/base/ratetracker.cc
index e03bfe0..7dcdb91 100644
--- a/webrtc/base/ratetracker.cc
+++ b/webrtc/base/ratetracker.cc
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *  Copyright 2015 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
@@ -9,56 +9,139 @@
  */
 
 #include "webrtc/base/ratetracker.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "webrtc/base/checks.h"
 #include "webrtc/base/timeutils.h"
 
 namespace rtc {
 
-RateTracker::RateTracker()
-    : total_units_(0), units_second_(0),
-      last_units_second_time_(~0u),
-      last_units_second_calc_(0) {
+RateTracker::RateTracker(
+    uint32 bucket_milliseconds, size_t bucket_count)
+    : bucket_milliseconds_(bucket_milliseconds),
+    bucket_count_(bucket_count),
+    sample_buckets_(new size_t[bucket_count + 1]),
+    total_sample_count_(0u),
+    bucket_start_time_milliseconds_(~0u) {
+  CHECK(bucket_milliseconds > 0u);
+  CHECK(bucket_count > 0u);
 }
 
-size_t RateTracker::total_units() const {
-  return total_units_;
+RateTracker::~RateTracker() {
+  delete[] sample_buckets_;
 }
 
-size_t RateTracker::units_second() {
-  // Snapshot units / second calculator. Determine how many seconds have
-  // elapsed since our last reference point. If over 1 second, establish
-  // a new reference point that is an integer number of seconds since the
-  // last one, and compute the units over that interval.
-  uint32 current_time = Time();
-  if (last_units_second_time_ == ~0u) {
-      last_units_second_time_ = current_time;
-      last_units_second_calc_ = total_units_;
-  } else {
-    int delta = rtc::TimeDiff(current_time, last_units_second_time_);
-    if (delta >= 1000) {
-      int fraction_time = delta % 1000;
-      int seconds = delta / 1000;
-      int fraction_units =
-          static_cast<int>(total_units_ - last_units_second_calc_) *
-              fraction_time / delta;
-      // Compute "units received during the interval" / "seconds in interval"
-      units_second_ =
-          (total_units_ - last_units_second_calc_ - fraction_units) / seconds;
-      last_units_second_time_ = current_time - fraction_time;
-      last_units_second_calc_ = total_units_ - fraction_units;
-    }
+double RateTracker::ComputeRateForInterval(
+    uint32 interval_milliseconds) const {
+  if (bucket_start_time_milliseconds_ == ~0u) {
+    return 0.0;
   }
-
-  return units_second_;
+  uint32 current_time = Time();
+  // Calculate which buckets to sum up given the current time.  If the time
+  // has passed to a new bucket then we have to skip some of the oldest buckets.
+  uint32 available_interval_milliseconds = std::min<uint32>(
+      interval_milliseconds,
+      bucket_milliseconds_ * static_cast<uint32>(bucket_count_));
+  // number of old buckets (i.e. after the current bucket in the ring buffer)
+  // that are expired given our current time interval.
+  size_t buckets_to_skip;
+  // Number of milliseconds of the first bucket that are not a portion of the
+  // current interval.
+  uint32 milliseconds_to_skip;
+  if (current_time >
+      initialization_time_milliseconds_ + available_interval_milliseconds) {
+    uint32 time_to_skip = current_time - bucket_start_time_milliseconds_ +
+        static_cast<uint32>(bucket_count_) * bucket_milliseconds_ -
+        available_interval_milliseconds;
+    buckets_to_skip = time_to_skip / bucket_milliseconds_;
+    milliseconds_to_skip = time_to_skip % bucket_milliseconds_;
+  } else {
+    buckets_to_skip = bucket_count_ - current_bucket_;
+    milliseconds_to_skip = 0u;
+    available_interval_milliseconds =
+        TimeDiff(current_time, initialization_time_milliseconds_);
+  }
+  // If we're skipping all buckets that means that there have been no samples
+  // within the sampling interval so report 0.
+  if (buckets_to_skip > bucket_count_ ||
+      available_interval_milliseconds == 0u) {
+    return 0.0;
+  }
+  size_t start_bucket = NextBucketIndex(current_bucket_ + buckets_to_skip);
+  // Only count a portion of the first bucket according to how much of the
+  // first bucket is within the current interval.
+  size_t total_samples = sample_buckets_[start_bucket] *
+      (bucket_milliseconds_ - milliseconds_to_skip) /
+      bucket_milliseconds_;
+  // All other buckets in the interval are counted in their entirety.
+  for (size_t i = NextBucketIndex(start_bucket);
+      i != NextBucketIndex(current_bucket_);
+      i = NextBucketIndex(i)) {
+    total_samples += sample_buckets_[i];
+  }
+  // Convert to samples per second.
+  return static_cast<double>(total_samples * 1000u) /
+      static_cast<double>(available_interval_milliseconds);
 }
 
-void RateTracker::Update(size_t units) {
-  if (last_units_second_time_ == ~0u)
-    last_units_second_time_ = Time();
-  total_units_ += units;
+double RateTracker::ComputeTotalRate() const {
+  if (bucket_start_time_milliseconds_ == ~0u) {
+    return 0.0;
+  }
+  uint32 current_time = Time();
+  if (TimeIsLaterOrEqual(current_time, initialization_time_milliseconds_)) {
+    return 0.0;
+  }
+  return static_cast<double>(total_sample_count_ * 1000u) /
+      static_cast<double>(
+          TimeDiff(current_time, initialization_time_milliseconds_));
+}
+
+size_t RateTracker::TotalSampleCount() const {
+  return total_sample_count_;
+}
+
+void RateTracker::AddSamples(size_t sample_count) {
+  EnsureInitialized();
+  uint32 current_time = Time();
+  // Advance the current bucket as needed for the current time, and reset
+  // bucket counts as we advance.
+  for (size_t i = 0u; i <= bucket_count_ &&
+      current_time >= bucket_start_time_milliseconds_ + bucket_milliseconds_;
+      ++i) {
+    bucket_start_time_milliseconds_ += bucket_milliseconds_;
+    current_bucket_ = NextBucketIndex(current_bucket_);
+    sample_buckets_[current_bucket_] = 0u;
+  }
+  // Ensure that bucket_start_time_milliseconds_ is updated appropriately if
+  // the entire buffer of samples has been expired.
+  bucket_start_time_milliseconds_ += bucket_milliseconds_ *
+      ((current_time - bucket_start_time_milliseconds_) / bucket_milliseconds_);
+  // Add all samples in the bucket that includes the current time.
+  sample_buckets_[current_bucket_] += sample_count;
+  total_sample_count_ += sample_count;
 }
 
 uint32 RateTracker::Time() const {
   return rtc::Time();
 }
 
+void RateTracker::EnsureInitialized() {
+  if (bucket_start_time_milliseconds_ == ~0u) {
+    initialization_time_milliseconds_ = Time();
+    bucket_start_time_milliseconds_ = initialization_time_milliseconds_;
+    current_bucket_ = 0u;
+    // We only need to initialize the first bucket because we reset buckets when
+    // current_bucket_ increments.
+    sample_buckets_[current_bucket_] = 0u;
+  }
+}
+
+size_t RateTracker::NextBucketIndex(size_t bucket_index) const {
+  return (bucket_index + 1u) % (bucket_count_ + 1u);
+}
+
 }  // namespace rtc
diff --git a/webrtc/base/ratetracker.h b/webrtc/base/ratetracker.h
index 575bff7..0e2e040 100644
--- a/webrtc/base/ratetracker.h
+++ b/webrtc/base/ratetracker.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *  Copyright 2015 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
@@ -16,25 +16,52 @@
 
 namespace rtc {
 
-// Computes instantaneous units per second.
+// Computes units per second over a given interval by tracking the units over
+// each bucket of a given size and calculating the instantaneous rate assuming
+// that over each bucket the rate was constant.
 class RateTracker {
  public:
-  RateTracker();
-  virtual ~RateTracker() {}
+  RateTracker(uint32 bucket_milliseconds, size_t bucket_count);
+  virtual ~RateTracker();
 
-  size_t total_units() const;
-  size_t units_second();
-  void Update(size_t units);
+  // Computes the average rate over the most recent interval_milliseconds,
+  // or if the first sample was added within this period, computes the rate
+  // since the first sample was added.
+  double ComputeRateForInterval(uint32 interval_milliseconds) const;
+
+  // Computes the average rate over the rate tracker's recording interval
+  // of bucket_milliseconds * bucket_count.
+  double ComputeRate() const {
+    return ComputeRateForInterval(
+        bucket_milliseconds_ * static_cast<uint32>(bucket_count_));
+  }
+
+  // Computes the average rate since the first sample was added to the
+  // rate tracker.
+  double ComputeTotalRate() const;
+
+  // The total number of samples added.
+  size_t TotalSampleCount() const;
+
+  // Reads the current time in order to determine the appropriate bucket for
+  // these samples, and increments the count for that bucket by sample_count.
+  void AddSamples(size_t sample_count);
 
  protected:
   // overrideable for tests
   virtual uint32 Time() const;
 
  private:
-  size_t total_units_;
-  size_t units_second_;
-  uint32 last_units_second_time_;
-  size_t last_units_second_calc_;
+  void EnsureInitialized();
+  size_t NextBucketIndex(size_t bucket_index) const;
+
+  const uint32 bucket_milliseconds_;
+  const size_t bucket_count_;
+  size_t* sample_buckets_;
+  size_t total_sample_count_;
+  size_t current_bucket_;
+  uint32 bucket_start_time_milliseconds_;
+  uint32 initialization_time_milliseconds_;
 };
 
 }  // namespace rtc
diff --git a/webrtc/base/ratetracker_unittest.cc b/webrtc/base/ratetracker_unittest.cc
index 1c20fd0..043f537 100644
--- a/webrtc/base/ratetracker_unittest.cc
+++ b/webrtc/base/ratetracker_unittest.cc
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *  Copyright 2015 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
@@ -15,7 +15,7 @@
 
 class RateTrackerForTest : public RateTracker {
  public:
-  RateTrackerForTest() : time_(0) {}
+  RateTrackerForTest() : RateTracker(100u, 10u), time_(0) {}
   virtual uint32 Time() const { return time_; }
   void AdvanceTime(uint32 delta) { time_ += delta; }
 
@@ -23,59 +23,140 @@
   uint32 time_;
 };
 
-TEST(RateTrackerTest, TestBasics) {
+TEST(RateTrackerTest, Test30FPS) {
   RateTrackerForTest tracker;
-  EXPECT_EQ(0U, tracker.total_units());
-  EXPECT_EQ(0U, tracker.units_second());
+
+  for (int i = 0; i < 300; ++i) {
+    tracker.AddSamples(1);
+    tracker.AdvanceTime(33);
+    if (i % 3 == 0) {
+      tracker.AdvanceTime(1);
+    }
+  }
+  EXPECT_DOUBLE_EQ(30.0, tracker.ComputeRateForInterval(50000u));
+}
+
+TEST(RateTrackerTest, Test60FPS) {
+  RateTrackerForTest tracker;
+
+  for (int i = 0; i < 300; ++i) {
+    tracker.AddSamples(1);
+    tracker.AdvanceTime(16);
+    if (i % 3 != 0) {
+      tracker.AdvanceTime(1);
+    }
+  }
+  EXPECT_DOUBLE_EQ(60.0, tracker.ComputeRateForInterval(1000u));
+}
+
+TEST(RateTrackerTest, TestRateTrackerBasics) {
+  RateTrackerForTest tracker;
+  EXPECT_DOUBLE_EQ(0.0, tracker.ComputeRateForInterval(1000u));
 
   // Add a sample.
-  tracker.Update(1234);
+  tracker.AddSamples(1234);
   // Advance the clock by 100 ms.
   tracker.AdvanceTime(100);
-  // total_units should advance, but units_second should stay 0.
-  EXPECT_EQ(1234U, tracker.total_units());
-  EXPECT_EQ(0U, tracker.units_second());
+  EXPECT_DOUBLE_EQ(12340.0, tracker.ComputeRateForInterval(1000u));
+  EXPECT_DOUBLE_EQ(12340.0, tracker.ComputeRate());
+  EXPECT_EQ(1234U, tracker.TotalSampleCount());
+  EXPECT_DOUBLE_EQ(12340.0, tracker.ComputeTotalRate());
 
   // Repeat.
-  tracker.Update(1234);
+  tracker.AddSamples(1234);
   tracker.AdvanceTime(100);
-  EXPECT_EQ(1234U * 2, tracker.total_units());
-  EXPECT_EQ(0U, tracker.units_second());
+  EXPECT_DOUBLE_EQ(12340.0, tracker.ComputeRateForInterval(1000u));
+  EXPECT_DOUBLE_EQ(12340.0, tracker.ComputeRate());
+  EXPECT_EQ(1234U * 2, tracker.TotalSampleCount());
+  EXPECT_DOUBLE_EQ(12340.0, tracker.ComputeTotalRate());
 
   // Advance the clock by 800 ms, so we've elapsed a full second.
   // units_second should now be filled in properly.
   tracker.AdvanceTime(800);
-  EXPECT_EQ(1234U * 2, tracker.total_units());
-  EXPECT_EQ(1234U * 2, tracker.units_second());
+  EXPECT_DOUBLE_EQ(1234.0 * 2.0, tracker.ComputeRateForInterval(1000u));
+  EXPECT_DOUBLE_EQ(1234.0 * 2.0, tracker.ComputeRate());
+  EXPECT_EQ(1234U * 2, tracker.TotalSampleCount());
+  EXPECT_DOUBLE_EQ(1234.0 * 2.0, tracker.ComputeTotalRate());
 
   // Poll the tracker again immediately. The reported rate should stay the same.
-  EXPECT_EQ(1234U * 2, tracker.total_units());
-  EXPECT_EQ(1234U * 2, tracker.units_second());
+  EXPECT_DOUBLE_EQ(1234.0 * 2.0, tracker.ComputeRateForInterval(1000u));
+  EXPECT_DOUBLE_EQ(1234.0 * 2.0, tracker.ComputeRate());
+  EXPECT_EQ(1234U * 2, tracker.TotalSampleCount());
+  EXPECT_DOUBLE_EQ(1234.0 * 2.0, tracker.ComputeTotalRate());
 
   // Do nothing and advance by a second. We should drop down to zero.
   tracker.AdvanceTime(1000);
-  EXPECT_EQ(1234U * 2, tracker.total_units());
-  EXPECT_EQ(0U, tracker.units_second());
+  EXPECT_DOUBLE_EQ(0.0, tracker.ComputeRateForInterval(1000u));
+  EXPECT_DOUBLE_EQ(0.0, tracker.ComputeRate());
+  EXPECT_EQ(1234U * 2, tracker.TotalSampleCount());
+  EXPECT_DOUBLE_EQ(1234.0, tracker.ComputeTotalRate());
 
   // Send a bunch of data at a constant rate for 5.5 "seconds".
   // We should report the rate properly.
   for (int i = 0; i < 5500; i += 100) {
-    tracker.Update(9876U);
+    tracker.AddSamples(9876U);
     tracker.AdvanceTime(100);
   }
-  EXPECT_EQ(9876U * 10, tracker.units_second());
+  EXPECT_DOUBLE_EQ(9876.0 * 10.0, tracker.ComputeRateForInterval(1000u));
+  EXPECT_DOUBLE_EQ(9876.0 * 10.0, tracker.ComputeRate());
+  EXPECT_EQ(1234U * 2 + 9876U * 55, tracker.TotalSampleCount());
+  EXPECT_DOUBLE_EQ((1234.0 * 2.0 + 9876.0 * 55.0) / 7.5,
+      tracker.ComputeTotalRate());
 
   // Advance the clock by 500 ms. Since we sent nothing over this half-second,
   // the reported rate should be reduced by half.
   tracker.AdvanceTime(500);
-  EXPECT_EQ(9876U * 5, tracker.units_second());
+  EXPECT_DOUBLE_EQ(9876.0 * 5.0, tracker.ComputeRateForInterval(1000u));
+  EXPECT_DOUBLE_EQ(9876.0 * 5.0, tracker.ComputeRate());
+  EXPECT_EQ(1234U * 2 + 9876U * 55, tracker.TotalSampleCount());
+  EXPECT_DOUBLE_EQ((1234.0 * 2.0 + 9876.0 * 55.0) / 8.0,
+      tracker.ComputeTotalRate());
+
+  // Rate over the last half second should be zero.
+  EXPECT_DOUBLE_EQ(0.0, tracker.ComputeRateForInterval(500u));
+}
+
+TEST(RateTrackerTest, TestLongPeriodBetweenSamples) {
+  RateTrackerForTest tracker;
+  tracker.AddSamples(1);
+  tracker.AdvanceTime(1000);
+  EXPECT_DOUBLE_EQ(1.0, tracker.ComputeRate());
+
+  tracker.AdvanceTime(2000);
+  EXPECT_DOUBLE_EQ(0.0, tracker.ComputeRate());
+
+  tracker.AdvanceTime(2000);
+  tracker.AddSamples(1);
+  EXPECT_DOUBLE_EQ(1.0, tracker.ComputeRate());
+}
+
+TEST(RateTrackerTest, TestRolloff) {
+  RateTrackerForTest tracker;
+  for (int i = 0; i < 10; ++i) {
+    tracker.AddSamples(1U);
+    tracker.AdvanceTime(100);
+  }
+  EXPECT_DOUBLE_EQ(10.0, tracker.ComputeRate());
+
+  for (int i = 0; i < 10; ++i) {
+    tracker.AddSamples(1U);
+    tracker.AdvanceTime(50);
+  }
+  EXPECT_DOUBLE_EQ(15.0, tracker.ComputeRate());
+  EXPECT_DOUBLE_EQ(20.0, tracker.ComputeRateForInterval(500u));
+
+  for (int i = 0; i < 10; ++i) {
+    tracker.AddSamples(1U);
+    tracker.AdvanceTime(50);
+  }
+  EXPECT_DOUBLE_EQ(20.0, tracker.ComputeRate());
 }
 
 TEST(RateTrackerTest, TestGetUnitSecondsAfterInitialValue) {
   RateTrackerForTest tracker;
-  tracker.Update(1234);
+  tracker.AddSamples(1234);
   tracker.AdvanceTime(1000);
-  EXPECT_EQ(1234u, tracker.units_second());
+  EXPECT_DOUBLE_EQ(1234.0, tracker.ComputeRateForInterval(1000u));
 }
 
 }  // namespace rtc
diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc
index eb63b02..4a5c06a 100644
--- a/webrtc/p2p/base/port.cc
+++ b/webrtc/p2p/base/port.cc
@@ -897,6 +897,8 @@
       last_ping_received_(0),
       last_data_received_(0),
       last_ping_response_received_(0),
+      recv_rate_tracker_(100u, 10u),
+      send_rate_tracker_(100u, 10u),
       sent_packets_discarded_(0),
       sent_packets_total_(0),
       reported_(false),
@@ -1008,7 +1010,7 @@
       // readable means data from this address is acceptable
       // Send it on!
       last_data_received_ = rtc::Time();
-      recv_rate_tracker_.Update(size);
+      recv_rate_tracker_.AddSamples(size);
       SignalReadPacket(this, data, size, packet_time);
 
       // If timed out sending writability checks, start up again
@@ -1455,19 +1457,19 @@
 }
 
 size_t Connection::recv_bytes_second() {
-  return recv_rate_tracker_.units_second();
+  return recv_rate_tracker_.ComputeRate();
 }
 
 size_t Connection::recv_total_bytes() {
-  return recv_rate_tracker_.total_units();
+  return recv_rate_tracker_.TotalSampleCount();
 }
 
 size_t Connection::sent_bytes_second() {
-  return send_rate_tracker_.units_second();
+  return send_rate_tracker_.ComputeRate();
 }
 
 size_t Connection::sent_total_bytes() {
-  return send_rate_tracker_.total_units();
+  return send_rate_tracker_.TotalSampleCount();
 }
 
 size_t Connection::sent_discarded_packets() {
@@ -1562,7 +1564,7 @@
     error_ = port_->GetError();
     sent_packets_discarded_++;
   } else {
-    send_rate_tracker_.Update(sent);
+    send_rate_tracker_.AddSamples(sent);
   }
   return sent;
 }
diff --git a/webrtc/p2p/base/tcpport.cc b/webrtc/p2p/base/tcpport.cc
index 91b9944..8fcbfe3 100644
--- a/webrtc/p2p/base/tcpport.cc
+++ b/webrtc/p2p/base/tcpport.cc
@@ -343,7 +343,7 @@
     sent_packets_discarded_++;
     error_ = socket_->GetError();
   } else {
-    send_rate_tracker_.Update(sent);
+    send_rate_tracker_.AddSamples(sent);
   }
   return sent;
 }
diff --git a/webrtc/video/receive_statistics_proxy.cc b/webrtc/video/receive_statistics_proxy.cc
index ec5aacb..eba28f5 100644
--- a/webrtc/video/receive_statistics_proxy.cc
+++ b/webrtc/video/receive_statistics_proxy.cc
@@ -20,7 +20,8 @@
     : clock_(clock),
       // 1000ms window, scale 1000 for ms to s.
       decode_fps_estimator_(1000, 1000),
-      renders_fps_estimator_(1000, 1000) {
+      renders_fps_estimator_(1000, 1000),
+      render_fps_tracker_(100u, 10u) {
   stats_.ssrc = ssrc;
 }
 
@@ -35,7 +36,7 @@
         fraction_lost);
   }
 
-  int render_fps = static_cast<int>(render_fps_tracker_total_.units_second());
+  int render_fps = static_cast<int>(render_fps_tracker_.ComputeTotalRate());
   if (render_fps > 0)
     RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.RenderFramesPerSecond", render_fps);
 
@@ -144,7 +145,7 @@
   stats_.render_frame_rate = renders_fps_estimator_.Rate(now);
   render_width_counter_.Add(width);
   render_height_counter_.Add(height);
-  render_fps_tracker_total_.Update(1);
+  render_fps_tracker_.AddSamples(1);
 }
 
 void ReceiveStatisticsProxy::OnReceiveRatesUpdated(uint32_t bitRate,
diff --git a/webrtc/video/receive_statistics_proxy.h b/webrtc/video/receive_statistics_proxy.h
index 3041d31..4f2550a 100644
--- a/webrtc/video/receive_statistics_proxy.h
+++ b/webrtc/video/receive_statistics_proxy.h
@@ -90,7 +90,7 @@
   VideoReceiveStream::Stats stats_ GUARDED_BY(crit_);
   RateStatistics decode_fps_estimator_ GUARDED_BY(crit_);
   RateStatistics renders_fps_estimator_ GUARDED_BY(crit_);
-  rtc::RateTracker render_fps_tracker_total_ GUARDED_BY(crit_);
+  rtc::RateTracker render_fps_tracker_ GUARDED_BY(crit_);
   SampleCounter render_width_counter_ GUARDED_BY(crit_);
   SampleCounter render_height_counter_ GUARDED_BY(crit_);
   SampleCounter decode_time_counter_ GUARDED_BY(crit_);
diff --git a/webrtc/video/send_statistics_proxy.cc b/webrtc/video/send_statistics_proxy.cc
index 551b4b5..e60614c 100644
--- a/webrtc/video/send_statistics_proxy.cc
+++ b/webrtc/video/send_statistics_proxy.cc
@@ -27,6 +27,8 @@
                                          const VideoSendStream::Config& config)
     : clock_(clock),
       config_(config),
+      input_frame_rate_tracker_(100u, 10u),
+      sent_frame_rate_tracker_(100u, 10u),
       last_sent_frame_timestamp_(0),
       max_sent_width_per_timestamp_(0),
       max_sent_height_per_timestamp_(0) {
@@ -38,11 +40,11 @@
 
 void SendStatisticsProxy::UpdateHistograms() {
   int input_fps =
-      static_cast<int>(input_frame_rate_tracker_total_.units_second());
+      static_cast<int>(input_frame_rate_tracker_.ComputeTotalRate());
   if (input_fps > 0)
     RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.InputFramesPerSecond", input_fps);
   int sent_fps =
-      static_cast<int>(sent_frame_rate_tracker_total_.units_second());
+      static_cast<int>(sent_frame_rate_tracker_.ComputeTotalRate());
   if (sent_fps > 0)
     RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.SentFramesPerSecond", sent_fps);
 
@@ -89,7 +91,7 @@
   rtc::CritScope lock(&crit_);
   PurgeOldStats();
   stats_.input_frame_rate =
-      static_cast<int>(input_frame_rate_tracker_.units_second());
+      static_cast<int>(input_frame_rate_tracker_.ComputeRate());
   return stats_;
 }
 
@@ -167,7 +169,7 @@
   // are encoded before the next start.
   if (last_sent_frame_timestamp_ > 0 &&
       encoded_image._timeStamp != last_sent_frame_timestamp_) {
-    sent_frame_rate_tracker_total_.Update(1);
+    sent_frame_rate_tracker_.AddSamples(1);
     sent_width_counter_.Add(max_sent_width_per_timestamp_);
     sent_height_counter_.Add(max_sent_height_per_timestamp_);
     max_sent_width_per_timestamp_ = 0;
@@ -184,8 +186,7 @@
 
 void SendStatisticsProxy::OnIncomingFrame(int width, int height) {
   rtc::CritScope lock(&crit_);
-  input_frame_rate_tracker_.Update(1);
-  input_frame_rate_tracker_total_.Update(1);
+  input_frame_rate_tracker_.AddSamples(1);
   input_width_counter_.Add(width);
   input_height_counter_.Add(height);
 }
diff --git a/webrtc/video/send_statistics_proxy.h b/webrtc/video/send_statistics_proxy.h
index 60d962f..6c6a0ba 100644
--- a/webrtc/video/send_statistics_proxy.h
+++ b/webrtc/video/send_statistics_proxy.h
@@ -117,8 +117,7 @@
   mutable rtc::CriticalSection crit_;
   VideoSendStream::Stats stats_ GUARDED_BY(crit_);
   rtc::RateTracker input_frame_rate_tracker_ GUARDED_BY(crit_);
-  rtc::RateTracker input_frame_rate_tracker_total_ GUARDED_BY(crit_);
-  rtc::RateTracker sent_frame_rate_tracker_total_ GUARDED_BY(crit_);
+  rtc::RateTracker sent_frame_rate_tracker_ GUARDED_BY(crit_);
   uint32_t last_sent_frame_timestamp_ GUARDED_BY(crit_);
   std::map<uint32_t, StatsUpdateTimes> update_times_ GUARDED_BY(crit_);