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
14namespace {
15
16int64_t kPacketSize = 1500;
17
18}  // namespace
19
20DevToolsNetworkInterceptor::DevToolsNetworkInterceptor()
21    : conditions_(new DevToolsNetworkConditions()),
22      weak_ptr_factory_(this) {
23}
24
25DevToolsNetworkInterceptor::~DevToolsNetworkInterceptor() {
26}
27
28base::WeakPtr<DevToolsNetworkInterceptor>
29DevToolsNetworkInterceptor::GetWeakPtr() {
30  return weak_ptr_factory_.GetWeakPtr();
31}
32
33void DevToolsNetworkInterceptor::AddTransaction(
34    DevToolsNetworkTransaction* transaction) {
35  DCHECK(transactions_.find(transaction) == transactions_.end());
36  transactions_.insert(transaction);
37}
38
39void 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
64void 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
122void DevToolsNetworkInterceptor::FireThrottledCallback(
123    DevToolsNetworkTransaction* transaction) {
124  if (transactions_.find(transaction) != transactions_.end())
125    transaction->FireThrottledCallback();
126}
127
128void 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
151void 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
166void 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
188void 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
224void 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
245bool 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
256bool 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