diff --git a/webrtc/call/call.cc b/webrtc/call/call.cc index 4b5bd3a292..8457572395 100644 --- a/webrtc/call/call.cc +++ b/webrtc/call/call.cc @@ -186,11 +186,10 @@ class Call : public webrtc::Call, // TODO(holmer): Remove this lock once BitrateController no longer calls // OnNetworkChanged from multiple threads. rtc::CriticalSection bitrate_crit_; - int64_t estimated_send_bitrate_sum_kbits_ GUARDED_BY(&bitrate_crit_); - int64_t pacer_bitrate_sum_kbits_ GUARDED_BY(&bitrate_crit_); uint32_t min_allocated_send_bitrate_bps_ GUARDED_BY(&bitrate_crit_); - int64_t num_bitrate_updates_ GUARDED_BY(&bitrate_crit_); uint32_t configured_max_padding_bitrate_bps_ GUARDED_BY(&bitrate_crit_); + AvgCounter estimated_send_bitrate_kbps_counter_ GUARDED_BY(&bitrate_crit_); + AvgCounter pacer_bitrate_kbps_counter_ GUARDED_BY(&bitrate_crit_); std::map network_routes_; @@ -244,11 +243,10 @@ Call::Call(const Call::Config& config) received_audio_bytes_per_second_counter_(clock_, nullptr, true), received_video_bytes_per_second_counter_(clock_, nullptr, true), received_rtcp_bytes_per_second_counter_(clock_, nullptr, true), - estimated_send_bitrate_sum_kbits_(0), - pacer_bitrate_sum_kbits_(0), min_allocated_send_bitrate_bps_(0), - num_bitrate_updates_(0), configured_max_padding_bitrate_bps_(0), + estimated_send_bitrate_kbps_counter_(clock_, nullptr, true), + pacer_bitrate_kbps_counter_(clock_, nullptr, true), remb_(clock_), congestion_controller_( new CongestionController(clock_, this, &remb_, event_log_.get())), @@ -320,22 +318,24 @@ void Call::UpdateHistograms() { } void Call::UpdateSendHistograms() { - if (num_bitrate_updates_ == 0 || first_packet_sent_ms_ == -1) + if (first_packet_sent_ms_ == -1) return; int64_t elapsed_sec = (clock_->TimeInMilliseconds() - first_packet_sent_ms_) / 1000; if (elapsed_sec < metrics::kMinRunTimeInSeconds) return; - int send_bitrate_kbps = - estimated_send_bitrate_sum_kbits_ / num_bitrate_updates_; - int pacer_bitrate_kbps = pacer_bitrate_sum_kbits_ / num_bitrate_updates_; - if (send_bitrate_kbps > 0) { + const int kMinRequiredPeriodicSamples = 5; + AggregatedStats send_bitrate_stats = + estimated_send_bitrate_kbps_counter_.ProcessAndGetStats(); + if (send_bitrate_stats.num_samples > kMinRequiredPeriodicSamples) { RTC_LOGGED_HISTOGRAM_COUNTS_100000("WebRTC.Call.EstimatedSendBitrateInKbps", - send_bitrate_kbps); + send_bitrate_stats.average); } - if (pacer_bitrate_kbps > 0) { + AggregatedStats pacer_bitrate_stats = + pacer_bitrate_kbps_counter_.ProcessAndGetStats(); + if (pacer_bitrate_stats.num_samples > kMinRequiredPeriodicSamples) { RTC_LOGGED_HISTOGRAM_COUNTS_100000("WebRTC.Call.PacerBitrateInKbps", - pacer_bitrate_kbps); + pacer_bitrate_stats.average); } } @@ -757,26 +757,32 @@ void Call::OnNetworkChanged(uint32_t target_bitrate_bps, uint8_t fraction_loss, bitrate_allocator_->OnNetworkChanged(target_bitrate_bps, fraction_loss, rtt_ms); - // Ignore updates where the bitrate is zero because the aggregate network - // state is down. - if (target_bitrate_bps > 0) { - { - ReadLockScoped read_lock(*send_crit_); - // Do not update the stats if we are not sending video. - if (video_send_streams_.empty()) - return; - } + // Ignore updates if bitrate is zero (the aggregate network state is down). + if (target_bitrate_bps == 0) { rtc::CritScope lock(&bitrate_crit_); - // We only update these stats if we have send streams, and assume that - // OnNetworkChanged is called roughly with a fixed frequency. - estimated_send_bitrate_sum_kbits_ += target_bitrate_bps / 1000; - // Pacer bitrate might be higher than bitrate estimate if enforcing min - // bitrate. - uint32_t pacer_bitrate_bps = - std::max(target_bitrate_bps, min_allocated_send_bitrate_bps_); - pacer_bitrate_sum_kbits_ += pacer_bitrate_bps / 1000; - ++num_bitrate_updates_; + estimated_send_bitrate_kbps_counter_.ProcessAndPause(); + pacer_bitrate_kbps_counter_.ProcessAndPause(); + return; } + + bool sending_video; + { + ReadLockScoped read_lock(*send_crit_); + sending_video = !video_send_streams_.empty(); + } + + rtc::CritScope lock(&bitrate_crit_); + if (!sending_video) { + // Do not update the stats if we are not sending video. + estimated_send_bitrate_kbps_counter_.ProcessAndPause(); + pacer_bitrate_kbps_counter_.ProcessAndPause(); + return; + } + estimated_send_bitrate_kbps_counter_.Add(target_bitrate_bps / 1000); + // Pacer bitrate may be higher than bitrate estimate if enforcing min bitrate. + uint32_t pacer_bitrate_bps = + std::max(target_bitrate_bps, min_allocated_send_bitrate_bps_); + pacer_bitrate_kbps_counter_.Add(pacer_bitrate_bps / 1000); } void Call::OnAllocationLimitsChanged(uint32_t min_send_bitrate_bps, diff --git a/webrtc/video/send_delay_stats.cc b/webrtc/video/send_delay_stats.cc index e84bf10287..0f73e1f888 100644 --- a/webrtc/video/send_delay_stats.cc +++ b/webrtc/video/send_delay_stats.cc @@ -10,6 +10,8 @@ #include "webrtc/video/send_delay_stats.h" +#include + #include "webrtc/base/logging.h" #include "webrtc/system_wrappers/include/metrics.h" @@ -61,7 +63,7 @@ AvgCounter* SendDelayStats::GetSendDelayCounter(uint32_t ssrc) { if (it != send_delay_counters_.end()) return it->second.get(); - AvgCounter* counter = new AvgCounter(clock_, nullptr); + AvgCounter* counter = new AvgCounter(clock_, nullptr, false); send_delay_counters_[ssrc].reset(counter); return counter; } diff --git a/webrtc/video/stats_counter.cc b/webrtc/video/stats_counter.cc index 5bcef991d2..0a66015cee 100644 --- a/webrtc/video/stats_counter.cc +++ b/webrtc/video/stats_counter.cc @@ -12,6 +12,7 @@ #include +#include "webrtc/base/checks.h" #include "webrtc/system_wrappers/include/clock.h" namespace webrtc { @@ -24,11 +25,12 @@ const int64_t kProcessIntervalMs = 2000; // Class holding periodically computed metrics. class AggregatedCounter { public: - AggregatedCounter() : sum_(0) {} + AggregatedCounter() : last_sample_(0), sum_samples_(0) {} ~AggregatedCounter() {} void Add(int sample) { - sum_ += sample; + last_sample_ = sample; + sum_samples_ += sample; ++stats_.num_samples; if (stats_.num_samples == 1) { stats_.min = sample; @@ -43,14 +45,20 @@ class AggregatedCounter { return stats_; } + bool Empty() const { return stats_.num_samples == 0; } + + int last_sample() const { return last_sample_; } + private: void Compute() { if (stats_.num_samples == 0) return; - stats_.average = (sum_ + stats_.num_samples / 2) / stats_.num_samples; + stats_.average = + (sum_samples_ + stats_.num_samples / 2) / stats_.num_samples; } - int64_t sum_; + int last_sample_; + int64_t sum_samples_; AggregatedStats stats_; }; @@ -62,11 +70,12 @@ StatsCounter::StatsCounter(Clock* clock, sum_(0), num_samples_(0), last_sum_(0), + aggregated_counter_(new AggregatedCounter()), clock_(clock), include_empty_intervals_(include_empty_intervals), observer_(observer), - aggregated_counter_(new AggregatedCounter()), - last_process_time_ms_(-1) {} + last_process_time_ms_(-1), + paused_(false) {} StatsCounter::~StatsCounter() {} @@ -74,11 +83,23 @@ AggregatedStats StatsCounter::GetStats() { return aggregated_counter_->ComputeStats(); } +AggregatedStats StatsCounter::ProcessAndGetStats() { + if (HasSample()) + TryProcess(); + return aggregated_counter_->ComputeStats(); +} + +void StatsCounter::ProcessAndPause() { + if (HasSample()) + TryProcess(); + paused_ = true; +} + bool StatsCounter::HasSample() const { return last_process_time_ms_ != -1; } -bool StatsCounter::TimeToProcess() { +bool StatsCounter::TimeToProcess(int* elapsed_intervals) { int64_t now = clock_->TimeInMilliseconds(); if (last_process_time_ms_ == -1) last_process_time_ms_ = now; @@ -91,14 +112,7 @@ bool StatsCounter::TimeToProcess() { 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); - } - } + *elapsed_intervals = num_intervals; return true; } @@ -106,6 +120,7 @@ void StatsCounter::Set(int sample) { TryProcess(); ++num_samples_; sum_ = sample; + paused_ = false; } void StatsCounter::Add(int sample) { @@ -116,29 +131,57 @@ void StatsCounter::Add(int sample) { if (num_samples_ == 1) max_ = sample; max_ = std::max(sample, max_); + paused_ = false; +} + +// Reports periodically computed metric. +void StatsCounter::ReportMetricToAggregatedCounter( + int value, + int num_values_to_add) const { + for (int i = 0; i < num_values_to_add; ++i) { + aggregated_counter_->Add(value); + if (observer_) + observer_->OnMetricUpdated(value); + } } void StatsCounter::TryProcess() { - if (!TimeToProcess()) + int elapsed_intervals; + if (!TimeToProcess(&elapsed_intervals)) return; + // Get and report periodically computed metric. int metric; - if (GetMetric(&metric)) { - aggregated_counter_->Add(metric); - if (observer_) - observer_->OnMetricUpdated(metric); + if (GetMetric(&metric)) + ReportMetricToAggregatedCounter(metric, 1); + + // Report value for elapsed intervals without samples. + if (IncludeEmptyIntervals()) { + // If there are no samples, all elapsed intervals are empty (otherwise one + // interval contains sample(s), discard this interval). + int empty_intervals = + (num_samples_ == 0) ? elapsed_intervals : (elapsed_intervals - 1); + ReportMetricToAggregatedCounter(GetValueForEmptyInterval(), + empty_intervals); } - last_sum_ = sum_; + + // Reset samples for elapsed interval. + if (num_samples_ > 0) + last_sum_ = sum_; sum_ = 0; max_ = 0; num_samples_ = 0; } +bool StatsCounter::IncludeEmptyIntervals() const { + return include_empty_intervals_ && !paused_ && !aggregated_counter_->Empty(); +} + // StatsCounter sub-classes. -AvgCounter::AvgCounter(Clock* clock, StatsCounterObserver* observer) - : StatsCounter(clock, - false, // |include_empty_intervals| - observer) {} +AvgCounter::AvgCounter(Clock* clock, + StatsCounterObserver* observer, + bool include_empty_intervals) + : StatsCounter(clock, include_empty_intervals, observer) {} void AvgCounter::Add(int sample) { StatsCounter::Add(sample); @@ -151,6 +194,10 @@ bool AvgCounter::GetMetric(int* metric) const { return true; } +int AvgCounter::GetValueForEmptyInterval() const { + return aggregated_counter_->last_sample(); +} + MaxCounter::MaxCounter(Clock* clock, StatsCounterObserver* observer) : StatsCounter(clock, false, // |include_empty_intervals| @@ -167,6 +214,11 @@ bool MaxCounter::GetMetric(int* metric) const { return true; } +int MaxCounter::GetValueForEmptyInterval() const { + RTC_NOTREACHED(); + return 0; +} + PercentCounter::PercentCounter(Clock* clock, StatsCounterObserver* observer) : StatsCounter(clock, false, // |include_empty_intervals| @@ -183,6 +235,11 @@ bool PercentCounter::GetMetric(int* metric) const { return true; } +int PercentCounter::GetValueForEmptyInterval() const { + RTC_NOTREACHED(); + return 0; +} + PermilleCounter::PermilleCounter(Clock* clock, StatsCounterObserver* observer) : StatsCounter(clock, false, // |include_empty_intervals| @@ -199,6 +256,11 @@ bool PermilleCounter::GetMetric(int* metric) const { return true; } +int PermilleCounter::GetValueForEmptyInterval() const { + RTC_NOTREACHED(); + return 0; +} + RateCounter::RateCounter(Clock* clock, StatsCounterObserver* observer, bool include_empty_intervals) @@ -215,6 +277,10 @@ bool RateCounter::GetMetric(int* metric) const { return true; } +int RateCounter::GetValueForEmptyInterval() const { + return 0; +} + RateAccCounter::RateAccCounter(Clock* clock, StatsCounterObserver* observer, bool include_empty_intervals) @@ -232,4 +298,8 @@ bool RateAccCounter::GetMetric(int* metric) const { return true; } +int RateAccCounter::GetValueForEmptyInterval() const { + return 0; +} + } // namespace webrtc diff --git a/webrtc/video/stats_counter.h b/webrtc/video/stats_counter.h index ba1bd457eb..603bbdaca0 100644 --- a/webrtc/video/stats_counter.h +++ b/webrtc/video/stats_counter.h @@ -78,10 +78,22 @@ class StatsCounter { public: virtual ~StatsCounter(); + // Gets metric within an interval. Returns true on success false otherwise. virtual bool GetMetric(int* metric) const = 0; + // Gets the value to use for an interval without samples. + virtual int GetValueForEmptyInterval() const = 0; + + // Gets aggregated stats (i.e. aggregate of periodically computed metrics). AggregatedStats GetStats(); + // Reports metrics for elapsed intervals to AggregatedCounter and GetStats. + AggregatedStats ProcessAndGetStats(); + + // Reports metrics for elapsed intervals to AggregatedCounter and pauses stats + // (i.e. empty intervals will be discarded until next sample is added). + void ProcessAndPause(); + // Checks if a sample has been added (i.e. Add or Set called). bool HasSample() const; @@ -98,15 +110,19 @@ class StatsCounter { int64_t num_samples_; int64_t last_sum_; + const std::unique_ptr aggregated_counter_; + private: - bool TimeToProcess(); + bool TimeToProcess(int* num_elapsed_intervals); void TryProcess(); + void ReportMetricToAggregatedCounter(int value, int num_values_to_add) const; + bool IncludeEmptyIntervals() const; 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_; + bool paused_; }; // AvgCounter: average of samples @@ -115,9 +131,15 @@ class StatsCounter { // | Add(5) Add(1) Add(6) | Add(5) Add(5) | // GetMetric | (5 + 1 + 6) / 3 | (5 + 5) / 2 | // +// |include_empty_intervals|: If set, intervals without samples will be included +// in the stats. The value for an interval is +// determined by GetValueForEmptyInterval(). +// class AvgCounter : public StatsCounter { public: - AvgCounter(Clock* clock, StatsCounterObserver* observer); + AvgCounter(Clock* clock, + StatsCounterObserver* observer, + bool include_empty_intervals); ~AvgCounter() override {} void Add(int sample); @@ -125,6 +147,9 @@ class AvgCounter : public StatsCounter { private: bool GetMetric(int* metric) const override; + // Returns the last computed metric (i.e. from GetMetric). + int GetValueForEmptyInterval() const override; + RTC_DISALLOW_COPY_AND_ASSIGN(AvgCounter); }; @@ -143,6 +168,7 @@ class MaxCounter : public StatsCounter { private: bool GetMetric(int* metric) const override; + int GetValueForEmptyInterval() const override; RTC_DISALLOW_COPY_AND_ASSIGN(MaxCounter); }; @@ -162,6 +188,7 @@ class PercentCounter : public StatsCounter { private: bool GetMetric(int* metric) const override; + int GetValueForEmptyInterval() const override; RTC_DISALLOW_COPY_AND_ASSIGN(PercentCounter); }; @@ -181,6 +208,7 @@ class PermilleCounter : public StatsCounter { private: bool GetMetric(int* metric) const override; + int GetValueForEmptyInterval() const override; RTC_DISALLOW_COPY_AND_ASSIGN(PermilleCounter); }; @@ -192,6 +220,10 @@ class PermilleCounter : public StatsCounter { // |<------ 2 sec ------->| | // GetMetric | (5 + 1 + 6) / 2 | (5 + 5) / 2 | // +// |include_empty_intervals|: If set, intervals without samples will be included +// in the stats. The value for an interval is +// determined by GetValueForEmptyInterval(). +// class RateCounter : public StatsCounter { public: RateCounter(Clock* clock, @@ -203,6 +235,7 @@ class RateCounter : public StatsCounter { private: bool GetMetric(int* metric) const override; + int GetValueForEmptyInterval() const override; // Returns zero. RTC_DISALLOW_COPY_AND_ASSIGN(RateCounter); }; @@ -214,6 +247,10 @@ class RateCounter : public StatsCounter { // |<------ 2 sec ------->| | // GetMetric | 8 / 2 | (13 - 8) / 2 | // +// |include_empty_intervals|: If set, intervals without samples will be included +// in the stats. The value for an interval is +// determined by GetValueForEmptyInterval(). +// class RateAccCounter : public StatsCounter { public: RateAccCounter(Clock* clock, @@ -225,6 +262,7 @@ class RateAccCounter : public StatsCounter { private: bool GetMetric(int* metric) const override; + int GetValueForEmptyInterval() const override; // Returns zero. RTC_DISALLOW_COPY_AND_ASSIGN(RateAccCounter); }; diff --git a/webrtc/video/stats_counter_unittest.cc b/webrtc/video/stats_counter_unittest.cc index d054eaa4b2..740e99effb 100644 --- a/webrtc/video/stats_counter_unittest.cc +++ b/webrtc/video/stats_counter_unittest.cc @@ -58,14 +58,14 @@ class StatsCounterTest : public ::testing::Test { }; TEST_F(StatsCounterTest, NoSamples) { - AvgCounter counter(&clock_, nullptr); + AvgCounter counter(&clock_, nullptr, false); VerifyStatsIsNotSet(counter.GetStats()); } TEST_F(StatsCounterTest, TestRegisterObserver) { StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); const int kSample = 22; - AvgCounter counter(&clock_, observer); + AvgCounter counter(&clock_, observer, false); AddSampleAndAdvance(kSample, kProcessIntervalMs, &counter); // Trigger process (sample included in next interval). counter.Add(111); @@ -73,7 +73,7 @@ TEST_F(StatsCounterTest, TestRegisterObserver) { } TEST_F(StatsCounterTest, HasSample) { - AvgCounter counter(&clock_, nullptr); + AvgCounter counter(&clock_, nullptr, false); EXPECT_FALSE(counter.HasSample()); counter.Add(1); EXPECT_TRUE(counter.HasSample()); @@ -81,7 +81,7 @@ TEST_F(StatsCounterTest, HasSample) { TEST_F(StatsCounterTest, VerifyProcessInterval) { StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); - AvgCounter counter(&clock_, observer); + AvgCounter counter(&clock_, observer, false); counter.Add(4); clock_.AdvanceTimeMilliseconds(kProcessIntervalMs - 1); // Try trigger process (interval has not passed). @@ -101,7 +101,7 @@ TEST_F(StatsCounterTest, VerifyProcessInterval) { TEST_F(StatsCounterTest, TestMetric_AvgCounter) { StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); - AvgCounter counter(&clock_, observer); + AvgCounter counter(&clock_, observer, false); counter.Add(4); counter.Add(8); counter.Add(9); @@ -213,7 +213,7 @@ TEST_F(StatsCounterTest, TestMetric_RateAccCounter) { } TEST_F(StatsCounterTest, TestGetStats_MultipleIntervals) { - AvgCounter counter(&clock_, nullptr); + AvgCounter counter(&clock_, nullptr, false); const int kSample1 = 1; const int kSample2 = 5; const int kSample3 = 8; @@ -236,7 +236,7 @@ TEST_F(StatsCounterTest, TestGetStats_MultipleIntervals) { TEST_F(StatsCounterTest, TestGetStatsTwice) { const int kSample1 = 4; const int kSample2 = 7; - AvgCounter counter(&clock_, nullptr); + AvgCounter counter(&clock_, nullptr, false); AddSampleAndAdvance(kSample1, kProcessIntervalMs, &counter); // Trigger process (sample included in next interval). counter.Add(kSample2); @@ -277,29 +277,63 @@ TEST_F(StatsCounterTest, TestRateAccCounter_NegativeRateIgnored) { EXPECT_EQ(200, stats.average); } -TEST_F(StatsCounterTest, TestAvgCounter_IntervalsWithoutSamplesIgnored) { +TEST_F(StatsCounterTest, TestAvgCounter_IntervalsWithoutSamplesIncluded) { + // Samples: | 6 | x | x | 8 | // x: empty interval + // Stats: | 6 | 6 | 6 | 8 | // x -> last value reported StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); - AvgCounter counter(&clock_, observer); + AvgCounter counter(&clock_, observer, true); 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); + // [6:3], 3 intervals passed (2 without samples -> last value reported). + AggregatedStats stats = counter.ProcessAndGetStats(); + EXPECT_EQ(3, stats.num_samples); EXPECT_EQ(6, stats.min); - EXPECT_EQ(8, stats.max); + EXPECT_EQ(6, stats.max); + // Make next interval pass and verify stats: [6:3],[8:1] + clock_.AdvanceTimeMilliseconds(1); + counter.ProcessAndGetStats(); + EXPECT_EQ(4, observer->num_calls_); + EXPECT_EQ(8, observer->last_sample_); +} + +TEST_F(StatsCounterTest, TestAvgCounter_WithPause) { + // Samples: | 6 | x | x | x | - | 22 | x | // x: empty interval, -: paused + // Stats: | 6 | 6 | 6 | 6 | - | 22 | 22 | // x -> last value reported + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + AvgCounter counter(&clock_, observer, true); + // Add sample and advance 3 intervals (2 w/o samples -> last value reported). + AddSampleAndAdvance(6, kProcessIntervalMs * 4 - 1, &counter); + // Trigger process and verify stats: [6:3] + counter.ProcessAndGetStats(); + EXPECT_EQ(3, observer->num_calls_); + EXPECT_EQ(6, observer->last_sample_); + // Make next interval pass (1 without samples). + // Process and pause. Verify stats: [6:4]. + clock_.AdvanceTimeMilliseconds(1); + counter.ProcessAndPause(); + EXPECT_EQ(4, observer->num_calls_); // Last value reported. + EXPECT_EQ(6, observer->last_sample_); + // Make next interval pass (1 without samples -> ignored while paused). + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs * 2 - 1); + counter.Add(22); // Stops pause. + EXPECT_EQ(4, observer->num_calls_); + EXPECT_EQ(6, observer->last_sample_); + // Make next interval pass, [6:4][22:1] + clock_.AdvanceTimeMilliseconds(1); + counter.ProcessAndGetStats(); + EXPECT_EQ(5, observer->num_calls_); + EXPECT_EQ(22, observer->last_sample_); + // Make 1 interval pass (1 w/o samples -> pause stopped, last value reported). + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + counter.ProcessAndGetStats(); + EXPECT_EQ(6, observer->num_calls_); + EXPECT_EQ(22, observer->last_sample_); } TEST_F(StatsCounterTest, TestRateCounter_IntervalsWithoutSamplesIgnored) { + // Samples: | 50 | x | 20 | // x: empty interval + // Stats: | 25 | x | 10 | // x -> ignored const bool kIncludeEmptyIntervals = false; StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); const int kSample1 = 50; // 50 / 2 sec @@ -309,23 +343,19 @@ TEST_F(StatsCounterTest, TestRateCounter_IntervalsWithoutSamplesIgnored) { clock_.AdvanceTimeMilliseconds(kProcessIntervalMs * 3 - 1); // Trigger process (sample included in next interval). counter.Add(kSample2); - // [25:1], one interval without samples passed. + // [25:1], 2 intervals passed (1 without samples -> ignored). EXPECT_EQ(1, observer->num_calls_); EXPECT_EQ(25, observer->last_sample_); - // Make last interval pass. + // Make next interval pass and verify stats: [10:1],[25:1] clock_.AdvanceTimeMilliseconds(1); - counter.Add(111); // Trigger process (sample included in next interval). - // [10:1],[25:1] + counter.ProcessAndGetStats(); EXPECT_EQ(2, observer->num_calls_); EXPECT_EQ(10, observer->last_sample_); - // Aggregated stats. - AggregatedStats stats = counter.GetStats(); - EXPECT_EQ(2, stats.num_samples); - EXPECT_EQ(10, stats.min); - EXPECT_EQ(25, stats.max); } TEST_F(StatsCounterTest, TestRateCounter_IntervalsWithoutSamplesIncluded) { + // Samples: | 50 | x | 20 | // x: empty interval + // Stats: | 25 | 0 | 10 | // x -> zero reported const bool kIncludeEmptyIntervals = true; StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); const int kSample1 = 50; // 50 / 2 sec @@ -335,20 +365,76 @@ TEST_F(StatsCounterTest, TestRateCounter_IntervalsWithoutSamplesIncluded) { clock_.AdvanceTimeMilliseconds(kProcessIntervalMs * 3 - 1); // Trigger process (sample included in next interval). counter.Add(kSample2); - // [0:1],[25:1], one interval without samples passed. + // [0:1],[25:1], 2 intervals passed (1 without samples -> zero reported). EXPECT_EQ(2, observer->num_calls_); - EXPECT_EQ(25, observer->last_sample_); - // Make last interval pass. + EXPECT_EQ(0, observer->last_sample_); + // Make last interval pass and verify stats: [0:1],[10:1],[25:1] clock_.AdvanceTimeMilliseconds(1); - counter.Add(111); // Trigger process (sample included in next interval). - // [0:1],[10:1],[25:1] + AggregatedStats stats = counter.ProcessAndGetStats(); + EXPECT_EQ(25, stats.max); 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); +} + +TEST_F(StatsCounterTest, TestRateAccCounter_IntervalsWithoutSamplesIncluded) { + // Samples: | 12 | x | x | x | 60 | // x: empty interval + // Stats: | 6 | 0 | 0 | 0 | 24 | // x -> zero reported + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + RateAccCounter counter(&clock_, observer, true); + VerifyStatsIsNotSet(counter.ProcessAndGetStats()); + // Advance one interval and verify stats. + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs); + VerifyStatsIsNotSet(counter.ProcessAndGetStats()); + // Add sample and advance 3 intervals (2 w/o samples -> zero reported). + counter.Set(12); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs * 4 - 1); + // Trigger process and verify stats: [0:2][6:1] + counter.ProcessAndGetStats(); + EXPECT_EQ(3, observer->num_calls_); + EXPECT_EQ(0, observer->last_sample_); + // Make next interval pass (1 w/o samples -> zero reported), [0:3][6:1] + clock_.AdvanceTimeMilliseconds(1); + counter.ProcessAndGetStats(); + EXPECT_EQ(4, observer->num_calls_); + EXPECT_EQ(0, observer->last_sample_); + // Insert sample and advance non-complete interval, no change, [0:3][6:1] + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs - 1); + counter.Set(60); + EXPECT_EQ(4, observer->num_calls_); + // Make next interval pass, [0:3][6:1][24:1] + clock_.AdvanceTimeMilliseconds(1); + AggregatedStats stats = counter.ProcessAndGetStats(); + EXPECT_EQ(5, observer->num_calls_); + EXPECT_EQ(24, observer->last_sample_); + EXPECT_EQ(6, stats.average); +} + +TEST_F(StatsCounterTest, TestRateAccCounter_IntervalsWithoutSamplesIgnored) { + // Samples: | 12 | x | x | x | 60 | // x: empty interval + // Stats: | 6 | x | x | x | 24 | // x -> ignored + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + RateAccCounter counter(&clock_, observer, false); + // Add sample and advance 3 intervals (2 w/o samples -> ignored). + counter.Set(12); + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs * 4 - 1); + // Trigger process and verify stats: [6:1] + counter.ProcessAndGetStats(); + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(6, observer->last_sample_); + // Make next interval pass (1 w/o samples -> ignored), [6:1] + clock_.AdvanceTimeMilliseconds(1); + counter.ProcessAndGetStats(); + EXPECT_EQ(1, observer->num_calls_); + // Insert sample and advance non-complete interval, no change, [6:1] + clock_.AdvanceTimeMilliseconds(kProcessIntervalMs - 1); + counter.Set(60); + counter.ProcessAndGetStats(); + EXPECT_EQ(1, observer->num_calls_); + // Make next interval pass, [6:1][24:1] + clock_.AdvanceTimeMilliseconds(1); + counter.ProcessAndGetStats(); + EXPECT_EQ(2, observer->num_calls_); + EXPECT_EQ(24, observer->last_sample_); } } // namespace webrtc