• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "video/stats_counter.h"
12 
13 #include <algorithm>
14 #include <limits>
15 #include <map>
16 
17 #include "rtc_base/checks.h"
18 #include "rtc_base/strings/string_builder.h"
19 #include "system_wrappers/include/clock.h"
20 
21 namespace webrtc {
22 
23 namespace {
24 // Default periodic time interval for processing samples.
25 const int64_t kDefaultProcessIntervalMs = 2000;
26 const uint32_t kStreamId0 = 0;
27 }  // namespace
28 
ToString() const29 std::string AggregatedStats::ToString() const {
30   return ToStringWithMultiplier(1);
31 }
32 
ToStringWithMultiplier(int multiplier) const33 std::string AggregatedStats::ToStringWithMultiplier(int multiplier) const {
34   rtc::StringBuilder ss;
35   ss << "periodic_samples:" << num_samples << ", {";
36   ss << "min:" << (min * multiplier) << ", ";
37   ss << "avg:" << (average * multiplier) << ", ";
38   ss << "max:" << (max * multiplier) << "}";
39   return ss.Release();
40 }
41 
42 // Class holding periodically computed metrics.
43 class AggregatedCounter {
44  public:
AggregatedCounter()45   AggregatedCounter() : last_sample_(0), sum_samples_(0) {}
~AggregatedCounter()46   ~AggregatedCounter() {}
47 
Add(int sample)48   void Add(int sample) {
49     last_sample_ = sample;
50     sum_samples_ += sample;
51     ++stats_.num_samples;
52     if (stats_.num_samples == 1) {
53       stats_.min = sample;
54       stats_.max = sample;
55     }
56     stats_.min = std::min(sample, stats_.min);
57     stats_.max = std::max(sample, stats_.max);
58   }
59 
ComputeStats()60   AggregatedStats ComputeStats() {
61     Compute();
62     return stats_;
63   }
64 
Empty() const65   bool Empty() const { return stats_.num_samples == 0; }
66 
last_sample() const67   int last_sample() const { return last_sample_; }
68 
69  private:
Compute()70   void Compute() {
71     if (stats_.num_samples == 0)
72       return;
73 
74     stats_.average =
75         (sum_samples_ + stats_.num_samples / 2) / stats_.num_samples;
76   }
77   int last_sample_;
78   int64_t sum_samples_;
79   AggregatedStats stats_;
80 };
81 
82 // Class holding gathered samples within a process interval.
83 class Samples {
84  public:
Samples()85   Samples() : total_count_(0) {}
~Samples()86   ~Samples() {}
87 
Add(int sample,uint32_t stream_id)88   void Add(int sample, uint32_t stream_id) {
89     samples_[stream_id].Add(sample);
90     ++total_count_;
91   }
Set(int64_t sample,uint32_t stream_id)92   void Set(int64_t sample, uint32_t stream_id) {
93     samples_[stream_id].Set(sample);
94     ++total_count_;
95   }
SetLast(int64_t sample,uint32_t stream_id)96   void SetLast(int64_t sample, uint32_t stream_id) {
97     samples_[stream_id].SetLast(sample);
98   }
GetLast(uint32_t stream_id)99   int64_t GetLast(uint32_t stream_id) { return samples_[stream_id].GetLast(); }
100 
Count() const101   int64_t Count() const { return total_count_; }
Empty() const102   bool Empty() const { return total_count_ == 0; }
103 
Sum() const104   int64_t Sum() const {
105     int64_t sum = 0;
106     for (const auto& it : samples_)
107       sum += it.second.sum_;
108     return sum;
109   }
110 
Max() const111   int Max() const {
112     int max = std::numeric_limits<int>::min();
113     for (const auto& it : samples_)
114       max = std::max(it.second.max_, max);
115     return max;
116   }
117 
Reset()118   void Reset() {
119     for (auto& it : samples_)
120       it.second.Reset();
121     total_count_ = 0;
122   }
123 
Diff() const124   int64_t Diff() const {
125     int64_t sum_diff = 0;
126     int count = 0;
127     for (const auto& it : samples_) {
128       if (it.second.count_ > 0) {
129         int64_t diff = it.second.sum_ - it.second.last_sum_;
130         if (diff >= 0) {
131           sum_diff += diff;
132           ++count;
133         }
134       }
135     }
136     return (count > 0) ? sum_diff : -1;
137   }
138 
139  private:
140   struct Stats {
Addwebrtc::Samples::Stats141     void Add(int sample) {
142       sum_ += sample;
143       ++count_;
144       max_ = std::max(sample, max_);
145     }
Setwebrtc::Samples::Stats146     void Set(int64_t sample) {
147       sum_ = sample;
148       ++count_;
149     }
SetLastwebrtc::Samples::Stats150     void SetLast(int64_t sample) { last_sum_ = sample; }
GetLastwebrtc::Samples::Stats151     int64_t GetLast() const { return last_sum_; }
Resetwebrtc::Samples::Stats152     void Reset() {
153       if (count_ > 0)
154         last_sum_ = sum_;
155       sum_ = 0;
156       count_ = 0;
157       max_ = std::numeric_limits<int>::min();
158     }
159 
160     int max_ = std::numeric_limits<int>::min();
161     int64_t count_ = 0;
162     int64_t sum_ = 0;
163     int64_t last_sum_ = 0;
164   };
165 
166   int64_t total_count_;
167   std::map<uint32_t, Stats> samples_;  // Gathered samples mapped by stream id.
168 };
169 
170 // StatsCounter class.
StatsCounter(Clock * clock,int64_t process_intervals_ms,bool include_empty_intervals,StatsCounterObserver * observer)171 StatsCounter::StatsCounter(Clock* clock,
172                            int64_t process_intervals_ms,
173                            bool include_empty_intervals,
174                            StatsCounterObserver* observer)
175     : include_empty_intervals_(include_empty_intervals),
176       process_intervals_ms_(process_intervals_ms),
177       aggregated_counter_(new AggregatedCounter()),
178       samples_(new Samples()),
179       clock_(clock),
180       observer_(observer),
181       last_process_time_ms_(-1),
182       paused_(false),
183       pause_time_ms_(-1),
184       min_pause_time_ms_(0) {
185   RTC_DCHECK_GT(process_intervals_ms_, 0);
186 }
187 
~StatsCounter()188 StatsCounter::~StatsCounter() {}
189 
GetStats()190 AggregatedStats StatsCounter::GetStats() {
191   return aggregated_counter_->ComputeStats();
192 }
193 
ProcessAndGetStats()194 AggregatedStats StatsCounter::ProcessAndGetStats() {
195   if (HasSample())
196     TryProcess();
197   return aggregated_counter_->ComputeStats();
198 }
199 
ProcessAndPauseForDuration(int64_t min_pause_time_ms)200 void StatsCounter::ProcessAndPauseForDuration(int64_t min_pause_time_ms) {
201   ProcessAndPause();
202   min_pause_time_ms_ = min_pause_time_ms;
203 }
204 
ProcessAndPause()205 void StatsCounter::ProcessAndPause() {
206   if (HasSample())
207     TryProcess();
208   paused_ = true;
209   pause_time_ms_ = clock_->TimeInMilliseconds();
210 }
211 
ProcessAndStopPause()212 void StatsCounter::ProcessAndStopPause() {
213   if (HasSample())
214     TryProcess();
215   Resume();
216 }
217 
HasSample() const218 bool StatsCounter::HasSample() const {
219   return last_process_time_ms_ != -1;
220 }
221 
TimeToProcess(int * elapsed_intervals)222 bool StatsCounter::TimeToProcess(int* elapsed_intervals) {
223   int64_t now = clock_->TimeInMilliseconds();
224   if (last_process_time_ms_ == -1)
225     last_process_time_ms_ = now;
226 
227   int64_t diff_ms = now - last_process_time_ms_;
228   if (diff_ms < process_intervals_ms_)
229     return false;
230 
231   // Advance number of complete `process_intervals_ms_` that have passed.
232   int64_t num_intervals = diff_ms / process_intervals_ms_;
233   last_process_time_ms_ += num_intervals * process_intervals_ms_;
234 
235   *elapsed_intervals = num_intervals;
236   return true;
237 }
238 
Add(int sample)239 void StatsCounter::Add(int sample) {
240   TryProcess();
241   samples_->Add(sample, kStreamId0);
242   ResumeIfMinTimePassed();
243 }
244 
Set(int64_t sample,uint32_t stream_id)245 void StatsCounter::Set(int64_t sample, uint32_t stream_id) {
246   if (paused_ && sample == samples_->GetLast(stream_id)) {
247     // Do not add same sample while paused (will reset pause).
248     return;
249   }
250   TryProcess();
251   samples_->Set(sample, stream_id);
252   ResumeIfMinTimePassed();
253 }
254 
SetLast(int64_t sample,uint32_t stream_id)255 void StatsCounter::SetLast(int64_t sample, uint32_t stream_id) {
256   RTC_DCHECK(!HasSample()) << "Should be set before first sample is added.";
257   samples_->SetLast(sample, stream_id);
258 }
259 
260 // Reports periodically computed metric.
ReportMetricToAggregatedCounter(int value,int num_values_to_add) const261 void StatsCounter::ReportMetricToAggregatedCounter(
262     int value,
263     int num_values_to_add) const {
264   for (int i = 0; i < num_values_to_add; ++i) {
265     aggregated_counter_->Add(value);
266     if (observer_)
267       observer_->OnMetricUpdated(value);
268   }
269 }
270 
TryProcess()271 void StatsCounter::TryProcess() {
272   int elapsed_intervals;
273   if (!TimeToProcess(&elapsed_intervals))
274     return;
275 
276   // Get and report periodically computed metric.
277   int metric;
278   if (GetMetric(&metric))
279     ReportMetricToAggregatedCounter(metric, 1);
280 
281   // Report value for elapsed intervals without samples.
282   if (IncludeEmptyIntervals()) {
283     // If there are no samples, all elapsed intervals are empty (otherwise one
284     // interval contains sample(s), discard this interval).
285     int empty_intervals =
286         samples_->Empty() ? elapsed_intervals : (elapsed_intervals - 1);
287     ReportMetricToAggregatedCounter(GetValueForEmptyInterval(),
288                                     empty_intervals);
289   }
290 
291   // Reset samples for elapsed interval.
292   samples_->Reset();
293 }
294 
IncludeEmptyIntervals() const295 bool StatsCounter::IncludeEmptyIntervals() const {
296   return include_empty_intervals_ && !paused_ && !aggregated_counter_->Empty();
297 }
ResumeIfMinTimePassed()298 void StatsCounter::ResumeIfMinTimePassed() {
299   if (paused_ &&
300       (clock_->TimeInMilliseconds() - pause_time_ms_) >= min_pause_time_ms_) {
301     Resume();
302   }
303 }
304 
Resume()305 void StatsCounter::Resume() {
306   paused_ = false;
307   min_pause_time_ms_ = 0;
308 }
309 
310 // StatsCounter sub-classes.
AvgCounter(Clock * clock,StatsCounterObserver * observer,bool include_empty_intervals)311 AvgCounter::AvgCounter(Clock* clock,
312                        StatsCounterObserver* observer,
313                        bool include_empty_intervals)
314     : StatsCounter(clock,
315                    kDefaultProcessIntervalMs,
316                    include_empty_intervals,
317                    observer) {}
318 
Add(int sample)319 void AvgCounter::Add(int sample) {
320   StatsCounter::Add(sample);
321 }
322 
GetMetric(int * metric) const323 bool AvgCounter::GetMetric(int* metric) const {
324   int64_t count = samples_->Count();
325   if (count == 0)
326     return false;
327 
328   *metric = (samples_->Sum() + count / 2) / count;
329   return true;
330 }
331 
GetValueForEmptyInterval() const332 int AvgCounter::GetValueForEmptyInterval() const {
333   return aggregated_counter_->last_sample();
334 }
335 
MaxCounter(Clock * clock,StatsCounterObserver * observer,int64_t process_intervals_ms)336 MaxCounter::MaxCounter(Clock* clock,
337                        StatsCounterObserver* observer,
338                        int64_t process_intervals_ms)
339     : StatsCounter(clock,
340                    process_intervals_ms,
341                    false,  // `include_empty_intervals`
342                    observer) {}
343 
Add(int sample)344 void MaxCounter::Add(int sample) {
345   StatsCounter::Add(sample);
346 }
347 
GetMetric(int * metric) const348 bool MaxCounter::GetMetric(int* metric) const {
349   if (samples_->Empty())
350     return false;
351 
352   *metric = samples_->Max();
353   return true;
354 }
355 
GetValueForEmptyInterval() const356 int MaxCounter::GetValueForEmptyInterval() const {
357   RTC_DCHECK_NOTREACHED();
358   return 0;
359 }
360 
PercentCounter(Clock * clock,StatsCounterObserver * observer)361 PercentCounter::PercentCounter(Clock* clock, StatsCounterObserver* observer)
362     : StatsCounter(clock,
363                    kDefaultProcessIntervalMs,
364                    false,  // `include_empty_intervals`
365                    observer) {}
366 
Add(bool sample)367 void PercentCounter::Add(bool sample) {
368   StatsCounter::Add(sample ? 1 : 0);
369 }
370 
GetMetric(int * metric) const371 bool PercentCounter::GetMetric(int* metric) const {
372   int64_t count = samples_->Count();
373   if (count == 0)
374     return false;
375 
376   *metric = (samples_->Sum() * 100 + count / 2) / count;
377   return true;
378 }
379 
GetValueForEmptyInterval() const380 int PercentCounter::GetValueForEmptyInterval() const {
381   RTC_DCHECK_NOTREACHED();
382   return 0;
383 }
384 
PermilleCounter(Clock * clock,StatsCounterObserver * observer)385 PermilleCounter::PermilleCounter(Clock* clock, StatsCounterObserver* observer)
386     : StatsCounter(clock,
387                    kDefaultProcessIntervalMs,
388                    false,  // `include_empty_intervals`
389                    observer) {}
390 
Add(bool sample)391 void PermilleCounter::Add(bool sample) {
392   StatsCounter::Add(sample ? 1 : 0);
393 }
394 
GetMetric(int * metric) const395 bool PermilleCounter::GetMetric(int* metric) const {
396   int64_t count = samples_->Count();
397   if (count == 0)
398     return false;
399 
400   *metric = (samples_->Sum() * 1000 + count / 2) / count;
401   return true;
402 }
403 
GetValueForEmptyInterval() const404 int PermilleCounter::GetValueForEmptyInterval() const {
405   RTC_DCHECK_NOTREACHED();
406   return 0;
407 }
408 
RateCounter(Clock * clock,StatsCounterObserver * observer,bool include_empty_intervals)409 RateCounter::RateCounter(Clock* clock,
410                          StatsCounterObserver* observer,
411                          bool include_empty_intervals)
412     : StatsCounter(clock,
413                    kDefaultProcessIntervalMs,
414                    include_empty_intervals,
415                    observer) {}
416 
Add(int sample)417 void RateCounter::Add(int sample) {
418   StatsCounter::Add(sample);
419 }
420 
GetMetric(int * metric) const421 bool RateCounter::GetMetric(int* metric) const {
422   if (samples_->Empty())
423     return false;
424 
425   *metric = (samples_->Sum() * 1000 + process_intervals_ms_ / 2) /
426             process_intervals_ms_;
427   return true;
428 }
429 
GetValueForEmptyInterval() const430 int RateCounter::GetValueForEmptyInterval() const {
431   return 0;
432 }
433 
RateAccCounter(Clock * clock,StatsCounterObserver * observer,bool include_empty_intervals)434 RateAccCounter::RateAccCounter(Clock* clock,
435                                StatsCounterObserver* observer,
436                                bool include_empty_intervals)
437     : StatsCounter(clock,
438                    kDefaultProcessIntervalMs,
439                    include_empty_intervals,
440                    observer) {}
441 
Set(int64_t sample,uint32_t stream_id)442 void RateAccCounter::Set(int64_t sample, uint32_t stream_id) {
443   StatsCounter::Set(sample, stream_id);
444 }
445 
SetLast(int64_t sample,uint32_t stream_id)446 void RateAccCounter::SetLast(int64_t sample, uint32_t stream_id) {
447   StatsCounter::SetLast(sample, stream_id);
448 }
449 
GetMetric(int * metric) const450 bool RateAccCounter::GetMetric(int* metric) const {
451   int64_t diff = samples_->Diff();
452   if (diff < 0 || (!include_empty_intervals_ && diff == 0))
453     return false;
454 
455   *metric = (diff * 1000 + process_intervals_ms_ / 2) / process_intervals_ms_;
456   return true;
457 }
458 
GetValueForEmptyInterval() const459 int RateAccCounter::GetValueForEmptyInterval() const {
460   return 0;
461 }
462 
463 }  // namespace webrtc
464