diff options
author | eustas@chromium.org <eustas@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-12 09:16:58 +0000 |
---|---|---|
committer | eustas@chromium.org <eustas@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-12 09:16:58 +0000 |
commit | 1aa053640fe4302a98d4e7e551e5d28ad66c98fd (patch) | |
tree | 41d9579e0169bf8dbe9d4ba09bc002ba0e47931e /chrome/browser/devtools | |
parent | a88e133e6ada6181b21302dcde5183d37f9b4e51 (diff) | |
download | chromium_src-1aa053640fe4302a98d4e7e551e5d28ad66c98fd.zip chromium_src-1aa053640fe4302a98d4e7e551e5d28ad66c98fd.tar.gz chromium_src-1aa053640fe4302a98d4e7e551e5d28ad66c98fd.tar.bz2 |
DevToolsNetworkController: support "limit throughput" network condition.
BUG=245436
Review URL: https://codereview.chromium.org/324953002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276596 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/devtools')
7 files changed, 263 insertions, 79 deletions
diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc index 31089ea..473309d 100644 --- a/chrome/browser/devtools/chrome_devtools_manager_delegate.cc +++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.cc @@ -114,6 +114,8 @@ chrome::devtools::Network::emulateNetworkConditions::kParamMaximalThroughput; scoped_refptr<DevToolsNetworkConditions> conditions; if (offline || maximal_throughput) conditions = new DevToolsNetworkConditions(domains, maximal_throughput); + + emulate_network_conditions_client_id_ = agent_host->GetId(); UpdateNetworkState(agent_host, conditions); return command->SuccessResponse(NULL); } @@ -124,12 +126,14 @@ void ChromeDevToolsManagerDelegate::UpdateNetworkState( Profile* profile = GetProfile(agent_host); if (!profile) return; - profile->GetDevToolsNetworkController()->SetNetworkState( - agent_host->GetId(), conditions); + profile->GetDevToolsNetworkController()->SetNetworkState(conditions); } void ChromeDevToolsManagerDelegate::OnDevToolsStateChanged( content::DevToolsAgentHost* agent_host, bool attached) { - UpdateNetworkState(agent_host, scoped_refptr<DevToolsNetworkConditions>()); + if (agent_host->GetId() == emulate_network_conditions_client_id_) { + emulate_network_conditions_client_id_ = std::string(); + UpdateNetworkState(agent_host, scoped_refptr<DevToolsNetworkConditions>()); + } } diff --git a/chrome/browser/devtools/chrome_devtools_manager_delegate.h b/chrome/browser/devtools/chrome_devtools_manager_delegate.h index 6385c0c..bdbbbbf 100644 --- a/chrome/browser/devtools/chrome_devtools_manager_delegate.h +++ b/chrome/browser/devtools/chrome_devtools_manager_delegate.h @@ -37,6 +37,7 @@ class ChromeDevToolsManagerDelegate : public content::DevToolsManagerDelegate { scoped_ptr<DevToolsProtocol::Response> EmulateNetworkConditions( content::DevToolsAgentHost* agent_host, DevToolsProtocol::Command* command); + std::string emulate_network_conditions_client_id_; void UpdateNetworkState( content::DevToolsAgentHost* agent_host, diff --git a/chrome/browser/devtools/devtools_network_controller.cc b/chrome/browser/devtools/devtools_network_controller.cc index 362dc46..176ac66 100644 --- a/chrome/browser/devtools/devtools_network_controller.cc +++ b/chrome/browser/devtools/devtools_network_controller.cc @@ -4,6 +4,7 @@ #include "chrome/browser/devtools/devtools_network_controller.h" +#include "base/time/time.h" #include "chrome/browser/devtools/devtools_network_conditions.h" #include "chrome/browser/devtools/devtools_network_transaction.h" #include "chrome/browser/profiles/profile.h" @@ -16,6 +17,7 @@ using content::BrowserThread; namespace { const char kDevToolsRequestInitiator[] = "X-DevTools-Request-Initiator"; +int64_t kPacketSize = 1500; } // namespace @@ -37,10 +39,17 @@ void DevToolsNetworkController::RemoveTransaction( DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(transactions_.find(transaction) != transactions_.end()); transactions_.erase(transaction); + + if (conditions_ && conditions_->IsThrottling()) { + UpdateThrottles(); + throttled_transactions_.erase(std::remove(throttled_transactions_.begin(), + throttled_transactions_.end(), transaction), + throttled_transactions_.end()); + ArmTimer(); + } } void DevToolsNetworkController::SetNetworkState( - const std::string& client_id, const scoped_refptr<DevToolsNetworkConditions> conditions) { DCHECK_CURRENTLY_ON(BrowserThread::UI); BrowserThread::PostTask( @@ -49,35 +58,53 @@ void DevToolsNetworkController::SetNetworkState( base::Bind( &DevToolsNetworkController::SetNetworkStateOnIO, weak_ptr_factory_.GetWeakPtr(), - client_id, conditions)); } void DevToolsNetworkController::SetNetworkStateOnIO( - const std::string& client_id, const scoped_refptr<DevToolsNetworkConditions> conditions) { DCHECK(thread_checker_.CalledOnValidThread()); - if (!conditions) { - if (client_id == active_client_id_) { - conditions_ = NULL; - active_client_id_ = std::string(); - } + if (conditions_ && conditions_->IsThrottling()) + UpdateThrottles(); + + conditions_ = conditions; + if (!conditions || !(conditions->IsThrottling() || conditions->IsOffline())) { + timer_.Stop(); + int64_t length = throttled_transactions_.size(); + for (int64_t i = 0; i < length; ++i) + throttled_transactions_[i]->FireThrottledCallback(); + throttled_transactions_.clear(); + } + + if (!conditions) return; + + if (conditions_->IsOffline()) { + // Iterate over a copy of set, because failing of transaction could + // result in creating a new one, or (theoretically) destroying one. + Transactions old_transactions(transactions_); + for (Transactions::iterator it = old_transactions.begin(); + it != old_transactions.end(); ++it) { + if (transactions_.find(*it) == transactions_.end()) + continue; + if (!(*it)->request() || (*it)->failed()) + continue; + if (ShouldFail((*it)->request())) + (*it)->Fail(); + } } - conditions_ = conditions; - active_client_id_ = client_id; - - // Iterate over a copy of set, because failing of transaction could result in - // creating a new one, or (theoretically) destroying one. - Transactions old_transactions(transactions_); - for (Transactions::iterator it = old_transactions.begin(); - it != old_transactions.end(); ++it) { - if (transactions_.find(*it) == transactions_.end()) - continue; - if (!(*it)->request() || (*it)->failed()) - continue; - if (ShouldFail((*it)->request())) - (*it)->Fail(); + + if (conditions_->IsThrottling()) { + DCHECK(conditions_->maximal_throughput() != 0); + offset_ = base::TimeTicks::Now(); + last_tick_ = 0; + int64_t us_tick_length = + (1000000L * kPacketSize) / conditions_->maximal_throughput(); + DCHECK(us_tick_length != 0); + if (us_tick_length == 0) + us_tick_length = 1; + tick_length_ = base::TimeDelta::FromMicroseconds(us_tick_length); + ArmTimer(); } } @@ -93,3 +120,84 @@ bool DevToolsNetworkController::ShouldFail( return !request->extra_headers.HasHeader(kDevToolsRequestInitiator); } + +bool DevToolsNetworkController::ShouldThrottle( + const net::HttpRequestInfo* request) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(request); + if (!conditions_ || !conditions_->IsThrottling()) + return false; + + if (!conditions_->HasMatchingDomain(request->url)) + return false; + + return !request->extra_headers.HasHeader(kDevToolsRequestInitiator); +} + +void DevToolsNetworkController::UpdateThrottles() { + int64_t last_tick = (base::TimeTicks::Now() - offset_) / tick_length_; + int64_t ticks = last_tick - last_tick_; + last_tick_ = last_tick; + + int64_t length = throttled_transactions_.size(); + if (!length) + return; + + int64_t shift = ticks % length; + for (int64_t i = 0; i < length; ++i) { + throttled_transactions_[i]->DecreaseThrottledByteCount( + (ticks / length) * kPacketSize + (i < shift ? kPacketSize : 0)); + } + std::rotate(throttled_transactions_.begin(), + throttled_transactions_.begin() + shift, throttled_transactions_.end()); +} + +void DevToolsNetworkController::OnTimer() { + UpdateThrottles(); + + std::vector<DevToolsNetworkTransaction*> active_transactions; + std::vector<DevToolsNetworkTransaction*> finished_transactions; + size_t length = throttled_transactions_.size(); + for (size_t i = 0; i < length; ++i) { + if (throttled_transactions_[i]->throttled_byte_count() < 0) + finished_transactions.push_back(throttled_transactions_[i]); + else + active_transactions.push_back(throttled_transactions_[i]); + } + throttled_transactions_.swap(active_transactions); + + length = finished_transactions.size(); + for (size_t i = 0; i < length; ++i) + finished_transactions[i]->FireThrottledCallback(); + + ArmTimer(); +} + +void DevToolsNetworkController::ArmTimer() { + size_t length = throttled_transactions_.size(); + if (!length) + return; + int64_t min_ticks_left = 0x10000L; + for (size_t i = 0; i < length; ++i) { + int64_t packets_left = (throttled_transactions_[i]->throttled_byte_count() + + kPacketSize - 1) / kPacketSize; + int64_t ticks_left = (i + 1) + length * (packets_left - 1); + if (i == 0 || ticks_left < min_ticks_left) + min_ticks_left = ticks_left; + } + base::TimeTicks desired_time = + offset_ + tick_length_ * (last_tick_ + min_ticks_left); + timer_.Start( + FROM_HERE, + desired_time - base::TimeTicks::Now(), + base::Bind( + &DevToolsNetworkController::OnTimer, + weak_ptr_factory_.GetWeakPtr())); +} + +void DevToolsNetworkController::ThrottleTransaction( + DevToolsNetworkTransaction* transaction) { + UpdateThrottles(); + throttled_transactions_.push_back(transaction); + ArmTimer(); +} diff --git a/chrome/browser/devtools/devtools_network_controller.h b/chrome/browser/devtools/devtools_network_controller.h index f057412..a9edd0f 100644 --- a/chrome/browser/devtools/devtools_network_controller.h +++ b/chrome/browser/devtools/devtools_network_controller.h @@ -7,18 +7,25 @@ #include <set> #include <string> +#include <vector> #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/threading/thread_checker.h" +#include "base/timer/timer.h" class DevToolsNetworkConditions; class DevToolsNetworkTransaction; class GURL; class Profile; +namespace base { +class TimeDelta; +class TimeTicks; +} + namespace content { class ResourceContext; } @@ -43,12 +50,12 @@ class DevToolsNetworkController { void RemoveTransaction(DevToolsNetworkTransaction* transaction); // Applies network emulation configuration. - // |client_id| should be DevToolsAgentHost GUID. void SetNetworkState( - const std::string& client_id, const scoped_refptr<DevToolsNetworkConditions> conditions); bool ShouldFail(const net::HttpRequestInfo* request); + bool ShouldThrottle(const net::HttpRequestInfo* request); + void ThrottleTransaction(DevToolsNetworkTransaction* transaction); protected: friend class test::DevToolsNetworkControllerHelper; @@ -59,19 +66,23 @@ class DevToolsNetworkController { typedef scoped_refptr<DevToolsNetworkConditions> Conditions; - void SetNetworkStateOnIO( - const std::string& client_id, - const Conditions conditions); + void SetNetworkStateOnIO(const Conditions conditions); typedef std::set<DevToolsNetworkTransaction*> Transactions; Transactions transactions_; - // Active client id. - std::string active_client_id_; - - // Active network conditions. Conditions conditions_; + void UpdateThrottles(); + void ArmTimer(); + void OnTimer(); + + std::vector<DevToolsNetworkTransaction*> throttled_transactions_; + base::OneShotTimer<DevToolsNetworkController> timer_; + base::TimeTicks offset_; + base::TimeDelta tick_length_; + uint64_t last_tick_; + base::WeakPtrFactory<DevToolsNetworkController> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(DevToolsNetworkController); diff --git a/chrome/browser/devtools/devtools_network_controller_unittest.cc b/chrome/browser/devtools/devtools_network_controller_unittest.cc index 38730db..708b9e1 100644 --- a/chrome/browser/devtools/devtools_network_controller_unittest.cc +++ b/chrome/browser/devtools/devtools_network_controller_unittest.cc @@ -17,9 +17,6 @@ namespace test { -const char kClientId[] = "42"; -const char kAnotherClientId[] = "24"; - const char kHttpDotCom[] = "http://dot.com"; const char kHttpDotOrg[] = "http://dot.org"; const char kCom[] = "com"; @@ -63,13 +60,13 @@ class DevToolsNetworkControllerHelper { return request_.get(); } - void SetNetworkState(const std::string& client_id, bool offline) { + void SetNetworkState(bool offline) { std::vector<std::string> domains; domains.push_back(kCom); scoped_refptr<DevToolsNetworkConditions> conditions; if (offline) conditions = new DevToolsNetworkConditions(domains, 0.0); - controller_.SetNetworkStateOnIO(client_id, conditions); + controller_.SetNetworkStateOnIO(conditions); } int Start() { @@ -108,31 +105,15 @@ TEST(DevToolsNetworkControllerTest, SingleDisableEnable) { net::HttpRequestInfo* request = helper.GetRequest(); EXPECT_FALSE(controller->ShouldFail(request)); - helper.SetNetworkState(kClientId, true); - EXPECT_TRUE(controller->ShouldFail(request)); - helper.SetNetworkState(kClientId, false); - EXPECT_FALSE(controller->ShouldFail(request)); -} - -TEST(DevToolsNetworkControllerTest, DoubleDisableEnable) { - DevToolsNetworkControllerHelper helper; - DevToolsNetworkController* controller = helper.controller(); - net::HttpRequestInfo* request = helper.GetRequest(); - - EXPECT_FALSE(controller->ShouldFail(request)); - helper.SetNetworkState(kClientId, true); - EXPECT_TRUE(controller->ShouldFail(request)); - helper.SetNetworkState(kAnotherClientId, true); - EXPECT_TRUE(controller->ShouldFail(request)); - helper.SetNetworkState(kClientId, false); + helper.SetNetworkState(true); EXPECT_TRUE(controller->ShouldFail(request)); - helper.SetNetworkState(kAnotherClientId, false); + helper.SetNetworkState(false); EXPECT_FALSE(controller->ShouldFail(request)); } TEST(DevToolsNetworkControllerTest, FailOnStart) { DevToolsNetworkControllerHelper helper; - helper.SetNetworkState(kClientId, true); + helper.SetNetworkState(true); int rv = helper.Start(); EXPECT_EQ(rv, net::ERR_INTERNET_DISCONNECTED); @@ -153,7 +134,7 @@ TEST(DevToolsNetworkControllerTest, FailRunningTransaction) { EXPECT_EQ(rv, net::ERR_IO_PENDING); EXPECT_EQ(callback->run_count(), 0); - helper.SetNetworkState(kClientId, true); + helper.SetNetworkState(true); EXPECT_EQ(callback->run_count(), 1); EXPECT_EQ(callback->value(), net::ERR_INTERNET_DISCONNECTED); @@ -164,8 +145,8 @@ TEST(DevToolsNetworkControllerTest, FailRunningTransaction) { EXPECT_EQ(callback->run_count(), 1); // Check that transaction in not failed second time. - helper.SetNetworkState(kClientId, false); - helper.SetNetworkState(kClientId, true); + helper.SetNetworkState(false); + helper.SetNetworkState(true); EXPECT_EQ(callback->run_count(), 1); } @@ -176,7 +157,7 @@ TEST(DevToolsNetworkControllerTest, ReadAfterFail) { EXPECT_EQ(rv, net::OK); EXPECT_TRUE(helper.transaction()->request()); - helper.SetNetworkState(kClientId, true); + helper.SetNetworkState(true); EXPECT_TRUE(helper.transaction()->failed()); scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(64)); @@ -196,7 +177,7 @@ TEST(DevToolsNetworkControllerTest, AllowsDevToolsRequests) { net::HttpRequestInfo* request = helper.GetRequest(); EXPECT_FALSE(controller->ShouldFail(request)); - helper.SetNetworkState(kClientId, true); + helper.SetNetworkState(true); EXPECT_FALSE(controller->ShouldFail(request)); } @@ -207,7 +188,7 @@ TEST(DevToolsNetworkControllerTest, AllowsNotMatchingRequests) { net::HttpRequestInfo* request = helper.GetRequest(); EXPECT_FALSE(controller->ShouldFail(request)); - helper.SetNetworkState(kClientId, true); + helper.SetNetworkState(true); EXPECT_FALSE(controller->ShouldFail(request)); } diff --git a/chrome/browser/devtools/devtools_network_transaction.cc b/chrome/browser/devtools/devtools_network_transaction.cc index bba196c..8a72e01 100644 --- a/chrome/browser/devtools/devtools_network_transaction.cc +++ b/chrome/browser/devtools/devtools_network_transaction.cc @@ -17,6 +17,8 @@ DevToolsNetworkTransaction::DevToolsNetworkTransaction( network_transaction_(network_transaction.Pass()), request_(NULL), failed_(false), + throttled_byte_count_(0), + callback_type_(NONE), proxy_callback_(base::Bind(&DevToolsNetworkTransaction::OnCallback, base::Unretained(this))) { DCHECK(controller); @@ -27,15 +29,66 @@ DevToolsNetworkTransaction::~DevToolsNetworkTransaction() { controller_->RemoveTransaction(this); } +void DevToolsNetworkTransaction::Throttle(int result) { + throttled_result_ = result; + + if (callback_type_ == START) + throttled_byte_count_ += network_transaction_->GetTotalReceivedBytes(); + if (result > 0) + throttled_byte_count_ += result; + + controller_->ThrottleTransaction(this); +} + void DevToolsNetworkTransaction::OnCallback(int rv) { if (failed_) return; DCHECK(!callback_.is_null()); + if (callback_type_ == START || callback_type_ == READ) { + if (controller_->ShouldThrottle(request_)) { + Throttle(rv); + return; + } + } net::CompletionCallback callback = callback_; callback_.Reset(); + callback_type_ = NONE; callback.Run(rv); } +int DevToolsNetworkTransaction::SetupCallback( + net::CompletionCallback callback, + int result, + CallbackType callback_type) { + DCHECK(callback_type_ == NONE); + + if (result == net::ERR_IO_PENDING) { + callback_type_ = callback_type; + callback_ = callback; + return result; + } + + if (!controller_->ShouldThrottle(request_)) + return result; + + // Only START and READ operation throttling is supported. + if (callback_type != START && callback_type != READ) + return result; + + // In case of error |throttled_byte_count_| is unknown. + if (result < 0) + return result; + + // URLRequestJob relies on synchronous end-of-stream notification. + if (callback_type == READ && result == 0) + return result; + + callback_type_ = callback_type; + callback_ = callback; + Throttle(result); + return net::ERR_IO_PENDING; +} + void DevToolsNetworkTransaction::Fail() { DCHECK(request_); DCHECK(!failed_); @@ -46,6 +99,7 @@ void DevToolsNetworkTransaction::Fail() { return; net::CompletionCallback callback = callback_; callback_.Reset(); + callback_type_ = NONE; callback.Run(net::ERR_INTERNET_DISCONNECTED); } @@ -63,9 +117,7 @@ int DevToolsNetworkTransaction::Start( return net::ERR_INTERNET_DISCONNECTED; } int rv = network_transaction_->Start(request, proxy_callback_, net_log); - if (rv == net::ERR_IO_PENDING) - callback_ = callback; - return rv; + return SetupCallback(callback, rv, START); } int DevToolsNetworkTransaction::RestartIgnoringLastError( @@ -73,9 +125,7 @@ int DevToolsNetworkTransaction::RestartIgnoringLastError( if (failed_) return net::ERR_INTERNET_DISCONNECTED; int rv = network_transaction_->RestartIgnoringLastError(proxy_callback_); - if (rv == net::ERR_IO_PENDING) - callback_ = callback; - return rv; + return SetupCallback(callback, rv, RESTART_IGNORING_LAST_ERROR); } int DevToolsNetworkTransaction::RestartWithCertificate( @@ -85,9 +135,7 @@ int DevToolsNetworkTransaction::RestartWithCertificate( return net::ERR_INTERNET_DISCONNECTED; int rv = network_transaction_->RestartWithCertificate( client_cert, proxy_callback_); - if (rv == net::ERR_IO_PENDING) - callback_ = callback; - return rv; + return SetupCallback(callback, rv, RESTART_WITH_CERTIFICATE); } int DevToolsNetworkTransaction::RestartWithAuth( @@ -96,9 +144,7 @@ int DevToolsNetworkTransaction::RestartWithAuth( if (failed_) return net::ERR_INTERNET_DISCONNECTED; int rv = network_transaction_->RestartWithAuth(credentials, proxy_callback_); - if (rv == net::ERR_IO_PENDING) - callback_ = callback; - return rv; + return SetupCallback(callback, rv, RESTART_WITH_AUTH); } bool DevToolsNetworkTransaction::IsReadyToRestartForAuth() { @@ -112,9 +158,7 @@ int DevToolsNetworkTransaction::Read( if (failed_) return net::ERR_INTERNET_DISCONNECTED; int rv = network_transaction_->Read(buf, buf_len, proxy_callback_); - if (rv == net::ERR_IO_PENDING) - callback_ = callback; - return rv; + return SetupCallback(callback, rv, READ); } void DevToolsNetworkTransaction::StopCaching() { @@ -176,3 +220,12 @@ int DevToolsNetworkTransaction::ResumeNetworkStart() { return net::ERR_INTERNET_DISCONNECTED; return network_transaction_->ResumeNetworkStart(); } + +void DevToolsNetworkTransaction::FireThrottledCallback() { + DCHECK(!callback_.is_null()); + DCHECK(callback_type_ == READ || callback_type_ == START); + net::CompletionCallback callback = callback_; + callback_.Reset(); + callback_type_ = NONE; + callback.Run(throttled_result_); +} diff --git a/chrome/browser/devtools/devtools_network_transaction.h b/chrome/browser/devtools/devtools_network_transaction.h index 5a51ab2..37e84ed 100644 --- a/chrome/browser/devtools/devtools_network_transaction.h +++ b/chrome/browser/devtools/devtools_network_transaction.h @@ -47,6 +47,13 @@ class DevToolsNetworkTransaction : public net::HttpTransaction { // Runs callback (if any) with net::ERR_INTERNET_DISCONNECTED result value. void Fail(); + int64_t throttled_byte_count() const { return throttled_byte_count_; } + void DecreaseThrottledByteCount(int64_t delta) { + throttled_byte_count_ -= delta; + } + + void FireThrottledCallback(); + // HttpTransaction methods: virtual int Start( const net::HttpRequestInfo* request, @@ -102,6 +109,25 @@ class DevToolsNetworkTransaction : public net::HttpTransaction { // True if Fail was already invoked. bool failed_; + enum CallbackType { + NONE, + READ, + RESTART_IGNORING_LAST_ERROR, + RESTART_WITH_AUTH, + RESTART_WITH_CERTIFICATE, + START + }; + + int SetupCallback( + net::CompletionCallback callback, + int result, + CallbackType callback_type); + + void Throttle(int result); + + int throttled_result_; + int64_t throttled_byte_count_; + CallbackType callback_type_; net::CompletionCallback proxy_callback_; net::CompletionCallback callback_; |