blob: af9eeffa1986500e4c7462c94be9a0b818e386a2 [file] [log] [blame]
/*
* Copyright (c) 2023 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/frequency_tracker.h"
#include <cstdlib>
#include <limits>
#include <optional>
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::AllOf;
using ::testing::Gt;
using ::testing::Lt;
constexpr TimeDelta kWindow = TimeDelta::Millis(500);
constexpr TimeDelta kEpsilon = TimeDelta::Millis(1);
TEST(FrequencyTrackerTest, ReturnsNulloptInitially) {
Timestamp now = Timestamp::Seconds(12'345);
FrequencyTracker stats(kWindow);
EXPECT_EQ(stats.Rate(now), std::nullopt);
}
TEST(FrequencyTrackerTest, ReturnsNulloptAfterSingleDataPoint) {
Timestamp now = Timestamp::Seconds(12'345);
FrequencyTracker stats(kWindow);
stats.Update(now);
now += TimeDelta::Millis(10);
EXPECT_EQ(stats.Rate(now), std::nullopt);
}
TEST(FrequencyTrackerTest, ReturnsRateAfterTwoMeasurements) {
Timestamp now = Timestamp::Seconds(12'345);
FrequencyTracker stats(kWindow);
stats.Update(now);
now += TimeDelta::Millis(1);
stats.Update(now);
// 1 event per 1 ms ~= 1'000 events per second.
EXPECT_EQ(stats.Rate(now), Frequency::Hertz(1'000));
}
TEST(FrequencyTrackerTest, MeasuresConstantRate) {
const Timestamp start = Timestamp::Seconds(12'345);
const TimeDelta kInterval = TimeDelta::Millis(10);
const Frequency kConstantRate = 1 / kInterval;
Timestamp now = start;
FrequencyTracker stats(kWindow);
stats.Update(now);
Frequency last_error = Frequency::PlusInfinity();
for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kInterval) {
SCOPED_TRACE(ToString(i));
now += kInterval;
stats.Update(now);
// Until window is full, rate is measured over a smaller window and might
// look larger than the constant rate.
std::optional<Frequency> rate = stats.Rate(now);
ASSERT_GE(rate, kConstantRate);
// Expect the estimation error to decrease as the window is extended.
Frequency error = *rate - kConstantRate;
EXPECT_LE(error, last_error);
last_error = error;
}
// Once window is full, rate measurment should be stable.
for (TimeDelta i = TimeDelta::Zero(); i < kInterval;
i += TimeDelta::Millis(1)) {
SCOPED_TRACE(ToString(i));
EXPECT_EQ(stats.Rate(now + i), kConstantRate);
}
}
TEST(FrequencyTrackerTest, CanMeasureFractionalRate) {
const TimeDelta kInterval = TimeDelta::Millis(134);
Timestamp now = Timestamp::Seconds(12'345);
// FrequencyTracker counts number of events in the window, thus when window is
// fraction of 1 second, number of events per second would always be integer.
const TimeDelta window = TimeDelta::Seconds(2);
FrequencyTracker framerate(window);
framerate.Update(now);
for (TimeDelta i = TimeDelta::Zero(); i < window; i += kInterval) {
now += kInterval;
framerate.Update(now);
}
// Should be aproximitly 7.5 fps
EXPECT_THAT(framerate.Rate(now),
AllOf(Gt(Frequency::Hertz(7)), Lt(Frequency::Hertz(8))));
}
TEST(FrequencyTrackerTest, IncreasingThenDecreasingRate) {
const int64_t kLargeSize = 1'500;
const int64_t kSmallSize = 300;
const TimeDelta kLargeInterval = TimeDelta::Millis(10);
const TimeDelta kSmallInterval = TimeDelta::Millis(2);
Timestamp now = Timestamp::Seconds(12'345);
FrequencyTracker stats(kWindow);
stats.Update(kLargeSize, now);
for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kLargeInterval) {
SCOPED_TRACE(ToString(i));
now += kLargeInterval;
stats.Update(kLargeSize, now);
}
std::optional<Frequency> last_rate = stats.Rate(now);
EXPECT_EQ(last_rate, kLargeSize / kLargeInterval);
// Decrease rate with smaller measurments.
for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kLargeInterval) {
SCOPED_TRACE(ToString(i));
now += kLargeInterval;
stats.Update(kSmallSize, now);
std::optional<Frequency> rate = stats.Rate(now);
EXPECT_LT(rate, last_rate);
last_rate = rate;
}
EXPECT_EQ(last_rate, kSmallSize / kLargeInterval);
// Increase rate with more frequent measurments.
for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kSmallInterval) {
SCOPED_TRACE(ToString(i));
now += kSmallInterval;
stats.Update(kSmallSize, now);
std::optional<Frequency> rate = stats.Rate(now);
EXPECT_GE(rate, last_rate);
last_rate = rate;
}
EXPECT_EQ(last_rate, kSmallSize / kSmallInterval);
}
TEST(FrequencyTrackerTest, ResetAfterSilence) {
const TimeDelta kInterval = TimeDelta::Millis(10);
const int64_t kPixels = 640 * 360;
Timestamp now = Timestamp::Seconds(12'345);
FrequencyTracker pixel_rate(kWindow);
// Feed data until window has been filled.
pixel_rate.Update(kPixels, now);
for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kInterval) {
now += kInterval;
pixel_rate.Update(kPixels, now);
}
ASSERT_GT(pixel_rate.Rate(now), Frequency::Zero());
now += kWindow + kEpsilon;
// Silence over window size should trigger auto reset for coming sample.
EXPECT_EQ(pixel_rate.Rate(now), std::nullopt);
pixel_rate.Update(kPixels, now);
// Single measurment after reset is not enough to estimate the rate.
EXPECT_EQ(pixel_rate.Rate(now), std::nullopt);
// Manual reset, add the same check again.
pixel_rate.Reset();
EXPECT_EQ(pixel_rate.Rate(now), std::nullopt);
now += kInterval;
pixel_rate.Update(kPixels, now);
EXPECT_EQ(pixel_rate.Rate(now), std::nullopt);
}
TEST(FrequencyTrackerTest, ReturnsNulloptWhenOverflows) {
Timestamp now = Timestamp::Seconds(12'345);
FrequencyTracker stats(kWindow);
int64_t very_large_number = std::numeric_limits<int64_t>::max();
stats.Update(very_large_number, now);
now += kEpsilon;
stats.Update(very_large_number, now);
EXPECT_EQ(stats.Rate(now), std::nullopt);
}
} // namespace
} // namespace webrtc