From c44593fa600ad351e927bc838d133b78928daf6e Mon Sep 17 00:00:00 2001 From: "sergeyu@chromium.org" Date: Sat, 16 Aug 2014 08:12:32 +0000 Subject: [PATCH] Implement network performance simulation for remoting perf tests. The new FakePacketSocketFactory allows to simulate fake network with given latency/bandwidth parameters. BUG=394067 Review URL: https://codereview.chromium.org/427613005 Cr-Commit-Position: refs/heads/master@{#290128} git-svn-id: svn://svn.chromium.org/chrome/trunk/src@290128 0039d316-1c4b-4281-b951-d872f2087c98 --- remoting/remoting_test.gypi | 10 + remoting/test/fake_network_dispatcher.cc | 88 ++++++ remoting/test/fake_network_dispatcher.h | 74 +++++ remoting/test/fake_network_manager.cc | 46 ++++ remoting/test/fake_network_manager.h | 37 +++ remoting/test/fake_port_allocator.cc | 124 +++++++++ remoting/test/fake_port_allocator.h | 47 ++++ remoting/test/fake_socket_factory.cc | 328 +++++++++++++++++++++++ remoting/test/fake_socket_factory.h | 123 +++++++++ remoting/test/leaky_bucket.cc | 41 +++ remoting/test/leaky_bucket.h | 38 +++ remoting/test/protocol_perftest.cc | 132 +++++++-- 12 files changed, 1064 insertions(+), 24 deletions(-) create mode 100644 remoting/test/fake_network_dispatcher.cc create mode 100644 remoting/test/fake_network_dispatcher.h create mode 100644 remoting/test/fake_network_manager.cc create mode 100644 remoting/test/fake_network_manager.h create mode 100644 remoting/test/fake_port_allocator.cc create mode 100644 remoting/test/fake_port_allocator.h create mode 100644 remoting/test/fake_socket_factory.cc create mode 100644 remoting/test/fake_socket_factory.h create mode 100644 remoting/test/leaky_bucket.cc create mode 100644 remoting/test/leaky_bucket.h diff --git a/remoting/remoting_test.gypi b/remoting/remoting_test.gypi index 6227b3a49765..0044e16d14e1 100644 --- a/remoting/remoting_test.gypi +++ b/remoting/remoting_test.gypi @@ -40,6 +40,16 @@ 'signaling/fake_signal_strategy.h', 'signaling/mock_signal_strategy.cc', 'signaling/mock_signal_strategy.h', + 'test/fake_network_dispatcher.cc', + 'test/fake_network_dispatcher.h', + 'test/fake_network_manager.cc', + 'test/fake_network_manager.h', + 'test/fake_port_allocator.cc', + 'test/fake_port_allocator.h', + 'test/fake_socket_factory.cc', + 'test/fake_socket_factory.h', + 'test/leaky_bucket.cc', + 'test/leaky_bucket.h', ], 'conditions': [ ['enable_remoting_host == 0', { diff --git a/remoting/test/fake_network_dispatcher.cc b/remoting/test/fake_network_dispatcher.cc new file mode 100644 index 000000000000..d2076abb04b4 --- /dev/null +++ b/remoting/test/fake_network_dispatcher.cc @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/fake_network_dispatcher.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "net/base/io_buffer.h" + +namespace remoting { + +FakeNetworkDispatcher::FakeNetworkDispatcher() + : allocated_address_(0) { +} + +FakeNetworkDispatcher::~FakeNetworkDispatcher() { + CHECK(nodes_.empty()); +} + +rtc::IPAddress FakeNetworkDispatcher::AllocateAddress() { + in6_addr addr; + memset(&addr, 0, sizeof(addr)); + + // fc00::/7 is reserved for unique local addresses. + addr.s6_addr[0] = 0xfc; + + // Copy |allocated_address_| to the end of |addr|. + ++allocated_address_; + for (size_t i = 0; i < sizeof(allocated_address_); ++i) { + addr.s6_addr[15 - i] = (allocated_address_ >> (8 * i)) & 0xff; + } + + return rtc::IPAddress(addr); +} + +void FakeNetworkDispatcher::AddNode(Node* node) { + DCHECK(node->GetThread()->BelongsToCurrentThread()); + + base::AutoLock auto_lock(nodes_lock_); + DCHECK(nodes_.find(node->GetAddress()) == nodes_.end()); + nodes_[node->GetAddress()] = node; +} + +void FakeNetworkDispatcher::RemoveNode(Node* node) { + DCHECK(node->GetThread()->BelongsToCurrentThread()); + + base::AutoLock auto_lock(nodes_lock_); + DCHECK(nodes_[node->GetAddress()] == node); + nodes_.erase(node->GetAddress()); +} + +void FakeNetworkDispatcher::DeliverPacket( + const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size) { + Node* node; + { + base::AutoLock auto_lock(nodes_lock_); + + NodesMap::iterator node_it = nodes_.find(to.ipaddr()); + if (node_it == nodes_.end()) { + LOG(ERROR) << "Tried to deliver packet to unknown target: " + << to.ToString(); + return; + } + + node = node_it->second; + + // Check if |node| belongs to a different thread and post a task in that + // case. + scoped_refptr task_runner = node->GetThread(); + if (!task_runner->BelongsToCurrentThread()) { + task_runner->PostTask(FROM_HERE, + base::Bind(&FakeNetworkDispatcher::DeliverPacket, + this, from, to, data, data_size)); + return; + } + } + + // Call ReceivePacket() without lock held. It's safe because at this point we + // know that |node| belongs to the current thread. + node->ReceivePacket(from, to, data, data_size); +} + +} // namespace remoting diff --git a/remoting/test/fake_network_dispatcher.h b/remoting/test/fake_network_dispatcher.h new file mode 100644 index 000000000000..44491a4d7bb0 --- /dev/null +++ b/remoting/test/fake_network_dispatcher.h @@ -0,0 +1,74 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_FAKE_NETWORK_DISPATCHER_H_ +#define REMOTING_TEST_FAKE_NETWORK_DISPATCHER_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "third_party/libjingle/source/talk/p2p/base/packetsocketfactory.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace net { +class IOBuffer; +} // namespace net + +namespace remoting { + +class FakeNetworkDispatcher + : public base::RefCountedThreadSafe { + public: + class Node { + public: + virtual ~Node() {}; + + // Return thread on which ReceivePacket() should be called. + virtual const scoped_refptr& GetThread() + const = 0; + virtual const rtc::IPAddress& GetAddress() const = 0; + + // Deliver a packet sent by a different node. + virtual void ReceivePacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size) = 0; + }; + + FakeNetworkDispatcher(); + + rtc::IPAddress AllocateAddress(); + + // Must be called on the thread that the |node| works on. + void AddNode(Node* node); + void RemoveNode(Node* node); + + void DeliverPacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size); + + private: + typedef std::map NodesMap; + + friend class base::RefCountedThreadSafe; + virtual ~FakeNetworkDispatcher(); + + NodesMap nodes_; + base::Lock nodes_lock_; + + // A counter used to allocate unique addresses in AllocateAddress(). + int allocated_address_; + + DISALLOW_COPY_AND_ASSIGN(FakeNetworkDispatcher); +}; + +} // namespace remoting + +#endif // REMOTING_TEST_FAKE_NETWORK_DISPATCHER_H_ diff --git a/remoting/test/fake_network_manager.cc b/remoting/test/fake_network_manager.cc new file mode 100644 index 000000000000..4139c8e7b7cd --- /dev/null +++ b/remoting/test/fake_network_manager.cc @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/fake_network_manager.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "jingle/glue/utils.h" +#include "third_party/webrtc/base/socketaddress.h" + +namespace remoting { + +FakeNetworkManager::FakeNetworkManager(const rtc::IPAddress& address) + : started_(false), + weak_factory_(this) { + network_.reset(new rtc::Network("fake", "Fake Network", address, 32)); + network_->AddIP(address); +} + +FakeNetworkManager::~FakeNetworkManager() { +} + +void FakeNetworkManager::StartUpdating() { + started_ = true; + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FakeNetworkManager::SendNetworksChangedSignal, + weak_factory_.GetWeakPtr())); +} + +void FakeNetworkManager::StopUpdating() { + started_ = false; +} + +void FakeNetworkManager::GetNetworks(NetworkList* networks) const { + networks->clear(); + networks->push_back(network_.get()); +} + +void FakeNetworkManager::SendNetworksChangedSignal() { + SignalNetworksChanged(); +} + +} // namespace remoting diff --git a/remoting/test/fake_network_manager.h b/remoting/test/fake_network_manager.h new file mode 100644 index 000000000000..20788b51a63e --- /dev/null +++ b/remoting/test/fake_network_manager.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_FAKE_NETWORK_MANAGER_H_ +#define REMOTING_TEST_FAKE_NETWORK_MANAGER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "third_party/webrtc/base/network.h" + +namespace remoting { + +// FakeNetworkManager always returns one interface with the IP address +// specified in the constructor. +class FakeNetworkManager : public rtc::NetworkManager { + public: + FakeNetworkManager(const rtc::IPAddress& address); + virtual ~FakeNetworkManager(); + + // rtc::NetworkManager interface. + virtual void StartUpdating() OVERRIDE; + virtual void StopUpdating() OVERRIDE; + virtual void GetNetworks(NetworkList* networks) const OVERRIDE; + + protected: + void SendNetworksChangedSignal(); + + bool started_; + scoped_ptr network_; + + base::WeakPtrFactory weak_factory_; +}; + +} // namespace remoting + +#endif // REMOTING_TEST_FAKE_NETWORK_MANAGER_H_ diff --git a/remoting/test/fake_port_allocator.cc b/remoting/test/fake_port_allocator.cc new file mode 100644 index 000000000000..18358233749e --- /dev/null +++ b/remoting/test/fake_port_allocator.cc @@ -0,0 +1,124 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/fake_port_allocator.h" + +#include "remoting/test/fake_network_dispatcher.h" +#include "remoting/test/fake_network_manager.h" +#include "remoting/test/fake_socket_factory.h" + +namespace remoting { + +namespace { + +class FakePortAllocatorSession + : public cricket::HttpPortAllocatorSessionBase { + public: + FakePortAllocatorSession( + cricket::HttpPortAllocatorBase* allocator, + const std::string& content_name, + int component, + const std::string& ice_username_fragment, + const std::string& ice_password, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay); + virtual ~FakePortAllocatorSession(); + + // cricket::HttpPortAllocatorBase overrides. + virtual void ConfigReady(cricket::PortConfiguration* config) OVERRIDE; + virtual void SendSessionRequest(const std::string& host, int port) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(FakePortAllocatorSession); +}; + +FakePortAllocatorSession::FakePortAllocatorSession( + cricket::HttpPortAllocatorBase* allocator, + const std::string& content_name, + int component, + const std::string& ice_username_fragment, + const std::string& ice_password, + const std::vector& stun_hosts, + const std::vector& relay_hosts, + const std::string& relay) + : HttpPortAllocatorSessionBase(allocator, + content_name, + component, + ice_username_fragment, + ice_password, + stun_hosts, + relay_hosts, + relay, + std::string()) { + set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | + cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG | + cricket::PORTALLOCATOR_ENABLE_IPV6 | + cricket::PORTALLOCATOR_DISABLE_STUN | + cricket::PORTALLOCATOR_DISABLE_RELAY); +} + +FakePortAllocatorSession::~FakePortAllocatorSession() { +} + +void FakePortAllocatorSession::ConfigReady( + cricket::PortConfiguration* config) { + // Filter out non-UDP relay ports, so that we don't try using TCP. + for (cricket::PortConfiguration::RelayList::iterator relay = + config->relays.begin(); relay != config->relays.end(); ++relay) { + cricket::PortList filtered_ports; + for (cricket::PortList::iterator port = + relay->ports.begin(); port != relay->ports.end(); ++port) { + if (port->proto == cricket::PROTO_UDP) { + filtered_ports.push_back(*port); + } + } + relay->ports = filtered_ports; + } + cricket::BasicPortAllocatorSession::ConfigReady(config); +} + +void FakePortAllocatorSession::SendSessionRequest( + const std::string& host, + int port) { + ReceiveSessionResponse(std::string()); +} + +} // namespace + +// static +scoped_ptr FakePortAllocator::Create( + scoped_refptr fake_network_dispatcher) { + scoped_ptr socket_factory( + new FakePacketSocketFactory(fake_network_dispatcher)); + scoped_ptr network_manager( + new FakeNetworkManager(socket_factory->GetAddress())); + + return scoped_ptr( + new FakePortAllocator(network_manager.Pass(), socket_factory.Pass())); +} + +FakePortAllocator::FakePortAllocator( + scoped_ptr network_manager, + scoped_ptr socket_factory) + : HttpPortAllocatorBase(network_manager.get(), + socket_factory.get(), + std::string()), + network_manager_(network_manager.Pass()), + socket_factory_(socket_factory.Pass()) {} + +FakePortAllocator::~FakePortAllocator() { +} + +cricket::PortAllocatorSession* FakePortAllocator::CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_username_fragment, + const std::string& ice_password) { + return new FakePortAllocatorSession( + this, content_name, component, ice_username_fragment, ice_password, + stun_hosts(), relay_hosts(), relay_token()); +} + +} // namespace remoting diff --git a/remoting/test/fake_port_allocator.h b/remoting/test/fake_port_allocator.h new file mode 100644 index 000000000000..18fd7bdac9da --- /dev/null +++ b/remoting/test/fake_port_allocator.h @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_FAKE_PORT_ALLOCATOR_H_ +#define REMOTING_TEST_FAKE_PORT_ALLOCATOR_H_ + +#include + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "third_party/libjingle/source/talk/p2p/client/httpportallocator.h" + +namespace remoting { + +class FakeNetworkDispatcher; +class FakePacketSocketFactory; + +class FakePortAllocator : public cricket::HttpPortAllocatorBase { + public: + static scoped_ptr Create( + scoped_refptr fake_network_dispatcher); + + virtual ~FakePortAllocator(); + + FakePacketSocketFactory* socket_factory() { return socket_factory_.get(); } + + // cricket::BasicPortAllocator overrides. + virtual cricket::PortAllocatorSession* CreateSessionInternal( + const std::string& content_name, + int component, + const std::string& ice_username_fragment, + const std::string& ice_password) OVERRIDE; + + private: + FakePortAllocator(scoped_ptr network_manager, + scoped_ptr socket_factory); + + scoped_ptr network_manager_; + scoped_ptr socket_factory_; + + DISALLOW_COPY_AND_ASSIGN(FakePortAllocator); +}; + +} // namespace remoting + +#endif // REMOTING_TEST_FAKE_PORT_ALLOCATOR_H_ diff --git a/remoting/test/fake_socket_factory.cc b/remoting/test/fake_socket_factory.cc new file mode 100644 index 000000000000..c9b927d3afe7 --- /dev/null +++ b/remoting/test/fake_socket_factory.cc @@ -0,0 +1,328 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include "remoting/test/fake_socket_factory.h" + +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/rand_util.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "net/base/io_buffer.h" +#include "remoting/test/leaky_bucket.h" +#include "third_party/webrtc/base/asyncpacketsocket.h" + +namespace remoting { + +namespace { + +const int kPortRangeStart = 1024; +const int kPortRangeEnd = 65535; + +double GetNormalRandom(double average, double stddev) { + // Based on Box-Muller transform, see + // http://en.wikipedia.org/wiki/Box_Muller_transform . + return average + + stddev * sqrt(-2.0 * log(1.0 - base::RandDouble())) * + cos(base::RandDouble() * 2.0 * M_PI); +} + +class FakeUdpSocket : public rtc::AsyncPacketSocket { + public: + FakeUdpSocket(FakePacketSocketFactory* factory, + scoped_refptr dispatcher, + const rtc::SocketAddress& local_address); + virtual ~FakeUdpSocket(); + + void ReceivePacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size); + + // rtc::AsyncPacketSocket interface. + virtual rtc::SocketAddress GetLocalAddress() const OVERRIDE; + virtual rtc::SocketAddress GetRemoteAddress() const OVERRIDE; + virtual int Send(const void* data, size_t data_size, + const rtc::PacketOptions& options) OVERRIDE; + virtual int SendTo(const void* data, size_t data_size, + const rtc::SocketAddress& address, + const rtc::PacketOptions& options) OVERRIDE; + virtual int Close() OVERRIDE; + virtual State GetState() const OVERRIDE; + virtual int GetOption(rtc::Socket::Option option, int* value) OVERRIDE; + virtual int SetOption(rtc::Socket::Option option, int value) OVERRIDE; + virtual int GetError() const OVERRIDE; + virtual void SetError(int error) OVERRIDE; + + private: + FakePacketSocketFactory* factory_; + scoped_refptr dispatcher_; + rtc::SocketAddress local_address_; + State state_; + + DISALLOW_COPY_AND_ASSIGN(FakeUdpSocket); +}; + +FakeUdpSocket::FakeUdpSocket(FakePacketSocketFactory* factory, + scoped_refptr dispatcher, + const rtc::SocketAddress& local_address) + : factory_(factory), + dispatcher_(dispatcher), + local_address_(local_address), + state_(STATE_BOUND) { +} + +FakeUdpSocket::~FakeUdpSocket() { + factory_->OnSocketDestroyed(local_address_.port()); +} + +void FakeUdpSocket::ReceivePacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size) { + SignalReadPacket( + this, data->data(), data_size, from, rtc::CreatePacketTime(0)); +} + +rtc::SocketAddress FakeUdpSocket::GetLocalAddress() const { + return local_address_; +} + +rtc::SocketAddress FakeUdpSocket::GetRemoteAddress() const { + NOTREACHED(); + return rtc::SocketAddress(); +} + +int FakeUdpSocket::Send(const void* data, size_t data_size, + const rtc::PacketOptions& options) { + NOTREACHED(); + return EINVAL; +} + +int FakeUdpSocket::SendTo(const void* data, size_t data_size, + const rtc::SocketAddress& address, + const rtc::PacketOptions& options) { + scoped_refptr buffer = new net::IOBuffer(data_size); + memcpy(buffer->data(), data, data_size); + dispatcher_->DeliverPacket(local_address_, address, buffer, data_size); + return data_size; +} + +int FakeUdpSocket::Close() { + state_ = STATE_CLOSED; + return 0; +} + +rtc::AsyncPacketSocket::State FakeUdpSocket::GetState() const { + return state_; +} + +int FakeUdpSocket::GetOption(rtc::Socket::Option option, int* value) { + NOTIMPLEMENTED(); + return -1; +} + +int FakeUdpSocket::SetOption(rtc::Socket::Option option, int value) { + NOTIMPLEMENTED(); + return -1; +} + +int FakeUdpSocket::GetError() const { + return 0; +} + +void FakeUdpSocket::SetError(int error) { + NOTREACHED(); +} + +} // namespace + +FakePacketSocketFactory::PendingPacket::PendingPacket() + : data_size(0) { +} + +FakePacketSocketFactory::PendingPacket::PendingPacket( + const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size) + : from(from), to(to), data(data), data_size(data_size) { +} + +FakePacketSocketFactory::PendingPacket::~PendingPacket() { +} + +FakePacketSocketFactory::FakePacketSocketFactory( + FakeNetworkDispatcher* dispatcher) + : task_runner_(base::ThreadTaskRunnerHandle::Get()), + dispatcher_(dispatcher), + address_(dispatcher_->AllocateAddress()), + out_of_order_rate_(0.0), + next_port_(kPortRangeStart), + weak_factory_(this) { + dispatcher_->AddNode(this); +} + +FakePacketSocketFactory::~FakePacketSocketFactory() { + CHECK(udp_sockets_.empty()); + dispatcher_->RemoveNode(this); +} + +void FakePacketSocketFactory::OnSocketDestroyed(int port) { + DCHECK(task_runner_->BelongsToCurrentThread()); + udp_sockets_.erase(port); +} + +void FakePacketSocketFactory::SetBandwidth(int bandwidth, int max_buffer) { + DCHECK(task_runner_->BelongsToCurrentThread()); + if (bandwidth <= 0) { + leaky_bucket_.reset(); + } else { + leaky_bucket_.reset(new LeakyBucket(max_buffer, bandwidth)); + } +} + +void FakePacketSocketFactory::SetLatency(base::TimeDelta average, + base::TimeDelta stddev) { + DCHECK(task_runner_->BelongsToCurrentThread()); + latency_average_ = average; + latency_stddev_ = stddev; +} + +rtc::AsyncPacketSocket* FakePacketSocketFactory::CreateUdpSocket( + const rtc::SocketAddress& local_address, + int min_port, int max_port) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + int port = -1; + if (min_port > 0 && max_port > 0) { + for (int i = min_port; i <= max_port; ++i) { + if (udp_sockets_.find(i) == udp_sockets_.end()) { + port = i; + break; + } + } + if (port < 0) + return NULL; + } else { + do { + port = next_port_; + next_port_ = + (next_port_ >= kPortRangeEnd) ? kPortRangeStart : (next_port_ + 1); + } while (udp_sockets_.find(port) != udp_sockets_.end()); + } + + CHECK(local_address.ipaddr() == address_); + + FakeUdpSocket* result = + new FakeUdpSocket(this, dispatcher_, + rtc::SocketAddress(local_address.ipaddr(), port)); + + udp_sockets_[port] = + base::Bind(&FakeUdpSocket::ReceivePacket, base::Unretained(result)); + + return result; +} + +rtc::AsyncPacketSocket* FakePacketSocketFactory::CreateServerTcpSocket( + const rtc::SocketAddress& local_address, + int min_port, int max_port, + int opts) { + return NULL; +} + +rtc::AsyncPacketSocket* FakePacketSocketFactory::CreateClientTcpSocket( + const rtc::SocketAddress& local_address, + const rtc::SocketAddress& remote_address, + const rtc::ProxyInfo& proxy_info, + const std::string& user_agent, + int opts) { + return NULL; +} + +rtc::AsyncResolverInterface* +FakePacketSocketFactory::CreateAsyncResolver() { + return NULL; +} + +const scoped_refptr& +FakePacketSocketFactory::GetThread() const { + return task_runner_; +} + +const rtc::IPAddress& FakePacketSocketFactory::GetAddress() const { + return address_; +} + +void FakePacketSocketFactory::ReceivePacket( + const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size) { + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK(to.ipaddr() == address_); + + base::TimeDelta delay; + + if (leaky_bucket_) { + delay = leaky_bucket_->AddPacket(data_size); + if (delay.is_max()) { + // Drop the packet. + return; + } + } + + if (latency_average_ > base::TimeDelta()) { + delay += base::TimeDelta::FromMillisecondsD( + GetNormalRandom(latency_average_.InMillisecondsF(), + latency_stddev_.InMillisecondsF())); + } + if (delay < base::TimeDelta()) + delay = base::TimeDelta(); + + // Put the packet to the |pending_packets_| and post a task for + // DoReceivePackets(). Note that the DoReceivePackets() task posted here may + // deliver a different packet, not the one added to the queue here. This + // would happen if another task gets posted with a shorted delay or when + // |out_of_order_rate_| is greater than 0. It's implemented this way to + // decouple latency variability from out-of-order delivery. + PendingPacket packet(from, to, data, data_size); + pending_packets_.push_back(packet); + task_runner_->PostDelayedTask( + FROM_HERE, + base::Bind(&FakePacketSocketFactory::DoReceivePacket, + weak_factory_.GetWeakPtr()), + delay); +} + +void FakePacketSocketFactory::DoReceivePacket() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + PendingPacket packet; + if (pending_packets_.size() > 1 && base::RandDouble() < out_of_order_rate_) { + std::list::iterator it = pending_packets_.begin(); + ++it; + packet = *it; + pending_packets_.erase(it); + } else { + packet = pending_packets_.front(); + pending_packets_.pop_front(); + } + + UdpSocketsMap::iterator iter = udp_sockets_.find(packet.to.port()); + if (iter == udp_sockets_.end()) { + // Invalid port number. + return; + } + + iter->second.Run(packet.from, packet.to, packet.data, packet.data_size); +} + +} // namespace remoting diff --git a/remoting/test/fake_socket_factory.h b/remoting/test/fake_socket_factory.h new file mode 100644 index 000000000000..c7efdab74683 --- /dev/null +++ b/remoting/test/fake_socket_factory.h @@ -0,0 +1,123 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_FAKE_SOCKET_FACTORY_H_ +#define REMOTING_TEST_FAKE_SOCKET_FACTORY_H_ + +#include + +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "remoting/test/fake_network_dispatcher.h" +#include "third_party/libjingle/source/talk/p2p/base/packetsocketfactory.h" + +namespace remoting { + +class FakeNetworkDispatcher; +class LeakyBucket; + +class FakePacketSocketFactory : public rtc::PacketSocketFactory, + public FakeNetworkDispatcher::Node { + public: + // |dispatcher| must outlive the factory. + explicit FakePacketSocketFactory(FakeNetworkDispatcher* dispatcher); + virtual ~FakePacketSocketFactory(); + + void OnSocketDestroyed(int port); + + // |bandwidth| - simulated link bandwidth in bytes/second. 0 indicates that + // bandwidth is unlimited. + // |max_buffer| - size of buffers in bytes. Ignored when |bandwidth| is 0. + void SetBandwidth(int bandwidth, int max_buffer); + + // Specifies parameters for simulated network latency. Latency is generated + // with normal distribution around |average| with the given |stddev|. Random + // latency calculated based on these parameters is added to the buffering + // delay (which is calculated based on the parameters passed to + // SetBandwidth()). I.e. total latency for each packet is calculated using the + // following formula + // + // l = NormalRand(average, stddev) + bytes_buffered / bandwidth . + // + // Where bytes_buffered is the current level in the leaky bucket used to + // control bandwidth. + void SetLatency(base::TimeDelta average, base::TimeDelta stddev); + + void set_out_of_order_rate(double out_of_order_rate) { + out_of_order_rate_ = out_of_order_rate; + } + + // rtc::PacketSocketFactory interface. + virtual rtc::AsyncPacketSocket* CreateUdpSocket( + const rtc::SocketAddress& local_address, + int min_port, int max_port) OVERRIDE; + virtual rtc::AsyncPacketSocket* CreateServerTcpSocket( + const rtc::SocketAddress& local_address, + int min_port, int max_port, + int opts) OVERRIDE; + virtual rtc::AsyncPacketSocket* CreateClientTcpSocket( + const rtc::SocketAddress& local_address, + const rtc::SocketAddress& remote_address, + const rtc::ProxyInfo& proxy_info, + const std::string& user_agent, + int opts) OVERRIDE; + virtual rtc::AsyncResolverInterface* CreateAsyncResolver() OVERRIDE; + + // FakeNetworkDispatcher::Node interface. + virtual const scoped_refptr& GetThread() + const OVERRIDE; + virtual const rtc::IPAddress& GetAddress() const OVERRIDE; + virtual void ReceivePacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size) OVERRIDE; + + private: + struct PendingPacket { + PendingPacket(); + PendingPacket( + const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + const scoped_refptr& data, + int data_size); + ~PendingPacket(); + + rtc::SocketAddress from; + rtc::SocketAddress to; + scoped_refptr data; + int data_size; + }; + + typedef base::Callback& data, + int data_size)> ReceiveCallback; + typedef std::map UdpSocketsMap; + + void DoReceivePacket(); + + scoped_refptr task_runner_; + scoped_refptr dispatcher_; + + rtc::IPAddress address_; + + scoped_ptr leaky_bucket_; + base::TimeDelta latency_average_; + base::TimeDelta latency_stddev_; + double out_of_order_rate_; + + UdpSocketsMap udp_sockets_; + uint16_t next_port_; + + std::list pending_packets_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FakePacketSocketFactory); +}; + +} // namespace remoting + +#endif // REMOTING_TEST_FAKE_SOCKET_FACTORY_H_ diff --git a/remoting/test/leaky_bucket.cc b/remoting/test/leaky_bucket.cc new file mode 100644 index 000000000000..ee8a62b06de1 --- /dev/null +++ b/remoting/test/leaky_bucket.cc @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/leaky_bucket.h" + +#include "base/logging.h" + +namespace remoting { + +LeakyBucket::LeakyBucket(double depth, double rate) + : depth_(depth), + rate_(rate), + level_(0.0), + last_update_(base::TimeTicks::Now()) { +} + +LeakyBucket::~LeakyBucket() { +} + +base::TimeDelta LeakyBucket::AddPacket(int size) { + UpdateLevel(); + + double new_level = level_ + size; + if (new_level > depth_) + return base::TimeDelta::Max(); + + base::TimeDelta result = base::TimeDelta::FromSecondsD(level_ / rate_); + level_ = new_level; + return result; +} + +void LeakyBucket::UpdateLevel() { + base::TimeTicks now = base::TimeTicks::Now(); + level_ -= rate_ * (now - last_update_).InSecondsF(); + if (level_ < 0.0) + level_ = 0.0; + last_update_ = now; +} + +} // namespace remoting diff --git a/remoting/test/leaky_bucket.h b/remoting/test/leaky_bucket.h new file mode 100644 index 000000000000..5b54b40e373d --- /dev/null +++ b/remoting/test/leaky_bucket.h @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_LEAKY_BUCKET_H_ +#define REMOTING_TEST_LEAKY_BUCKET_H_ + +#include "base/basictypes.h" +#include "base/time/time.h" + +namespace remoting { + +class LeakyBucket { + public: + // |depth| is in bytes. |rate| is specified in bytes/second. + LeakyBucket(double depth, double rate); + ~LeakyBucket(); + + // Adds a packet of the given |size| to the bucket and returns packet delay. + // Returns TimeDelta::Max() if the packet overflows the bucket, in which case + // it should be dropped. + base::TimeDelta AddPacket(int size); + + private: + void UpdateLevel(); + + double depth_; + double rate_; + + double level_; + base::TimeTicks last_update_; + + DISALLOW_COPY_AND_ASSIGN(LeakyBucket); +}; + +} // namespace remoting + +#endif // REMOTING_TEST_LEAKY_BUCKET_H_ diff --git a/remoting/test/protocol_perftest.cc b/remoting/test/protocol_perftest.cc index 438cbb89821a..2387ddb5d9c5 100644 --- a/remoting/test/protocol_perftest.cc +++ b/remoting/test/protocol_perftest.cc @@ -5,6 +5,7 @@ #include "base/base64.h" #include "base/file_util.h" #include "base/message_loop/message_loop.h" +#include "base/rand_util.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/waitable_event.h" @@ -22,13 +23,15 @@ #include "remoting/host/chromoting_host_context.h" #include "remoting/host/fake_desktop_environment.h" #include "remoting/host/video_scheduler.h" -#include "remoting/protocol/chromium_port_allocator.h" #include "remoting/protocol/jingle_session_manager.h" #include "remoting/protocol/libjingle_transport_factory.h" #include "remoting/protocol/me2me_host_authenticator_factory.h" #include "remoting/protocol/negotiating_client_authenticator.h" #include "remoting/protocol/session_config.h" #include "remoting/signaling/fake_signal_strategy.h" +#include "remoting/test/fake_network_dispatcher.h" +#include "remoting/test/fake_port_allocator.h" +#include "remoting/test/fake_socket_factory.h" #include "testing/gtest/include/gtest/gtest.h" namespace remoting { @@ -39,10 +42,31 @@ const char kHostJid[] = "host_jid@example.com/host"; const char kHostOwner[] = "jane.doe@example.com"; const char kClientJid[] = "jane.doe@example.com/client"; -class ProtocolPerfTest : public testing::Test, - public ClientUserInterface, - public VideoRenderer, - public HostStatusObserver { +struct NetworkPerformanceParams { + NetworkPerformanceParams(int bandwidth, + int max_buffers, + double latency_average_ms, + double latency_stddev_ms, + double out_of_order_rate) + : bandwidth(bandwidth), + max_buffers(max_buffers), + latency_average(base::TimeDelta::FromMillisecondsD(latency_average_ms)), + latency_stddev(base::TimeDelta::FromMillisecondsD(latency_stddev_ms)), + out_of_order_rate(out_of_order_rate) {} + + int bandwidth; + int max_buffers; + base::TimeDelta latency_average; + base::TimeDelta latency_stddev; + double out_of_order_rate; +}; + +class ProtocolPerfTest + : public testing::Test, + public testing::WithParamInterface, + public ClientUserInterface, + public VideoRenderer, + public HostStatusObserver { public: ProtocolPerfTest() : host_thread_("host"), @@ -54,6 +78,7 @@ class ProtocolPerfTest : public testing::Test, capture_thread_.Start(); encode_thread_.Start(); } + virtual ~ProtocolPerfTest() { host_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, host_.release()); host_thread_.message_loop_proxy()->DeleteSoon(FROM_HERE, @@ -162,6 +187,8 @@ class ProtocolPerfTest : public testing::Test, // host is started on |host_thread_| while the client works on the main // thread. void StartHostAndClient(protocol::ChannelConfig::Codec video_codec) { + fake_network_dispatcher_ = new FakeNetworkDispatcher(); + client_signaling_.reset(new FakeSignalStrategy(kClientJid)); jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); @@ -189,15 +216,18 @@ class ProtocolPerfTest : public testing::Test, protocol::NetworkSettings network_settings( protocol::NetworkSettings::NAT_TRAVERSAL_OUTGOING); - // TODO(sergeyu): Replace with a fake port allocator. - scoped_ptr host_port_allocator = - protocol::ChromiumPortAllocator::Create(NULL, network_settings) - .PassAs(); - + scoped_ptr port_allocator( + FakePortAllocator::Create(fake_network_dispatcher_)); + port_allocator->socket_factory()->SetBandwidth(GetParam().bandwidth, + GetParam().max_buffers); + port_allocator->socket_factory()->SetLatency(GetParam().latency_average, + GetParam().latency_stddev); + port_allocator->socket_factory()->set_out_of_order_rate( + GetParam().out_of_order_rate); scoped_ptr host_transport_factory( new protocol::LibjingleTransportFactory( host_signaling_.get(), - host_port_allocator.Pass(), + port_allocator.PassAs(), network_settings)); scoped_ptr session_manager( @@ -257,15 +287,19 @@ class ProtocolPerfTest : public testing::Test, client_context_.reset( new ClientContext(base::ThreadTaskRunnerHandle::Get())); - // TODO(sergeyu): Replace with a fake port allocator - scoped_ptr client_port_allocator = - protocol::ChromiumPortAllocator::Create(NULL, network_settings) - .PassAs(); - + scoped_ptr port_allocator( + FakePortAllocator::Create(fake_network_dispatcher_)); + port_allocator->socket_factory()->SetBandwidth(GetParam().bandwidth, + GetParam().max_buffers); + port_allocator->socket_factory()->SetLatency(GetParam().latency_average, + GetParam().latency_stddev); + port_allocator->socket_factory()->set_out_of_order_rate( + GetParam().out_of_order_rate); scoped_ptr client_transport_factory( - new protocol::LibjingleTransportFactory(client_signaling_.get(), - client_port_allocator.Pass(), - network_settings)); + new protocol::LibjingleTransportFactory( + client_signaling_.get(), + port_allocator.PassAs(), + network_settings)); std::vector auth_methods; auth_methods.push_back(protocol::AuthenticationMethod::Spake2( @@ -294,6 +328,8 @@ class ProtocolPerfTest : public testing::Test, base::MessageLoopForIO message_loop_; + scoped_refptr fake_network_dispatcher_; + base::Thread host_thread_; base::Thread capture_thread_; base::Thread encode_thread_; @@ -321,7 +357,40 @@ class ProtocolPerfTest : public testing::Test, DISALLOW_COPY_AND_ASSIGN(ProtocolPerfTest); }; -TEST_F(ProtocolPerfTest, StreamFrameRate) { +INSTANTIATE_TEST_CASE_P( + NoDelay, + ProtocolPerfTest, + ::testing::Values(NetworkPerformanceParams(0, 0, 0, 0, 0.0))); + +INSTANTIATE_TEST_CASE_P( + HighLatency, + ProtocolPerfTest, + ::testing::Values(NetworkPerformanceParams(0, 0, 300, 30, 0.0), + NetworkPerformanceParams(0, 0, 30, 10, 0.0))); + +INSTANTIATE_TEST_CASE_P( + OutOfOrder, + ProtocolPerfTest, + ::testing::Values(NetworkPerformanceParams(0, 0, 2, 0, 0.01), + NetworkPerformanceParams(0, 0, 30, 1, 0.01), + NetworkPerformanceParams(0, 0, 30, 1, 0.1), + NetworkPerformanceParams(0, 0, 300, 20, 0.01), + NetworkPerformanceParams(0, 0, 300, 20, 0.1))); + +INSTANTIATE_TEST_CASE_P( + LimitedBandwidth, + ProtocolPerfTest, + ::testing::Values( + // 100 MBps + NetworkPerformanceParams(800000000, 800000000, 2, 1, 0.0), + // 8 MBps + NetworkPerformanceParams(1000000, 300000, 30, 5, 0.01), + NetworkPerformanceParams(1000000, 2000000, 30, 5, 0.01), + // 800 kBps + NetworkPerformanceParams(100000, 30000, 130, 5, 0.01), + NetworkPerformanceParams(100000, 200000, 130, 5, 0.01))); + +TEST_P(ProtocolPerfTest, StreamFrameRate) { StartHostAndClient(protocol::ChannelConfig::CODEC_VP8); ASSERT_NO_FATAL_FAILURE(WaitConnected()); @@ -338,6 +407,8 @@ TEST_F(ProtocolPerfTest, StreamFrameRate) { LOG(INFO) << "Maximum latency: " << latency.InMillisecondsF() << "ms"; } +const int kIntermittentFrameSize = 100 * 1000; + // Frame generator that rewrites the whole screen every 60th frame. Should only // be used with the VERBATIM codec as the allocated frame may contain arbitrary // data. @@ -349,8 +420,8 @@ class IntermittentChangeFrameGenerator scoped_ptr GenerateFrame( webrtc::DesktopCapturer::Callback* callback) { - const int kWidth = 800; - const int kHeight = 600; + const int kWidth = 1000; + const int kHeight = kIntermittentFrameSize / kWidth / 4; bool fresh_frame = false; if (frame_index_ % 60 == 0 || !current_frame_) { @@ -379,7 +450,7 @@ class IntermittentChangeFrameGenerator DISALLOW_COPY_AND_ASSIGN(IntermittentChangeFrameGenerator); }; -TEST_F(ProtocolPerfTest, IntermittentChanges) { +TEST_P(ProtocolPerfTest, IntermittentChanges) { desktop_environment_factory_.set_frame_generator( base::Bind(&IntermittentChangeFrameGenerator::GenerateFrame, new IntermittentChangeFrameGenerator())); @@ -389,14 +460,27 @@ TEST_F(ProtocolPerfTest, IntermittentChanges) { ReceiveFrame(NULL); - for (int i = 0; i < 5; ++i) { + base::TimeDelta expected = GetParam().latency_average; + if (GetParam().bandwidth > 0) { + expected += base::TimeDelta::FromSecondsD(kIntermittentFrameSize / + GetParam().bandwidth); + } + LOG(INFO) << "Expected: " << expected.InMillisecondsF() << "ms"; + + base::TimeDelta sum; + + const int kFrames = 5; + for (int i = 0; i < kFrames; ++i) { base::TimeDelta latency; ReceiveFrame(&latency); LOG(INFO) << "Latency: " << latency.InMillisecondsF() << "ms Encode: " << last_video_packet_->encode_time_ms() << "ms Capture: " << last_video_packet_->capture_time_ms() << "ms"; + sum += latency; } + + LOG(INFO) << "Average: " << (sum / kFrames).InMillisecondsF(); } } // namespace remoting