| // 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 "services/network/throttling/throttling_network_interceptor.h" |
| |
| #include <stddef.h> |
| #include <algorithm> |
| #include <limits> |
| #include <utility> |
| |
| #include "base/time/time.h" |
| #include "net/base/net_errors.h" |
| #include "services/network/throttling/network_conditions.h" |
| |
| namespace network { |
| |
| namespace { |
| |
| int64_t kPacketSize = 1500; |
| |
| base::TimeDelta CalculateTickLength(double throughput) { |
| if (!throughput) |
| return base::TimeDelta::FromMicroseconds(1); |
| int64_t us_tick_length = (1000000L * kPacketSize) / throughput; |
| DCHECK(us_tick_length != 0); |
| if (us_tick_length == 0) |
| us_tick_length = 1; |
| return base::TimeDelta::FromMicroseconds(us_tick_length); |
| } |
| |
| } // namespace |
| |
| ThrottlingNetworkInterceptor::ThrottleRecord::ThrottleRecord() {} |
| |
| ThrottlingNetworkInterceptor::ThrottleRecord::ThrottleRecord( |
| const ThrottleRecord& other) = default; |
| |
| ThrottlingNetworkInterceptor::ThrottleRecord::~ThrottleRecord() {} |
| |
| ThrottlingNetworkInterceptor::ThrottlingNetworkInterceptor() |
| : conditions_(new NetworkConditions()), |
| download_last_tick_(0), |
| upload_last_tick_(0), |
| weak_ptr_factory_(this) {} |
| |
| ThrottlingNetworkInterceptor::~ThrottlingNetworkInterceptor() {} |
| |
| base::WeakPtr<ThrottlingNetworkInterceptor> |
| ThrottlingNetworkInterceptor::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void ThrottlingNetworkInterceptor::FinishRecords(ThrottleRecords* records, |
| bool offline) { |
| ThrottleRecords temp; |
| temp.swap(*records); |
| for (const ThrottleRecord& record : temp) { |
| bool failed = offline && !record.is_upload; |
| record.callback.Run(failed ? net::ERR_INTERNET_DISCONNECTED : record.result, |
| record.bytes); |
| } |
| } |
| |
| void ThrottlingNetworkInterceptor::UpdateConditions( |
| std::unique_ptr<NetworkConditions> conditions) { |
| DCHECK(conditions); |
| base::TimeTicks now = base::TimeTicks::Now(); |
| if (conditions_->IsThrottling()) |
| UpdateThrottled(now); |
| |
| conditions_ = std::move(conditions); |
| |
| bool offline = conditions_->offline(); |
| if (offline || !conditions_->IsThrottling()) { |
| timer_.Stop(); |
| FinishRecords(&download_, offline); |
| FinishRecords(&upload_, offline); |
| FinishRecords(&suspended_, offline); |
| return; |
| } |
| |
| // Throttling. |
| DCHECK(conditions_->download_throughput() != 0 || |
| conditions_->upload_throughput() != 0); |
| offset_ = now; |
| |
| download_last_tick_ = 0; |
| download_tick_length_ = |
| CalculateTickLength(conditions_->download_throughput()); |
| |
| upload_last_tick_ = 0; |
| upload_tick_length_ = CalculateTickLength(conditions_->upload_throughput()); |
| |
| latency_length_ = base::TimeDelta(); |
| double latency = conditions_->latency(); |
| if (latency > 0) |
| latency_length_ = base::TimeDelta::FromMillisecondsD(latency); |
| ArmTimer(now); |
| } |
| |
| uint64_t ThrottlingNetworkInterceptor::UpdateThrottledRecords( |
| base::TimeTicks now, |
| ThrottleRecords* records, |
| uint64_t last_tick, |
| base::TimeDelta tick_length) { |
| if (tick_length.is_zero()) { |
| DCHECK(records->empty()); |
| return last_tick; |
| } |
| |
| int64_t new_tick = (now - offset_) / tick_length; |
| int64_t ticks = new_tick - last_tick; |
| |
| int64_t length = records->size(); |
| if (!length) |
| return new_tick; |
| |
| int64_t shift = ticks % length; |
| for (int64_t i = 0; i < length; ++i) { |
| (*records)[i].bytes -= |
| (ticks / length) * kPacketSize + (i < shift ? kPacketSize : 0); |
| } |
| std::rotate(records->begin(), records->begin() + shift, records->end()); |
| return new_tick; |
| } |
| |
| void ThrottlingNetworkInterceptor::UpdateThrottled(base::TimeTicks now) { |
| download_last_tick_ = UpdateThrottledRecords( |
| now, &download_, download_last_tick_, download_tick_length_); |
| upload_last_tick_ = UpdateThrottledRecords(now, &upload_, upload_last_tick_, |
| upload_tick_length_); |
| UpdateSuspended(now); |
| } |
| |
| void ThrottlingNetworkInterceptor::UpdateSuspended(base::TimeTicks now) { |
| int64_t activation_baseline = |
| (now - latency_length_ - base::TimeTicks()).InMicroseconds(); |
| ThrottleRecords suspended; |
| for (const ThrottleRecord& record : suspended_) { |
| if (record.send_end <= activation_baseline) { |
| if (record.is_upload) |
| upload_.push_back(record); |
| else |
| download_.push_back(record); |
| } else { |
| suspended.push_back(record); |
| } |
| } |
| suspended_.swap(suspended); |
| } |
| |
| void ThrottlingNetworkInterceptor::CollectFinished(ThrottleRecords* records, |
| ThrottleRecords* finished) { |
| ThrottleRecords active; |
| for (const ThrottleRecord& record : *records) { |
| if (record.bytes < 0) |
| finished->push_back(record); |
| else |
| active.push_back(record); |
| } |
| records->swap(active); |
| } |
| |
| void ThrottlingNetworkInterceptor::OnTimer() { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| UpdateThrottled(now); |
| |
| ThrottleRecords finished; |
| CollectFinished(&download_, &finished); |
| CollectFinished(&upload_, &finished); |
| for (const ThrottleRecord& record : finished) |
| record.callback.Run(record.result, record.bytes); |
| |
| ArmTimer(now); |
| } |
| |
| base::TimeTicks ThrottlingNetworkInterceptor::CalculateDesiredTime( |
| const ThrottleRecords& records, |
| uint64_t last_tick, |
| base::TimeDelta tick_length) { |
| int64_t min_ticks_left = 0x10000L; |
| size_t count = records.size(); |
| for (size_t i = 0; i < count; ++i) { |
| int64_t packets_left = (records[i].bytes + kPacketSize - 1) / kPacketSize; |
| int64_t ticks_left = (i + 1) + count * (packets_left - 1); |
| if (i == 0 || ticks_left < min_ticks_left) |
| min_ticks_left = ticks_left; |
| } |
| return offset_ + tick_length * (last_tick + min_ticks_left); |
| } |
| |
| void ThrottlingNetworkInterceptor::ArmTimer(base::TimeTicks now) { |
| size_t suspend_count = suspended_.size(); |
| if (download_.empty() && upload_.empty() && !suspend_count) |
| return; |
| |
| base::TimeTicks desired_time = CalculateDesiredTime( |
| download_, download_last_tick_, download_tick_length_); |
| |
| base::TimeTicks upload_time = |
| CalculateDesiredTime(upload_, upload_last_tick_, upload_tick_length_); |
| if (upload_time < desired_time) |
| desired_time = upload_time; |
| |
| int64_t min_baseline = std::numeric_limits<int64_t>::max(); |
| for (size_t i = 0; i < suspend_count; ++i) { |
| if (suspended_[i].send_end < min_baseline) |
| min_baseline = suspended_[i].send_end; |
| } |
| if (suspend_count) { |
| base::TimeTicks activation_time = |
| base::TimeTicks() + base::TimeDelta::FromMicroseconds(min_baseline) + |
| latency_length_; |
| if (activation_time < desired_time) |
| desired_time = activation_time; |
| } |
| |
| timer_.Start(FROM_HERE, desired_time - now, |
| base::Bind(&ThrottlingNetworkInterceptor::OnTimer, |
| base::Unretained(this))); |
| } |
| |
| int ThrottlingNetworkInterceptor::StartThrottle( |
| int result, |
| int64_t bytes, |
| base::TimeTicks send_end, |
| bool start, |
| bool is_upload, |
| const ThrottleCallback& callback) { |
| if (result < 0) |
| return result; |
| |
| if (conditions_->offline()) |
| return is_upload ? result : net::ERR_INTERNET_DISCONNECTED; |
| |
| if (!conditions_->latency() && |
| ((is_upload && !conditions_->upload_throughput()) || |
| (!is_upload && !conditions_->download_throughput()))) { |
| return result; |
| } |
| |
| ThrottleRecord record; |
| record.result = result; |
| record.bytes = bytes; |
| record.callback = callback; |
| // TODO(dgozman): use upload throughput. |
| record.is_upload = is_upload; |
| |
| base::TimeTicks now = base::TimeTicks::Now(); |
| UpdateThrottled(now); |
| if (start && !latency_length_.is_zero()) { |
| record.send_end = (send_end - base::TimeTicks()).InMicroseconds(); |
| suspended_.push_back(record); |
| UpdateSuspended(now); |
| } else { |
| if (is_upload) |
| upload_.push_back(record); |
| else |
| download_.push_back(record); |
| } |
| ArmTimer(now); |
| |
| return net::ERR_IO_PENDING; |
| } |
| |
| void ThrottlingNetworkInterceptor::StopThrottle( |
| const ThrottleCallback& callback) { |
| RemoveRecord(&download_, callback); |
| RemoveRecord(&upload_, callback); |
| RemoveRecord(&suspended_, callback); |
| } |
| |
| void ThrottlingNetworkInterceptor::RemoveRecord( |
| ThrottleRecords* records, |
| const ThrottleCallback& callback) { |
| records->erase(std::remove_if(records->begin(), records->end(), |
| [&callback](const ThrottleRecord& record) { |
| return record.callback.Equals(callback); |
| }), |
| records->end()); |
| } |
| |
| bool ThrottlingNetworkInterceptor::IsOffline() { |
| return conditions_->offline(); |
| } |
| |
| } // namespace network |