Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: components/data_usage/android/traffic_stats_amortizer.cc

Issue 1390993005: Amortize data usage using TrafficStats on Android. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@data_use_buffering
Patch Set: Simplified and polished design, still ironing out tests Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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.
tbansal1 2015/11/10 18:53:55 Is algorithm not banned in Chromium?
sclittle 2015/11/11 02:10:07 I don't think it's banned, lots of stuff uses it,
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<
23 std::pair<linked_ptr<DataUse>, DataUseAmortizer::DataUseConsumerCallback>>
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;
bengr 2015/11/10 18:12:50 So does this mean we will lose accuracy whenever t
sclittle 2015/11/11 02:10:07 Yes, this is true. I doubt it will be a problem in
40
41 // Scales |bytes| by |ratio|, using |remainder| to hold the running rounding
42 // error.
43 int64_t ScaleByteCount(int64_t bytes, double ratio, double* remainder) {
44 double intpart;
45 *remainder =
46 std::modf(static_cast<double>(bytes) * ratio + (*remainder), &intpart);
bengr 2015/11/10 18:12:50 Are there any assumptions about ratio? E.g., >= 1.
sclittle 2015/11/11 02:10:07 Added DCHECKs and comments. |ratio| can be any dou
47
48 DCHECK_GE(intpart, 0.0);
49 DCHECK_LE(intpart, static_cast<double>(INT64_MAX));
50 DCHECK_GE(*remainder, 0.0);
51 DCHECK_LT(*remainder, 1.0);
52 return static_cast<int64_t>(intpart);
53 }
54
55 // Amortize the difference between |desired_post_amortization_total_tx_bytes|
56 // and |pre_amortization_total_tx_bytes| into each of the DataUse objects in
57 // |data_use_sequence| by scaling the DataUse's |tx_bytes| appropriately. Does
58 // the same with the |rx_bytes| using those respective parameters.
59 void AmortizeDataUseSequence(DataUseBuffer* data_use_sequence,
60 int64_t pre_amortization_total_tx_bytes,
61 int64_t desired_post_amortization_total_tx_bytes,
62 int64_t pre_amortization_total_rx_bytes,
63 int64_t desired_post_amortization_total_rx_bytes) {
64 DCHECK(data_use_sequence);
65 DCHECK(!data_use_sequence->empty());
66
67 if (pre_amortization_total_tx_bytes != 0) {
tbansal1 2015/11/10 18:53:55 if |pre_amortization_total_tx_bytes| is 0 but |des
sclittle 2015/11/11 02:10:07 Added a TODO to handle this case gracefully. I se
68 const double ratio =
69 static_cast<double>(desired_post_amortization_total_tx_bytes) /
70 static_cast<double>(pre_amortization_total_tx_bytes);
71 double remainder = 0.0;
72 for (auto& data_use_buffer_pair : *data_use_sequence) {
73 data_use_buffer_pair.first->tx_bytes = ScaleByteCount(
74 data_use_buffer_pair.first->tx_bytes, ratio, &remainder);
75 // TODO(sclittle): Record UMA about values before vs. after amortization.
76 }
77 }
78
79 if (pre_amortization_total_rx_bytes != 0) {
80 const double ratio =
81 static_cast<double>(desired_post_amortization_total_rx_bytes) /
82 static_cast<double>(pre_amortization_total_rx_bytes);
83 double remainder = 0.0;
84 for (auto& data_use_buffer_pair : *data_use_sequence) {
85 data_use_buffer_pair.first->rx_bytes = ScaleByteCount(
86 data_use_buffer_pair.first->rx_bytes, ratio, &remainder);
87 // TODO(sclittle): Record UMA about values before vs. after amortization.
88 }
89 }
90 }
91
92 } // namespace
93
94 TrafficStatsAmortizer::TrafficStatsAmortizer()
95 : TrafficStatsAmortizer(
96 scoped_ptr<base::TickClock>(new base::DefaultTickClock()),
97 scoped_ptr<base::Timer>(new base::Timer(false, false)),
98 base::TimeDelta::FromMilliseconds(kDefaultTrafficStatsQueryDelayMs),
99 base::TimeDelta::FromMilliseconds(kDefaultMaxAmortizationDelayMs),
100 kDefaultMaxDataUseBufferSize) {}
bengr 2015/11/10 18:12:50 Since there should only be one instance, would it
sclittle 2015/11/11 02:10:07 On second thought, that's not exactly true, I've r
101
102 TrafficStatsAmortizer::~TrafficStatsAmortizer() {}
103
104 void TrafficStatsAmortizer::AmortizeDataUse(
105 scoped_ptr<DataUse> data_use,
106 const DataUseConsumerCallback& callback) {
107 DCHECK(thread_checker_.CalledOnValidThread());
108 DCHECK(!callback.is_null());
109 int64_t tx_bytes = data_use->tx_bytes, rx_bytes = data_use->rx_bytes;
110
111 // TODO(sclittle): Combine consecutive buffered DataUse objects that are
112 // identical except for byte counts and have the same callback.
113 buffered_data_use_.push_back(
114 std::make_pair(linked_ptr<DataUse>(data_use.release()), callback));
115
116 AddPreAmortizationBytes(tx_bytes, rx_bytes);
117 }
118
119 void TrafficStatsAmortizer::OnExtraBytes(int64_t extra_tx_bytes,
120 int64_t extra_rx_bytes) {
121 DCHECK(thread_checker_.CalledOnValidThread());
122 AddPreAmortizationBytes(extra_tx_bytes, extra_rx_bytes);
123 }
124
125 base::WeakPtr<TrafficStatsAmortizer> TrafficStatsAmortizer::GetWeakPtr() {
126 DCHECK(thread_checker_.CalledOnValidThread());
127 return weak_ptr_factory_.GetWeakPtr();
128 }
129
130 TrafficStatsAmortizer::TrafficStatsAmortizer(
131 scoped_ptr<base::TickClock> tick_clock,
132 scoped_ptr<base::Timer> traffic_stats_query_timer,
133 const base::TimeDelta& traffic_stats_query_delay,
134 const base::TimeDelta& max_amortization_delay,
135 size_t max_data_use_buffer_size)
136 : tick_clock_(tick_clock.Pass()),
137 traffic_stats_query_timer_(traffic_stats_query_timer.Pass()),
138 traffic_stats_query_delay_(traffic_stats_query_delay),
139 max_amortization_delay_(max_amortization_delay),
140 max_data_use_buffer_size_(max_data_use_buffer_size),
141 is_amortization_run_in_progress_(false),
tbansal1 2015/11/10 18:53:55 Is it possible to get rid of is_amortization_run_i
sclittle 2015/11/11 02:10:07 The null TimeTicks (i.e. base::TimeTicks()) is act
142 are_last_amortization_traffic_stats_available_(false),
143 last_amortization_traffic_stats_tx_bytes_(-1),
144 last_amortization_traffic_stats_rx_bytes_(-1),
145 pre_amortization_tx_bytes_(0),
146 pre_amortization_rx_bytes_(0),
147 weak_ptr_factory_(this) {}
148
149 bool TrafficStatsAmortizer::QueryTrafficStats(int64_t* tx_bytes,
150 int64_t* rx_bytes) const {
151 DCHECK(thread_checker_.CalledOnValidThread());
152 return net::android::traffic_stats::GetCurrentUidTxBytes(tx_bytes) &&
153 net::android::traffic_stats::GetCurrentUidRxBytes(rx_bytes);
154 }
155
156 void TrafficStatsAmortizer::AddPreAmortizationBytes(int64_t tx_bytes,
157 int64_t rx_bytes) {
158 DCHECK(thread_checker_.CalledOnValidThread());
159 DCHECK_GE(tx_bytes, 0);
160 DCHECK_GE(rx_bytes, 0);
161 base::TimeTicks now_ticks = tick_clock_->NowTicks();
162
163 if (!is_amortization_run_in_progress_) {
bengr 2015/11/10 18:12:50 I'd just call this is_amortization_in_progress_.
sclittle 2015/11/11 02:10:07 Done.
164 is_amortization_run_in_progress_ = true;
165 current_amortization_run_start_time_ = now_ticks;
166 }
167
168 pre_amortization_tx_bytes_ += tx_bytes;
169 pre_amortization_rx_bytes_ += rx_bytes;
170
171 if (buffered_data_use_.size() > max_data_use_buffer_size_) {
172 // Enforce a maximum limit on the size of |buffered_data_use_| to avoid
173 // hogging memory. Note that this will likely cause the post-amortization
174 // byte counts calculated here to be less accurate than if the amortizer
175 // waited to perform amortization.
176 traffic_stats_query_timer_->Stop();
bengr 2015/11/10 18:12:50 Add UMA (or a TODO for UMA).
sclittle 2015/11/11 02:10:07 Done.
177 AmortizeNow();
178 return;
179 }
180
181 // Cap any amortization delay to |max_amortization_delay_|. Note that if
182 // |max_amortization_delay_| comes earlier, then this will likely cause the
183 // post-amortization byte counts calculated here to be less accurate than if
184 // the amortizer waited to perform amortization.
185 base::TimeDelta query_delay = std::min(
186 traffic_stats_query_delay_, current_amortization_run_start_time_ +
187 max_amortization_delay_ - now_ticks);
188
189 // Set the timer to query TrafficStats and amortize after a delay. If the
190 // timer was already set, then this overrides the previous delay.
bengr 2015/11/10 18:12:50 So the point of this is that we will hopefully amo
sclittle 2015/11/11 02:10:07 Yes, added comment.
191 traffic_stats_query_timer_->Start(
tbansal1 2015/11/10 18:53:55 does this post immediately if the delay is negativ
sclittle 2015/11/11 02:10:07 Yes, see https://code.google.com/p/chromium/codese
192 FROM_HERE, query_delay,
193 base::Bind(&TrafficStatsAmortizer::AmortizeNow, GetWeakPtr()));
194 }
195
196 void TrafficStatsAmortizer::AmortizeNow() {
197 DCHECK(thread_checker_.CalledOnValidThread());
198
199 int64_t current_traffic_stats_tx_bytes = -1;
bengr 2015/11/10 18:12:50 What's the significance of -1?
sclittle 2015/11/11 02:10:07 It's just for debug purposes. If something goes wr
bengr 2015/11/11 15:41:37 Please add a comment.
200 int64_t current_traffic_stats_rx_bytes = -1;
201 bool are_current_traffic_stats_available = QueryTrafficStats(
202 &current_traffic_stats_tx_bytes, &current_traffic_stats_rx_bytes);
203
204 if (are_current_traffic_stats_available &&
205 are_last_amortization_traffic_stats_available_ &&
206 !buffered_data_use_.empty()) {
207 // These TrafficStats byte counts are guaranteed to increase monotonically
208 // since device boot.
209 DCHECK_GE(current_traffic_stats_tx_bytes,
210 last_amortization_traffic_stats_tx_bytes_);
211 DCHECK_GE(current_traffic_stats_rx_bytes,
212 last_amortization_traffic_stats_rx_bytes_);
213
214 int64_t desired_post_amortization_total_tx_bytes =
215 current_traffic_stats_tx_bytes -
216 last_amortization_traffic_stats_tx_bytes_;
217 int64_t desired_post_amortization_total_rx_bytes =
218 current_traffic_stats_rx_bytes -
219 last_amortization_traffic_stats_rx_bytes_;
220
221 AmortizeDataUseSequence(&buffered_data_use_, pre_amortization_tx_bytes_,
222 desired_post_amortization_total_tx_bytes,
223 pre_amortization_rx_bytes_,
224 desired_post_amortization_total_rx_bytes);
225 }
226
227 // TODO(sclittle): Record some UMA about the delay before amortizing and how
228 // big the buffer was before amortizing.
229
230 // Reset state now that the amortization run has finished.
231 is_amortization_run_in_progress_ = false;
232 current_amortization_run_start_time_ = base::TimeTicks();
233
234 are_last_amortization_traffic_stats_available_ =
235 are_current_traffic_stats_available;
236 last_amortization_traffic_stats_tx_bytes_ = current_traffic_stats_tx_bytes;
237 last_amortization_traffic_stats_rx_bytes_ = current_traffic_stats_rx_bytes;
238
239 pre_amortization_tx_bytes_ = 0;
240 pre_amortization_rx_bytes_ = 0;
241
242 // Pass post-amortization DataUse to their respective callbacks.
bengr 2015/11/10 18:12:50 DataUse -> DataUse objects
sclittle 2015/11/11 02:10:07 Done.
243 DataUseBuffer data_use_sequence;
244 data_use_sequence.swap(buffered_data_use_);
245 for (auto& data_use_buffer_pair : data_use_sequence) {
246 scoped_ptr<DataUse> data_use(data_use_buffer_pair.first.release());
247 data_use_buffer_pair.second.Run(data_use.Pass());
248 }
249 }
250
251 } // namespace android
252 } // namespace data_usage
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698