/* * 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/scenario.h" #include #include "absl/memory/memory.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "rtc_base/flags.h" #include "rtc_base/socket_address.h" #include "test/logging/file_log_writer.h" #include "test/scenario/network/network_emulation.h" #include "test/testsupport/file_utils.h" WEBRTC_DEFINE_bool(scenario_logs, false, "Save logs from scenario framework."); WEBRTC_DEFINE_string(scenario_logs_root, "", "Output root path, based on project root if unset."); namespace webrtc { namespace test { namespace { int64_t kMicrosPerSec = 1000000; std::unique_ptr GetScenarioLogManager( std::string file_name) { if (FLAG_scenario_logs && !file_name.empty()) { std::string output_root = FLAG_scenario_logs_root; if (output_root.empty()) output_root = OutputPath() + "output_data/"; auto base_filename = output_root + file_name + "."; RTC_LOG(LS_INFO) << "Saving scenario logs to: " << base_filename; return absl::make_unique(base_filename); } return nullptr; } } RepeatedActivity::RepeatedActivity(TimeDelta interval, std::function function) : interval_(interval), function_(function) {} void RepeatedActivity::Stop() { interval_ = TimeDelta::PlusInfinity(); } void RepeatedActivity::Poll(Timestamp time) { RTC_DCHECK(last_update_.IsFinite()); if (time >= last_update_ + interval_) { function_(time - last_update_); last_update_ = time; } } void RepeatedActivity::SetStartTime(Timestamp time) { last_update_ = time; } Timestamp RepeatedActivity::NextTime() { RTC_DCHECK(last_update_.IsFinite()); return last_update_ + interval_; } Scenario::Scenario() : Scenario(std::unique_ptr(), true) {} Scenario::Scenario(std::string file_name) : Scenario(file_name, true) {} Scenario::Scenario(std::string file_name, bool real_time) : Scenario(GetScenarioLogManager(file_name), real_time) {} Scenario::Scenario( std::unique_ptr log_writer_factory, bool real_time) : log_writer_factory_(std::move(log_writer_factory)), real_time_mode_(real_time), sim_clock_(100000 * kMicrosPerSec), clock_(real_time ? Clock::GetRealTimeClock() : &sim_clock_), audio_decoder_factory_(CreateBuiltinAudioDecoderFactory()), audio_encoder_factory_(CreateBuiltinAudioEncoderFactory()) { if (!real_time_mode_ && log_writer_factory_) { rtc::SetClockForTesting(&event_log_fake_clock_); event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * 1000); } } Scenario::~Scenario() { if (start_time_.IsFinite()) Stop(); if (!real_time_mode_) rtc::SetClockForTesting(nullptr); } ColumnPrinter Scenario::TimePrinter() { return ColumnPrinter::Lambda("time", [this](rtc::SimpleStringBuilder& sb) { sb.AppendFormat("%.3lf", Now().seconds()); }, 32); } StatesPrinter* Scenario::CreatePrinter(std::string name, TimeDelta interval, std::vector printers) { std::vector all_printers{TimePrinter()}; for (auto& printer : printers) all_printers.push_back(printer); StatesPrinter* printer = new StatesPrinter(GetLogWriter(name), all_printers); printers_.emplace_back(printer); printer->PrintHeaders(); if (interval.IsFinite()) Every(interval, [printer] { printer->PrintRow(); }); return printer; } CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) { RTC_DCHECK(real_time_mode_); CallClient* client = new CallClient(clock_, GetLogWriterFactory(name), config); if (config.transport.state_log_interval.IsFinite()) { Every(config.transport.state_log_interval, [this, client]() { client->network_controller_factory_.LogCongestionControllerStats(Now()); }); } clients_.emplace_back(client); return client; } CallClient* Scenario::CreateClient( std::string name, std::function config_modifier) { CallClientConfig config; config_modifier(&config); return CreateClient(name, config); } CallClientPair* Scenario::CreateRoutes( CallClient* first, std::vector send_link, CallClient* second, std::vector return_link) { return CreateRoutes(first, send_link, DataSize::bytes(PacketOverhead::kDefault), second, return_link, DataSize::bytes(PacketOverhead::kDefault)); } CallClientPair* Scenario::CreateRoutes( CallClient* first, std::vector send_link, DataSize first_overhead, CallClient* second, std::vector return_link, DataSize second_overhead) { CallClientPair* client_pair = new CallClientPair(first, second); ChangeRoute(client_pair->forward(), send_link, first_overhead); ChangeRoute(client_pair->reverse(), return_link, second_overhead); client_pairs_.emplace_back(client_pair); return client_pair; } void Scenario::ChangeRoute(std::pair clients, std::vector over_nodes) { ChangeRoute(clients, over_nodes, DataSize::bytes(PacketOverhead::kDefault)); } void Scenario::ChangeRoute(std::pair clients, std::vector over_nodes, DataSize overhead) { uint64_t route_id = next_route_id_++; clients.second->route_overhead_.insert({route_id, overhead}); EmulatedNetworkNode::CreateRoute(route_id, over_nodes, clients.second); clients.first->transport_.Connect(over_nodes.front(), route_id, overhead); } SimulatedTimeClient* Scenario::CreateSimulatedTimeClient( std::string name, SimulatedTimeClientConfig config, std::vector stream_configs, std::vector send_link, std::vector return_link) { uint64_t send_id = next_route_id_++; uint64_t return_id = next_route_id_++; SimulatedTimeClient* client = new SimulatedTimeClient( GetLogWriterFactory(name), config, stream_configs, send_link, return_link, send_id, return_id, Now()); if (log_writer_factory_ && !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( std::function config_modifier) { NetworkNodeConfig config; config_modifier(&config); return CreateSimulationNode(config); } SimulationNode* Scenario::CreateSimulationNode(NetworkNodeConfig config) { RTC_DCHECK(config.mode == NetworkNodeConfig::TrafficMode::kSimulation); auto network_node = SimulationNode::Create(config); SimulationNode* sim_node = network_node.get(); network_nodes_.emplace_back(std::move(network_node)); Every(config.update_frequency, [this, sim_node] { sim_node->Process(Now()); }); return sim_node; } EmulatedNetworkNode* Scenario::CreateNetworkNode( NetworkNodeConfig config, std::unique_ptr behavior) { RTC_DCHECK(config.mode == NetworkNodeConfig::TrafficMode::kCustom); network_nodes_.emplace_back(new EmulatedNetworkNode(std::move(behavior))); EmulatedNetworkNode* network_node = network_nodes_.back().get(); Every(config.update_frequency, [this, network_node] { network_node->Process(Now()); }); return network_node; } void Scenario::TriggerPacketBurst(std::vector over_nodes, size_t num_packets, size_t packet_size) { uint64_t route_id = next_route_id_++; EmulatedNetworkNode::CreateRoute(route_id, over_nodes, &null_receiver_); for (size_t i = 0; i < num_packets; ++i) over_nodes[0]->OnPacketReceived(EmulatedIpPacket( rtc::SocketAddress() /*from*/, rtc::SocketAddress(), /*to*/ route_id, rtc::CopyOnWriteBuffer(packet_size), Now())); } void Scenario::NetworkDelayedAction( std::vector over_nodes, size_t packet_size, std::function action) { uint64_t route_id = next_route_id_++; action_receivers_.emplace_back(new ActionReceiver(action)); EmulatedNetworkNode::CreateRoute(route_id, over_nodes, action_receivers_.back().get()); over_nodes[0]->OnPacketReceived(EmulatedIpPacket( rtc::SocketAddress() /*from*/, rtc::SocketAddress() /*to*/, route_id, rtc::CopyOnWriteBuffer(packet_size), Now())); } CrossTrafficSource* Scenario::CreateCrossTraffic( std::vector over_nodes, std::function config_modifier) { CrossTrafficConfig cross_config; config_modifier(&cross_config); return CreateCrossTraffic(over_nodes, cross_config); } CrossTrafficSource* Scenario::CreateCrossTraffic( std::vector over_nodes, CrossTrafficConfig config) { uint64_t route_id = next_route_id_++; cross_traffic_sources_.emplace_back( new CrossTrafficSource(over_nodes.front(), route_id, config)); CrossTrafficSource* node = cross_traffic_sources_.back().get(); EmulatedNetworkNode::CreateRoute(route_id, over_nodes, &null_receiver_); Every(config.min_packet_interval, [this, node](TimeDelta delta) { node->Process(Now(), delta); }); return node; } VideoStreamPair* Scenario::CreateVideoStream( std::pair clients, std::function config_modifier) { VideoStreamConfig config; config_modifier(&config); return CreateVideoStream(clients, config); } VideoStreamPair* Scenario::CreateVideoStream( std::pair clients, VideoStreamConfig config) { std::unique_ptr quality_logger; if (config.analyzer.log_to_file) quality_logger = clients.first->GetLogWriter(".video_quality.txt"); video_streams_.emplace_back(new VideoStreamPair( clients.first, clients.second, config, std::move(quality_logger))); return video_streams_.back().get(); } AudioStreamPair* Scenario::CreateAudioStream( std::pair clients, std::function config_modifier) { AudioStreamConfig config; config_modifier(&config); return CreateAudioStream(clients, config); } AudioStreamPair* Scenario::CreateAudioStream( std::pair clients, AudioStreamConfig config) { audio_streams_.emplace_back( new AudioStreamPair(clients.first, audio_encoder_factory_, clients.second, audio_decoder_factory_, config)); return audio_streams_.back().get(); } RepeatedActivity* Scenario::Every(TimeDelta interval, std::function function) { repeated_activities_.emplace_back(new RepeatedActivity(interval, function)); if (start_time_.IsFinite()) { repeated_activities_.back()->SetStartTime(Now()); } return repeated_activities_.back().get(); } RepeatedActivity* Scenario::Every(TimeDelta interval, std::function function) { auto function_with_argument = [function](TimeDelta) { function(); }; repeated_activities_.emplace_back( new RepeatedActivity(interval, function_with_argument)); if (start_time_.IsFinite()) { repeated_activities_.back()->SetStartTime(Now()); } return repeated_activities_.back().get(); } void Scenario::At(TimeDelta offset, std::function function) { pending_activities_.emplace_back(new PendingActivity{offset, function}); } void Scenario::RunFor(TimeDelta duration) { RunUntil(Duration() + duration); } void Scenario::RunUntil(TimeDelta max_duration) { RunUntil(max_duration, TimeDelta::PlusInfinity(), []() { return false; }); } void Scenario::RunUntil(TimeDelta max_duration, TimeDelta poll_interval, std::function exit_function) { if (start_time_.IsInfinite()) Start(); rtc::Event done_; while (!exit_function() && Duration() < max_duration) { Timestamp current_time = Now(); TimeDelta duration = current_time - start_time_; Timestamp next_time = current_time + poll_interval; for (auto& activity : repeated_activities_) { activity->Poll(current_time); next_time = std::min(next_time, activity->NextTime()); } for (auto activity = pending_activities_.begin(); activity < pending_activities_.end(); activity++) { if (duration > (*activity)->after_duration) { (*activity)->function(); pending_activities_.erase(activity); } } TimeDelta wait_time = next_time - current_time; if (real_time_mode_) { done_.Wait(wait_time.ms()); } else { 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 (log_writer_factory_) event_log_fake_clock_.SetTimeNanos(sim_clock_.TimeInMicroseconds() * 1000); } } } void Scenario::Start() { start_time_ = Timestamp::us(clock_->TimeInMicroseconds()); for (auto& activity : repeated_activities_) { activity->SetStartTime(start_time_); } for (auto& stream_pair : video_streams_) stream_pair->receive()->Start(); for (auto& stream_pair : audio_streams_) stream_pair->receive()->Start(); for (auto& stream_pair : video_streams_) { if (stream_pair->config_.autostart) { stream_pair->send()->Start(); } } for (auto& stream_pair : audio_streams_) { if (stream_pair->config_.autostart) { stream_pair->send()->Start(); } } } void Scenario::Stop() { RTC_DCHECK(start_time_.IsFinite()); for (auto& stream_pair : video_streams_) { stream_pair->send()->Stop(); } for (auto& stream_pair : audio_streams_) stream_pair->send()->Stop(); for (auto& stream_pair : video_streams_) stream_pair->receive()->Stop(); for (auto& stream_pair : audio_streams_) stream_pair->receive()->Stop(); start_time_ = Timestamp::PlusInfinity(); } Timestamp Scenario::Now() { return Timestamp::us(clock_->TimeInMicroseconds()); } TimeDelta Scenario::Duration() { if (start_time_.IsInfinite()) return TimeDelta::Zero(); return Now() - start_time_; } } // namespace test } // namespace webrtc