| /* |
| * 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 <algorithm> |
| #include <limits> |
| #include <map> |
| |
| #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<int>::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<int>::min(); |
| } |
| |
| int max_ = std::numeric_limits<int>::min(); |
| int64_t count_ = 0; |
| int64_t sum_ = 0; |
| int64_t last_sum_ = 0; |
| }; |
| |
| int64_t total_count_; |
| std::map<uint32_t, Stats> 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_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_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_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 |