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 // Amortize 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 // Amortize the difference between |desired_post_amortization_total_tx_bytes| | |
bengr
2015/11/11 15:41:38
Amortize -> Amortizes
sclittle
2015/11/11 20:02:20
Done.
| |
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 |