blob: 298c147d1664fa0edc13a99e1b9778e716065830 [file]
/*
* Copyright 2026 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 "modules/congestion_controller/scream/loss_rate_estimator.h"
#include <vector>
#include "api/environment/environment.h"
#include "api/transport/network_types.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "modules/congestion_controller/scream/scream_v2_parameters.h"
#include "system_wrappers/include/clock.h"
#include "test/create_test_environment.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
TransportPacketsFeedback CreateFeedbackWithLoss(Timestamp feedback_time,
bool with_loss) {
TransportPacketsFeedback feedback;
feedback.feedback_time = feedback_time;
PacketResult received_packet;
received_packet.sent_packet.size = DataSize::Bytes(1000);
received_packet.receive_time = feedback_time;
feedback.packet_feedbacks.push_back(received_packet);
if (with_loss) {
PacketResult lost_packet;
lost_packet.sent_packet.size = DataSize::Bytes(1000);
lost_packet.receive_time = Timestamp::PlusInfinity();
lost_packet.reported_lost_for_the_first_time = true;
feedback.packet_feedbacks.push_back(lost_packet);
}
return feedback;
}
TEST(LossRateEstimatorTest, EstimatesEwmaLossRate) {
SimulatedClock clock(Timestamp::Seconds(1000));
Environment env = CreateTestEnvironment({.time = &clock});
ScreamV2Parameters params(env.field_trials());
LossRateEstimator estimator(params);
EXPECT_EQ(estimator.loss_event_rate(), 0.0);
// RTT 1: Loss occurs. Since last_rtt_update_time_ starts at MinusInfinity,
// this report executes the RTT boundary update immediately.
TransportPacketsFeedback feedback1 =
CreateFeedbackWithLoss(clock.CurrentTime(), /*with_loss=*/true);
EXPECT_TRUE(estimator.Update(feedback1, TimeDelta::Millis(25)));
EXPECT_NEAR(estimator.loss_event_rate(), 0.1, 0.001);
clock.AdvanceTime(TimeDelta::Millis(25));
// Trigger next RTT boundary update without loss.
TransportPacketsFeedback feedback2 =
CreateFeedbackWithLoss(clock.CurrentTime(), /*with_loss=*/false);
EXPECT_FALSE(estimator.Update(feedback2, TimeDelta::Millis(25)));
EXPECT_NEAR(estimator.loss_event_rate(), 0.08, 0.001);
}
TEST(LossRateEstimatorTest, ClearsLossRateUponFullRecovery) {
SimulatedClock clock(Timestamp::Seconds(1000));
Environment env = CreateTestEnvironment({.time = &clock});
ScreamV2Parameters params(env.field_trials());
LossRateEstimator estimator(params);
// Report loss.
TransportPacketsFeedback feedback1 =
CreateFeedbackWithLoss(clock.CurrentTime(), /*with_loss=*/true);
EXPECT_TRUE(estimator.Update(feedback1, TimeDelta::Millis(25)));
// Trigger RTT boundary update.
clock.AdvanceTime(TimeDelta::Millis(25));
TransportPacketsFeedback feedback2 =
CreateFeedbackWithLoss(clock.CurrentTime(), /*with_loss=*/false);
EXPECT_FALSE(estimator.Update(feedback2, TimeDelta::Millis(25)));
EXPECT_GT(estimator.loss_event_rate(), 0.0);
// Report recovery.
TransportPacketsFeedback recovery_feedback;
recovery_feedback.feedback_time = clock.CurrentTime();
PacketResult recovery_res;
recovery_res.sent_packet.size = DataSize::Bytes(1000);
recovery_res.receive_time = clock.CurrentTime();
recovery_res.reported_recovered_for_the_first_time = true;
recovery_feedback.packet_feedbacks.push_back(recovery_res);
EXPECT_FALSE(estimator.Update(recovery_feedback, TimeDelta::Millis(25)));
EXPECT_EQ(estimator.loss_event_rate(), 0.0);
}
} // namespace
} // namespace webrtc