diff --git a/webrtc/video/stats_counter.cc b/webrtc/video/stats_counter.cc index f1b82e8f57..9923ed20d7 100644 --- a/webrtc/video/stats_counter.cc +++ b/webrtc/video/stats_counter.cc @@ -11,6 +11,8 @@ #include "webrtc/video/stats_counter.h" #include +#include +#include #include "webrtc/base/checks.h" #include "webrtc/system_wrappers/include/clock.h" @@ -20,6 +22,7 @@ namespace webrtc { namespace { // Default periodic time interval for processing samples. const int64_t kDefaultProcessIntervalMs = 2000; +const uint32_t kStreamId0 = 0; } // namespace // Class holding periodically computed metrics. @@ -62,19 +65,98 @@ class AggregatedCounter { AggregatedStats stats_; }; +// Class holding gathered samples within a process interval. +class Samples { + public: + Samples() : total_count_(0) {} + ~Samples() {} + + void Add(int sample, uint32_t stream_id) { + samples_[stream_id].Add(sample); + ++total_count_; + } + void Set(int sample, uint32_t stream_id) { + samples_[stream_id].Set(sample); + ++total_count_; + } + + int64_t Count() const { return total_count_; } + bool Empty() const { return total_count_ == 0; } + + int64_t Sum() const { + int64_t sum = 0; + for (const auto& it : samples_) + sum += it.second.sum_; + return sum; + } + + int Max() const { + int max = std::numeric_limits::min(); + for (const auto& it : samples_) + max = std::max(it.second.max_, max); + return max; + } + + void Reset() { + for (auto& it : samples_) + it.second.Reset(); + total_count_ = 0; + } + + int64_t Diff() const { + int64_t sum_diff = 0; + int count = 0; + for (const auto& it : samples_) { + if (it.second.count_ > 0) { + int64_t diff = it.second.sum_ - it.second.last_sum_; + if (diff >= 0) { + sum_diff += diff; + ++count; + } + } + } + return (count > 0) ? sum_diff : -1; + } + + private: + struct Stats { + void Add(int sample) { + sum_ += sample; + ++count_; + max_ = std::max(sample, max_); + } + void Set(int sample) { + sum_ = sample; + ++count_; + } + void Reset() { + if (count_ > 0) + last_sum_ = sum_; + sum_ = 0; + count_ = 0; + max_ = std::numeric_limits::min(); + } + + int max_ = std::numeric_limits::min(); + int64_t count_ = 0; + int64_t sum_ = 0; + int64_t last_sum_ = 0; + }; + + int64_t total_count_; + std::map samples_; // Gathered samples mapped by stream id. +}; + // StatsCounter class. StatsCounter::StatsCounter(Clock* clock, int64_t process_intervals_ms, bool include_empty_intervals, StatsCounterObserver* observer) - : max_(0), - sum_(0), - num_samples_(0), - last_sum_(0), - aggregated_counter_(new AggregatedCounter()), + : include_empty_intervals_(include_empty_intervals), process_intervals_ms_(process_intervals_ms), + aggregated_counter_(new AggregatedCounter()), + samples_(new Samples()), clock_(clock), - include_empty_intervals_(include_empty_intervals), observer_(observer), last_process_time_ms_(-1), paused_(false) { @@ -120,21 +202,15 @@ bool StatsCounter::TimeToProcess(int* elapsed_intervals) { return true; } -void StatsCounter::Set(int sample) { +void StatsCounter::Set(int sample, uint32_t stream_id) { TryProcess(); - ++num_samples_; - sum_ = sample; + samples_->Set(sample, stream_id); paused_ = false; } void StatsCounter::Add(int sample) { TryProcess(); - ++num_samples_; - sum_ += sample; - - if (num_samples_ == 1) - max_ = sample; - max_ = std::max(sample, max_); + samples_->Add(sample, kStreamId0); paused_ = false; } @@ -164,17 +240,13 @@ void StatsCounter::TryProcess() { // 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); + samples_->Empty() ? elapsed_intervals : (elapsed_intervals - 1); ReportMetricToAggregatedCounter(GetValueForEmptyInterval(), empty_intervals); } // Reset samples for elapsed interval. - if (num_samples_ > 0) - last_sum_ = sum_; - sum_ = 0; - max_ = 0; - num_samples_ = 0; + samples_->Reset(); } bool StatsCounter::IncludeEmptyIntervals() const { @@ -195,9 +267,11 @@ void AvgCounter::Add(int sample) { } bool AvgCounter::GetMetric(int* metric) const { - if (num_samples_ == 0) + int64_t count = samples_->Count(); + if (count == 0) return false; - *metric = (sum_ + num_samples_ / 2) / num_samples_; + + *metric = (samples_->Sum() + count / 2) / count; return true; } @@ -218,9 +292,10 @@ void MaxCounter::Add(int sample) { } bool MaxCounter::GetMetric(int* metric) const { - if (num_samples_ == 0) + if (samples_->Empty()) return false; - *metric = max_; + + *metric = samples_->Max(); return true; } @@ -240,9 +315,11 @@ void PercentCounter::Add(bool sample) { } bool PercentCounter::GetMetric(int* metric) const { - if (num_samples_ == 0) + int64_t count = samples_->Count(); + if (count == 0) return false; - *metric = (sum_ * 100 + num_samples_ / 2) / num_samples_; + + *metric = (samples_->Sum() * 100 + count / 2) / count; return true; } @@ -262,9 +339,11 @@ void PermilleCounter::Add(bool sample) { } bool PermilleCounter::GetMetric(int* metric) const { - if (num_samples_ == 0) + int64_t count = samples_->Count(); + if (count == 0) return false; - *metric = (sum_ * 1000 + num_samples_ / 2) / num_samples_; + + *metric = (samples_->Sum() * 1000 + count / 2) / count; return true; } @@ -286,9 +365,11 @@ void RateCounter::Add(int sample) { } bool RateCounter::GetMetric(int* metric) const { - if (num_samples_ == 0) + if (samples_->Empty()) return false; - *metric = (sum_ * 1000 + process_intervals_ms_ / 2) / process_intervals_ms_; + + *metric = (samples_->Sum() * 1000 + process_intervals_ms_ / 2) / + process_intervals_ms_; return true; } @@ -304,15 +385,16 @@ RateAccCounter::RateAccCounter(Clock* clock, include_empty_intervals, observer) {} -void RateAccCounter::Set(int sample) { - StatsCounter::Set(sample); +void RateAccCounter::Set(int sample, uint32_t stream_id) { + StatsCounter::Set(sample, stream_id); } bool RateAccCounter::GetMetric(int* metric) const { - if (num_samples_ == 0 || last_sum_ > sum_) + int64_t diff = samples_->Diff(); + if (diff < 0 || (!include_empty_intervals_ && diff == 0)) return false; - *metric = ((sum_ - last_sum_) * 1000 + process_intervals_ms_ / 2) / - process_intervals_ms_; + + *metric = (diff * 1000 + process_intervals_ms_ / 2) / process_intervals_ms_; return true; } diff --git a/webrtc/video/stats_counter.h b/webrtc/video/stats_counter.h index c6e6151933..08663f8015 100644 --- a/webrtc/video/stats_counter.h +++ b/webrtc/video/stats_counter.h @@ -20,6 +20,7 @@ namespace webrtc { class AggregatedCounter; class Clock; +class Samples; // |StatsCounterObserver| is called periodically when a metric is updated. class StatsCounterObserver { @@ -104,15 +105,12 @@ class StatsCounter { StatsCounterObserver* observer); void Add(int sample); - void Set(int sample); + void Set(int sample, uint32_t stream_id); - int max_; - int64_t sum_; - int64_t num_samples_; - int64_t last_sum_; - - const std::unique_ptr aggregated_counter_; + const bool include_empty_intervals_; const int64_t process_intervals_ms_; + const std::unique_ptr aggregated_counter_; + const std::unique_ptr samples_; private: bool TimeToProcess(int* num_elapsed_intervals); @@ -121,7 +119,6 @@ class StatsCounter { bool IncludeEmptyIntervals() const; Clock* const clock_; - const bool include_empty_intervals_; const std::unique_ptr observer_; int64_t last_process_time_ms_; bool paused_; @@ -262,7 +259,7 @@ class RateAccCounter : public StatsCounter { bool include_empty_intervals); ~RateAccCounter() override {} - void Set(int sample); + void Set(int sample, uint32_t stream_id); private: bool GetMetric(int* metric) const override; diff --git a/webrtc/video/stats_counter_unittest.cc b/webrtc/video/stats_counter_unittest.cc index 3c9633a8db..7484c18a89 100644 --- a/webrtc/video/stats_counter_unittest.cc +++ b/webrtc/video/stats_counter_unittest.cc @@ -16,6 +16,7 @@ namespace webrtc { namespace { const int kDefaultProcessIntervalMs = 2000; +const uint32_t kStreamId = 123456; class StatsCounterObserverImpl : public StatsCounterObserver { public: @@ -42,7 +43,7 @@ class StatsCounterTest : public ::testing::Test { void SetSampleAndAdvance(int sample, int interval_ms, RateAccCounter* counter) { - counter->Set(sample); + counter->Set(sample, kStreamId); clock_.AdvanceTimeMilliseconds(interval_ms); } @@ -197,11 +198,11 @@ TEST_F(StatsCounterTest, TestMetric_RateCounter) { TEST_F(StatsCounterTest, TestMetric_RateAccCounter) { StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); RateAccCounter counter(&clock_, observer, true); - counter.Set(175); - counter.Set(188); + counter.Set(175, kStreamId); + counter.Set(188, kStreamId); clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs); // Trigger process (sample included in next interval). - counter.Set(192); + counter.Set(192, kStreamId); // Rate per interval: (188 - 0) / 2 sec = 94 samples/sec EXPECT_EQ(1, observer->num_calls_); EXPECT_EQ(94, observer->last_sample_); @@ -212,6 +213,37 @@ TEST_F(StatsCounterTest, TestMetric_RateAccCounter) { EXPECT_EQ(94, stats.max); } +TEST_F(StatsCounterTest, TestMetric_RateAccCounterWithMultipleStreamIds) { + StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); + RateAccCounter counter(&clock_, observer, true); + counter.Set(175, kStreamId); + counter.Set(188, kStreamId); + counter.Set(100, kStreamId + 1); + clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Set(150, kStreamId + 1); + // Rate per interval: ((188 - 0) + (100 - 0)) / 2 sec = 144 samples/sec + EXPECT_EQ(1, observer->num_calls_); + EXPECT_EQ(144, observer->last_sample_); + clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Set(198, kStreamId); + // Rate per interval: (0 + (150 - 100)) / 2 sec = 25 samples/sec + EXPECT_EQ(2, observer->num_calls_); + EXPECT_EQ(25, observer->last_sample_); + clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs); + // Trigger process (sample included in next interval). + counter.Set(200, kStreamId); + // Rate per interval: ((198 - 188) + (0)) / 2 sec = 5 samples/sec + EXPECT_EQ(3, observer->num_calls_); + EXPECT_EQ(5, observer->last_sample_); + // Aggregated stats. + AggregatedStats stats = counter.GetStats(); + EXPECT_EQ(3, stats.num_samples); + EXPECT_EQ(5, stats.min); + EXPECT_EQ(144, stats.max); +} + TEST_F(StatsCounterTest, TestGetStats_MultipleIntervals) { AvgCounter counter(&clock_, nullptr, false); const int kSample1 = 1; @@ -266,7 +298,7 @@ TEST_F(StatsCounterTest, TestRateAccCounter_NegativeRateIgnored) { EXPECT_EQ(1, observer->num_calls_); EXPECT_EQ(100, observer->last_sample_); // Trigger process (sample included in next interval). - counter.Set(2000); + counter.Set(2000, kStreamId); EXPECT_EQ(2, observer->num_calls_); EXPECT_EQ(300, observer->last_sample_); // Aggregated stats. @@ -386,7 +418,7 @@ TEST_F(StatsCounterTest, TestRateAccCounter_IntervalsWithoutSamplesIncluded) { clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs); VerifyStatsIsNotSet(counter.ProcessAndGetStats()); // Add sample and advance 3 intervals (2 w/o samples -> zero reported). - counter.Set(12); + counter.Set(12, kStreamId); clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs * 4 - 1); // Trigger process and verify stats: [0:2][6:1] counter.ProcessAndGetStats(); @@ -399,7 +431,7 @@ TEST_F(StatsCounterTest, TestRateAccCounter_IntervalsWithoutSamplesIncluded) { EXPECT_EQ(0, observer->last_sample_); // Insert sample and advance non-complete interval, no change, [0:3][6:1] clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs - 1); - counter.Set(60); + counter.Set(60, kStreamId); EXPECT_EQ(4, observer->num_calls_); // Make next interval pass, [0:3][6:1][24:1] clock_.AdvanceTimeMilliseconds(1); @@ -415,7 +447,7 @@ TEST_F(StatsCounterTest, TestRateAccCounter_IntervalsWithoutSamplesIgnored) { StatsCounterObserverImpl* observer = new StatsCounterObserverImpl(); RateAccCounter counter(&clock_, observer, false); // Add sample and advance 3 intervals (2 w/o samples -> ignored). - counter.Set(12); + counter.Set(12, kStreamId); clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs * 4 - 1); // Trigger process and verify stats: [6:1] counter.ProcessAndGetStats(); @@ -427,7 +459,7 @@ TEST_F(StatsCounterTest, TestRateAccCounter_IntervalsWithoutSamplesIgnored) { EXPECT_EQ(1, observer->num_calls_); // Insert sample and advance non-complete interval, no change, [6:1] clock_.AdvanceTimeMilliseconds(kDefaultProcessIntervalMs - 1); - counter.Set(60); + counter.Set(60, kStreamId); counter.ProcessAndGetStats(); EXPECT_EQ(1, observer->num_calls_); // Make next interval pass, [6:1][24:1]