1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
|
// Copyright 2015 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 "components/data_usage/android/traffic_stats_amortizer.h"
#include <algorithm> // For std::min.
#include <cmath> // For std::modf.
#include <utility>
#include "base/location.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/default_tick_clock.h"
#include "base/timer/timer.h"
#include "components/data_usage/core/data_use.h"
#include "net/android/traffic_stats.h"
namespace data_usage {
namespace android {
namespace {
// Convenience typedef.
typedef std::vector<std::pair<scoped_ptr<DataUse>,
DataUseAmortizer::AmortizationCompleteCallback>>
DataUseBuffer;
// The delay between receiving DataUse and querying TrafficStats byte counts for
// amortization.
// TODO(sclittle): Control this with a field trial parameter.
const int64_t kDefaultTrafficStatsQueryDelayMs = 50;
// The longest amount of time that an amortization run can be delayed for.
// TODO(sclittle): Control this with a field trial parameter.
const int64_t kDefaultMaxAmortizationDelayMs = 500;
// The maximum allowed size of the DataUse buffer. If the buffer ever exceeds
// this size, then DataUse will be amortized immediately and the buffer will be
// flushed.
// TODO(sclittle): Control this with a field trial parameter.
const size_t kDefaultMaxDataUseBufferSize = 128;
// Returns |byte_count| as a histogram sample capped at the maximum histogram
// sample value that's suitable for being recorded without overflowing.
base::HistogramBase::Sample GetByteCountAsHistogramSample(int64_t byte_count) {
DCHECK_GE(byte_count, 0);
if (byte_count >= base::HistogramBase::kSampleType_MAX) {
// Return kSampleType_MAX - 1 because it's invalid to record
// kSampleType_MAX, which would cause a CHECK to fail in the histogram code.
return base::HistogramBase::kSampleType_MAX - 1;
}
return static_cast<base::HistogramBase::Sample>(byte_count);
}
// Scales |bytes| by |ratio|, using |remainder| to hold the running rounding
// error. |bytes| must be non-negative, and multiplying |bytes| by |ratio| must
// yield a number that's representable within the bounds of a non-negative
// int64_t.
int64_t ScaleByteCount(int64_t bytes, double ratio, double* remainder) {
DCHECK_GE(bytes, 0);
DCHECK_GE(ratio, 0.0);
DCHECK_LE(ratio, static_cast<double>(INT64_MAX));
DCHECK_GE(*remainder, 0.0);
DCHECK_LT(*remainder, 1.0);
double intpart;
*remainder =
std::modf(static_cast<double>(bytes) * ratio + (*remainder), &intpart);
DCHECK_GE(intpart, 0.0);
DCHECK_LE(intpart, static_cast<double>(INT64_MAX));
DCHECK_GE(*remainder, 0.0);
DCHECK_LT(*remainder, 1.0);
// Due to floating point error, casting the double |intpart| to an int64_t
// could cause it to overflow, even though it's already been checked to be
// less than the double representation of INT64_MAX. If this happens, cap the
// scaled value at INT64_MAX.
uint64_t scaled_bytes = std::min(static_cast<uint64_t>(intpart),
static_cast<uint64_t>(INT64_MAX));
return static_cast<int64_t>(scaled_bytes);
}
// Amortizes the difference between |desired_post_amortization_total| and
// |pre_amortization_total| into each of the DataUse objects in
// |data_use_sequence| by scaling the byte counts determined by the
// |get_byte_count_fn| function (e.g. tx_bytes, rx_bytes) for each DataUse
// appropriately. |pre_amortization_total| must not be 0.
void AmortizeByteCountSequence(DataUseBuffer* data_use_sequence,
int64_t* (*get_byte_count_fn)(DataUse*),
int64_t pre_amortization_total,
int64_t desired_post_amortization_total) {
DCHECK_GT(pre_amortization_total, 0);
DCHECK_GE(desired_post_amortization_total, 0);
const double ratio = static_cast<double>(desired_post_amortization_total) /
static_cast<double>(pre_amortization_total);
double remainder = 0.0;
for (auto& data_use_buffer_pair : *data_use_sequence) {
int64_t* byte_count = get_byte_count_fn(data_use_buffer_pair.first.get());
*byte_count = ScaleByteCount(*byte_count, ratio, &remainder);
}
}
int64_t* GetTxBytes(DataUse* data_use) {
return &data_use->tx_bytes;
}
int64_t* GetRxBytes(DataUse* data_use) {
return &data_use->rx_bytes;
}
// Returns the total transmitted bytes contained in |data_use_sequence|.
int64_t GetTotalTxBytes(const DataUseBuffer& data_use_sequence) {
int64_t sum = 0;
for (const auto& data_use_buffer_pair : data_use_sequence)
sum += data_use_buffer_pair.first->tx_bytes;
return sum;
}
// Returns the total received bytes contained in |data_use_sequence|.
int64_t GetTotalRxBytes(const DataUseBuffer& data_use_sequence) {
int64_t sum = 0;
for (const auto& data_use_buffer_pair : data_use_sequence)
sum += data_use_buffer_pair.first->rx_bytes;
return sum;
}
} // namespace
TrafficStatsAmortizer::TrafficStatsAmortizer()
: TrafficStatsAmortizer(
scoped_ptr<base::TickClock>(new base::DefaultTickClock()),
scoped_ptr<base::Timer>(new base::Timer(false, false)),
base::TimeDelta::FromMilliseconds(kDefaultTrafficStatsQueryDelayMs),
base::TimeDelta::FromMilliseconds(kDefaultMaxAmortizationDelayMs),
kDefaultMaxDataUseBufferSize) {}
TrafficStatsAmortizer::~TrafficStatsAmortizer() {}
void TrafficStatsAmortizer::AmortizeDataUse(
scoped_ptr<DataUse> data_use,
const AmortizationCompleteCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!callback.is_null());
int64_t tx_bytes = data_use->tx_bytes, rx_bytes = data_use->rx_bytes;
// TODO(sclittle): Combine consecutive buffered DataUse objects that are
// identical except for byte counts and have the same callback.
buffered_data_use_.push_back(
std::pair<scoped_ptr<DataUse>, AmortizationCompleteCallback>(
std::move(data_use), callback));
AddPreAmortizationBytes(tx_bytes, rx_bytes);
}
void TrafficStatsAmortizer::OnExtraBytes(int64_t extra_tx_bytes,
int64_t extra_rx_bytes) {
DCHECK(thread_checker_.CalledOnValidThread());
AddPreAmortizationBytes(extra_tx_bytes, extra_rx_bytes);
}
base::WeakPtr<TrafficStatsAmortizer> TrafficStatsAmortizer::GetWeakPtr() {
DCHECK(thread_checker_.CalledOnValidThread());
return weak_ptr_factory_.GetWeakPtr();
}
TrafficStatsAmortizer::TrafficStatsAmortizer(
scoped_ptr<base::TickClock> tick_clock,
scoped_ptr<base::Timer> traffic_stats_query_timer,
const base::TimeDelta& traffic_stats_query_delay,
const base::TimeDelta& max_amortization_delay,
size_t max_data_use_buffer_size)
: tick_clock_(std::move(tick_clock)),
traffic_stats_query_timer_(std::move(traffic_stats_query_timer)),
traffic_stats_query_delay_(traffic_stats_query_delay),
max_amortization_delay_(max_amortization_delay),
max_data_use_buffer_size_(max_data_use_buffer_size),
is_amortization_in_progress_(false),
are_last_amortization_traffic_stats_available_(false),
last_amortization_traffic_stats_tx_bytes_(-1),
last_amortization_traffic_stats_rx_bytes_(-1),
pre_amortization_tx_bytes_(0),
pre_amortization_rx_bytes_(0),
weak_ptr_factory_(this) {}
bool TrafficStatsAmortizer::QueryTrafficStats(int64_t* tx_bytes,
int64_t* rx_bytes) const {
DCHECK(thread_checker_.CalledOnValidThread());
return net::android::traffic_stats::GetCurrentUidTxBytes(tx_bytes) &&
net::android::traffic_stats::GetCurrentUidRxBytes(rx_bytes);
}
void TrafficStatsAmortizer::AddPreAmortizationBytes(int64_t tx_bytes,
int64_t rx_bytes) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_GE(tx_bytes, 0);
DCHECK_GE(rx_bytes, 0);
base::TimeTicks now_ticks = tick_clock_->NowTicks();
if (!is_amortization_in_progress_) {
is_amortization_in_progress_ = true;
current_amortization_run_start_time_ = now_ticks;
}
pre_amortization_tx_bytes_ += tx_bytes;
pre_amortization_rx_bytes_ += rx_bytes;
if (buffered_data_use_.size() > max_data_use_buffer_size_) {
// Enforce a maximum limit on the size of |buffered_data_use_| to avoid
// hogging memory. Note that this will likely cause the post-amortization
// byte counts calculated here to be less accurate than if the amortizer
// waited to perform amortization.
traffic_stats_query_timer_->Stop();
AmortizeNow();
return;
}
// Cap any amortization delay to |max_amortization_delay_|. Note that if
// |max_amortization_delay_| comes earlier, then this will likely cause the
// post-amortization byte counts calculated here to be less accurate than if
// the amortizer waited to perform amortization.
base::TimeDelta query_delay = std::min(
traffic_stats_query_delay_, current_amortization_run_start_time_ +
max_amortization_delay_ - now_ticks);
// Set the timer to query TrafficStats and amortize after a delay, so that
// it's more likely that TrafficStats will be queried when the network is
// idle. If the timer was already set, then this overrides the previous delay.
traffic_stats_query_timer_->Start(
FROM_HERE, query_delay,
base::Bind(&TrafficStatsAmortizer::AmortizeNow, GetWeakPtr()));
}
void TrafficStatsAmortizer::AmortizeNow() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(is_amortization_in_progress_);
if (!buffered_data_use_.empty()) {
// Record histograms for the pre-amortization byte counts of the DataUse
// objects.
UMA_HISTOGRAM_COUNTS(
"TrafficStatsAmortizer.PreAmortizationRunDataUseBytes.Tx",
GetByteCountAsHistogramSample(GetTotalTxBytes(buffered_data_use_)));
UMA_HISTOGRAM_COUNTS(
"TrafficStatsAmortizer.PreAmortizationRunDataUseBytes.Rx",
GetByteCountAsHistogramSample(GetTotalRxBytes(buffered_data_use_)));
}
int64_t current_traffic_stats_tx_bytes = -1;
int64_t current_traffic_stats_rx_bytes = -1;
bool are_current_traffic_stats_available = QueryTrafficStats(
¤t_traffic_stats_tx_bytes, ¤t_traffic_stats_rx_bytes);
if (are_current_traffic_stats_available &&
are_last_amortization_traffic_stats_available_ &&
!buffered_data_use_.empty()) {
// These TrafficStats byte counts are guaranteed to increase monotonically
// since device boot.
DCHECK_GE(current_traffic_stats_tx_bytes,
last_amortization_traffic_stats_tx_bytes_);
DCHECK_GE(current_traffic_stats_rx_bytes,
last_amortization_traffic_stats_rx_bytes_);
// Only attempt to amortize network overhead from TrafficStats if any of
// those bytes are reflected in the pre-amortization byte totals. Otherwise,
// that network overhead will be amortized in a later amortization run.
if (pre_amortization_tx_bytes_ != 0) {
AmortizeByteCountSequence(&buffered_data_use_, &GetTxBytes,
pre_amortization_tx_bytes_,
current_traffic_stats_tx_bytes -
last_amortization_traffic_stats_tx_bytes_);
}
if (pre_amortization_rx_bytes_ != 0) {
AmortizeByteCountSequence(&buffered_data_use_, &GetRxBytes,
pre_amortization_rx_bytes_,
current_traffic_stats_rx_bytes -
last_amortization_traffic_stats_rx_bytes_);
}
}
if (!buffered_data_use_.empty()) {
// Record histograms for the post-amortization byte counts of the DataUse
// objects.
UMA_HISTOGRAM_COUNTS(
"TrafficStatsAmortizer.PostAmortizationRunDataUseBytes.Tx",
GetByteCountAsHistogramSample(GetTotalTxBytes(buffered_data_use_)));
UMA_HISTOGRAM_COUNTS(
"TrafficStatsAmortizer.PostAmortizationRunDataUseBytes.Rx",
GetByteCountAsHistogramSample(GetTotalRxBytes(buffered_data_use_)));
}
UMA_HISTOGRAM_TIMES(
"TrafficStatsAmortizer.AmortizationDelay",
tick_clock_->NowTicks() - current_amortization_run_start_time_);
UMA_HISTOGRAM_COUNTS_1000("TrafficStatsAmortizer.BufferSizeOnFlush",
buffered_data_use_.size());
// Reset state now that the amortization run has finished.
is_amortization_in_progress_ = false;
current_amortization_run_start_time_ = base::TimeTicks();
// Don't update the previous amortization run's TrafficStats byte counts if
// none of the bytes since then are reflected in the pre-amortization byte
// totals. This way, the overhead that wasn't handled in this amortization run
// can be handled in a later amortization run that actually has bytes in that
// direction. This mitigates the problem of losing TrafficStats overhead bytes
// on slow networks due to TrafficStats seeing the bytes much earlier than the
// network stack reports them, or vice versa.
if (!are_last_amortization_traffic_stats_available_ ||
pre_amortization_tx_bytes_ != 0) {
last_amortization_traffic_stats_tx_bytes_ = current_traffic_stats_tx_bytes;
}
if (!are_last_amortization_traffic_stats_available_ ||
pre_amortization_rx_bytes_ != 0) {
last_amortization_traffic_stats_rx_bytes_ = current_traffic_stats_rx_bytes;
}
are_last_amortization_traffic_stats_available_ =
are_current_traffic_stats_available;
pre_amortization_tx_bytes_ = 0;
pre_amortization_rx_bytes_ = 0;
DataUseBuffer data_use_sequence;
data_use_sequence.swap(buffered_data_use_);
// Pass post-amortization DataUse objects to their respective callbacks.
for (auto& data_use_buffer_pair : data_use_sequence)
data_use_buffer_pair.second.Run(std::move(data_use_buffer_pair.first));
}
} // namespace android
} // namespace data_usage
|