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 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
|
// 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 <set>
#include <utility>
#include "base/location.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/default_tick_clock.h"
#include "base/timer/timer.h"
#include "components/data_usage/core/data_use.h"
#include "components/variations/variations_associated_data.h"
#include "net/android/traffic_stats.h"
namespace data_usage {
namespace android {
namespace {
// Convenience typedef.
typedef std::vector<std::pair<std::unique_ptr<DataUse>,
DataUseAmortizer::AmortizationCompleteCallback>>
DataUseBuffer;
// Name of the field trial.
const char kExternalDataUseObserverFieldTrial[] = "ExternalDataUseObserver";
// The delay between receiving DataUse and querying TrafficStats byte counts for
// amortization.
const int64_t kDefaultTrafficStatsQueryDelayMs = 50;
// The longest amount of time that an amortization run can be delayed for.
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.
const size_t kDefaultMaxDataUseBufferSize = 128;
base::TimeDelta GetTrafficStatsQueryDelay() {
int64_t duration_ms = kDefaultTrafficStatsQueryDelayMs;
std::string variation_value = variations::GetVariationParamValue(
kExternalDataUseObserverFieldTrial, "traffic_stats_query_delay_ms");
if (!variation_value.empty() &&
base::StringToInt64(variation_value, &duration_ms) && duration_ms >= 0) {
return base::TimeDelta::FromMilliseconds(duration_ms);
}
return base::TimeDelta::FromMilliseconds(kDefaultTrafficStatsQueryDelayMs);
}
base::TimeDelta GetMaxAmortizationDelay() {
int64_t duration_ms = kDefaultMaxAmortizationDelayMs;
std::string variation_value = variations::GetVariationParamValue(
kExternalDataUseObserverFieldTrial, "max_amortization_delay_ms");
if (!variation_value.empty() &&
base::StringToInt64(variation_value, &duration_ms) && duration_ms >= 0) {
return base::TimeDelta::FromMilliseconds(duration_ms);
}
return base::TimeDelta::FromMilliseconds(kDefaultMaxAmortizationDelayMs);
}
size_t GetMaxDataUseBufferSize() {
size_t max_buffer_size = kDefaultMaxDataUseBufferSize;
std::string variation_value = variations::GetVariationParamValue(
kExternalDataUseObserverFieldTrial, "max_data_use_buffer_size");
if (!variation_value.empty() &&
base::StringToSizeT(variation_value, &max_buffer_size)) {
return max_buffer_size;
}
return kDefaultMaxDataUseBufferSize;
}
// 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;
}
void RecordConcurrentTabsHistogram(const DataUseBuffer& data_use_buffer) {
std::set<int32_t> unique_tabs;
for (const auto& data_use_buffer_pair : data_use_buffer)
unique_tabs.insert(data_use_buffer_pair.first->tab_id);
UMA_HISTOGRAM_COUNTS_100("TrafficStatsAmortizer.ConcurrentTabs",
unique_tabs.size());
}
} // namespace
TrafficStatsAmortizer::TrafficStatsAmortizer()
: TrafficStatsAmortizer(
std::unique_ptr<base::TickClock>(new base::DefaultTickClock()),
std::unique_ptr<base::Timer>(new base::Timer(false, false)),
GetTrafficStatsQueryDelay(),
GetMaxAmortizationDelay(),
GetMaxDataUseBufferSize()) {}
TrafficStatsAmortizer::~TrafficStatsAmortizer() {}
void TrafficStatsAmortizer::AmortizeDataUse(
std::unique_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;
// As an optimization, combine consecutive buffered DataUse objects that are
// identical except for byte counts and have the same callback.
if (!buffered_data_use_.empty() &&
buffered_data_use_.back().first->CanCombineWith(*data_use) &&
buffered_data_use_.back().second.Equals(callback)) {
buffered_data_use_.back().first->tx_bytes += data_use->tx_bytes;
buffered_data_use_.back().first->rx_bytes += data_use->rx_bytes;
} else {
buffered_data_use_.push_back(
std::pair<std::unique_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(
std::unique_ptr<base::TickClock> tick_clock,
std::unique_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_)));
RecordConcurrentTabsHistogram(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
|