| /* | 
 |  *  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 <limits> | 
 | #include <map> | 
 |  | 
 | #include "webrtc/base/checks.h" | 
 | #include "webrtc/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 { | 
 |   std::stringstream ss; | 
 |   ss << "periodic_samples:" << num_samples << ", {"; | 
 |   ss << "min:" << (min * multiplier) << ", "; | 
 |   ss << "avg:" << (average * multiplier) << ", "; | 
 |   ss << "max:" << (max * multiplier) << "}"; | 
 |   return ss.str(); | 
 | } | 
 |  | 
 | // 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(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<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(int sample) { | 
 |       sum_ = sample; | 
 |       ++count_; | 
 |     } | 
 |     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) { | 
 |   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::ProcessAndPause() { | 
 |   if (HasSample()) | 
 |     TryProcess(); | 
 |   paused_ = true; | 
 | } | 
 |  | 
 | 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::Set(int sample, uint32_t stream_id) { | 
 |   TryProcess(); | 
 |   samples_->Set(sample, stream_id); | 
 |   paused_ = false; | 
 | } | 
 |  | 
 | void StatsCounter::Add(int sample) { | 
 |   TryProcess(); | 
 |   samples_->Add(sample, kStreamId0); | 
 |   paused_ = false; | 
 | } | 
 |  | 
 | // 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(); | 
 | } | 
 |  | 
 | // 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(int sample, uint32_t stream_id) { | 
 |   StatsCounter::Set(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 |