OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 "components/data_usage/android/traffic_stats_amortizer.h" |
| 6 |
| 7 #include <algorithm> // For std::min. |
| 8 #include <cmath> // For std::modf. |
| 9 |
| 10 #include "base/location.h" |
| 11 #include "base/time/default_tick_clock.h" |
| 12 #include "base/timer/timer.h" |
| 13 #include "components/data_usage/core/data_use.h" |
| 14 #include "net/android/traffic_stats.h" |
| 15 |
| 16 namespace data_usage { |
| 17 namespace android { |
| 18 |
| 19 namespace { |
| 20 |
| 21 // Convenience typedef. |
| 22 typedef std::vector<std::pair<linked_ptr<DataUse>, |
| 23 DataUseAmortizer::AmortizationCompleteCallback>> |
| 24 DataUseBuffer; |
| 25 |
| 26 // The delay between receiving DataUse and querying TrafficStats byte counts for |
| 27 // amortization. |
| 28 // TODO(sclittle): Control this with a field trial parameter. |
| 29 const int64_t kDefaultTrafficStatsQueryDelayMs = 50; |
| 30 |
| 31 // The longest amount of time that an amortization run can be delayed for. |
| 32 // TODO(sclittle): Control this with a field trial parameter. |
| 33 const int64_t kDefaultMaxAmortizationDelayMs = 500; |
| 34 |
| 35 // The maximum allowed size of the DataUse buffer. If the buffer ever exceeds |
| 36 // this size, then DataUse will be amortized immediately and the buffer will be |
| 37 // flushed. |
| 38 // TODO(sclittle): Control this with a field trial parameter. |
| 39 const size_t kDefaultMaxDataUseBufferSize = 128; |
| 40 |
| 41 // Scales |bytes| by |ratio|, using |remainder| to hold the running rounding |
| 42 // error. |bytes| must be non-negative, and multiplying |bytes| by |ratio| must |
| 43 // yield a number that's representable within the bounds of a non-negative |
| 44 // int64_t. |
| 45 int64_t ScaleByteCount(int64_t bytes, double ratio, double* remainder) { |
| 46 DCHECK_GE(bytes, 0); |
| 47 DCHECK_GE(ratio, 0.0); |
| 48 DCHECK_LE(ratio, static_cast<double>(INT64_MAX)); |
| 49 DCHECK_GE(*remainder, 0.0); |
| 50 DCHECK_LT(*remainder, 1.0); |
| 51 |
| 52 double intpart; |
| 53 *remainder = |
| 54 std::modf(static_cast<double>(bytes) * ratio + (*remainder), &intpart); |
| 55 |
| 56 DCHECK_GE(intpart, 0.0); |
| 57 DCHECK_LE(intpart, static_cast<double>(INT64_MAX)); |
| 58 DCHECK_GE(*remainder, 0.0); |
| 59 DCHECK_LT(*remainder, 1.0); |
| 60 |
| 61 // Due to floating point error, casting the double |intpart| to an int64_t |
| 62 // could cause it to overflow, even though it's already been checked to be |
| 63 // less than the double representation of INT64_MAX. If this happens, cap the |
| 64 // scaled value at INT64_MAX. |
| 65 uint64_t scaled_bytes = std::min(static_cast<uint64_t>(intpart), |
| 66 static_cast<uint64_t>(INT64_MAX)); |
| 67 return static_cast<int64_t>(scaled_bytes); |
| 68 } |
| 69 |
| 70 // Amortizes the difference between |desired_post_amortization_total| and |
| 71 // |pre_amortization_total| into each of the DataUse objects in |
| 72 // |data_use_sequence| by scaling the byte counts determined by the |
| 73 // |get_byte_count_fn| function (e.g. tx_bytes, rx_bytes) for each DataUse |
| 74 // appropriately. |
| 75 void AmortizeByteCountSequence(DataUseBuffer* data_use_sequence, |
| 76 int64_t* (*get_byte_count_fn)(DataUse*), |
| 77 int64_t pre_amortization_total, |
| 78 int64_t desired_post_amortization_total) { |
| 79 DCHECK_GE(pre_amortization_total, 0); |
| 80 DCHECK_GE(desired_post_amortization_total, 0); |
| 81 |
| 82 if (pre_amortization_total == 0) { |
| 83 // TODO(sclittle): If |desired_post_amortization_total| is non-zero, this |
| 84 // could be ignoring overhead that should be amortized in somehow. Handle |
| 85 // this case gracefully. |
| 86 return; |
| 87 } |
| 88 |
| 89 const double ratio = static_cast<double>(desired_post_amortization_total) / |
| 90 static_cast<double>(pre_amortization_total); |
| 91 |
| 92 double remainder = 0.0; |
| 93 for (auto& data_use_buffer_pair : *data_use_sequence) { |
| 94 int64_t* byte_count = get_byte_count_fn(data_use_buffer_pair.first.get()); |
| 95 *byte_count = ScaleByteCount(*byte_count, ratio, &remainder); |
| 96 // TODO(sclittle): Record UMA about values before vs. after amortization. |
| 97 } |
| 98 } |
| 99 |
| 100 int64_t* GetTxBytes(DataUse* data_use) { |
| 101 return &data_use->tx_bytes; |
| 102 } |
| 103 int64_t* GetRxBytes(DataUse* data_use) { |
| 104 return &data_use->rx_bytes; |
| 105 } |
| 106 |
| 107 // Amortizes the difference between |desired_post_amortization_total_tx_bytes| |
| 108 // and |pre_amortization_total_tx_bytes| into each of the DataUse objects in |
| 109 // |data_use_sequence| by scaling the DataUse's |tx_bytes| appropriately. Does |
| 110 // the same with the |rx_bytes| using those respective parameters. |
| 111 void AmortizeDataUseSequence(DataUseBuffer* data_use_sequence, |
| 112 int64_t pre_amortization_total_tx_bytes, |
| 113 int64_t desired_post_amortization_total_tx_bytes, |
| 114 int64_t pre_amortization_total_rx_bytes, |
| 115 int64_t desired_post_amortization_total_rx_bytes) { |
| 116 DCHECK(data_use_sequence); |
| 117 DCHECK(!data_use_sequence->empty()); |
| 118 |
| 119 AmortizeByteCountSequence(data_use_sequence, &GetTxBytes, |
| 120 pre_amortization_total_tx_bytes, |
| 121 desired_post_amortization_total_tx_bytes); |
| 122 |
| 123 AmortizeByteCountSequence(data_use_sequence, &GetRxBytes, |
| 124 pre_amortization_total_rx_bytes, |
| 125 desired_post_amortization_total_rx_bytes); |
| 126 } |
| 127 |
| 128 } // namespace |
| 129 |
| 130 TrafficStatsAmortizer::TrafficStatsAmortizer() |
| 131 : TrafficStatsAmortizer( |
| 132 scoped_ptr<base::TickClock>(new base::DefaultTickClock()), |
| 133 scoped_ptr<base::Timer>(new base::Timer(false, false)), |
| 134 base::TimeDelta::FromMilliseconds(kDefaultTrafficStatsQueryDelayMs), |
| 135 base::TimeDelta::FromMilliseconds(kDefaultMaxAmortizationDelayMs), |
| 136 kDefaultMaxDataUseBufferSize) {} |
| 137 |
| 138 TrafficStatsAmortizer::~TrafficStatsAmortizer() {} |
| 139 |
| 140 void TrafficStatsAmortizer::AmortizeDataUse( |
| 141 scoped_ptr<DataUse> data_use, |
| 142 const AmortizationCompleteCallback& callback) { |
| 143 DCHECK(thread_checker_.CalledOnValidThread()); |
| 144 DCHECK(!callback.is_null()); |
| 145 int64_t tx_bytes = data_use->tx_bytes, rx_bytes = data_use->rx_bytes; |
| 146 |
| 147 // TODO(sclittle): Combine consecutive buffered DataUse objects that are |
| 148 // identical except for byte counts and have the same callback. |
| 149 buffered_data_use_.push_back( |
| 150 std::make_pair(linked_ptr<DataUse>(data_use.release()), callback)); |
| 151 |
| 152 AddPreAmortizationBytes(tx_bytes, rx_bytes); |
| 153 } |
| 154 |
| 155 void TrafficStatsAmortizer::OnExtraBytes(int64_t extra_tx_bytes, |
| 156 int64_t extra_rx_bytes) { |
| 157 DCHECK(thread_checker_.CalledOnValidThread()); |
| 158 AddPreAmortizationBytes(extra_tx_bytes, extra_rx_bytes); |
| 159 } |
| 160 |
| 161 base::WeakPtr<TrafficStatsAmortizer> TrafficStatsAmortizer::GetWeakPtr() { |
| 162 DCHECK(thread_checker_.CalledOnValidThread()); |
| 163 return weak_ptr_factory_.GetWeakPtr(); |
| 164 } |
| 165 |
| 166 TrafficStatsAmortizer::TrafficStatsAmortizer( |
| 167 scoped_ptr<base::TickClock> tick_clock, |
| 168 scoped_ptr<base::Timer> traffic_stats_query_timer, |
| 169 const base::TimeDelta& traffic_stats_query_delay, |
| 170 const base::TimeDelta& max_amortization_delay, |
| 171 size_t max_data_use_buffer_size) |
| 172 : tick_clock_(tick_clock.Pass()), |
| 173 traffic_stats_query_timer_(traffic_stats_query_timer.Pass()), |
| 174 traffic_stats_query_delay_(traffic_stats_query_delay), |
| 175 max_amortization_delay_(max_amortization_delay), |
| 176 max_data_use_buffer_size_(max_data_use_buffer_size), |
| 177 is_amortization_in_progress_(false), |
| 178 are_last_amortization_traffic_stats_available_(false), |
| 179 last_amortization_traffic_stats_tx_bytes_(-1), |
| 180 last_amortization_traffic_stats_rx_bytes_(-1), |
| 181 pre_amortization_tx_bytes_(0), |
| 182 pre_amortization_rx_bytes_(0), |
| 183 weak_ptr_factory_(this) {} |
| 184 |
| 185 bool TrafficStatsAmortizer::QueryTrafficStats(int64_t* tx_bytes, |
| 186 int64_t* rx_bytes) const { |
| 187 DCHECK(thread_checker_.CalledOnValidThread()); |
| 188 return net::android::traffic_stats::GetCurrentUidTxBytes(tx_bytes) && |
| 189 net::android::traffic_stats::GetCurrentUidRxBytes(rx_bytes); |
| 190 } |
| 191 |
| 192 void TrafficStatsAmortizer::AddPreAmortizationBytes(int64_t tx_bytes, |
| 193 int64_t rx_bytes) { |
| 194 DCHECK(thread_checker_.CalledOnValidThread()); |
| 195 DCHECK_GE(tx_bytes, 0); |
| 196 DCHECK_GE(rx_bytes, 0); |
| 197 base::TimeTicks now_ticks = tick_clock_->NowTicks(); |
| 198 |
| 199 if (!is_amortization_in_progress_) { |
| 200 is_amortization_in_progress_ = true; |
| 201 current_amortization_run_start_time_ = now_ticks; |
| 202 } |
| 203 |
| 204 pre_amortization_tx_bytes_ += tx_bytes; |
| 205 pre_amortization_rx_bytes_ += rx_bytes; |
| 206 |
| 207 if (buffered_data_use_.size() > max_data_use_buffer_size_) { |
| 208 // Enforce a maximum limit on the size of |buffered_data_use_| to avoid |
| 209 // hogging memory. Note that this will likely cause the post-amortization |
| 210 // byte counts calculated here to be less accurate than if the amortizer |
| 211 // waited to perform amortization. |
| 212 // TODO(sclittle): Record UMA about how often this occurs. |
| 213 traffic_stats_query_timer_->Stop(); |
| 214 AmortizeNow(); |
| 215 return; |
| 216 } |
| 217 |
| 218 // Cap any amortization delay to |max_amortization_delay_|. Note that if |
| 219 // |max_amortization_delay_| comes earlier, then this will likely cause the |
| 220 // post-amortization byte counts calculated here to be less accurate than if |
| 221 // the amortizer waited to perform amortization. |
| 222 // TODO(sclittle): Record UMA about how often |max_amortization_delay_| comes |
| 223 // earlier. |
| 224 base::TimeDelta query_delay = std::min( |
| 225 traffic_stats_query_delay_, current_amortization_run_start_time_ + |
| 226 max_amortization_delay_ - now_ticks); |
| 227 |
| 228 // Set the timer to query TrafficStats and amortize after a delay, so that |
| 229 // it's more likely that TrafficStats will be queried when the network is |
| 230 // idle. If the timer was already set, then this overrides the previous delay. |
| 231 traffic_stats_query_timer_->Start( |
| 232 FROM_HERE, query_delay, |
| 233 base::Bind(&TrafficStatsAmortizer::AmortizeNow, GetWeakPtr())); |
| 234 } |
| 235 |
| 236 void TrafficStatsAmortizer::AmortizeNow() { |
| 237 DCHECK(thread_checker_.CalledOnValidThread()); |
| 238 |
| 239 int64_t current_traffic_stats_tx_bytes = -1; |
| 240 int64_t current_traffic_stats_rx_bytes = -1; |
| 241 bool are_current_traffic_stats_available = QueryTrafficStats( |
| 242 ¤t_traffic_stats_tx_bytes, ¤t_traffic_stats_rx_bytes); |
| 243 |
| 244 if (are_current_traffic_stats_available && |
| 245 are_last_amortization_traffic_stats_available_ && |
| 246 !buffered_data_use_.empty()) { |
| 247 // These TrafficStats byte counts are guaranteed to increase monotonically |
| 248 // since device boot. |
| 249 DCHECK_GE(current_traffic_stats_tx_bytes, |
| 250 last_amortization_traffic_stats_tx_bytes_); |
| 251 DCHECK_GE(current_traffic_stats_rx_bytes, |
| 252 last_amortization_traffic_stats_rx_bytes_); |
| 253 |
| 254 int64_t desired_post_amortization_total_tx_bytes = |
| 255 current_traffic_stats_tx_bytes - |
| 256 last_amortization_traffic_stats_tx_bytes_; |
| 257 int64_t desired_post_amortization_total_rx_bytes = |
| 258 current_traffic_stats_rx_bytes - |
| 259 last_amortization_traffic_stats_rx_bytes_; |
| 260 |
| 261 AmortizeDataUseSequence(&buffered_data_use_, pre_amortization_tx_bytes_, |
| 262 desired_post_amortization_total_tx_bytes, |
| 263 pre_amortization_rx_bytes_, |
| 264 desired_post_amortization_total_rx_bytes); |
| 265 } |
| 266 |
| 267 // TODO(sclittle): Record some UMA about the delay before amortizing and how |
| 268 // big the buffer was before amortizing. |
| 269 |
| 270 // Reset state now that the amortization run has finished. |
| 271 is_amortization_in_progress_ = false; |
| 272 current_amortization_run_start_time_ = base::TimeTicks(); |
| 273 |
| 274 are_last_amortization_traffic_stats_available_ = |
| 275 are_current_traffic_stats_available; |
| 276 last_amortization_traffic_stats_tx_bytes_ = current_traffic_stats_tx_bytes; |
| 277 last_amortization_traffic_stats_rx_bytes_ = current_traffic_stats_rx_bytes; |
| 278 |
| 279 pre_amortization_tx_bytes_ = 0; |
| 280 pre_amortization_rx_bytes_ = 0; |
| 281 |
| 282 // Pass post-amortization DataUse objects to their respective callbacks. |
| 283 DataUseBuffer data_use_sequence; |
| 284 data_use_sequence.swap(buffered_data_use_); |
| 285 for (auto& data_use_buffer_pair : data_use_sequence) { |
| 286 scoped_ptr<DataUse> data_use(data_use_buffer_pair.first.release()); |
| 287 data_use_buffer_pair.second.Run(data_use.Pass()); |
| 288 } |
| 289 } |
| 290 |
| 291 } // namespace android |
| 292 } // namespace data_usage |
OLD | NEW |