Home | History | Annotate | Download | only in devtools
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/devtools/devtools_network_interceptor.h"
      6 
      7 #include <limits>
      8 
      9 #include "base/time/time.h"
     10 #include "chrome/browser/devtools/devtools_network_conditions.h"
     11 #include "chrome/browser/devtools/devtools_network_transaction.h"
     12 #include "net/base/load_timing_info.h"
     13 
     14 namespace {
     15 
     16 int64_t kPacketSize = 1500;
     17 
     18 }  // namespace
     19 
     20 DevToolsNetworkInterceptor::DevToolsNetworkInterceptor()
     21     : conditions_(new DevToolsNetworkConditions()),
     22       weak_ptr_factory_(this) {
     23 }
     24 
     25 DevToolsNetworkInterceptor::~DevToolsNetworkInterceptor() {
     26 }
     27 
     28 base::WeakPtr<DevToolsNetworkInterceptor>
     29 DevToolsNetworkInterceptor::GetWeakPtr() {
     30   return weak_ptr_factory_.GetWeakPtr();
     31 }
     32 
     33 void DevToolsNetworkInterceptor::AddTransaction(
     34     DevToolsNetworkTransaction* transaction) {
     35   DCHECK(transactions_.find(transaction) == transactions_.end());
     36   transactions_.insert(transaction);
     37 }
     38 
     39 void DevToolsNetworkInterceptor::RemoveTransaction(
     40     DevToolsNetworkTransaction* transaction) {
     41   DCHECK(transactions_.find(transaction) != transactions_.end());
     42   transactions_.erase(transaction);
     43 
     44   if (!conditions_->IsThrottling())
     45     return;
     46 
     47   base::TimeTicks now = base::TimeTicks::Now();
     48   UpdateThrottledTransactions(now);
     49   throttled_transactions_.erase(std::remove(throttled_transactions_.begin(),
     50       throttled_transactions_.end(), transaction),
     51       throttled_transactions_.end());
     52 
     53   SuspendedTransactions::iterator it = suspended_transactions_.begin();
     54   for (; it != suspended_transactions_.end(); ++it) {
     55     if (it->first == transaction) {
     56       suspended_transactions_.erase(it);
     57       break;
     58     }
     59   }
     60 
     61   ArmTimer(now);
     62 }
     63 
     64 void DevToolsNetworkInterceptor::UpdateConditions(
     65     scoped_ptr<DevToolsNetworkConditions> conditions) {
     66   DCHECK(conditions);
     67   base::TimeTicks now = base::TimeTicks::Now();
     68   if (conditions_->IsThrottling())
     69     UpdateThrottledTransactions(now);
     70 
     71   conditions_ = conditions.Pass();
     72 
     73   if (conditions_->offline()) {
     74     timer_.Stop();
     75     throttled_transactions_.clear();
     76     suspended_transactions_.clear();
     77     Transactions old_transactions(transactions_);
     78     Transactions::iterator it = old_transactions.begin();
     79     for (;it != old_transactions.end(); ++it) {
     80       if (transactions_.find(*it) == transactions_.end())
     81         continue;
     82       if (!(*it)->request() || (*it)->failed())
     83         continue;
     84       if (ShouldFail(*it))
     85         (*it)->Fail();
     86     }
     87     return;
     88   }
     89 
     90   if (conditions_->IsThrottling()) {
     91     DCHECK(conditions_->download_throughput() != 0);
     92     offset_ = now;
     93     last_tick_ = 0;
     94     int64_t us_tick_length =
     95         (1000000L * kPacketSize) / conditions_->download_throughput();
     96     DCHECK(us_tick_length != 0);
     97     if (us_tick_length == 0)
     98       us_tick_length = 1;
     99     tick_length_ = base::TimeDelta::FromMicroseconds(us_tick_length);
    100     latency_length_ = base::TimeDelta();
    101     double latency = conditions_->latency();
    102     if (latency > 0)
    103       latency_length_ = base::TimeDelta::FromMillisecondsD(latency);
    104     ArmTimer(now);
    105   } else {
    106     timer_.Stop();
    107 
    108     std::vector<DevToolsNetworkTransaction*> throttled_transactions;
    109     throttled_transactions.swap(throttled_transactions_);
    110     size_t throttle_count = throttled_transactions.size();
    111     for (size_t i = 0; i < throttle_count; ++i)
    112       FireThrottledCallback(throttled_transactions[i]);
    113 
    114     SuspendedTransactions suspended_transactions;
    115     suspended_transactions_.swap(suspended_transactions_);
    116     size_t suspend_count = suspended_transactions.size();
    117     for (size_t i = 0; i < suspend_count; ++i)
    118       FireThrottledCallback(suspended_transactions[i].first);
    119   }
    120 }
    121 
    122 void DevToolsNetworkInterceptor::FireThrottledCallback(
    123     DevToolsNetworkTransaction* transaction) {
    124   if (transactions_.find(transaction) != transactions_.end())
    125     transaction->FireThrottledCallback();
    126 }
    127 
    128 void DevToolsNetworkInterceptor::UpdateThrottledTransactions(
    129     base::TimeTicks now) {
    130   int64_t last_tick = (now - offset_) / tick_length_;
    131   int64_t ticks = last_tick - last_tick_;
    132   last_tick_ = last_tick;
    133 
    134   int64_t length = throttled_transactions_.size();
    135   if (!length) {
    136     UpdateSuspendedTransactions(now);
    137     return;
    138   }
    139 
    140   int64_t shift = ticks % length;
    141   for (int64_t i = 0; i < length; ++i) {
    142     throttled_transactions_[i]->DecreaseThrottledByteCount(
    143         (ticks / length) * kPacketSize + (i < shift ? kPacketSize : 0));
    144   }
    145   std::rotate(throttled_transactions_.begin(),
    146       throttled_transactions_.begin() + shift, throttled_transactions_.end());
    147 
    148   UpdateSuspendedTransactions(now);
    149 }
    150 
    151 void DevToolsNetworkInterceptor::UpdateSuspendedTransactions(
    152     base::TimeTicks now) {
    153   int64_t activation_baseline =
    154       (now - latency_length_ - base::TimeTicks()).InMicroseconds();
    155   SuspendedTransactions suspended_transactions;
    156   SuspendedTransactions::iterator it = suspended_transactions_.begin();
    157   for (; it != suspended_transactions_.end(); ++it) {
    158     if (it->second <= activation_baseline)
    159       throttled_transactions_.push_back(it->first);
    160     else
    161       suspended_transactions.push_back(*it);
    162   }
    163   suspended_transactions_.swap(suspended_transactions);
    164 }
    165 
    166 void DevToolsNetworkInterceptor::OnTimer() {
    167   base::TimeTicks now = base::TimeTicks::Now();
    168   UpdateThrottledTransactions(now);
    169 
    170   std::vector<DevToolsNetworkTransaction*> active_transactions;
    171   std::vector<DevToolsNetworkTransaction*> finished_transactions;
    172   size_t length = throttled_transactions_.size();
    173   for (size_t i = 0; i < length; ++i) {
    174     if (throttled_transactions_[i]->throttled_byte_count() < 0)
    175       finished_transactions.push_back(throttled_transactions_[i]);
    176     else
    177       active_transactions.push_back(throttled_transactions_[i]);
    178   }
    179   throttled_transactions_.swap(active_transactions);
    180 
    181   length = finished_transactions.size();
    182   for (size_t i = 0; i < length; ++i)
    183     FireThrottledCallback(finished_transactions[i]);
    184 
    185   ArmTimer(now);
    186 }
    187 
    188 void DevToolsNetworkInterceptor::ArmTimer(base::TimeTicks now) {
    189   size_t throttle_count = throttled_transactions_.size();
    190   size_t suspend_count = suspended_transactions_.size();
    191   if (!throttle_count && !suspend_count)
    192     return;
    193   int64_t min_ticks_left = 0x10000L;
    194   for (size_t i = 0; i < throttle_count; ++i) {
    195     int64_t packets_left = (throttled_transactions_[i]->throttled_byte_count() +
    196         kPacketSize - 1) / kPacketSize;
    197     int64_t ticks_left = (i + 1) + throttle_count * (packets_left - 1);
    198     if (i == 0 || ticks_left < min_ticks_left)
    199       min_ticks_left = ticks_left;
    200   }
    201   base::TimeTicks desired_time =
    202       offset_ + tick_length_ * (last_tick_ + min_ticks_left);
    203 
    204   int64_t min_baseline = std::numeric_limits<int64>::max();
    205   for (size_t i = 0; i < suspend_count; ++i) {
    206     if (suspended_transactions_[i].second < min_baseline)
    207       min_baseline = suspended_transactions_[i].second;
    208   }
    209   if (suspend_count) {
    210     base::TimeTicks activation_time = base::TimeTicks() +
    211         base::TimeDelta::FromMicroseconds(min_baseline) + latency_length_;
    212     if (activation_time < desired_time)
    213       desired_time = activation_time;
    214   }
    215 
    216   timer_.Start(
    217       FROM_HERE,
    218       desired_time - now,
    219       base::Bind(
    220           &DevToolsNetworkInterceptor::OnTimer,
    221           base::Unretained(this)));
    222 }
    223 
    224 void DevToolsNetworkInterceptor::ThrottleTransaction(
    225     DevToolsNetworkTransaction* transaction, bool start) {
    226   base::TimeTicks now = base::TimeTicks::Now();
    227   UpdateThrottledTransactions(now);
    228   if (start && latency_length_ != base::TimeDelta()) {
    229     net::LoadTimingInfo load_timing_info;
    230     base::TimeTicks send_end;
    231     if (transaction->GetLoadTimingInfo(&load_timing_info))
    232       send_end = load_timing_info.send_end;
    233     if (send_end.is_null())
    234       send_end = now;
    235     int64_t us_send_end = (send_end - base::TimeTicks()).InMicroseconds();
    236     suspended_transactions_.push_back(
    237         SuspendedTransaction(transaction, us_send_end));
    238     UpdateSuspendedTransactions(now);
    239   } else {
    240     throttled_transactions_.push_back(transaction);
    241   }
    242   ArmTimer(now);
    243 }
    244 
    245 bool DevToolsNetworkInterceptor::ShouldFail(
    246     const DevToolsNetworkTransaction* transaction) {
    247   if (!conditions_->offline())
    248     return false;
    249 
    250   if (!transaction->request_initiator().empty())
    251     return false;
    252 
    253   return true;
    254 }
    255 
    256 bool DevToolsNetworkInterceptor::ShouldThrottle(
    257     const DevToolsNetworkTransaction* transaction) {
    258   if (!conditions_->IsThrottling())
    259     return false;
    260 
    261   if (!transaction->request_initiator().empty())
    262     return false;
    263 
    264   return true;
    265 }
    266