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