Adds simulated time scenario client.
Adds SimulatedTimeClient, a class that simulates time so congestion controllers can be tested using the Scenario test framework without running in real time. This allows using simplified scenario tests as unit tests, narrowing the gap between end to end tests and unit tests. Bug: webrtc:9510 Change-Id: I61ab388bd610f636b926675b1f14b8d85e3c1114 Reviewed-on: https://webrtc-review.googlesource.com/99801 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Christoffer Rodbro <crodbro@webrtc.org> Cr-Commit-Position: refs/heads/master@{#24890}
This commit is contained in:

committed by
Commit Bot

parent
1f3206cca4
commit
71a091e24e
@ -166,6 +166,9 @@ if (rtc_include_tests) {
|
|||||||
"probe_controller_unittest.cc",
|
"probe_controller_unittest.cc",
|
||||||
"trendline_estimator_unittest.cc",
|
"trendline_estimator_unittest.cc",
|
||||||
]
|
]
|
||||||
|
if (!build_with_chromium && is_clang) {
|
||||||
|
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
|
||||||
|
}
|
||||||
deps = [
|
deps = [
|
||||||
":alr_detector",
|
":alr_detector",
|
||||||
":delay_based_bwe",
|
":delay_based_bwe",
|
||||||
@ -183,6 +186,7 @@ if (rtc_include_tests) {
|
|||||||
"../../../system_wrappers:field_trial",
|
"../../../system_wrappers:field_trial",
|
||||||
"../../../test:field_trial",
|
"../../../test:field_trial",
|
||||||
"../../../test:test_support",
|
"../../../test:test_support",
|
||||||
|
"../../../test/scenario",
|
||||||
"../../pacing",
|
"../../pacing",
|
||||||
"../../remote_bitrate_estimator",
|
"../../remote_bitrate_estimator",
|
||||||
"../../rtp_rtcp:rtp_rtcp_format",
|
"../../rtp_rtcp:rtp_rtcp_format",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "api/transport/test/network_control_tester.h"
|
#include "api/transport/test/network_control_tester.h"
|
||||||
#include "logging/rtc_event_log/mock/mock_rtc_event_log.h"
|
#include "logging/rtc_event_log/mock/mock_rtc_event_log.h"
|
||||||
#include "modules/congestion_controller/goog_cc/include/goog_cc_factory.h"
|
#include "modules/congestion_controller/goog_cc/include/goog_cc_factory.h"
|
||||||
|
#include "test/scenario/scenario.h"
|
||||||
|
|
||||||
#include "test/gtest.h"
|
#include "test/gtest.h"
|
||||||
|
|
||||||
@ -21,7 +22,6 @@ using testing::Property;
|
|||||||
using testing::_;
|
using testing::_;
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace webrtc_cc {
|
|
||||||
namespace test {
|
namespace test {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -285,9 +285,8 @@ TEST_F(GoogCcNetworkControllerTest, UpdatesDelayBasedEstimate) {
|
|||||||
TEST_F(GoogCcNetworkControllerTest,
|
TEST_F(GoogCcNetworkControllerTest,
|
||||||
FeedbackVersionUpdatesTargetSendRateBasedOnFeedback) {
|
FeedbackVersionUpdatesTargetSendRateBasedOnFeedback) {
|
||||||
GoogCcFeedbackNetworkControllerFactory factory(&event_log_);
|
GoogCcFeedbackNetworkControllerFactory factory(&event_log_);
|
||||||
webrtc::test::NetworkControllerTester tester(&factory,
|
NetworkControllerTester tester(&factory, InitialConfig(60, 0, 600));
|
||||||
InitialConfig(60, 0, 600));
|
auto packet_producer = &SimpleTargetRateProducer::ProduceNext;
|
||||||
auto packet_producer = &webrtc::test::SimpleTargetRateProducer::ProduceNext;
|
|
||||||
|
|
||||||
tester.RunSimulation(TimeDelta::seconds(10), TimeDelta::ms(10),
|
tester.RunSimulation(TimeDelta::seconds(10), TimeDelta::ms(10),
|
||||||
DataRate::kbps(300), TimeDelta::ms(100),
|
DataRate::kbps(300), TimeDelta::ms(100),
|
||||||
@ -308,6 +307,56 @@ TEST_F(GoogCcNetworkControllerTest,
|
|||||||
20);
|
20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(GoogCcNetworkControllerTest, ScenarioQuickTest) {
|
||||||
|
Scenario s("googcc_unit/scenario_quick", false);
|
||||||
|
SimulatedTimeClientConfig config;
|
||||||
|
config.transport.cc =
|
||||||
|
TransportControllerConfig::CongestionController::kGoogCcFeedback;
|
||||||
|
config.transport.rates.min_rate = DataRate::kbps(10);
|
||||||
|
config.transport.rates.max_rate = DataRate::kbps(1500);
|
||||||
|
config.transport.rates.start_rate = DataRate::kbps(300);
|
||||||
|
NetworkNodeConfig net_conf;
|
||||||
|
auto send_net = s.CreateSimulationNode([](NetworkNodeConfig* c) {
|
||||||
|
c->simulation.bandwidth = DataRate::kbps(500);
|
||||||
|
c->simulation.delay = TimeDelta::ms(100);
|
||||||
|
c->update_frequency = TimeDelta::ms(5);
|
||||||
|
});
|
||||||
|
auto ret_net = s.CreateSimulationNode([](NetworkNodeConfig* c) {
|
||||||
|
c->simulation.delay = TimeDelta::ms(100);
|
||||||
|
c->update_frequency = TimeDelta::ms(5);
|
||||||
|
});
|
||||||
|
StatesPrinter* truth = s.CreatePrinter(
|
||||||
|
"send.truth.txt", TimeDelta::PlusInfinity(), {send_net->ConfigPrinter()});
|
||||||
|
SimulatedTimeClient* client = s.CreateSimulatedTimeClient(
|
||||||
|
"send", config, {PacketStreamConfig()}, {send_net}, {ret_net});
|
||||||
|
|
||||||
|
truth->PrintRow();
|
||||||
|
s.RunFor(TimeDelta::seconds(25));
|
||||||
|
truth->PrintRow();
|
||||||
|
EXPECT_NEAR(client->target_rate_kbps(), 450, 100);
|
||||||
|
|
||||||
|
send_net->UpdateConfig([](NetworkNodeConfig* c) {
|
||||||
|
c->simulation.bandwidth = DataRate::kbps(800);
|
||||||
|
c->simulation.delay = TimeDelta::ms(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
truth->PrintRow();
|
||||||
|
s.RunFor(TimeDelta::seconds(20));
|
||||||
|
truth->PrintRow();
|
||||||
|
EXPECT_NEAR(client->target_rate_kbps(), 750, 150);
|
||||||
|
|
||||||
|
send_net->UpdateConfig([](NetworkNodeConfig* c) {
|
||||||
|
c->simulation.bandwidth = DataRate::kbps(100);
|
||||||
|
c->simulation.delay = TimeDelta::ms(200);
|
||||||
|
});
|
||||||
|
ret_net->UpdateConfig(
|
||||||
|
[](NetworkNodeConfig* c) { c->simulation.delay = TimeDelta::ms(200); });
|
||||||
|
|
||||||
|
truth->PrintRow();
|
||||||
|
s.RunFor(TimeDelta::seconds(30));
|
||||||
|
truth->PrintRow();
|
||||||
|
EXPECT_NEAR(client->target_rate_kbps(), 90, 20);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace webrtc_cc
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@ -26,6 +26,8 @@ if (rtc_include_tests) {
|
|||||||
"scenario.h",
|
"scenario.h",
|
||||||
"scenario_config.cc",
|
"scenario_config.cc",
|
||||||
"scenario_config.h",
|
"scenario_config.h",
|
||||||
|
"simulated_time.cc",
|
||||||
|
"simulated_time.h",
|
||||||
"video_stream.cc",
|
"video_stream.cc",
|
||||||
"video_stream.h",
|
"video_stream.h",
|
||||||
]
|
]
|
||||||
|
@ -68,7 +68,7 @@ Scenario::Scenario(std::string file_name, bool real_time)
|
|||||||
base_filename_ = OutputPath() + "output_data/" + file_name;
|
base_filename_ = OutputPath() + "output_data/" + file_name;
|
||||||
RTC_LOG(LS_INFO) << "Saving scenario logs to: " << base_filename_;
|
RTC_LOG(LS_INFO) << "Saving scenario logs to: " << base_filename_;
|
||||||
}
|
}
|
||||||
if (!real_time_mode_) {
|
if (!real_time_mode_ && !base_filename_.empty()) {
|
||||||
rtc::SetClockForTesting(&event_log_fake_clock_);
|
rtc::SetClockForTesting(&event_log_fake_clock_);
|
||||||
event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * 1000);
|
event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * 1000);
|
||||||
}
|
}
|
||||||
@ -104,6 +104,7 @@ StatesPrinter* Scenario::CreatePrinter(std::string name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) {
|
CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) {
|
||||||
|
RTC_DCHECK(real_time_mode_);
|
||||||
CallClient* client = new CallClient(clock_, GetFullPathOrEmpty(name), config);
|
CallClient* client = new CallClient(clock_, GetFullPathOrEmpty(name), config);
|
||||||
if (config.transport.state_log_interval.IsFinite()) {
|
if (config.transport.state_log_interval.IsFinite()) {
|
||||||
Every(config.transport.state_log_interval, [this, client]() {
|
Every(config.transport.state_log_interval, [this, client]() {
|
||||||
@ -122,6 +123,31 @@ CallClient* Scenario::CreateClient(
|
|||||||
return CreateClient(name, config);
|
return CreateClient(name, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SimulatedTimeClient* Scenario::CreateSimulatedTimeClient(
|
||||||
|
std::string name,
|
||||||
|
SimulatedTimeClientConfig config,
|
||||||
|
std::vector<PacketStreamConfig> stream_configs,
|
||||||
|
std::vector<NetworkNode*> send_link,
|
||||||
|
std::vector<NetworkNode*> return_link) {
|
||||||
|
uint64_t send_id = next_receiver_id_++;
|
||||||
|
uint64_t return_id = next_receiver_id_++;
|
||||||
|
SimulatedTimeClient* client = new SimulatedTimeClient(
|
||||||
|
GetFullPathOrEmpty(name), config, stream_configs, send_link, return_link,
|
||||||
|
send_id, return_id, Now());
|
||||||
|
if (!base_filename_.empty() && !name.empty() &&
|
||||||
|
config.transport.state_log_interval.IsFinite()) {
|
||||||
|
Every(config.transport.state_log_interval, [this, client]() {
|
||||||
|
client->network_controller_factory_.LogCongestionControllerStats(Now());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Every(client->GetNetworkControllerProcessInterval(),
|
||||||
|
[this, client] { client->CongestionProcess(Now()); });
|
||||||
|
Every(TimeDelta::ms(5), [this, client] { client->PacerProcess(Now()); });
|
||||||
|
simulated_time_clients_.emplace_back(client);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
SimulationNode* Scenario::CreateSimulationNode(
|
SimulationNode* Scenario::CreateSimulationNode(
|
||||||
std::function<void(NetworkNodeConfig*)> config_modifier) {
|
std::function<void(NetworkNodeConfig*)> config_modifier) {
|
||||||
NetworkNodeConfig config;
|
NetworkNodeConfig config;
|
||||||
@ -313,6 +339,9 @@ void Scenario::RunUntil(TimeDelta max_duration,
|
|||||||
done_.Wait(wait_time.ms<int>());
|
done_.Wait(wait_time.ms<int>());
|
||||||
} else {
|
} else {
|
||||||
sim_clock_.AdvanceTimeMicroseconds(wait_time.us());
|
sim_clock_.AdvanceTimeMicroseconds(wait_time.us());
|
||||||
|
// The fake clock is quite slow to update, we only update it if logging is
|
||||||
|
// turned on to save time.
|
||||||
|
if (!base_filename_.empty())
|
||||||
event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() *
|
event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() *
|
||||||
1000);
|
1000);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "test/scenario/column_printer.h"
|
#include "test/scenario/column_printer.h"
|
||||||
#include "test/scenario/network_node.h"
|
#include "test/scenario/network_node.h"
|
||||||
#include "test/scenario/scenario_config.h"
|
#include "test/scenario/scenario_config.h"
|
||||||
|
#include "test/scenario/simulated_time.h"
|
||||||
#include "test/scenario/video_stream.h"
|
#include "test/scenario/video_stream.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
@ -77,6 +78,13 @@ class Scenario {
|
|||||||
std::string name,
|
std::string name,
|
||||||
std::function<void(CallClientConfig*)> config_modifier);
|
std::function<void(CallClientConfig*)> config_modifier);
|
||||||
|
|
||||||
|
SimulatedTimeClient* CreateSimulatedTimeClient(
|
||||||
|
std::string name,
|
||||||
|
SimulatedTimeClientConfig config,
|
||||||
|
std::vector<PacketStreamConfig> stream_configs,
|
||||||
|
std::vector<NetworkNode*> send_link,
|
||||||
|
std::vector<NetworkNode*> return_link);
|
||||||
|
|
||||||
VideoStreamPair* CreateVideoStream(
|
VideoStreamPair* CreateVideoStream(
|
||||||
CallClient* sender,
|
CallClient* sender,
|
||||||
std::vector<NetworkNode*> send_link,
|
std::vector<NetworkNode*> send_link,
|
||||||
@ -164,6 +172,8 @@ class Scenario {
|
|||||||
std::vector<std::unique_ptr<VideoStreamPair>> video_streams_;
|
std::vector<std::unique_ptr<VideoStreamPair>> video_streams_;
|
||||||
std::vector<std::unique_ptr<AudioStreamPair>> audio_streams_;
|
std::vector<std::unique_ptr<AudioStreamPair>> audio_streams_;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<SimulatedTimeClient>> simulated_time_clients_;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<RepeatedActivity>> repeated_activities_;
|
std::vector<std::unique_ptr<RepeatedActivity>> repeated_activities_;
|
||||||
std::vector<std::unique_ptr<ActionReceiver>> action_receivers_;
|
std::vector<std::unique_ptr<ActionReceiver>> action_receivers_;
|
||||||
std::vector<std::unique_ptr<PendingActivity>> pending_activities_;
|
std::vector<std::unique_ptr<PendingActivity>> pending_activities_;
|
||||||
|
@ -17,6 +17,10 @@ TransportControllerConfig::Rates::Rates(
|
|||||||
const TransportControllerConfig::Rates&) = default;
|
const TransportControllerConfig::Rates&) = default;
|
||||||
TransportControllerConfig::Rates::~Rates() = default;
|
TransportControllerConfig::Rates::~Rates() = default;
|
||||||
|
|
||||||
|
PacketStreamConfig::PacketStreamConfig() = default;
|
||||||
|
PacketStreamConfig::PacketStreamConfig(const PacketStreamConfig&) = default;
|
||||||
|
PacketStreamConfig::~PacketStreamConfig() = default;
|
||||||
|
|
||||||
VideoStreamConfig::Encoder::Encoder() = default;
|
VideoStreamConfig::Encoder::Encoder() = default;
|
||||||
VideoStreamConfig::Encoder::Encoder(const VideoStreamConfig::Encoder&) =
|
VideoStreamConfig::Encoder::Encoder(const VideoStreamConfig::Encoder&) =
|
||||||
default;
|
default;
|
||||||
|
@ -49,6 +49,25 @@ struct CallClientConfig {
|
|||||||
DataRate priority_target_rate = DataRate::Zero();
|
DataRate priority_target_rate = DataRate::Zero();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SimulatedTimeClientConfig {
|
||||||
|
TransportControllerConfig transport;
|
||||||
|
struct Feedback {
|
||||||
|
TimeDelta interval = TimeDelta::ms(100);
|
||||||
|
} feedback;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PacketStreamConfig {
|
||||||
|
PacketStreamConfig();
|
||||||
|
PacketStreamConfig(const PacketStreamConfig&);
|
||||||
|
~PacketStreamConfig();
|
||||||
|
int frame_rate = 30;
|
||||||
|
DataRate max_data_rate = DataRate::Infinity();
|
||||||
|
DataSize max_packet_size = DataSize::bytes(1400);
|
||||||
|
DataSize min_frame_size = DataSize::bytes(100);
|
||||||
|
double keyframe_multiplier = 1;
|
||||||
|
DataSize packet_overhead = DataSize::bytes(PacketOverhead::kDefault);
|
||||||
|
};
|
||||||
|
|
||||||
struct VideoStreamConfig {
|
struct VideoStreamConfig {
|
||||||
bool autostart = true;
|
bool autostart = true;
|
||||||
struct Source {
|
struct Source {
|
||||||
|
348
test/scenario/simulated_time.cc
Normal file
348
test/scenario/simulated_time.cc
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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 "test/scenario/simulated_time.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "rtc_base/format_macros.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
namespace {
|
||||||
|
struct RawFeedbackReportPacket {
|
||||||
|
static constexpr int MAX_FEEDBACKS = 10;
|
||||||
|
struct Feedback {
|
||||||
|
int16_t seq_offset;
|
||||||
|
int32_t recv_offset_ms;
|
||||||
|
};
|
||||||
|
uint8_t count;
|
||||||
|
int64_t first_seq_num;
|
||||||
|
int64_t first_recv_time_ms;
|
||||||
|
Feedback feedbacks[MAX_FEEDBACKS - 1];
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PacketStream::PacketStream(PacketStreamConfig config) : config_(config) {}
|
||||||
|
|
||||||
|
std::vector<int64_t> PacketStream::PullPackets(Timestamp at_time) {
|
||||||
|
if (next_frame_time_.IsInfinite())
|
||||||
|
next_frame_time_ = at_time;
|
||||||
|
|
||||||
|
TimeDelta frame_interval = TimeDelta::seconds(1) / config_.frame_rate;
|
||||||
|
int64_t frame_allowance = (frame_interval * target_rate_).bytes();
|
||||||
|
|
||||||
|
if (next_frame_is_keyframe_) {
|
||||||
|
frame_allowance *= config_.keyframe_multiplier;
|
||||||
|
next_frame_is_keyframe_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int64_t> packets;
|
||||||
|
while (at_time >= next_frame_time_) {
|
||||||
|
next_frame_time_ += frame_interval;
|
||||||
|
|
||||||
|
int64_t frame_size = budget_ + frame_allowance;
|
||||||
|
frame_size = std::max(frame_size, config_.min_frame_size.bytes());
|
||||||
|
budget_ += frame_allowance - frame_size;
|
||||||
|
|
||||||
|
int64_t packet_budget = frame_size;
|
||||||
|
int64_t max_packet_size = config_.max_packet_size.bytes();
|
||||||
|
while (packet_budget > max_packet_size) {
|
||||||
|
packets.push_back(max_packet_size);
|
||||||
|
packet_budget -= max_packet_size;
|
||||||
|
}
|
||||||
|
packets.push_back(packet_budget);
|
||||||
|
}
|
||||||
|
for (int64_t& packet : packets)
|
||||||
|
packet += config_.packet_overhead.bytes();
|
||||||
|
return packets;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PacketStream::OnTargetRateUpdate(DataRate target_rate) {
|
||||||
|
target_rate_ = std::min(target_rate, config_.max_data_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleFeedbackReportPacket FeedbackFromBuffer(
|
||||||
|
rtc::CopyOnWriteBuffer raw_buffer) {
|
||||||
|
RTC_CHECK_LE(sizeof(RawFeedbackReportPacket), raw_buffer.size());
|
||||||
|
const RawFeedbackReportPacket& raw_packet =
|
||||||
|
*reinterpret_cast<const RawFeedbackReportPacket*>(raw_buffer.cdata());
|
||||||
|
RTC_CHECK_GE(raw_packet.count, 1);
|
||||||
|
SimpleFeedbackReportPacket packet;
|
||||||
|
packet.receive_times.emplace_back(SimpleFeedbackReportPacket::ReceiveInfo{
|
||||||
|
raw_packet.first_seq_num, Timestamp::ms(raw_packet.first_recv_time_ms)});
|
||||||
|
for (int i = 1; i < raw_packet.count; ++i)
|
||||||
|
packet.receive_times.emplace_back(SimpleFeedbackReportPacket::ReceiveInfo{
|
||||||
|
raw_packet.first_seq_num + raw_packet.feedbacks[i - 1].seq_offset,
|
||||||
|
Timestamp::ms(raw_packet.first_recv_time_ms +
|
||||||
|
raw_packet.feedbacks[i - 1].recv_offset_ms)});
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
rtc::CopyOnWriteBuffer FeedbackToBuffer(
|
||||||
|
const SimpleFeedbackReportPacket packet) {
|
||||||
|
RTC_CHECK_LE(packet.receive_times.size(),
|
||||||
|
RawFeedbackReportPacket::MAX_FEEDBACKS);
|
||||||
|
RawFeedbackReportPacket report;
|
||||||
|
report.count = packet.receive_times.size();
|
||||||
|
RTC_CHECK(!packet.receive_times.empty());
|
||||||
|
report.first_seq_num = packet.receive_times.front().sequence_number;
|
||||||
|
report.first_recv_time_ms = packet.receive_times.front().receive_time.ms();
|
||||||
|
|
||||||
|
for (int i = 1; i < report.count; ++i) {
|
||||||
|
report.feedbacks[i - 1].seq_offset = static_cast<int16_t>(
|
||||||
|
packet.receive_times[i].sequence_number - report.first_seq_num);
|
||||||
|
report.feedbacks[i - 1].recv_offset_ms = static_cast<int32_t>(
|
||||||
|
packet.receive_times[i].receive_time.ms() - report.first_recv_time_ms);
|
||||||
|
}
|
||||||
|
return rtc::CopyOnWriteBuffer(reinterpret_cast<uint8_t*>(&report),
|
||||||
|
sizeof(RawFeedbackReportPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
SimulatedSender::SimulatedSender(NetworkNode* send_node,
|
||||||
|
uint64_t send_receiver_id)
|
||||||
|
: send_node_(send_node), send_receiver_id_(send_receiver_id) {}
|
||||||
|
|
||||||
|
SimulatedSender::~SimulatedSender() {}
|
||||||
|
|
||||||
|
TransportPacketsFeedback SimulatedSender::PullFeedbackReport(
|
||||||
|
SimpleFeedbackReportPacket packet,
|
||||||
|
Timestamp at_time) {
|
||||||
|
TransportPacketsFeedback report;
|
||||||
|
report.prior_in_flight = data_in_flight_;
|
||||||
|
report.feedback_time = at_time;
|
||||||
|
|
||||||
|
for (auto& receive_info : packet.receive_times) {
|
||||||
|
// Look up sender side information for all packets up to and including each
|
||||||
|
// packet with feedback in the report.
|
||||||
|
for (; next_feedback_seq_num_ <= receive_info.sequence_number;
|
||||||
|
++next_feedback_seq_num_) {
|
||||||
|
PacketResult feedback;
|
||||||
|
if (next_feedback_seq_num_ == receive_info.sequence_number) {
|
||||||
|
feedback.receive_time = receive_info.receive_time;
|
||||||
|
} else {
|
||||||
|
// If we did not get any feedback for this packet, mark it as lost by
|
||||||
|
// setting receive time to infinity. Note that this can also happen due
|
||||||
|
// to reordering, we will newer send feedback out of order. In this case
|
||||||
|
// the packet was not really lost, but we don't have that information.
|
||||||
|
feedback.receive_time = Timestamp::PlusInfinity();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looking up send side information.
|
||||||
|
for (auto it = sent_packets_.begin(); it != sent_packets_.end(); ++it) {
|
||||||
|
if (it->sequence_number == next_feedback_seq_num_) {
|
||||||
|
feedback.sent_packet = *it;
|
||||||
|
if (feedback.receive_time.IsFinite())
|
||||||
|
sent_packets_.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data_in_flight_ -= feedback.sent_packet->size;
|
||||||
|
report.packet_feedbacks.push_back(feedback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report.data_in_flight = data_in_flight_;
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies pacing and congetsion window based on the configuration from the
|
||||||
|
// congestion controller. This is not a complete implementation of the real
|
||||||
|
// pacer but useful for unit tests since it isn't limited to real time.
|
||||||
|
std::vector<SimulatedSender::PacketReadyToSend>
|
||||||
|
SimulatedSender::PaceAndPullSendPackets(Timestamp at_time) {
|
||||||
|
// TODO(srte): Extract the behavior of PacedSender to a threading and time
|
||||||
|
// independent component and use that here to allow a truthful simulation.
|
||||||
|
if (last_update_.IsInfinite()) {
|
||||||
|
pacing_budget_ = 0;
|
||||||
|
} else {
|
||||||
|
TimeDelta delta = at_time - last_update_;
|
||||||
|
pacing_budget_ += (delta * pacer_config_.data_rate()).bytes();
|
||||||
|
}
|
||||||
|
std::vector<PacketReadyToSend> to_send;
|
||||||
|
while (data_in_flight_ <= max_in_flight_ && pacing_budget_ >= 0 &&
|
||||||
|
!packet_queue_.empty()) {
|
||||||
|
PendingPacket pending = packet_queue_.front();
|
||||||
|
pacing_budget_ -= pending.size;
|
||||||
|
packet_queue_.pop_front();
|
||||||
|
SentPacket sent;
|
||||||
|
sent.sequence_number = next_sequence_number_++;
|
||||||
|
sent.size = DataSize::bytes(pending.size);
|
||||||
|
data_in_flight_ += sent.size;
|
||||||
|
sent.data_in_flight = data_in_flight_;
|
||||||
|
sent.pacing_info = PacedPacketInfo();
|
||||||
|
sent.send_time = at_time;
|
||||||
|
sent_packets_.push_back(sent);
|
||||||
|
rtc::CopyOnWriteBuffer packet(
|
||||||
|
std::max<size_t>(pending.size, sizeof(sent.sequence_number)));
|
||||||
|
memcpy(packet.data(), &sent.sequence_number, sizeof(sent.sequence_number));
|
||||||
|
to_send.emplace_back(PacketReadyToSend{sent, packet});
|
||||||
|
}
|
||||||
|
pacing_budget_ = std::min<int64_t>(pacing_budget_, 0);
|
||||||
|
last_update_ = at_time;
|
||||||
|
return to_send;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedSender::Update(NetworkControlUpdate update) {
|
||||||
|
if (update.pacer_config)
|
||||||
|
pacer_config_ = *update.pacer_config;
|
||||||
|
if (update.congestion_window)
|
||||||
|
max_in_flight_ = *update.congestion_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
SimulatedFeedback::SimulatedFeedback(SimulatedTimeClientConfig config,
|
||||||
|
uint64_t return_receiver_id,
|
||||||
|
NetworkNode* return_node)
|
||||||
|
: config_(config),
|
||||||
|
return_receiver_id_(return_receiver_id),
|
||||||
|
return_node_(return_node) {}
|
||||||
|
|
||||||
|
// Polls receiver side for a feedback report and sends it to the stream sender
|
||||||
|
// via return_node_,
|
||||||
|
bool SimulatedFeedback::TryDeliverPacket(rtc::CopyOnWriteBuffer packet,
|
||||||
|
uint64_t receiver,
|
||||||
|
Timestamp at_time) {
|
||||||
|
int64_t sequence_number;
|
||||||
|
memcpy(&sequence_number, packet.cdata(), sizeof(sequence_number));
|
||||||
|
receive_times_.insert({sequence_number, at_time});
|
||||||
|
if (last_feedback_time_.IsInfinite())
|
||||||
|
last_feedback_time_ = at_time;
|
||||||
|
if (at_time >= last_feedback_time_ + config_.feedback.interval) {
|
||||||
|
SimpleFeedbackReportPacket report;
|
||||||
|
for (; next_feedback_seq_num_ <= sequence_number;
|
||||||
|
++next_feedback_seq_num_) {
|
||||||
|
auto it = receive_times_.find(next_feedback_seq_num_);
|
||||||
|
if (it != receive_times_.end()) {
|
||||||
|
report.receive_times.emplace_back(
|
||||||
|
SimpleFeedbackReportPacket::ReceiveInfo{next_feedback_seq_num_,
|
||||||
|
it->second});
|
||||||
|
receive_times_.erase(it);
|
||||||
|
}
|
||||||
|
if (receive_times_.size() >= RawFeedbackReportPacket::MAX_FEEDBACKS) {
|
||||||
|
return_node_->TryDeliverPacket(FeedbackToBuffer(report),
|
||||||
|
return_receiver_id_, at_time);
|
||||||
|
report = SimpleFeedbackReportPacket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!report.receive_times.empty())
|
||||||
|
return_node_->TryDeliverPacket(FeedbackToBuffer(report),
|
||||||
|
return_receiver_id_, at_time);
|
||||||
|
last_feedback_time_ = at_time;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SimulatedTimeClient::SimulatedTimeClient(
|
||||||
|
std::string log_filename,
|
||||||
|
SimulatedTimeClientConfig config,
|
||||||
|
std::vector<PacketStreamConfig> stream_configs,
|
||||||
|
std::vector<NetworkNode*> send_link,
|
||||||
|
std::vector<NetworkNode*> return_link,
|
||||||
|
uint64_t send_receiver_id,
|
||||||
|
uint64_t return_receiver_id,
|
||||||
|
Timestamp at_time)
|
||||||
|
: network_controller_factory_(log_filename, config.transport),
|
||||||
|
send_link_(send_link),
|
||||||
|
return_link_(return_link),
|
||||||
|
sender_(send_link.front(), send_receiver_id),
|
||||||
|
feedback_(config, return_receiver_id, return_link.front()) {
|
||||||
|
NetworkControllerConfig initial_config;
|
||||||
|
initial_config.constraints.at_time = at_time;
|
||||||
|
initial_config.constraints.starting_rate = config.transport.rates.start_rate;
|
||||||
|
initial_config.constraints.min_data_rate = config.transport.rates.min_rate;
|
||||||
|
initial_config.constraints.max_data_rate = config.transport.rates.max_rate;
|
||||||
|
congestion_controller_ = network_controller_factory_.Create(initial_config);
|
||||||
|
for (auto& stream_config : stream_configs)
|
||||||
|
packet_streams_.emplace_back(new PacketStream(stream_config));
|
||||||
|
NetworkNode::Route(send_receiver_id, send_link, &feedback_);
|
||||||
|
NetworkNode::Route(return_receiver_id, return_link, this);
|
||||||
|
|
||||||
|
CongestionProcess(at_time);
|
||||||
|
network_controller_factory_.LogCongestionControllerStats(at_time);
|
||||||
|
if (!log_filename.empty()) {
|
||||||
|
std::string packet_log_name = log_filename + ".packets.txt";
|
||||||
|
packet_log_ = fopen(packet_log_name.c_str(), "w");
|
||||||
|
fprintf(packet_log_,
|
||||||
|
"transport_seq packet_size send_time recv_time feed_time\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pulls feedback reports from sender side based on the recieved feedback
|
||||||
|
// packet. Updates congestion controller with the resulting report.
|
||||||
|
bool SimulatedTimeClient::TryDeliverPacket(rtc::CopyOnWriteBuffer raw_buffer,
|
||||||
|
uint64_t receiver,
|
||||||
|
Timestamp at_time) {
|
||||||
|
auto report =
|
||||||
|
sender_.PullFeedbackReport(FeedbackFromBuffer(raw_buffer), at_time);
|
||||||
|
for (PacketResult& feedback : report.packet_feedbacks) {
|
||||||
|
if (packet_log_)
|
||||||
|
fprintf(packet_log_, "%" PRId64 " %" PRId64 " %.3lf %.3lf %.3lf\n",
|
||||||
|
feedback.sent_packet->sequence_number,
|
||||||
|
feedback.sent_packet->size.bytes(),
|
||||||
|
feedback.sent_packet->send_time.seconds<double>(),
|
||||||
|
feedback.receive_time.seconds<double>(),
|
||||||
|
at_time.seconds<double>());
|
||||||
|
}
|
||||||
|
Update(congestion_controller_->OnTransportPacketsFeedback(report));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
SimulatedTimeClient::~SimulatedTimeClient() {
|
||||||
|
if (packet_log_)
|
||||||
|
fclose(packet_log_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedTimeClient::Update(NetworkControlUpdate update) {
|
||||||
|
sender_.Update(update);
|
||||||
|
if (update.target_rate) {
|
||||||
|
// TODO(srte): Implement more realistic distribution of bandwidths between
|
||||||
|
// streams. Either using BitrateAllocationStrategy directly or using
|
||||||
|
// BitrateAllocation.
|
||||||
|
double ratio_per_stream = 1.0 / packet_streams_.size();
|
||||||
|
DataRate rate_per_stream =
|
||||||
|
update.target_rate->target_rate * ratio_per_stream;
|
||||||
|
target_rate_ = update.target_rate->target_rate;
|
||||||
|
for (auto& stream : packet_streams_)
|
||||||
|
stream->OnTargetRateUpdate(rate_per_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedTimeClient::CongestionProcess(Timestamp at_time) {
|
||||||
|
ProcessInterval msg;
|
||||||
|
msg.at_time = at_time;
|
||||||
|
Update(congestion_controller_->OnProcessInterval(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedTimeClient::PacerProcess(Timestamp at_time) {
|
||||||
|
ProcessFrames(at_time);
|
||||||
|
for (auto to_send : sender_.PaceAndPullSendPackets(at_time)) {
|
||||||
|
sender_.send_node_->TryDeliverPacket(to_send.data,
|
||||||
|
sender_.send_receiver_id_, at_time);
|
||||||
|
Update(congestion_controller_->OnSentPacket(to_send.send_info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedTimeClient::ProcessFrames(Timestamp at_time) {
|
||||||
|
for (auto& stream : packet_streams_) {
|
||||||
|
for (int64_t packet_size : stream->PullPackets(at_time)) {
|
||||||
|
sender_.packet_queue_.push_back(
|
||||||
|
SimulatedSender::PendingPacket{packet_size});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeDelta SimulatedTimeClient::GetNetworkControllerProcessInterval() const {
|
||||||
|
return network_controller_factory_.GetProcessInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
double SimulatedTimeClient::target_rate_kbps() const {
|
||||||
|
return target_rate_.kbps<double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
155
test/scenario/simulated_time.h
Normal file
155
test/scenario/simulated_time.h
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*/
|
||||||
|
#ifndef TEST_SCENARIO_SIMULATED_TIME_H_
|
||||||
|
#define TEST_SCENARIO_SIMULATED_TIME_H_
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "test/scenario/call_client.h"
|
||||||
|
#include "test/scenario/column_printer.h"
|
||||||
|
#include "test/scenario/network_node.h"
|
||||||
|
#include "test/scenario/scenario_config.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace test {
|
||||||
|
class PacketStream {
|
||||||
|
public:
|
||||||
|
explicit PacketStream(PacketStreamConfig config);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<int64_t> PullPackets(Timestamp at_time);
|
||||||
|
void OnTargetRateUpdate(DataRate target_rate);
|
||||||
|
|
||||||
|
friend class SimulatedTimeClient;
|
||||||
|
PacketStreamConfig config_;
|
||||||
|
bool next_frame_is_keyframe_ = true;
|
||||||
|
Timestamp next_frame_time_ = Timestamp::MinusInfinity();
|
||||||
|
DataRate target_rate_ = DataRate::Zero();
|
||||||
|
int64_t budget_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SimulatedFeedback : NetworkReceiverInterface {
|
||||||
|
public:
|
||||||
|
SimulatedFeedback(SimulatedTimeClientConfig config,
|
||||||
|
uint64_t return_receiver_id,
|
||||||
|
NetworkNode* return_node);
|
||||||
|
bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet,
|
||||||
|
uint64_t receiver,
|
||||||
|
Timestamp at_time) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class SimulatedTimeClient;
|
||||||
|
const SimulatedTimeClientConfig config_;
|
||||||
|
const uint64_t return_receiver_id_;
|
||||||
|
NetworkNode* return_node_;
|
||||||
|
Timestamp last_feedback_time_ = Timestamp::MinusInfinity();
|
||||||
|
int32_t next_feedback_seq_num_ = 1;
|
||||||
|
std::map<int64_t, Timestamp> receive_times_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SimpleFeedbackReportPacket {
|
||||||
|
struct ReceiveInfo {
|
||||||
|
int64_t sequence_number;
|
||||||
|
Timestamp receive_time;
|
||||||
|
};
|
||||||
|
std::vector<ReceiveInfo> receive_times;
|
||||||
|
};
|
||||||
|
|
||||||
|
SimpleFeedbackReportPacket FeedbackFromBuffer(
|
||||||
|
rtc::CopyOnWriteBuffer raw_buffer);
|
||||||
|
rtc::CopyOnWriteBuffer FeedbackToBuffer(
|
||||||
|
const SimpleFeedbackReportPacket packet);
|
||||||
|
|
||||||
|
class SimulatedSender {
|
||||||
|
public:
|
||||||
|
struct PacketReadyToSend {
|
||||||
|
SentPacket send_info;
|
||||||
|
rtc::CopyOnWriteBuffer data;
|
||||||
|
};
|
||||||
|
struct PendingPacket {
|
||||||
|
int64_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
SimulatedSender(NetworkNode* send_node, uint64_t send_receiver_id);
|
||||||
|
SimulatedSender(const SimulatedSender&) = delete;
|
||||||
|
~SimulatedSender();
|
||||||
|
TransportPacketsFeedback PullFeedbackReport(SimpleFeedbackReportPacket report,
|
||||||
|
Timestamp at_time);
|
||||||
|
std::vector<PacketReadyToSend> PaceAndPullSendPackets(Timestamp at_time);
|
||||||
|
void Update(NetworkControlUpdate update);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class SimulatedTimeClient;
|
||||||
|
NetworkNode* send_node_;
|
||||||
|
uint64_t send_receiver_id_;
|
||||||
|
PacerConfig pacer_config_;
|
||||||
|
DataSize max_in_flight_ = DataSize::Infinity();
|
||||||
|
|
||||||
|
std::deque<PendingPacket> packet_queue_;
|
||||||
|
std::vector<SentPacket> sent_packets_;
|
||||||
|
|
||||||
|
Timestamp last_update_ = Timestamp::MinusInfinity();
|
||||||
|
int64_t pacing_budget_ = 0;
|
||||||
|
int64_t next_sequence_number_ = 1;
|
||||||
|
int64_t next_feedback_seq_num_ = 1;
|
||||||
|
DataSize data_in_flight_ = DataSize::Zero();
|
||||||
|
};
|
||||||
|
|
||||||
|
// SimulatedTimeClient emulates core parts of the behavior of WebRTC from the
|
||||||
|
// perspective of congestion controllers. This is intended for use in functional
|
||||||
|
// unit tests to ensure that congestion controllers behave in a reasonable way.
|
||||||
|
// It does not, however, completely simulate the actual behavior of WebRTC. For
|
||||||
|
// a more accurate simulation, use the real time only CallClient.
|
||||||
|
class SimulatedTimeClient : NetworkReceiverInterface {
|
||||||
|
public:
|
||||||
|
SimulatedTimeClient(std::string log_filename,
|
||||||
|
SimulatedTimeClientConfig config,
|
||||||
|
std::vector<PacketStreamConfig> stream_configs,
|
||||||
|
std::vector<NetworkNode*> send_link,
|
||||||
|
std::vector<NetworkNode*> return_link,
|
||||||
|
uint64_t send_receiver_id,
|
||||||
|
uint64_t return_receiver_id,
|
||||||
|
Timestamp at_time);
|
||||||
|
SimulatedTimeClient(const SimulatedTimeClient&) = delete;
|
||||||
|
~SimulatedTimeClient();
|
||||||
|
void Update(NetworkControlUpdate update);
|
||||||
|
void CongestionProcess(Timestamp at_time);
|
||||||
|
void PacerProcess(Timestamp at_time);
|
||||||
|
void ProcessFrames(Timestamp at_time);
|
||||||
|
TimeDelta GetNetworkControllerProcessInterval() const;
|
||||||
|
double target_rate_kbps() const;
|
||||||
|
|
||||||
|
bool TryDeliverPacket(rtc::CopyOnWriteBuffer packet,
|
||||||
|
uint64_t receiver,
|
||||||
|
Timestamp at_time) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Scenario;
|
||||||
|
LoggingNetworkControllerFactory network_controller_factory_;
|
||||||
|
std::unique_ptr<NetworkControllerInterface> congestion_controller_;
|
||||||
|
std::vector<NetworkNode*> send_link_;
|
||||||
|
std::vector<NetworkNode*> return_link_;
|
||||||
|
SimulatedSender sender_;
|
||||||
|
SimulatedFeedback feedback_;
|
||||||
|
DataRate target_rate_ = DataRate::Infinity();
|
||||||
|
FILE* packet_log_ = nullptr;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<PacketStream>> packet_streams_;
|
||||||
|
};
|
||||||
|
} // namespace test
|
||||||
|
} // namespace webrtc
|
||||||
|
|
||||||
|
#endif // TEST_SCENARIO_SIMULATED_TIME_H_
|
Reference in New Issue
Block a user