| /* |
| * 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 |