/* * 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 "video/stats_counter.h" #include #include #include #include "rtc_base/checks.h" #include "rtc_base/strings/string_builder.h" #include "system_wrappers/include/clock.h" namespace webrtc { namespace { // Default periodic time interval for processing samples. const int64_t kDefaultProcessIntervalMs = 2000; const uint32_t kStreamId0 = 0; } // namespace std::string AggregatedStats::ToString() const { return ToStringWithMultiplier(1); } std::string AggregatedStats::ToStringWithMultiplier(int multiplier) const { rtc::StringBuilder ss; ss << "periodic_samples:" << num_samples << ", {"; ss << "min:" << (min * multiplier) << ", "; ss << "avg:" << (average * multiplier) << ", "; ss << "max:" << (max * multiplier) << "}"; return ss.Release(); } // Class holding periodically computed metrics. class AggregatedCounter { public: AggregatedCounter() : last_sample_(0), sum_samples_(0) {} ~AggregatedCounter() {} void Add(int sample) { last_sample_ = sample; sum_samples_ += 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_; } 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_samples_ + stats_.num_samples / 2) / stats_.num_samples; } int last_sample_; int64_t sum_samples_; 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(int64_t sample, uint32_t stream_id) { samples_[stream_id].Set(sample); ++total_count_; } void SetLast(int64_t sample, uint32_t stream_id) { samples_[stream_id].SetLast(sample); } int64_t GetLast(uint32_t stream_id) { return samples_[stream_id].GetLast(); } 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(int64_t sample) { sum_ = sample; ++count_; } void SetLast(int64_t sample) { last_sum_ = sample; } int64_t GetLast() const { return last_sum_; } 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) : include_empty_intervals_(include_empty_intervals), process_intervals_ms_(process_intervals_ms), aggregated_counter_(new AggregatedCounter()), samples_(new Samples()), clock_(clock), observer_(observer), last_process_time_ms_(-1), paused_(false), pause_time_ms_(-1), min_pause_time_ms_(0) { RTC_DCHECK_GT(process_intervals_ms_, 0); } StatsCounter::~StatsCounter() {} AggregatedStats StatsCounter::GetStats() { return aggregated_counter_->ComputeStats(); } AggregatedStats StatsCounter::ProcessAndGetStats() { if (HasSample()) TryProcess(); return aggregated_counter_->ComputeStats(); } void StatsCounter::ProcessAndPauseForDuration(int64_t min_pause_time_ms) { ProcessAndPause(); min_pause_time_ms_ = min_pause_time_ms; } void StatsCounter::ProcessAndPause() { if (HasSample()) TryProcess(); paused_ = true; pause_time_ms_ = clock_->TimeInMilliseconds(); } void StatsCounter::ProcessAndStopPause() { if (HasSample()) TryProcess(); Resume(); } bool StatsCounter::HasSample() const { return last_process_time_ms_ != -1; } bool StatsCounter::TimeToProcess(int* elapsed_intervals) { 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 < process_intervals_ms_) return false; // Advance number of complete `process_intervals_ms_` that have passed. int64_t num_intervals = diff_ms / process_intervals_ms_; last_process_time_ms_ += num_intervals * process_intervals_ms_; *elapsed_intervals = num_intervals; return true; } void StatsCounter::Add(int sample) { TryProcess(); samples_->Add(sample, kStreamId0); ResumeIfMinTimePassed(); } void StatsCounter::Set(int64_t sample, uint32_t stream_id) { if (paused_ && sample == samples_->GetLast(stream_id)) { // Do not add same sample while paused (will reset pause). return; } TryProcess(); samples_->Set(sample, stream_id); ResumeIfMinTimePassed(); } void StatsCounter::SetLast(int64_t sample, uint32_t stream_id) { RTC_DCHECK(!HasSample()) << "Should be set before first sample is added."; samples_->SetLast(sample, stream_id); } // 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() { int elapsed_intervals; if (!TimeToProcess(&elapsed_intervals)) return; // Get and report periodically computed metric. int 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 = samples_->Empty() ? elapsed_intervals : (elapsed_intervals - 1); ReportMetricToAggregatedCounter(GetValueForEmptyInterval(), empty_intervals); } // Reset samples for elapsed interval. samples_->Reset(); } bool StatsCounter::IncludeEmptyIntervals() const { return include_empty_intervals_ && !paused_ && !aggregated_counter_->Empty(); } void StatsCounter::ResumeIfMinTimePassed() { if (paused_ && (clock_->TimeInMilliseconds() - pause_time_ms_) >= min_pause_time_ms_) { Resume(); } } void StatsCounter::Resume() { paused_ = false; min_pause_time_ms_ = 0; } // StatsCounter sub-classes. AvgCounter::AvgCounter(Clock* clock, StatsCounterObserver* observer, bool include_empty_intervals) : StatsCounter(clock, kDefaultProcessIntervalMs, include_empty_intervals, observer) {} void AvgCounter::Add(int sample) { StatsCounter::Add(sample); } bool AvgCounter::GetMetric(int* metric) const { int64_t count = samples_->Count(); if (count == 0) return false; *metric = (samples_->Sum() + count / 2) / count; return true; } int AvgCounter::GetValueForEmptyInterval() const { return aggregated_counter_->last_sample(); } MaxCounter::MaxCounter(Clock* clock, StatsCounterObserver* observer, int64_t process_intervals_ms) : StatsCounter(clock, process_intervals_ms, false, // `include_empty_intervals` observer) {} void MaxCounter::Add(int sample) { StatsCounter::Add(sample); } bool MaxCounter::GetMetric(int* metric) const { if (samples_->Empty()) return false; *metric = samples_->Max(); return true; } int MaxCounter::GetValueForEmptyInterval() const { RTC_DCHECK_NOTREACHED(); return 0; } PercentCounter::PercentCounter(Clock* clock, StatsCounterObserver* observer) : StatsCounter(clock, kDefaultProcessIntervalMs, false, // `include_empty_intervals` observer) {} void PercentCounter::Add(bool sample) { StatsCounter::Add(sample ? 1 : 0); } bool PercentCounter::GetMetric(int* metric) const { int64_t count = samples_->Count(); if (count == 0) return false; *metric = (samples_->Sum() * 100 + count / 2) / count; return true; } int PercentCounter::GetValueForEmptyInterval() const { RTC_DCHECK_NOTREACHED(); return 0; } PermilleCounter::PermilleCounter(Clock* clock, StatsCounterObserver* observer) : StatsCounter(clock, kDefaultProcessIntervalMs, false, // `include_empty_intervals` observer) {} void PermilleCounter::Add(bool sample) { StatsCounter::Add(sample ? 1 : 0); } bool PermilleCounter::GetMetric(int* metric) const { int64_t count = samples_->Count(); if (count == 0) return false; *metric = (samples_->Sum() * 1000 + count / 2) / count; return true; } int PermilleCounter::GetValueForEmptyInterval() const { RTC_DCHECK_NOTREACHED(); return 0; } RateCounter::RateCounter(Clock* clock, StatsCounterObserver* observer, bool include_empty_intervals) : StatsCounter(clock, kDefaultProcessIntervalMs, include_empty_intervals, observer) {} void RateCounter::Add(int sample) { StatsCounter::Add(sample); } bool RateCounter::GetMetric(int* metric) const { if (samples_->Empty()) return false; *metric = (samples_->Sum() * 1000 + process_intervals_ms_ / 2) / process_intervals_ms_; return true; } int RateCounter::GetValueForEmptyInterval() const { return 0; } RateAccCounter::RateAccCounter(Clock* clock, StatsCounterObserver* observer, bool include_empty_intervals) : StatsCounter(clock, kDefaultProcessIntervalMs, include_empty_intervals, observer) {} void RateAccCounter::Set(int64_t sample, uint32_t stream_id) { StatsCounter::Set(sample, stream_id); } void RateAccCounter::SetLast(int64_t sample, uint32_t stream_id) { StatsCounter::SetLast(sample, stream_id); } bool RateAccCounter::GetMetric(int* metric) const { int64_t diff = samples_->Diff(); if (diff < 0 || (!include_empty_intervals_ && diff == 0)) return false; *metric = (diff * 1000 + process_intervals_ms_ / 2) / process_intervals_ms_; return true; } int RateAccCounter::GetValueForEmptyInterval() const { return 0; } } // namespace webrtc