diff options
Diffstat (limited to 'third_party/libwebrtc/video/stats_counter.cc')
-rw-r--r-- | third_party/libwebrtc/video/stats_counter.cc | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/stats_counter.cc b/third_party/libwebrtc/video/stats_counter.cc new file mode 100644 index 0000000000..dc548ea3c3 --- /dev/null +++ b/third_party/libwebrtc/video/stats_counter.cc @@ -0,0 +1,463 @@ +/* + * 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_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 |