Index: webrtc/video/stats_counter.cc |
diff --git a/webrtc/video/stats_counter.cc b/webrtc/video/stats_counter.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0d5bd41f0f4b7d8ffdb7a0036dac65ebe2ac5d45 |
--- /dev/null |
+++ b/webrtc/video/stats_counter.cc |
@@ -0,0 +1,336 @@ |
+/* |
+ * 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 "webrtc/video/stats_counter.h" |
+ |
+#include <algorithm> |
+ |
+#include "webrtc/base/checks.h" |
+#include "webrtc/base/logging.h" |
+#include "webrtc/system_wrappers/include/clock.h" |
+ |
+namespace webrtc { |
+ |
+namespace { |
+// Periodic time interval for processing samples. |
+const int64_t kProcessIntervalMs = 2000; |
+ |
+// Limit for the maximum number of buckets to use. |
+const size_t kMaxBuckets = 250; |
+} // namespace |
+ |
+StatsCounter::Histogram::Stats::Stats() |
+ : num_samples(0), |
+ min(-1), |
+ max(-1), |
+ average(-1), |
+ percentile10(-1), |
+ percentile50(-1), |
+ percentile90(-1) {} |
+ |
+// Histogram class. |
+StatsCounter::Histogram::Histogram(size_t bucket_count, size_t bucket_max) |
+ : bucket_count_(bucket_count), |
+ bucket_max_(bucket_max), |
+ bucket_size1_(bucket_count == bucket_max), |
stefan-webrtc
2016/01/29 15:22:46
I don't think I fully understand what this variabl
|
+ sum_(0) { |
+ RTC_CHECK_GT(kProcessIntervalMs, 0); |
+ RTC_CHECK_GT(bucket_count, 0u); |
+ RTC_CHECK_GT(bucket_max, 0u); |
+ RTC_CHECK_LE(bucket_count, bucket_max); |
+ InitializeBuckets(); |
+} |
+ |
+void StatsCounter::Histogram::InitializeBuckets() { |
+ if (bucket_size1_) { |
+ // Buckets created when needed. |
+ return; |
+ } |
+ RTC_CHECK_LE(bucket_count_, kMaxBuckets); |
+ double log_max = log(static_cast<double>(bucket_max_)); |
+ buckets_[0] = 0; // [0, bucket min) |
+ size_t cur = 1; // bucket min |
+ buckets_[cur] = 0; // [bucket min,...) |
+ for (size_t i = 2; i < bucket_count_; ++i) { |
+ // cur * x^(remaining buckets) = max |
+ double log_cur = log(static_cast<double>(cur)); |
+ double log_x = (log_max - log_cur) / (bucket_count_ - i); |
+ double log_next = log_cur + log_x; |
stefan-webrtc
2016/01/29 15:22:46
I'm trying to follow this and I have a bit of a ha
|
+ size_t next = static_cast<size_t>(exp(log_next) + 0.5); |
+ cur = (next > cur) ? next : (cur + 1); |
+ buckets_[cur] = 0; // [cur,...) |
+ } |
+ RTC_DCHECK(buckets_.rbegin()->first == 1 || |
+ buckets_.rbegin()->first == bucket_max_); |
+} |
+ |
+void StatsCounter::Histogram::Add(int sample) { |
+ sum_ += 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); |
+ |
+ if (sample < 0) |
+ sample = 0; |
+ |
+ AddToBucket(sample); |
+} |
+ |
+void StatsCounter::Histogram::AddToBucket(size_t sample) { |
+ if (sample > bucket_max_) |
+ sample = bucket_max_; |
+ |
+ if (bucket_size1_) { |
+ if (buckets_.size() == kMaxBuckets && |
+ buckets_.find(sample) == buckets_.end()) { |
+ LOG(LS_WARNING) << "Max buckets reached. Sample not added " << sample; |
+ return; |
+ } |
+ ++buckets_[sample]; |
+ return; |
+ } |
+ |
+ // Find bucket. |
+ RTC_DCHECK(buckets_.size() >= 2); |
+ auto it = buckets_.upper_bound(sample); |
+ --it; |
+ it->second++; |
+} |
+ |
+StatsCounter::Histogram::Stats StatsCounter::Histogram::stats() { |
+ Compute(); |
+ return stats_; |
+} |
+ |
+void StatsCounter::Histogram::Compute() { |
+ if (stats_.num_samples == 0) |
+ return; |
+ |
+ stats_.average = (sum_ + (stats_.num_samples / 2)) / stats_.num_samples; |
+ |
+ size_t sample10 = round(std::max(stats_.num_samples * 0.1f, 1.0f)); |
+ size_t sample50 = round(std::max(stats_.num_samples * 0.5f, 1.0f)); |
+ size_t sample90 = round(std::max(stats_.num_samples * 0.9f, 1.0f)); |
+ |
+ stats_.percentile10 = -1; |
+ stats_.percentile50 = -1; |
+ stats_.percentile90 = -1; |
+ |
+ size_t samples = 0; |
+ int last = 0; |
+ for (const auto bucket : buckets_) { |
+ int cur = static_cast<int>(bucket.first); |
+ samples += bucket.second; |
+ // 10th percentile. |
+ if (!bucket_size1_ && stats_.percentile10 == last) |
+ stats_.percentile10 += ((cur - last) / 2); |
+ if (samples >= sample10 && stats_.percentile10 == -1) |
+ stats_.percentile10 = cur; |
+ // 50th percentile. |
+ if (!bucket_size1_ && stats_.percentile50 == last) |
+ stats_.percentile50 += ((cur - last) / 2); |
+ if (samples >= sample50 && stats_.percentile50 == -1) |
+ stats_.percentile50 = cur; |
+ // 90th percentile. |
+ if (!bucket_size1_ && stats_.percentile90 == last) |
+ stats_.percentile90 += ((cur - last) / 2); |
+ if (samples >= sample90 && stats_.percentile90 == -1) |
+ stats_.percentile90 = cur; |
+ last = cur; |
+ } |
+ |
+ // Limit percentiles by max (mid bucket value used). |
+ if (stats_.max > 0) { |
+ stats_.percentile10 = std::min(stats_.percentile10, stats_.max); |
+ stats_.percentile50 = std::min(stats_.percentile50, stats_.max); |
+ stats_.percentile90 = std::min(stats_.percentile90, stats_.max); |
+ } |
+} |
+ |
+// StatsCounter class. |
+StatsCounter::StatsCounter(Clock* clock, |
+ size_t bucket_count, |
+ size_t bucket_max, |
+ bool include_empty_intervals, |
+ StatsCounterObserver* observer) |
+ : sum_(0), |
+ num_samples_(0), |
+ last_sum_(0), |
+ clock_(clock), |
+ include_empty_intervals_(include_empty_intervals), |
+ observer_(observer), |
+ last_process_time_ms_(-1), |
+ histogram_(bucket_count, bucket_max) {} |
+ |
+StatsCounter::Histogram::Stats StatsCounter::GetStats() { |
+ return histogram_.stats(); |
+} |
+ |
+bool StatsCounter::TimeToProcess() { |
+ 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 < kProcessIntervalMs) |
+ return false; |
+ |
+ // Advance number of complete kProcessIntervalMs that have passed. |
+ 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) { |
+ histogram_.Add(0); |
+ if (observer_) |
+ observer_->OnMetricUpdated(0); |
+ } |
+ } |
+ return true; |
+} |
+ |
+void StatsCounter::Set(int sample) { |
+ Process(); |
+ ++num_samples_; |
+ sum_ = sample; |
+} |
+ |
+void StatsCounter::Add(int sample) { |
+ Process(); |
+ ++num_samples_; |
+ sum_ += sample; |
+} |
+ |
+void StatsCounter::Process() { |
+ if (!TimeToProcess()) |
+ return; |
+ |
+ int metric; |
+ if (GetMetric(&metric)) { |
+ histogram_.Add(metric); |
+ if (observer_) |
+ observer_->OnMetricUpdated(metric); |
+ } |
+ last_sum_ = sum_; |
+ sum_ = 0; |
+ num_samples_ = 0; |
+} |
+ |
+// StatsCounter sub-classes. |
+AvgCounter::AvgCounter(Clock* clock, |
+ size_t bucket_count, |
+ size_t bucket_max, |
+ StatsCounterObserver* observer) |
+ : StatsCounter::StatsCounter(clock, |
+ bucket_count, |
+ bucket_max, |
+ false, // include_empty_intervals |
+ observer) {} |
+ |
+void AvgCounter::Add(int sample) { |
+ StatsCounter::Add(sample); |
+} |
+ |
+bool AvgCounter::GetMetric(int* metric) const { |
+ if (num_samples_ == 0) |
+ return false; |
+ *metric = (sum_ + (num_samples_ / 2)) / num_samples_; |
+ return true; |
+} |
+ |
+PercentCounter::PercentCounter(Clock* clock, StatsCounterObserver* observer) |
+ : StatsCounter::StatsCounter(clock, |
+ 101, // bucket_count |
+ 101, // bucket_max |
+ false, // include_empty_intervals |
+ observer) {} |
+ |
+void PercentCounter::Add(bool sample) { |
+ StatsCounter::Add(sample ? 1 : 0); |
+} |
+ |
+bool PercentCounter::GetMetric(int* metric) const { |
+ if (num_samples_ == 0) |
+ return false; |
+ *metric = (sum_ * 100 + (num_samples_ / 2)) / num_samples_; |
+ return true; |
+} |
+ |
+PermilleCounter::PermilleCounter(Clock* clock, |
+ size_t bucket_count, |
+ StatsCounterObserver* observer) |
+ : StatsCounter::StatsCounter(clock, |
+ bucket_count, |
+ 1001, // bucket_max |
+ false, // include_empty_intervals |
+ observer) {} |
+ |
+void PermilleCounter::Add(bool sample) { |
+ StatsCounter::Add(sample ? 1 : 0); |
+} |
+ |
+bool PermilleCounter::GetMetric(int* metric) const { |
+ if (num_samples_ == 0) |
+ return false; |
+ *metric = (sum_ * 1000 + (num_samples_ / 2)) / num_samples_; |
+ return true; |
+} |
+ |
+RateCounter::RateCounter(Clock* clock, |
+ size_t bucket_count, |
+ size_t bucket_max, |
+ bool include_empty_intervals, |
+ StatsCounterObserver* observer) |
+ : StatsCounter::StatsCounter(clock, |
+ bucket_count, |
+ bucket_max, |
+ include_empty_intervals, |
+ observer) {} |
+ |
+void RateCounter::Add(int sample) { |
+ StatsCounter::Add(sample); |
+} |
+ |
+bool RateCounter::GetMetric(int* metric) const { |
+ if (num_samples_ == 0) |
+ return false; |
+ *metric = (sum_ * 1000 + (kProcessIntervalMs / 2)) / kProcessIntervalMs; |
+ return true; |
+} |
+ |
+RateAccCounter::RateAccCounter(Clock* clock, |
+ size_t bucket_count, |
+ size_t bucket_max, |
+ bool include_empty_intervals, |
+ StatsCounterObserver* observer) |
+ : StatsCounter::StatsCounter(clock, |
+ bucket_count, |
+ bucket_max, |
+ include_empty_intervals, |
+ observer) {} |
+ |
+void RateAccCounter::Set(int sample) { |
+ StatsCounter::Set(sample); |
+} |
+ |
+bool RateAccCounter::GetMetric(int* metric) const { |
+ if (num_samples_ == 0 || last_sum_ > sum_) |
+ return false; |
+ *metric = ((sum_ - last_sum_) * 1000 + (kProcessIntervalMs / 2)) / |
+ kProcessIntervalMs; |
+ return true; |
+} |
+ |
+} // namespace webrtc |