| /* |
| * Copyright (c) 2013 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 "rtc_base/rate_statistics.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| |
| #include "rtc_base/checks.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/numerics/safe_conversions.h" |
| |
| namespace webrtc { |
| |
| RateStatistics::Bucket::Bucket(int64_t timestamp) |
| : sum(0), num_samples(0), timestamp(timestamp) {} |
| |
| RateStatistics::RateStatistics(int64_t window_size_ms, float scale) |
| : accumulated_count_(0), |
| first_timestamp_(-1), |
| num_samples_(0), |
| scale_(scale), |
| max_window_size_ms_(window_size_ms), |
| current_window_size_ms_(max_window_size_ms_) {} |
| |
| RateStatistics::RateStatistics(const RateStatistics& other) |
| : buckets_(other.buckets_), |
| accumulated_count_(other.accumulated_count_), |
| first_timestamp_(other.first_timestamp_), |
| overflow_(other.overflow_), |
| num_samples_(other.num_samples_), |
| scale_(other.scale_), |
| max_window_size_ms_(other.max_window_size_ms_), |
| current_window_size_ms_(other.current_window_size_ms_) {} |
| |
| RateStatistics::RateStatistics(RateStatistics&& other) = default; |
| |
| RateStatistics::~RateStatistics() {} |
| |
| void RateStatistics::Reset() { |
| accumulated_count_ = 0; |
| overflow_ = false; |
| num_samples_ = 0; |
| first_timestamp_ = -1; |
| current_window_size_ms_ = max_window_size_ms_; |
| buckets_.clear(); |
| } |
| |
| void RateStatistics::Update(int64_t count, int64_t now_ms) { |
| RTC_DCHECK_GE(count, 0); |
| |
| EraseOld(now_ms); |
| if (first_timestamp_ == -1 || num_samples_ == 0) { |
| first_timestamp_ = now_ms; |
| } |
| |
| if (buckets_.empty() || now_ms != buckets_.back().timestamp) { |
| if (!buckets_.empty() && now_ms < buckets_.back().timestamp) { |
| RTC_LOG(LS_WARNING) << "Timestamp " << now_ms |
| << " is before the last added " |
| "timestamp in the rate window: " |
| << buckets_.back().timestamp << ", aligning to that."; |
| now_ms = buckets_.back().timestamp; |
| } |
| buckets_.emplace_back(now_ms); |
| } |
| Bucket& last_bucket = buckets_.back(); |
| last_bucket.sum += count; |
| ++last_bucket.num_samples; |
| |
| if (std::numeric_limits<int64_t>::max() - accumulated_count_ > count) { |
| accumulated_count_ += count; |
| } else { |
| overflow_ = true; |
| } |
| ++num_samples_; |
| } |
| |
| std::optional<int64_t> RateStatistics::Rate(int64_t now_ms) const { |
| // Yeah, this const_cast ain't pretty, but the alternative is to declare most |
| // of the members as mutable... |
| const_cast<RateStatistics*>(this)->EraseOld(now_ms); |
| |
| int active_window_size = 0; |
| if (first_timestamp_ != -1) { |
| if (first_timestamp_ <= now_ms - current_window_size_ms_) { |
| // Count window as full even if no data points currently in view, if the |
| // data stream started before the window. |
| active_window_size = current_window_size_ms_; |
| } else { |
| // Size of a single bucket is 1ms, so even if now_ms == first_timestmap_ |
| // the window size should be 1. |
| active_window_size = now_ms - first_timestamp_ + 1; |
| } |
| } |
| |
| // If window is a single bucket or there is only one sample in a data set that |
| // has not grown to the full window size, or if the accumulator has |
| // overflowed, treat this as rate unavailable. |
| if (num_samples_ == 0 || active_window_size <= 1 || |
| (num_samples_ <= 1 && |
| rtc::SafeLt(active_window_size, current_window_size_ms_)) || |
| overflow_) { |
| return std::nullopt; |
| } |
| |
| float scale = static_cast<float>(scale_) / active_window_size; |
| float result = accumulated_count_ * scale + 0.5f; |
| |
| // Better return unavailable rate than garbage value (undefined behavior). |
| if (result > static_cast<float>(std::numeric_limits<int64_t>::max())) { |
| return std::nullopt; |
| } |
| return rtc::dchecked_cast<int64_t>(result); |
| } |
| |
| void RateStatistics::EraseOld(int64_t now_ms) { |
| // New oldest time that is included in data set. |
| const int64_t new_oldest_time = now_ms - current_window_size_ms_ + 1; |
| |
| // Loop over buckets and remove too old data points. |
| while (!buckets_.empty() && buckets_.front().timestamp < new_oldest_time) { |
| const Bucket& oldest_bucket = buckets_.front(); |
| RTC_DCHECK_GE(accumulated_count_, oldest_bucket.sum); |
| RTC_DCHECK_GE(num_samples_, oldest_bucket.num_samples); |
| accumulated_count_ -= oldest_bucket.sum; |
| num_samples_ -= oldest_bucket.num_samples; |
| buckets_.pop_front(); |
| // This does not clear overflow_ even when counter is empty. |
| // TODO(https://bugs.webrtc.org/11247): Consider if overflow_ can be reset. |
| } |
| } |
| |
| bool RateStatistics::SetWindowSize(int64_t window_size_ms, int64_t now_ms) { |
| if (window_size_ms <= 0 || window_size_ms > max_window_size_ms_) |
| return false; |
| if (first_timestamp_ != -1) { |
| // If the window changes (e.g. decreases - removing data point, then |
| // increases again) we need to update the first timestamp mark as |
| // otherwise it indicates the window coveres a region of zeros, suddenly |
| // under-estimating the rate. |
| first_timestamp_ = std::max(first_timestamp_, now_ms - window_size_ms + 1); |
| } |
| current_window_size_ms_ = window_size_ms; |
| EraseOld(now_ms); |
| return true; |
| } |
| |
| } // namespace webrtc |