diff --git a/webrtc/video/BUILD.gn b/webrtc/video/BUILD.gn index 9e350f0cdb..12d4dc3301 100644 --- a/webrtc/video/BUILD.gn +++ b/webrtc/video/BUILD.gn @@ -30,6 +30,8 @@ source_set("video") { "send_delay_stats.h", "send_statistics_proxy.cc", "send_statistics_proxy.h", + "stats_counter.cc", + "stats_counter.h", "stream_synchronization.cc", "stream_synchronization.h", "video_capture_input.cc", diff --git a/webrtc/video/stats_counter.cc b/webrtc/video/stats_counter.cc new file mode 100644 index 0000000000..918e998c4d --- /dev/null +++ b/webrtc/video/stats_counter.cc @@ -0,0 +1,231 @@ +/* + * 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/video/stats_counter.h" + +#include + +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { + +namespace { +// Periodic time interval for processing samples. +const int64_t kProcessIntervalMs = 2000; +} // namespace + +// Class holding periodically computed metrics. +class AggregatedCounter { + public: + AggregatedCounter() : sum_(0) {} + ~AggregatedCounter() {} + + void Add(int sample) { + sum_ += sample; + ++stats_.num_samples; + if (stats_.num_samples == 1) { + stats_.min = sample; + stats_.max = sample; + } + stats_.min = std::min(sample, stats_.min); + stats_.max = std::max(sample, stats_.max); + } + + AggregatedStats ComputeStats() { + Compute(); + return stats_; + } + + private: + void Compute() { + if (stats_.num_samples == 0) + return; + + stats_.average = (sum_ + stats_.num_samples / 2) / stats_.num_samples; + } + int64_t sum_; + AggregatedStats stats_; +}; + +// StatsCounter class. +StatsCounter::StatsCounter(Clock* clock, + bool include_empty_intervals, + StatsCounterObserver* observer) + : max_(0), + sum_(0), + num_samples_(0), + last_sum_(0), + clock_(clock), + include_empty_intervals_(include_empty_intervals), + observer_(observer), + aggregated_counter_(new AggregatedCounter()), + last_process_time_ms_(-1) {} + +StatsCounter::~StatsCounter() {} + +AggregatedStats StatsCounter::GetStats() { + return aggregated_counter_->ComputeStats(); +} + +bool StatsCounter::TimeToProcess() { + int64_t now = clock_->TimeInMilliseconds(); + if (last_process_time_ms_ == -1) + last_process_time_ms_ = now; + + int64_t diff_ms = now - last_process_time_ms_; + if (diff_ms < kProcessIntervalMs) + return false; + + // Advance number of complete kProcessIntervalMs that have passed. + int64_t num_intervals = diff_ms / kProcessIntervalMs; + last_process_time_ms_ += num_intervals * kProcessIntervalMs; + + // Add zero for intervals without samples. + if (include_empty_intervals_) { + for (int64_t i = 0; i < num_intervals - 1; ++i) { + aggregated_counter_->Add(0); + if (observer_) + observer_->OnMetricUpdated(0); + } + } + return true; +} + +void StatsCounter::Set(int sample) { + TryProcess(); + ++num_samples_; + sum_ = sample; +} + +void StatsCounter::Add(int sample) { + TryProcess(); + ++num_samples_; + sum_ += sample; + + if (num_samples_ == 1) + max_ = sample; + max_ = std::max(sample, max_); +} + +void StatsCounter::TryProcess() { + if (!TimeToProcess()) + return; + + int metric; + if (GetMetric(&metric)) { + aggregated_counter_->Add(metric); + if (observer_) + observer_->OnMetricUpdated(metric); + } + last_sum_ = sum_; + sum_ = 0; + max_ = 0; + num_samples_ = 0; +} + +// StatsCounter sub-classes. +AvgCounter::AvgCounter(Clock* clock, StatsCounterObserver* observer) + : StatsCounter(clock, + false, // |include_empty_intervals| + observer) {} + +void AvgCounter::Add(int sample) { + StatsCounter::Add(sample); +} + +bool AvgCounter::GetMetric(int* metric) const { + if (num_samples_ == 0) + return false; + *metric = (sum_ + num_samples_ / 2) / num_samples_; + return true; +} + +MaxCounter::MaxCounter(Clock* clock, StatsCounterObserver* observer) + : StatsCounter(clock, + false, // |include_empty_intervals| + observer) {} + +void MaxCounter::Add(int sample) { + StatsCounter::Add(sample); +} + +bool MaxCounter::GetMetric(int* metric) const { + if (num_samples_ == 0) + return false; + *metric = max_; + return true; +} + +PercentCounter::PercentCounter(Clock* clock, StatsCounterObserver* observer) + : StatsCounter(clock, + false, // |include_empty_intervals| + observer) {} + +void PercentCounter::Add(bool sample) { + StatsCounter::Add(sample ? 1 : 0); +} + +bool PercentCounter::GetMetric(int* metric) const { + if (num_samples_ == 0) + return false; + *metric = (sum_ * 100 + num_samples_ / 2) / num_samples_; + return true; +} + +PermilleCounter::PermilleCounter(Clock* clock, StatsCounterObserver* observer) + : StatsCounter(clock, + false, // |include_empty_intervals| + observer) {} + +void PermilleCounter::Add(bool sample) { + StatsCounter::Add(sample ? 1 : 0); +} + +bool PermilleCounter::GetMetric(int* metric) const { + if (num_samples_ == 0) + return false; + *metric = (sum_ * 1000 + num_samples_ / 2) / num_samples_; + return true; +} + +RateCounter::RateCounter(Clock* clock, StatsCounterObserver* observer) + : StatsCounter(clock, + true, // |include_empty_intervals| + observer) {} + +void RateCounter::Add(int sample) { + StatsCounter::Add(sample); +} + +bool RateCounter::GetMetric(int* metric) const { + if (num_samples_ == 0) + return false; + *metric = (sum_ * 1000 + kProcessIntervalMs / 2) / kProcessIntervalMs; + return true; +} + +RateAccCounter::RateAccCounter(Clock* clock, StatsCounterObserver* observer) + : StatsCounter(clock, + true, // |include_empty_intervals| + observer) {} + +void RateAccCounter::Set(int sample) { + StatsCounter::Set(sample); +} + +bool RateAccCounter::GetMetric(int* metric) const { + if (num_samples_ == 0 || last_sum_ > sum_) + return false; + *metric = + ((sum_ - last_sum_) * 1000 + kProcessIntervalMs / 2) / kProcessIntervalMs; + return true; +} + +} // namespace webrtc diff --git a/webrtc/video/stats_counter.h b/webrtc/video/stats_counter.h new file mode 100644 index 0000000000..477e38e900 --- /dev/null +++ b/webrtc/video/stats_counter.h @@ -0,0 +1,227 @@ +/* + * 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_VIDEO_STATS_COUNTER_H_ +#define WEBRTC_VIDEO_STATS_COUNTER_H_ + +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/typedefs.h" + +namespace webrtc { + +class AggregatedCounter; +class Clock; + +// |StatsCounterObserver| is called periodically when a metric is updated. +class StatsCounterObserver { + public: + virtual void OnMetricUpdated(int sample) = 0; + + virtual ~StatsCounterObserver() {} +}; + +struct AggregatedStats { + int64_t num_samples = 0; + int min = -1; + int max = -1; + int average = -1; + // TODO(asapersson): Consider adding median/percentiles. +}; + +// Classes which periodically computes a metric. +// +// During a period, |kProcessIntervalMs|, different metrics can be computed e.g: +// - |AvgCounter|: average of samples +// - |PercentCounter|: percentage of samples +// - |PermilleCounter|: permille of samples +// +// Each periodic metric can be either: +// - reported to an |observer| each period +// - aggregated during the call (e.g. min, max, average) +// +// periodically computed +// GetMetric() GetMetric() => AggregatedStats +// ^ ^ (e.g. min/max/avg) +// | | +// | * * * * | ** * * * * | ... +// |<- process interval ->| +// +// (*) - samples +// +// +// Example usage: +// +// AvgCounter counter(&clock, nullptr); +// counter.Add(5); +// counter.Add(1); +// counter.Add(6); // process interval passed -> GetMetric() avg:4 +// counter.Add(7); +// counter.Add(3); // process interval passed -> GetMetric() avg:5 +// counter.Add(10); +// counter.Add(20); // process interval passed -> GetMetric() avg:15 +// AggregatedStats stats = counter.GetStats(); +// stats: {min:4, max:15, avg:8} +// + +// Note: StatsCounter takes ownership of |observer|. + +class StatsCounter { + public: + virtual ~StatsCounter(); + + virtual bool GetMetric(int* metric) const = 0; + + AggregatedStats GetStats(); + + protected: + StatsCounter(Clock* clock, + bool include_empty_intervals, + StatsCounterObserver* observer); + + void Add(int sample); + void Set(int sample); + + int max_; + int64_t sum_; + int64_t num_samples_; + int64_t last_sum_; + + private: + bool TimeToProcess(); + void TryProcess(); + + Clock* const clock_; + const bool include_empty_intervals_; + const std::unique_ptr observer_; + const std::unique_ptr aggregated_counter_; + int64_t last_process_time_ms_; +}; + +// AvgCounter: average of samples +// +// | * * * | * * | ... +// | Add(5) Add(1) Add(6) | Add(5) Add(5) | +// GetMetric | (5 + 1 + 6) / 3 | (5 + 5) / 2 | +// +class AvgCounter : public StatsCounter { + public: + AvgCounter(Clock* clock, StatsCounterObserver* observer); + ~AvgCounter() override {} + + void Add(int sample); + + private: + bool GetMetric(int* metric) const override; + + RTC_DISALLOW_COPY_AND_ASSIGN(AvgCounter); +}; + +// MaxCounter: maximum of samples +// +// | * * * | * * | ... +// | Add(5) Add(1) Add(6) | Add(5) Add(5) | +// GetMetric | max: (5, 1, 6) | max: (5, 5) | +// +class MaxCounter : public StatsCounter { + public: + MaxCounter(Clock* clock, StatsCounterObserver* observer); + ~MaxCounter() override {} + + void Add(int sample); + + private: + bool GetMetric(int* metric) const override; + + RTC_DISALLOW_COPY_AND_ASSIGN(MaxCounter); +}; + +// PercentCounter: percentage of samples +// +// | * * * | * * | ... +// | Add(T) Add(F) Add(T) | Add(F) Add(T) | +// GetMetric | 100 * 2 / 3 | 100 * 1 / 2 | +// +class PercentCounter : public StatsCounter { + public: + PercentCounter(Clock* clock, StatsCounterObserver* observer); + ~PercentCounter() override {} + + void Add(bool sample); + + private: + bool GetMetric(int* metric) const override; + + RTC_DISALLOW_COPY_AND_ASSIGN(PercentCounter); +}; + +// PermilleCounter: permille of samples +// +// | * * * | * * | ... +// | Add(T) Add(F) Add(T) | Add(F) Add(T) | +// GetMetric | 1000 * 2 / 3 | 1000 * 1 / 2 | +// +class PermilleCounter : public StatsCounter { + public: + PermilleCounter(Clock* clock, StatsCounterObserver* observer); + ~PermilleCounter() override {} + + void Add(bool sample); + + private: + bool GetMetric(int* metric) const override; + + RTC_DISALLOW_COPY_AND_ASSIGN(PermilleCounter); +}; + +// RateCounter: units per second +// +// | * * * | * * | ... +// | Add(5) Add(1) Add(6) | Add(5) Add(5) | +// |<------ 2 sec ------->| | +// GetMetric | (5 + 1 + 6) / 2 | (5 + 5) / 2 | +// +class RateCounter : public StatsCounter { + public: + RateCounter(Clock* clock, StatsCounterObserver* observer); + ~RateCounter() override {} + + void Add(int sample); + + private: + bool GetMetric(int* metric) const override; + + RTC_DISALLOW_COPY_AND_ASSIGN(RateCounter); +}; + +// RateAccCounter: units per second (used for counters) +// +// | * * * | * * | ... +// | Set(5) Set(6) Set(8) | Set(11) Set(13) | +// |<------ 2 sec ------->| | +// GetMetric | 8 / 2 | (13 - 8) / 2 | +// +class RateAccCounter : public StatsCounter { + public: + RateAccCounter(Clock* clock, StatsCounterObserver* observer); + ~RateAccCounter() override {} + + void Set(int sample); + + private: + bool GetMetric(int* metric) const override; + + RTC_DISALLOW_COPY_AND_ASSIGN(RateAccCounter); +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_STATS_COUNTER_H_ diff --git a/webrtc/video/stats_counter_unittest.cc b/webrtc/video/stats_counter_unittest.cc new file mode 100644 index 0000000000..7a352e6416 --- /dev/null +++ b/webrtc/video/stats_counter_unittest.cc @@ -0,0 +1,320 @@ +/* + * 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/video/stats_counter.h" + +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/system_wrappers/include/clock.h" + +namespace webrtc { +namespace { +const int kProcessIntervalMs = 2000; + +class StatsCounterObserverImpl : public StatsCounterObserver { + public: + StatsCounterObserverImpl() : num_calls_(0), last_sample_(-1) {} + void OnMetricUpdated(int sample) override { + ++num_calls_; + last_sample_ = sample; + } + int num_calls_; + int last_sample_; +}; +} // namespace + +class StatsCounterTest : public ::testing::Test { + protected: + StatsCounterTest() + : clock_(1234) {} + + void AddSampleAndAdvance(int sample, int interval_ms, AvgCounter* counter) { + counter->Add(sample); + clock_.AdvanceTimeMilliseconds(interval_ms); + } + + void SetSampleAndAdvance(int sample, + int interval_ms, + RateAccCounter* counter) { + counter->Set(sample); + clock_.AdvanceTimeMilliseconds(interval_ms); + } + + void VerifyStatsIsNotSet(const AggregatedStats& stats) { + EXPECT_EQ(0, stats.num_samples); + EXPECT_EQ(-1, stats.min); + EXPECT_EQ(-1, stats.max); + EXPECT_EQ(-1, stats.average); + } + + SimulatedClock clock_; +}; + +TEST_F(StatsCounterTest, NoSamples) { + AvgCounter counter(&clock_, nullptr); + VerifyStatsIsNotSet(counter.GetStats()); +} + +TEST_F(StatsCounterTest, TestRegisterObserver) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + const int kSample = 22; + AvgCounter counter(&clock_, observer); + AddSampleAndAdvance(kSample, kProcessIntervalMs, &counter); + // Trigger process (sample included in next interval). + counter.Add(111); + EXPECT_EQ(1, observer->num_calls_); +} + +TEST_F(StatsCounterTest, VerifyProcessInterval) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + AvgCounter counter(&clock_, observer); + counter.Add(4); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs - 1); + // Try trigger process (interval has not passed). + counter.Add(8); + EXPECT_EQ(0, observer->num_calls_); + VerifyStatsIsNotSet(counter.GetStats()); + // Make process interval pass. + clock_.AdvanceTimeMilliseconds(1); + // Trigger process (sample included in next interval). + counter.Add(111); + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(6, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); +} + +TEST_F(StatsCounterTest, TestMetric_AvgCounter) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + AvgCounter counter(&clock_, observer); + counter.Add(4); + counter.Add(8); + counter.Add(9); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Add(111); + // Average per interval. + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(7, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); + EXPECT_EQ(7, stats.min); + EXPECT_EQ(7, stats.max); + EXPECT_EQ(7, stats.average); +} + +TEST_F(StatsCounterTest, TestMetric_MaxCounter) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + MaxCounter counter(&clock_, observer); + counter.Add(4); + counter.Add(9); + counter.Add(8); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Add(111); + // Average per interval. + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(9, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); + EXPECT_EQ(9, stats.min); + EXPECT_EQ(9, stats.max); + EXPECT_EQ(9, stats.average); +} + +TEST_F(StatsCounterTest, TestMetric_PercentCounter) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + PercentCounter counter(&clock_, observer); + counter.Add(true); + counter.Add(false); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Add(false); + // Percentage per interval. + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(50, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); + EXPECT_EQ(50, stats.min); + EXPECT_EQ(50, stats.max); +} + +TEST_F(StatsCounterTest, TestMetric_PermilleCounter) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + PermilleCounter counter(&clock_, observer); + counter.Add(true); + counter.Add(false); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Add(false); + // Permille per interval. + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(500, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); + EXPECT_EQ(500, stats.min); + EXPECT_EQ(500, stats.max); +} + +TEST_F(StatsCounterTest, TestMetric_RateCounter) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + RateCounter counter(&clock_, observer); + counter.Add(186); + counter.Add(350); + counter.Add(22); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Add(111); + // Rate per interval, (186 + 350 + 22) / 2 sec = 279 samples/sec + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(279, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); + EXPECT_EQ(279, stats.min); + EXPECT_EQ(279, stats.max); +} + +TEST_F(StatsCounterTest, TestMetric_RateAccCounter) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + RateAccCounter counter(&clock_, observer); + counter.Set(175); + counter.Set(188); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Set(192); + // Rate per interval: (188 - 0) / 2 sec = 94 samples/sec + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(94, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); + EXPECT_EQ(94, stats.min); + EXPECT_EQ(94, stats.max); +} + +TEST_F(StatsCounterTest, TestGetStats_MultipleIntervals) { + AvgCounter counter(&clock_, nullptr); + const int kSample1 = 1; + const int kSample2 = 5; + const int kSample3 = 8; + const int kSample4 = 11; + const int kSample5 = 50; + AddSampleAndAdvance(kSample1, kProcessIntervalMs, &counter); + AddSampleAndAdvance(kSample2, kProcessIntervalMs, &counter); + AddSampleAndAdvance(kSample3, kProcessIntervalMs, &counter); + AddSampleAndAdvance(kSample4, kProcessIntervalMs, &counter); + AddSampleAndAdvance(kSample5, kProcessIntervalMs, &counter); + // Trigger process (sample included in next interval). + counter.Add(111); + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(5, stats.num_samples); + EXPECT_EQ(kSample1, stats.min); + EXPECT_EQ(kSample5, stats.max); + EXPECT_EQ(15, stats.average); +} + +TEST_F(StatsCounterTest, TestGetStatsTwice) { + const int kSample1 = 4; + const int kSample2 = 7; + AvgCounter counter(&clock_, nullptr); + AddSampleAndAdvance(kSample1, kProcessIntervalMs, &counter); + // Trigger process (sample included in next interval). + counter.Add(kSample2); + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(1, stats.num_samples); + EXPECT_EQ(kSample1, stats.min); + EXPECT_EQ(kSample1, stats.max); + // Trigger process (sample included in next interval). + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + counter.Add(111); + stats = counter.GetStats(); + EXPECT_EQ(2, stats.num_samples); + EXPECT_EQ(kSample1, stats.min); + EXPECT_EQ(kSample2, stats.max); + EXPECT_EQ(6, stats.average); +} + +TEST_F(StatsCounterTest, TestRateAccCounter_NegativeRateIgnored) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + const int kSample1 = 200; // 200 / 2 sec + const int kSample2 = 100; // -100 / 2 sec - negative ignored + const int kSample3 = 700; // 600 / 2 sec + RateAccCounter counter(&clock_, observer); + SetSampleAndAdvance(kSample1, kProcessIntervalMs, &counter); + SetSampleAndAdvance(kSample2, kProcessIntervalMs, &counter); + SetSampleAndAdvance(kSample3, kProcessIntervalMs, &counter); + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(100, observer->last_sample_); + // Trigger process (sample included in next interval). + counter.Set(2000); + EXPECT_EQ(2, observer->num_calls_); + EXPECT_EQ(300, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(2, stats.num_samples); + EXPECT_EQ(100, stats.min); + EXPECT_EQ(300, stats.max); + EXPECT_EQ(200, stats.average); +} + +TEST_F(StatsCounterTest, TestAvgCounter_IntervalsWithoutSamplesIgnored) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + AvgCounter counter(&clock_, observer); + AddSampleAndAdvance(6, kProcessIntervalMs * 4 - 1, &counter); + // Trigger process (sample included in next interval). + counter.Add(8); + // [6:1], two intervals without samples passed. + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(6, observer->last_sample_); + // Make last interval pass. + clock_.AdvanceTimeMilliseconds(1); + counter.Add(111); // Trigger process (sample included in next interval). + // [6:1],[8:1] + EXPECT_EQ(2, observer->num_calls_); + EXPECT_EQ(8, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(2, stats.num_samples); + EXPECT_EQ(6, stats.min); + EXPECT_EQ(8, stats.max); +} + +TEST_F(StatsCounterTest, TestRateCounter_IntervalsWithoutSamplesIncluded) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + const int kSample1 = 50; // 50 / 2 sec + const int kSample2 = 20; // 20 / 2 sec + RateCounter counter(&clock_, observer); + counter.Add(kSample1); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs * 3 - 1); + // Trigger process (sample included in next interval). + counter.Add(kSample2); + // [0:1],[25:1], one interval without samples passed. + EXPECT_EQ(2, observer->num_calls_); + EXPECT_EQ(25, observer->last_sample_); + // Make last interval pass. + clock_.AdvanceTimeMilliseconds(1); + counter.Add(111); // Trigger process (sample included in next interval). + // [0:1],[10:1],[25:1] + EXPECT_EQ(3, observer->num_calls_); + EXPECT_EQ(10, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(3, stats.num_samples); + EXPECT_EQ(0, stats.min); + EXPECT_EQ(25, stats.max); +} + +} // namespace webrtc diff --git a/webrtc/video/webrtc_video.gypi b/webrtc/video/webrtc_video.gypi index 3d6afdee07..b7ef5b906d 100644 --- a/webrtc/video/webrtc_video.gypi +++ b/webrtc/video/webrtc_video.gypi @@ -44,6 +44,8 @@ 'video/send_delay_stats.h', 'video/send_statistics_proxy.cc', 'video/send_statistics_proxy.h', + 'video/stats_counter.cc', + 'video/stats_counter.h', 'video/stream_synchronization.cc', 'video/stream_synchronization.h', 'video/video_capture_input.cc', diff --git a/webrtc/webrtc_tests.gypi b/webrtc/webrtc_tests.gypi index ae31c6c33c..848f7ca8b5 100644 --- a/webrtc/webrtc_tests.gypi +++ b/webrtc/webrtc_tests.gypi @@ -170,6 +170,7 @@ 'video/report_block_stats_unittest.cc', 'video/send_delay_stats_unittest.cc', 'video/send_statistics_proxy_unittest.cc', + 'video/stats_counter_unittest.cc', 'video/stream_synchronization_unittest.cc', 'video/video_capture_input_unittest.cc', 'video/video_decoder_unittest.cc',