xref: /aosp_15_r20/external/webrtc/rtc_base/rate_statistics.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1*d9f75844SAndroid Build Coastguard Worker /*
2*d9f75844SAndroid Build Coastguard Worker  *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3*d9f75844SAndroid Build Coastguard Worker  *
4*d9f75844SAndroid Build Coastguard Worker  *  Use of this source code is governed by a BSD-style license
5*d9f75844SAndroid Build Coastguard Worker  *  that can be found in the LICENSE file in the root of the source
6*d9f75844SAndroid Build Coastguard Worker  *  tree. An additional intellectual property rights grant can be found
7*d9f75844SAndroid Build Coastguard Worker  *  in the file PATENTS.  All contributing project authors may
8*d9f75844SAndroid Build Coastguard Worker  *  be found in the AUTHORS file in the root of the source tree.
9*d9f75844SAndroid Build Coastguard Worker  */
10*d9f75844SAndroid Build Coastguard Worker 
11*d9f75844SAndroid Build Coastguard Worker #include "rtc_base/rate_statistics.h"
12*d9f75844SAndroid Build Coastguard Worker 
13*d9f75844SAndroid Build Coastguard Worker #include <algorithm>
14*d9f75844SAndroid Build Coastguard Worker #include <limits>
15*d9f75844SAndroid Build Coastguard Worker #include <memory>
16*d9f75844SAndroid Build Coastguard Worker 
17*d9f75844SAndroid Build Coastguard Worker #include "rtc_base/checks.h"
18*d9f75844SAndroid Build Coastguard Worker #include "rtc_base/logging.h"
19*d9f75844SAndroid Build Coastguard Worker #include "rtc_base/numerics/safe_conversions.h"
20*d9f75844SAndroid Build Coastguard Worker 
21*d9f75844SAndroid Build Coastguard Worker namespace webrtc {
22*d9f75844SAndroid Build Coastguard Worker 
Bucket(int64_t timestamp)23*d9f75844SAndroid Build Coastguard Worker RateStatistics::Bucket::Bucket(int64_t timestamp)
24*d9f75844SAndroid Build Coastguard Worker     : sum(0), num_samples(0), timestamp(timestamp) {}
25*d9f75844SAndroid Build Coastguard Worker 
RateStatistics(int64_t window_size_ms,float scale)26*d9f75844SAndroid Build Coastguard Worker RateStatistics::RateStatistics(int64_t window_size_ms, float scale)
27*d9f75844SAndroid Build Coastguard Worker     : accumulated_count_(0),
28*d9f75844SAndroid Build Coastguard Worker       first_timestamp_(-1),
29*d9f75844SAndroid Build Coastguard Worker       num_samples_(0),
30*d9f75844SAndroid Build Coastguard Worker       scale_(scale),
31*d9f75844SAndroid Build Coastguard Worker       max_window_size_ms_(window_size_ms),
32*d9f75844SAndroid Build Coastguard Worker       current_window_size_ms_(max_window_size_ms_) {}
33*d9f75844SAndroid Build Coastguard Worker 
RateStatistics(const RateStatistics & other)34*d9f75844SAndroid Build Coastguard Worker RateStatistics::RateStatistics(const RateStatistics& other)
35*d9f75844SAndroid Build Coastguard Worker     : buckets_(other.buckets_),
36*d9f75844SAndroid Build Coastguard Worker       accumulated_count_(other.accumulated_count_),
37*d9f75844SAndroid Build Coastguard Worker       first_timestamp_(other.first_timestamp_),
38*d9f75844SAndroid Build Coastguard Worker       overflow_(other.overflow_),
39*d9f75844SAndroid Build Coastguard Worker       num_samples_(other.num_samples_),
40*d9f75844SAndroid Build Coastguard Worker       scale_(other.scale_),
41*d9f75844SAndroid Build Coastguard Worker       max_window_size_ms_(other.max_window_size_ms_),
42*d9f75844SAndroid Build Coastguard Worker       current_window_size_ms_(other.current_window_size_ms_) {}
43*d9f75844SAndroid Build Coastguard Worker 
44*d9f75844SAndroid Build Coastguard Worker RateStatistics::RateStatistics(RateStatistics&& other) = default;
45*d9f75844SAndroid Build Coastguard Worker 
~RateStatistics()46*d9f75844SAndroid Build Coastguard Worker RateStatistics::~RateStatistics() {}
47*d9f75844SAndroid Build Coastguard Worker 
Reset()48*d9f75844SAndroid Build Coastguard Worker void RateStatistics::Reset() {
49*d9f75844SAndroid Build Coastguard Worker   accumulated_count_ = 0;
50*d9f75844SAndroid Build Coastguard Worker   overflow_ = false;
51*d9f75844SAndroid Build Coastguard Worker   num_samples_ = 0;
52*d9f75844SAndroid Build Coastguard Worker   first_timestamp_ = -1;
53*d9f75844SAndroid Build Coastguard Worker   current_window_size_ms_ = max_window_size_ms_;
54*d9f75844SAndroid Build Coastguard Worker   buckets_.clear();
55*d9f75844SAndroid Build Coastguard Worker }
56*d9f75844SAndroid Build Coastguard Worker 
Update(int64_t count,int64_t now_ms)57*d9f75844SAndroid Build Coastguard Worker void RateStatistics::Update(int64_t count, int64_t now_ms) {
58*d9f75844SAndroid Build Coastguard Worker   RTC_DCHECK_GE(count, 0);
59*d9f75844SAndroid Build Coastguard Worker 
60*d9f75844SAndroid Build Coastguard Worker   EraseOld(now_ms);
61*d9f75844SAndroid Build Coastguard Worker   if (first_timestamp_ == -1 || num_samples_ == 0) {
62*d9f75844SAndroid Build Coastguard Worker     first_timestamp_ = now_ms;
63*d9f75844SAndroid Build Coastguard Worker   }
64*d9f75844SAndroid Build Coastguard Worker 
65*d9f75844SAndroid Build Coastguard Worker   if (buckets_.empty() || now_ms != buckets_.back().timestamp) {
66*d9f75844SAndroid Build Coastguard Worker     if (!buckets_.empty() && now_ms < buckets_.back().timestamp) {
67*d9f75844SAndroid Build Coastguard Worker       RTC_LOG(LS_WARNING) << "Timestamp " << now_ms
68*d9f75844SAndroid Build Coastguard Worker                           << " is before the last added "
69*d9f75844SAndroid Build Coastguard Worker                              "timestamp in the rate window: "
70*d9f75844SAndroid Build Coastguard Worker                           << buckets_.back().timestamp << ", aligning to that.";
71*d9f75844SAndroid Build Coastguard Worker       now_ms = buckets_.back().timestamp;
72*d9f75844SAndroid Build Coastguard Worker     }
73*d9f75844SAndroid Build Coastguard Worker     buckets_.emplace_back(now_ms);
74*d9f75844SAndroid Build Coastguard Worker   }
75*d9f75844SAndroid Build Coastguard Worker   Bucket& last_bucket = buckets_.back();
76*d9f75844SAndroid Build Coastguard Worker   last_bucket.sum += count;
77*d9f75844SAndroid Build Coastguard Worker   ++last_bucket.num_samples;
78*d9f75844SAndroid Build Coastguard Worker 
79*d9f75844SAndroid Build Coastguard Worker   if (std::numeric_limits<int64_t>::max() - accumulated_count_ > count) {
80*d9f75844SAndroid Build Coastguard Worker     accumulated_count_ += count;
81*d9f75844SAndroid Build Coastguard Worker   } else {
82*d9f75844SAndroid Build Coastguard Worker     overflow_ = true;
83*d9f75844SAndroid Build Coastguard Worker   }
84*d9f75844SAndroid Build Coastguard Worker   ++num_samples_;
85*d9f75844SAndroid Build Coastguard Worker }
86*d9f75844SAndroid Build Coastguard Worker 
Rate(int64_t now_ms) const87*d9f75844SAndroid Build Coastguard Worker absl::optional<int64_t> RateStatistics::Rate(int64_t now_ms) const {
88*d9f75844SAndroid Build Coastguard Worker   // Yeah, this const_cast ain't pretty, but the alternative is to declare most
89*d9f75844SAndroid Build Coastguard Worker   // of the members as mutable...
90*d9f75844SAndroid Build Coastguard Worker   const_cast<RateStatistics*>(this)->EraseOld(now_ms);
91*d9f75844SAndroid Build Coastguard Worker 
92*d9f75844SAndroid Build Coastguard Worker   int active_window_size = 0;
93*d9f75844SAndroid Build Coastguard Worker   if (first_timestamp_ != -1) {
94*d9f75844SAndroid Build Coastguard Worker     if (first_timestamp_ <= now_ms - current_window_size_ms_) {
95*d9f75844SAndroid Build Coastguard Worker       // Count window as full even if no data points currently in view, if the
96*d9f75844SAndroid Build Coastguard Worker       // data stream started before the window.
97*d9f75844SAndroid Build Coastguard Worker       active_window_size = current_window_size_ms_;
98*d9f75844SAndroid Build Coastguard Worker     } else {
99*d9f75844SAndroid Build Coastguard Worker       // Size of a single bucket is 1ms, so even if now_ms == first_timestmap_
100*d9f75844SAndroid Build Coastguard Worker       // the window size should be 1.
101*d9f75844SAndroid Build Coastguard Worker       active_window_size = now_ms - first_timestamp_ + 1;
102*d9f75844SAndroid Build Coastguard Worker     }
103*d9f75844SAndroid Build Coastguard Worker   }
104*d9f75844SAndroid Build Coastguard Worker 
105*d9f75844SAndroid Build Coastguard Worker   // If window is a single bucket or there is only one sample in a data set that
106*d9f75844SAndroid Build Coastguard Worker   // has not grown to the full window size, or if the accumulator has
107*d9f75844SAndroid Build Coastguard Worker   // overflowed, treat this as rate unavailable.
108*d9f75844SAndroid Build Coastguard Worker   if (num_samples_ == 0 || active_window_size <= 1 ||
109*d9f75844SAndroid Build Coastguard Worker       (num_samples_ <= 1 &&
110*d9f75844SAndroid Build Coastguard Worker        rtc::SafeLt(active_window_size, current_window_size_ms_)) ||
111*d9f75844SAndroid Build Coastguard Worker       overflow_) {
112*d9f75844SAndroid Build Coastguard Worker     return absl::nullopt;
113*d9f75844SAndroid Build Coastguard Worker   }
114*d9f75844SAndroid Build Coastguard Worker 
115*d9f75844SAndroid Build Coastguard Worker   float scale = static_cast<float>(scale_) / active_window_size;
116*d9f75844SAndroid Build Coastguard Worker   float result = accumulated_count_ * scale + 0.5f;
117*d9f75844SAndroid Build Coastguard Worker 
118*d9f75844SAndroid Build Coastguard Worker   // Better return unavailable rate than garbage value (undefined behavior).
119*d9f75844SAndroid Build Coastguard Worker   if (result > static_cast<float>(std::numeric_limits<int64_t>::max())) {
120*d9f75844SAndroid Build Coastguard Worker     return absl::nullopt;
121*d9f75844SAndroid Build Coastguard Worker   }
122*d9f75844SAndroid Build Coastguard Worker   return rtc::dchecked_cast<int64_t>(result);
123*d9f75844SAndroid Build Coastguard Worker }
124*d9f75844SAndroid Build Coastguard Worker 
EraseOld(int64_t now_ms)125*d9f75844SAndroid Build Coastguard Worker void RateStatistics::EraseOld(int64_t now_ms) {
126*d9f75844SAndroid Build Coastguard Worker   // New oldest time that is included in data set.
127*d9f75844SAndroid Build Coastguard Worker   const int64_t new_oldest_time = now_ms - current_window_size_ms_ + 1;
128*d9f75844SAndroid Build Coastguard Worker 
129*d9f75844SAndroid Build Coastguard Worker   // Loop over buckets and remove too old data points.
130*d9f75844SAndroid Build Coastguard Worker   while (!buckets_.empty() && buckets_.front().timestamp < new_oldest_time) {
131*d9f75844SAndroid Build Coastguard Worker     const Bucket& oldest_bucket = buckets_.front();
132*d9f75844SAndroid Build Coastguard Worker     RTC_DCHECK_GE(accumulated_count_, oldest_bucket.sum);
133*d9f75844SAndroid Build Coastguard Worker     RTC_DCHECK_GE(num_samples_, oldest_bucket.num_samples);
134*d9f75844SAndroid Build Coastguard Worker     accumulated_count_ -= oldest_bucket.sum;
135*d9f75844SAndroid Build Coastguard Worker     num_samples_ -= oldest_bucket.num_samples;
136*d9f75844SAndroid Build Coastguard Worker     buckets_.pop_front();
137*d9f75844SAndroid Build Coastguard Worker     // This does not clear overflow_ even when counter is empty.
138*d9f75844SAndroid Build Coastguard Worker     // TODO(https://bugs.webrtc.org/11247): Consider if overflow_ can be reset.
139*d9f75844SAndroid Build Coastguard Worker   }
140*d9f75844SAndroid Build Coastguard Worker }
141*d9f75844SAndroid Build Coastguard Worker 
SetWindowSize(int64_t window_size_ms,int64_t now_ms)142*d9f75844SAndroid Build Coastguard Worker bool RateStatistics::SetWindowSize(int64_t window_size_ms, int64_t now_ms) {
143*d9f75844SAndroid Build Coastguard Worker   if (window_size_ms <= 0 || window_size_ms > max_window_size_ms_)
144*d9f75844SAndroid Build Coastguard Worker     return false;
145*d9f75844SAndroid Build Coastguard Worker   if (first_timestamp_ != -1) {
146*d9f75844SAndroid Build Coastguard Worker     // If the window changes (e.g. decreases - removing data point, then
147*d9f75844SAndroid Build Coastguard Worker     // increases again) we need to update the first timestamp mark as
148*d9f75844SAndroid Build Coastguard Worker     // otherwise it indicates the window coveres a region of zeros, suddenly
149*d9f75844SAndroid Build Coastguard Worker     // under-estimating the rate.
150*d9f75844SAndroid Build Coastguard Worker     first_timestamp_ = std::max(first_timestamp_, now_ms - window_size_ms + 1);
151*d9f75844SAndroid Build Coastguard Worker   }
152*d9f75844SAndroid Build Coastguard Worker   current_window_size_ms_ = window_size_ms;
153*d9f75844SAndroid Build Coastguard Worker   EraseOld(now_ms);
154*d9f75844SAndroid Build Coastguard Worker   return true;
155*d9f75844SAndroid Build Coastguard Worker }
156*d9f75844SAndroid Build Coastguard Worker 
157*d9f75844SAndroid Build Coastguard Worker }  // namespace webrtc
158