summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsclittle <sclittle@chromium.org>2015-11-11 17:12:09 -0800
committerCommit bot <commit-bot@chromium.org>2015-11-12 01:13:06 +0000
commitc441f78fcb48ef54a880e32fc2b43996958d3eb9 (patch)
tree0d4dbca6f358252290486ec9e3892d849fd7bfb7
parent708eadbf7e32aa3e90e2bd4da2d07d025de91593 (diff)
downloadchromium_src-c441f78fcb48ef54a880e32fc2b43996958d3eb9.zip
chromium_src-c441f78fcb48ef54a880e32fc2b43996958d3eb9.tar.gz
chromium_src-c441f78fcb48ef54a880e32fc2b43996958d3eb9.tar.bz2
Amortize data usage using TrafficStats on Android.
This CL adds a DataUseAmortizer interface to the data_usage component, and makes the DataUseAggregator use it to amortize data usage byte counts on supported platforms. This CL also adds an implementation of the DataUseAmortizer based on TrafficStats for Android. TBR=brettw@chromium.org,mmenke@chromium.org BUG=518051 Review URL: https://codereview.chromium.org/1390993005 Cr-Commit-Position: refs/heads/master@{#359198}
-rw-r--r--chrome/browser/BUILD.gn1
-rw-r--r--chrome/browser/DEPS2
-rw-r--r--chrome/browser/android/data_usage/external_data_use_observer_unittest.cc4
-rw-r--r--chrome/browser/io_thread.cc10
-rw-r--r--chrome/browser/net/chrome_network_delegate_unittest.cc4
-rw-r--r--chrome/chrome_browser.gypi1
-rw-r--r--components/components_tests.gyp2
-rw-r--r--components/data_usage.gypi23
-rw-r--r--components/data_usage/android/BUILD.gn34
-rw-r--r--components/data_usage/android/DEPS5
-rw-r--r--components/data_usage/android/traffic_stats_amortizer.cc292
-rw-r--r--components/data_usage/android/traffic_stats_amortizer.h163
-rw-r--r--components/data_usage/android/traffic_stats_amortizer_unittest.cc416
-rw-r--r--components/data_usage/core/BUILD.gn1
-rw-r--r--components/data_usage/core/data_use_aggregator.cc66
-rw-r--r--components/data_usage/core/data_use_aggregator.h34
-rw-r--r--components/data_usage/core/data_use_aggregator_unittest.cc340
-rw-r--r--components/data_usage/core/data_use_amortizer.h42
18 files changed, 1199 insertions, 241 deletions
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2835fdc..2eac38e 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -727,6 +727,7 @@ source_set("browser") {
":delta_file_proto",
":jni_headers",
"//components/cdm/browser",
+ "//components/data_usage/android",
"//components/enhanced_bookmarks",
"//components/precache/content",
"//components/precache/core",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 93ca219..8ffb6c8 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -34,7 +34,7 @@ include_rules = [
"+components/crash",
"+components/crx_file",
"+components/data_reduction_proxy",
- "+components/data_usage/core",
+ "+components/data_usage",
"+components/data_use_measurement/core",
"+components/device_event_log",
"+components/dom_distiller",
diff --git a/chrome/browser/android/data_usage/external_data_use_observer_unittest.cc b/chrome/browser/android/data_usage/external_data_use_observer_unittest.cc
index 9153ad447..580ba8c 100644
--- a/chrome/browser/android/data_usage/external_data_use_observer_unittest.cc
+++ b/chrome/browser/android/data_usage/external_data_use_observer_unittest.cc
@@ -15,6 +15,7 @@
#include "base/thread_task_runner_handle.h"
#include "components/data_usage/core/data_use.h"
#include "components/data_usage/core/data_use_aggregator.h"
+#include "components/data_usage/core/data_use_amortizer.h"
#include "components/data_usage/core/data_use_annotator.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/browser_thread.h"
@@ -37,7 +38,8 @@ class ExternalDataUseObserverTest : public testing::Test {
ui_task_runner_ = content::BrowserThread::GetMessageLoopProxyForThread(
content::BrowserThread::UI);
data_use_aggregator_.reset(new data_usage::DataUseAggregator(
- scoped_ptr<data_usage::DataUseAnnotator>()));
+ scoped_ptr<data_usage::DataUseAnnotator>(),
+ scoped_ptr<data_usage::DataUseAmortizer>()));
external_data_use_observer_.reset(new ExternalDataUseObserver(
data_use_aggregator_.get(), io_task_runner_.get(),
ui_task_runner_.get()));
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index ec1f91e..35ed57e 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -43,6 +43,7 @@
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_prefs.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h"
+#include "components/data_usage/core/data_use_amortizer.h"
#include "components/net_log/chrome_net_log.h"
#include "components/policy/core/common/policy_service.h"
#include "components/proxy_config/pref_proxy_config_tracker.h"
@@ -115,6 +116,7 @@
#include "base/android/build_info.h"
#include "chrome/browser/android/data_usage/external_data_use_observer.h"
#include "chrome/browser/android/net/external_estimate_provider_android.h"
+#include "components/data_usage/android/traffic_stats_amortizer.h"
#endif
#if defined(OS_CHROMEOS)
@@ -606,9 +608,15 @@ void IOThread::Init() {
extension_event_router_forwarder_;
#endif
+ scoped_ptr<data_usage::DataUseAmortizer> data_use_amortizer;
+#if defined(OS_ANDROID)
+ data_use_amortizer.reset(new data_usage::android::TrafficStatsAmortizer());
+#endif
+
data_use_aggregator_.reset(new data_usage::DataUseAggregator(
scoped_ptr<data_usage::DataUseAnnotator>(
- new chrome_browser_data_usage::TabIdAnnotator())));
+ new chrome_browser_data_usage::TabIdAnnotator()),
+ data_use_amortizer.Pass()));
// TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466432
// is fixed.
diff --git a/chrome/browser/net/chrome_network_delegate_unittest.cc b/chrome/browser/net/chrome_network_delegate_unittest.cc
index 39f29d0..69435bc 100644
--- a/chrome/browser/net/chrome_network_delegate_unittest.cc
+++ b/chrome/browser/net/chrome_network_delegate_unittest.cc
@@ -23,6 +23,7 @@
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/data_usage/core/data_use_aggregator.h"
+#include "components/data_usage/core/data_use_amortizer.h"
#include "components/data_usage/core/data_use_annotator.h"
#include "components/syncable_prefs/testing_pref_service_syncable.h"
#include "content/public/browser/resource_request_info.h"
@@ -101,7 +102,8 @@ class FakeDataUseAggregator : public data_usage::DataUseAggregator {
public:
FakeDataUseAggregator()
: data_usage::DataUseAggregator(
- scoped_ptr<data_usage::DataUseAnnotator>()),
+ scoped_ptr<data_usage::DataUseAnnotator>(),
+ scoped_ptr<data_usage::DataUseAmortizer>()),
on_the_record_tx_bytes_(0),
on_the_record_rx_bytes_(0),
off_the_record_tx_bytes_(0),
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index d0d5cc9..314abd0 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -3667,6 +3667,7 @@
'../build/android/ndk.gyp:cpu_features',
'../components/components.gyp:cdm_browser',
'../components/components.gyp:data_reduction_proxy_content',
+ '../components/components.gyp:data_usage_android',
'../components/components.gyp:enhanced_bookmarks',
'../components/components.gyp:offline_pages',
'../components/components.gyp:precache_content',
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 8187a69..98a1380 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -1283,6 +1283,7 @@
'data_reduction_proxy/content/browser/data_reduction_proxy_debug_blocking_page_unittest.cc',
'data_reduction_proxy/content/browser/data_reduction_proxy_debug_resource_throttle_unittest.cc',
'data_reduction_proxy/content/browser/data_reduction_proxy_debug_ui_manager_unittest.cc',
+ 'data_usage/android/traffic_stats_amortizer_unittest.cc',
'invalidation/impl/invalidation_logger_unittest.cc',
'invalidation/impl/invalidation_service_android_unittest.cc',
],
@@ -1303,6 +1304,7 @@
'dependencies': [
'components.gyp:cronet_static',
'components.gyp:data_reduction_proxy_content',
+ 'components.gyp:data_usage_android',
'components.gyp:safe_json_java',
'../content/content.gyp:content_java',
'../testing/android/native_test.gyp:native_test_native_code',
diff --git a/components/data_usage.gypi b/components/data_usage.gypi
index ba77b57..6fcef19 100644
--- a/components/data_usage.gypi
+++ b/components/data_usage.gypi
@@ -16,8 +16,29 @@
'data_usage/core/data_use.h',
'data_usage/core/data_use_aggregator.cc',
'data_usage/core/data_use_aggregator.h',
+ 'data_usage/core/data_use_amortizer.h',
'data_usage/core/data_use_annotator.h',
]
},
- ]
+ ],
+ 'conditions': [
+ ['OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'data_usage_android',
+ 'type': 'static_library',
+ 'dependencies': [
+ ':data_usage_core',
+ '../base/base.gyp:base',
+ '../net/net.gyp:net',
+ '../url/url.gyp:url_lib',
+ ],
+ 'sources': [
+ 'data_usage/android/traffic_stats_amortizer.cc',
+ 'data_usage/android/traffic_stats_amortizer.h',
+ ]
+ },
+ ]
+ }], # OS=="android"
+ ],
}
diff --git a/components/data_usage/android/BUILD.gn b/components/data_usage/android/BUILD.gn
new file mode 100644
index 0000000..f3d70da
--- /dev/null
+++ b/components/data_usage/android/BUILD.gn
@@ -0,0 +1,34 @@
+# 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.
+
+source_set("android") {
+ sources = [
+ "traffic_stats_amortizer.cc",
+ "traffic_stats_amortizer.h",
+ ]
+ deps = [
+ "//base",
+ "//components/data_usage/core",
+ "//net",
+ "//url",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "traffic_stats_amortizer_unittest.cc",
+ ]
+
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ deps = [
+ ":android",
+ "//base",
+ "//base/test:test_support",
+ "//components/data_usage/core",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/components/data_usage/android/DEPS b/components/data_usage/android/DEPS
new file mode 100644
index 0000000..75ebc8b4
--- /dev/null
+++ b/components/data_usage/android/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+components/data_usage/core",
+ "+net",
+ "+url",
+]
diff --git a/components/data_usage/android/traffic_stats_amortizer.cc b/components/data_usage/android/traffic_stats_amortizer.cc
new file mode 100644
index 0000000..e9ed1fd
--- /dev/null
+++ b/components/data_usage/android/traffic_stats_amortizer.cc
@@ -0,0 +1,292 @@
+// 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 "base/location.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<linked_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;
+
+// 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.
+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_GE(pre_amortization_total, 0);
+ DCHECK_GE(desired_post_amortization_total, 0);
+
+ if (pre_amortization_total == 0) {
+ // TODO(sclittle): If |desired_post_amortization_total| is non-zero, this
+ // could be ignoring overhead that should be amortized in somehow. Handle
+ // this case gracefully.
+ return;
+ }
+
+ 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);
+ // TODO(sclittle): Record UMA about values before vs. after amortization.
+ }
+}
+
+int64_t* GetTxBytes(DataUse* data_use) {
+ return &data_use->tx_bytes;
+}
+int64_t* GetRxBytes(DataUse* data_use) {
+ return &data_use->rx_bytes;
+}
+
+// Amortizes the difference between |desired_post_amortization_total_tx_bytes|
+// and |pre_amortization_total_tx_bytes| into each of the DataUse objects in
+// |data_use_sequence| by scaling the DataUse's |tx_bytes| appropriately. Does
+// the same with the |rx_bytes| using those respective parameters.
+void AmortizeDataUseSequence(DataUseBuffer* data_use_sequence,
+ int64_t pre_amortization_total_tx_bytes,
+ int64_t desired_post_amortization_total_tx_bytes,
+ int64_t pre_amortization_total_rx_bytes,
+ int64_t desired_post_amortization_total_rx_bytes) {
+ DCHECK(data_use_sequence);
+ DCHECK(!data_use_sequence->empty());
+
+ AmortizeByteCountSequence(data_use_sequence, &GetTxBytes,
+ pre_amortization_total_tx_bytes,
+ desired_post_amortization_total_tx_bytes);
+
+ AmortizeByteCountSequence(data_use_sequence, &GetRxBytes,
+ pre_amortization_total_rx_bytes,
+ desired_post_amortization_total_rx_bytes);
+}
+
+} // 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::make_pair(linked_ptr<DataUse>(data_use.release()), 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_(tick_clock.Pass()),
+ traffic_stats_query_timer_(traffic_stats_query_timer.Pass()),
+ 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.
+ // TODO(sclittle): Record UMA about how often this occurs.
+ 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.
+ // TODO(sclittle): Record UMA about how often |max_amortization_delay_| comes
+ // earlier.
+ 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());
+
+ int64_t current_traffic_stats_tx_bytes = -1;
+ int64_t current_traffic_stats_rx_bytes = -1;
+ bool are_current_traffic_stats_available = QueryTrafficStats(
+ &current_traffic_stats_tx_bytes, &current_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_);
+
+ int64_t desired_post_amortization_total_tx_bytes =
+ current_traffic_stats_tx_bytes -
+ last_amortization_traffic_stats_tx_bytes_;
+ int64_t desired_post_amortization_total_rx_bytes =
+ current_traffic_stats_rx_bytes -
+ last_amortization_traffic_stats_rx_bytes_;
+
+ AmortizeDataUseSequence(&buffered_data_use_, pre_amortization_tx_bytes_,
+ desired_post_amortization_total_tx_bytes,
+ pre_amortization_rx_bytes_,
+ desired_post_amortization_total_rx_bytes);
+ }
+
+ // TODO(sclittle): Record some UMA about the delay before amortizing and how
+ // big the buffer was before amortizing.
+
+ // Reset state now that the amortization run has finished.
+ is_amortization_in_progress_ = false;
+ current_amortization_run_start_time_ = base::TimeTicks();
+
+ are_last_amortization_traffic_stats_available_ =
+ are_current_traffic_stats_available;
+ last_amortization_traffic_stats_tx_bytes_ = current_traffic_stats_tx_bytes;
+ last_amortization_traffic_stats_rx_bytes_ = current_traffic_stats_rx_bytes;
+
+ pre_amortization_tx_bytes_ = 0;
+ pre_amortization_rx_bytes_ = 0;
+
+ // Pass post-amortization DataUse objects to their respective callbacks.
+ DataUseBuffer data_use_sequence;
+ data_use_sequence.swap(buffered_data_use_);
+ for (auto& data_use_buffer_pair : data_use_sequence) {
+ scoped_ptr<DataUse> data_use(data_use_buffer_pair.first.release());
+ data_use_buffer_pair.second.Run(data_use.Pass());
+ }
+}
+
+} // namespace android
+} // namespace data_usage
diff --git a/components/data_usage/android/traffic_stats_amortizer.h b/components/data_usage/android/traffic_stats_amortizer.h
new file mode 100644
index 0000000..c42dc46
--- /dev/null
+++ b/components/data_usage/android/traffic_stats_amortizer.h
@@ -0,0 +1,163 @@
+// 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.
+
+#ifndef COMPONENTS_DATA_USAGE_ANDROID_TRAFFIC_STATS_AMORTIZER_H_
+#define COMPONENTS_DATA_USAGE_ANDROID_TRAFFIC_STATS_AMORTIZER_H_
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/data_usage/core/data_use_amortizer.h"
+
+namespace base {
+class TickClock;
+class Timer;
+}
+
+namespace data_usage {
+
+struct DataUse;
+
+namespace android {
+
+// Class that uses Android TrafficStats to amortize any unincluded overhead
+// (e.g. network layer, TLS, DNS) into the data usage reported by the network
+// stack. Should only be used on the IO thread. Since TrafficStats measurements
+// are global for the entire application, a TrafficStatsAmortizer should be
+// notified of every byte possible, or else it might mistakenly classify the
+// corresponding additional TrafficStats bytes for those as overhead. The
+// TrafficStats API has been available in Android since API level 8 (Android
+// 2.2).
+class TrafficStatsAmortizer : public DataUseAmortizer {
+ public:
+ TrafficStatsAmortizer();
+ ~TrafficStatsAmortizer() override;
+
+ // Amortizes any unincluded network bytes overhead for |data_use| into
+ // |data_use|, and passes the updated |data_use| to |callback| once
+ // amortization is complete.
+ void AmortizeDataUse(scoped_ptr<DataUse> data_use,
+ const AmortizationCompleteCallback& callback) override;
+
+ // Notifies the amortizer that some extra bytes have been transferred that
+ // aren't associated with any DataUse objects (e.g. off-the-record traffic),
+ // so that the TrafficStatsAmortizer can avoid mistakenly counting these bytes
+ // as overhead.
+ void OnExtraBytes(int64_t extra_tx_bytes, int64_t extra_rx_bytes) override;
+
+ base::WeakPtr<TrafficStatsAmortizer> GetWeakPtr();
+
+ protected:
+ // Constructor for testing purposes, allowing for tests to take full control
+ // over the timing of the TrafficStatsAmortizer and the byte counts returned
+ // from TrafficStats. |traffic_stats_query_timer| must not be a repeating
+ // timer.
+ 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);
+
+ // Queries the total transmitted and received bytes for the application from
+ // TrafficStats. Stores the byte counts in |tx_bytes| and |rx_bytes|
+ // respectively and returns true if both values are available from
+ // TrafficStats, otherwise returns false. |tx_bytes| and |rx_bytes| must not
+ // be NULL.
+ // Virtual for testing.
+ virtual bool QueryTrafficStats(int64_t* tx_bytes, int64_t* rx_bytes) const;
+
+ private:
+ // Adds |tx_bytes| and |rx_bytes| as data usage that should not be counted as
+ // overhead (i.e. bytes from DataUse objects and extra bytes reported to this
+ // TrafficStatsAmortizer), and schedules amortization to happen later.
+ void AddPreAmortizationBytes(int64_t tx_bytes, int64_t rx_bytes);
+
+ // Amortizes any additional overhead from TrafficStats byte counts into the
+ // |buffered_data_use_|, then passes the post-amortization DataUse objects to
+ // their respective callbacks, flushing |buffered_data_use_|. Overhead is
+ // calculated as the difference between the TrafficStats byte counts and the
+ // pre-amortization byte counts.
+ void AmortizeNow();
+
+ base::ThreadChecker thread_checker_;
+
+ // TickClock for determining the current time tick.
+ scoped_ptr<base::TickClock> tick_clock_;
+
+ // One-shot timer used to wait a short time after receiving DataUse before
+ // querying TrafficStats, to give TrafficStats time to update and give the
+ // network stack time to finish reporting multiple DataUse objects that happen
+ // in rapid succession. This must not be a repeating timer.
+ // |traffic_stats_query_timer_| is owned as a scoped_ptr so that fake timers
+ // can be passed in for tests.
+ scoped_ptr<base::Timer> traffic_stats_query_timer_;
+
+ // The delay between data usage being reported to the amortizer before
+ // querying TrafficStats. Used with |traffic_stats_query_timer_|.
+ const base::TimeDelta traffic_stats_query_delay_;
+
+ // The maximum amount of time that the TrafficStatsAmortizer is allowed to
+ // spend waiting to perform amortization. Used with
+ // |traffic_stats_query_timer_|.
+ const base::TimeDelta max_amortization_delay_;
+
+ // The maximum allowed size of the |buffered_data_use_| buffer, to prevent the
+ // buffer from hogging memory.
+ const size_t max_data_use_buffer_size_;
+
+ // Indicates whether or not the TrafficStatsAmortizer currently has
+ // pre-amortization bytes waiting for amortization to be performed.
+ bool is_amortization_in_progress_;
+
+ // The time when the first pre-amortization bytes for the current amortization
+ // run were given to this TrafficStatsAmortizer.
+ base::TimeTicks current_amortization_run_start_time_;
+
+ // Buffer of pre-amortization data use that has accumulated since the last
+ // time amortization was performed, paired with the callbacks for each DataUse
+ // object. Only the |buffered_data_use_| may hold linked_ptrs to the DataUse
+ // objects, so that these linked_ptrs can be released later.
+ std::vector<std::pair<linked_ptr<DataUse>, AmortizationCompleteCallback>>
+ buffered_data_use_;
+
+ // Indicates if TrafficStats byte counts were available during the last time
+ // amortization was performed.
+ bool are_last_amortization_traffic_stats_available_;
+
+ // The total transmitted bytes according to TrafficStats during the last time
+ // amortization was performed, if they were available.
+ int64_t last_amortization_traffic_stats_tx_bytes_;
+
+ // The total received bytes according to TrafficStats during the last time
+ // amortization was performed, if they were available.
+ int64_t last_amortization_traffic_stats_rx_bytes_;
+
+ // Total pre-amortization transmitted bytes since the last time amortization
+ // was performed, including bytes from |buffered_data_use_| and any extra
+ // bytes that were added.
+ int64_t pre_amortization_tx_bytes_;
+
+ // Total pre-amortization received bytes since the last time amortization was
+ // performed, including bytes from |buffered_data_use_| and any extra bytes
+ // that were added.
+ int64_t pre_amortization_rx_bytes_;
+
+ base::WeakPtrFactory<TrafficStatsAmortizer> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrafficStatsAmortizer);
+};
+
+} // namespace android
+
+} // namespace data_usage
+
+#endif // COMPONENTS_DATA_USAGE_ANDROID_TRAFFIC_STATS_AMORTIZER_H_
diff --git a/components/data_usage/android/traffic_stats_amortizer_unittest.cc b/components/data_usage/android/traffic_stats_amortizer_unittest.cc
new file mode 100644
index 0000000..b6b7f56
--- /dev/null
+++ b/components/data_usage/android/traffic_stats_amortizer_unittest.cc
@@ -0,0 +1,416 @@
+// 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 <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/timer/mock_timer.h"
+#include "base/timer/timer.h"
+#include "components/data_usage/core/data_use.h"
+#include "components/data_usage/core/data_use_amortizer.h"
+#include "net/base/network_change_notifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace data_usage {
+namespace android {
+
+namespace {
+
+// The delay between receiving DataUse and querying TrafficStats byte counts for
+// amortization.
+const base::TimeDelta kTrafficStatsQueryDelay =
+ base::TimeDelta::FromMilliseconds(50);
+
+// The longest amount of time that an amortization run can be delayed for.
+const base::TimeDelta kMaxAmortizationDelay =
+ base::TimeDelta::FromMilliseconds(200);
+
+// The maximum allowed size of the DataUse buffer.
+const size_t kMaxDataUseBufferSize = 8;
+
+// Synthesizes a fake scoped_ptr<DataUse> with the given |tx_bytes| and
+// |rx_bytes|, using arbitrary values for all other fields.
+scoped_ptr<DataUse> CreateDataUse(int64_t tx_bytes, int64_t rx_bytes) {
+ return scoped_ptr<DataUse>(new DataUse(
+ GURL("http://example.com"), base::TimeTicks() /* request_start */,
+ GURL("http://examplefirstparty.com"), 10 /* tab_id */,
+ net::NetworkChangeNotifier::CONNECTION_2G, "example_mcc_mnc", tx_bytes,
+ rx_bytes));
+}
+
+// Class that represents a base::MockTimer with an attached base::TickClock, so
+// that it can update its |desired_run_time()| according to the current time
+// when the timer is reset.
+class MockTimerWithTickClock : public base::MockTimer {
+ public:
+ MockTimerWithTickClock(bool retain_user_task,
+ bool is_repeating,
+ base::TickClock* tick_clock)
+ : base::MockTimer(retain_user_task, is_repeating),
+ tick_clock_(tick_clock) {}
+
+ ~MockTimerWithTickClock() override {}
+
+ void Reset() override {
+ base::MockTimer::Reset();
+ set_desired_run_time(tick_clock_->NowTicks() + GetCurrentDelay());
+ }
+
+ private:
+ base::TickClock* tick_clock_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTimerWithTickClock);
+};
+
+// A TrafficStatsAmortizer for testing that allows for tests to simulate the
+// byte counts returned from TrafficStats.
+class TestTrafficStatsAmortizer : public TrafficStatsAmortizer {
+ public:
+ TestTrafficStatsAmortizer(scoped_ptr<base::TickClock> tick_clock,
+ scoped_ptr<base::Timer> traffic_stats_query_timer)
+ : TrafficStatsAmortizer(tick_clock.Pass(),
+ traffic_stats_query_timer.Pass(),
+ kTrafficStatsQueryDelay,
+ kMaxAmortizationDelay,
+ kMaxDataUseBufferSize),
+ next_traffic_stats_available_(false),
+ next_traffic_stats_tx_bytes_(-1),
+ next_traffic_stats_rx_bytes_(-1) {}
+
+ ~TestTrafficStatsAmortizer() override {}
+
+ void SetNextTrafficStats(bool available, int64_t tx_bytes, int64_t rx_bytes) {
+ next_traffic_stats_available_ = available;
+ next_traffic_stats_tx_bytes_ = tx_bytes;
+ next_traffic_stats_rx_bytes_ = rx_bytes;
+ }
+
+ void AddTrafficStats(int64_t tx_bytes, int64_t rx_bytes) {
+ next_traffic_stats_tx_bytes_ += tx_bytes;
+ next_traffic_stats_rx_bytes_ += rx_bytes;
+ }
+
+ protected:
+ bool QueryTrafficStats(int64_t* tx_bytes, int64_t* rx_bytes) const override {
+ *tx_bytes = next_traffic_stats_tx_bytes_;
+ *rx_bytes = next_traffic_stats_rx_bytes_;
+ return next_traffic_stats_available_;
+ }
+
+ private:
+ bool next_traffic_stats_available_;
+ int64_t next_traffic_stats_tx_bytes_;
+ int64_t next_traffic_stats_rx_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTrafficStatsAmortizer);
+};
+
+class TrafficStatsAmortizerTest : public testing::Test {
+ public:
+ TrafficStatsAmortizerTest()
+ : test_tick_clock_(new base::SimpleTestTickClock()),
+ mock_timer_(new MockTimerWithTickClock(false, false, test_tick_clock_)),
+ amortizer_(scoped_ptr<base::TickClock>(test_tick_clock_),
+ scoped_ptr<base::Timer>(mock_timer_)),
+ data_use_callback_call_count_(0) {}
+
+ ~TrafficStatsAmortizerTest() override {
+ EXPECT_FALSE(mock_timer_->IsRunning());
+ }
+
+ // Simulates the passage of time by |delta|, firing timers when appropriate.
+ void AdvanceTime(const base::TimeDelta& delta) {
+ const base::TimeTicks end_time = test_tick_clock_->NowTicks() + delta;
+ base::RunLoop().RunUntilIdle();
+
+ while (test_tick_clock_->NowTicks() < end_time) {
+ PumpMockTimer();
+
+ // If |mock_timer_| is scheduled to fire in the future before |end_time|,
+ // advance to that time.
+ if (mock_timer_->IsRunning() &&
+ mock_timer_->desired_run_time() < end_time) {
+ test_tick_clock_->Advance(mock_timer_->desired_run_time() -
+ test_tick_clock_->NowTicks());
+ } else {
+ // Otherwise, advance to |end_time|.
+ test_tick_clock_->Advance(end_time - test_tick_clock_->NowTicks());
+ }
+ }
+ PumpMockTimer();
+ }
+
+ // Skip the first amortization run where TrafficStats byte count deltas are
+ // unavailable, for convenience.
+ void SkipFirstAmortizationRun() {
+ // The initial values of TrafficStats shouldn't matter.
+ amortizer()->SetNextTrafficStats(true, 0, 0);
+
+ // Do the first amortization run with TrafficStats unavailable.
+ amortizer()->OnExtraBytes(100, 1000);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(0, data_use_callback_call_count());
+ }
+
+ // Expects that |expected| and |actual| are equivalent.
+ void ExpectDataUse(scoped_ptr<DataUse> expected, scoped_ptr<DataUse> actual) {
+ ++data_use_callback_call_count_;
+
+ // Have separate checks for the |tx_bytes| and |rx_bytes|, since those are
+ // calculated with floating point arithmetic.
+ EXPECT_DOUBLE_EQ(static_cast<double>(expected->tx_bytes),
+ static_cast<double>(actual->tx_bytes));
+ EXPECT_DOUBLE_EQ(static_cast<double>(expected->rx_bytes),
+ static_cast<double>(actual->rx_bytes));
+
+ // Copy the byte counts over from |expected| just in case they're only
+ // slightly different due to floating point error, so that this doesn't
+ // cause the equality comparison below to fail.
+ actual->tx_bytes = expected->tx_bytes;
+ actual->rx_bytes = expected->rx_bytes;
+ EXPECT_EQ(*expected, *actual);
+ }
+
+ // Creates an ExpectDataUse callback, as a convenience.
+ DataUseAmortizer::AmortizationCompleteCallback ExpectDataUseCallback(
+ scoped_ptr<DataUse> expected) {
+ return base::Bind(&TrafficStatsAmortizerTest::ExpectDataUse,
+ base::Unretained(this), base::Passed(&expected));
+ }
+
+ base::TimeTicks NowTicks() const { return test_tick_clock_->NowTicks(); }
+
+ TestTrafficStatsAmortizer* amortizer() { return &amortizer_; }
+
+ int data_use_callback_call_count() const {
+ return data_use_callback_call_count_;
+ }
+
+ private:
+ // Pumps |mock_timer_|, firing it while it's scheduled to run now or in the
+ // past. After calling this, |mock_timer_| is either not running or is
+ // scheduled to run in the future.
+ void PumpMockTimer() {
+ // Fire the |mock_timer_| if the time has come up. Use a while loop in case
+ // the fired task started the timer again to fire immediately.
+ while (mock_timer_->IsRunning() &&
+ mock_timer_->desired_run_time() <= test_tick_clock_->NowTicks()) {
+ mock_timer_->Fire();
+ base::RunLoop().RunUntilIdle();
+ }
+ }
+
+ base::MessageLoop message_loop_;
+
+ // Weak, owned by |amortizer_|.
+ base::SimpleTestTickClock* test_tick_clock_;
+
+ // Weak, owned by |amortizer_|.
+ MockTimerWithTickClock* mock_timer_;
+
+ TestTrafficStatsAmortizer amortizer_;
+
+ // The number of times ExpectDataUse has been called.
+ int data_use_callback_call_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrafficStatsAmortizerTest);
+};
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeWithTrafficStatsAlwaysUnavailable) {
+ amortizer()->SetNextTrafficStats(false, -1, -1);
+ // Do it three times for good measure.
+ for (int i = 0; i < 3; ++i) {
+ // Extra bytes should be ignored since TrafficStats are unavailable.
+ amortizer()->OnExtraBytes(1337, 9001);
+ // The original DataUse should be unchanged.
+ amortizer()->AmortizeDataUse(
+ CreateDataUse(100, 1000),
+ ExpectDataUseCallback(CreateDataUse(100, 1000)));
+
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(i + 1, data_use_callback_call_count());
+ }
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeDataUse) {
+ // The initial values of TrafficStats shouldn't matter.
+ amortizer()->SetNextTrafficStats(true, 1337, 9001);
+
+ // The first amortization run should not change any byte counts because
+ // there's no TrafficStats delta to work with.
+ amortizer()->AmortizeDataUse(CreateDataUse(50, 500),
+ ExpectDataUseCallback(CreateDataUse(50, 500)));
+ amortizer()->AmortizeDataUse(CreateDataUse(100, 1000),
+ ExpectDataUseCallback(CreateDataUse(100, 1000)));
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(2, data_use_callback_call_count());
+
+ // This amortization run, tx_bytes and rx_bytes should be doubled.
+ amortizer()->AmortizeDataUse(CreateDataUse(50, 500),
+ ExpectDataUseCallback(CreateDataUse(100, 1000)));
+ AdvanceTime(kTrafficStatsQueryDelay / 2);
+
+ // Another DataUse is reported before the amortizer queries TrafficStats.
+ amortizer()->AmortizeDataUse(CreateDataUse(100, 1000),
+ ExpectDataUseCallback(CreateDataUse(200, 2000)));
+ AdvanceTime(kTrafficStatsQueryDelay / 2);
+
+ // Then, the TrafficStats values update with the new bytes. The second run
+ // callbacks should not have been called yet.
+ amortizer()->AddTrafficStats(300, 3000);
+ EXPECT_EQ(2, data_use_callback_call_count());
+
+ // The callbacks should fire once kTrafficStatsQueryDelay has passed since the
+ // DataUse was passed to the amortizer.
+ AdvanceTime(kTrafficStatsQueryDelay / 2);
+ EXPECT_EQ(4, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeWithExtraBytes) {
+ SkipFirstAmortizationRun();
+
+ // Byte counts should double.
+ amortizer()->AmortizeDataUse(CreateDataUse(50, 500),
+ ExpectDataUseCallback(CreateDataUse(100, 1000)));
+ amortizer()->OnExtraBytes(500, 5000);
+ amortizer()->AddTrafficStats(1100, 11000);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(1, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeWithNegativeOverhead) {
+ SkipFirstAmortizationRun();
+
+ // Byte counts should halve.
+ amortizer()->AmortizeDataUse(CreateDataUse(50, 500),
+ ExpectDataUseCallback(CreateDataUse(25, 250)));
+ amortizer()->AddTrafficStats(25, 250);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(1, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeWithMaxIntByteCounts) {
+ SkipFirstAmortizationRun();
+
+ // Byte counts should be unchanged.
+ amortizer()->AmortizeDataUse(
+ CreateDataUse(INT64_MAX, INT64_MAX),
+ ExpectDataUseCallback(CreateDataUse(INT64_MAX, INT64_MAX)));
+ amortizer()->SetNextTrafficStats(true, INT64_MAX, INT64_MAX);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(1, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeWithMaxIntScaleFactor) {
+ SkipFirstAmortizationRun();
+
+ // Byte counts should be scaled up to INT64_MAX.
+ amortizer()->AmortizeDataUse(
+ CreateDataUse(1, 1),
+ ExpectDataUseCallback(CreateDataUse(INT64_MAX, INT64_MAX)));
+ amortizer()->SetNextTrafficStats(true, INT64_MAX, INT64_MAX);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(1, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroScaleFactor) {
+ SkipFirstAmortizationRun();
+
+ // Byte counts should be scaled down to 0.
+ amortizer()->AmortizeDataUse(CreateDataUse(INT64_MAX, INT64_MAX),
+ ExpectDataUseCallback(CreateDataUse(0, 0)));
+ amortizer()->SetNextTrafficStats(true, 0, 0);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(1, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroPreAmortizationBytes) {
+ SkipFirstAmortizationRun();
+
+ // Both byte counts should stay 0, even though TrafficStats saw bytes.
+ amortizer()->AmortizeDataUse(CreateDataUse(0, 0),
+ ExpectDataUseCallback(CreateDataUse(0, 0)));
+ amortizer()->AddTrafficStats(100, 1000);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(1, data_use_callback_call_count());
+
+ // This time, only TX bytes are 0, so RX bytes should double, but TX bytes
+ // should stay 0.
+ amortizer()->AmortizeDataUse(CreateDataUse(0, 500),
+ ExpectDataUseCallback(CreateDataUse(0, 1000)));
+ amortizer()->AddTrafficStats(100, 1000);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(2, data_use_callback_call_count());
+
+ // This time, only RX bytes are 0, so TX bytes should double, but RX bytes
+ // should stay 0.
+ amortizer()->AmortizeDataUse(CreateDataUse(50, 0),
+ ExpectDataUseCallback(CreateDataUse(100, 0)));
+ amortizer()->AddTrafficStats(100, 1000);
+ AdvanceTime(kTrafficStatsQueryDelay);
+ EXPECT_EQ(3, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxDelay) {
+ SkipFirstAmortizationRun();
+
+ // Byte counts should double.
+ amortizer()->AddTrafficStats(1000, 10000);
+ amortizer()->AmortizeDataUse(CreateDataUse(50, 500),
+ ExpectDataUseCallback(CreateDataUse(100, 1000)));
+
+ // kSmallDelay is a delay that's shorter than the delay before TrafficStats
+ // would be queried, where kMaxAmortizationDelay is a multiple of kSmallDelay.
+ const base::TimeDelta kSmallDelay = kMaxAmortizationDelay / 10;
+ EXPECT_LT(kSmallDelay, kMaxAmortizationDelay);
+
+ // Simulate multiple cases of extra bytes being reported, each before
+ // TrafficStats would be queried, until kMaxAmortizationDelay has elapsed.
+ AdvanceTime(kSmallDelay);
+ for (int64_t i = 0; i < kMaxAmortizationDelay / kSmallDelay - 1; ++i) {
+ EXPECT_EQ(0, data_use_callback_call_count());
+ amortizer()->OnExtraBytes(50, 500);
+ AdvanceTime(kSmallDelay);
+ }
+
+ // The final time, the amortizer should have given up on waiting to query
+ // TrafficStats and just have amortized as soon as it hit the deadline of
+ // kMaxAmortizationDelay.
+ EXPECT_EQ(1, data_use_callback_call_count());
+}
+
+TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxBufferSize) {
+ SkipFirstAmortizationRun();
+
+ // Report (max buffer size + 1) consecutive DataUse objects, which will be
+ // amortized immediately once the buffer exceeds maximum size.
+ amortizer()->AddTrafficStats(100 * (kMaxDataUseBufferSize + 1),
+ 1000 * (kMaxDataUseBufferSize + 1));
+ for (size_t i = 0; i < kMaxDataUseBufferSize + 1; ++i) {
+ EXPECT_EQ(0, data_use_callback_call_count());
+ amortizer()->AmortizeDataUse(
+ CreateDataUse(50, 500),
+ ExpectDataUseCallback(CreateDataUse(100, 1000)));
+ }
+
+ EXPECT_EQ(static_cast<int>(kMaxDataUseBufferSize + 1),
+ data_use_callback_call_count());
+}
+
+} // namespace
+
+} // namespace android
+} // namespace data_usage
diff --git a/components/data_usage/core/BUILD.gn b/components/data_usage/core/BUILD.gn
index 06e19b9..4d4a1aa 100644
--- a/components/data_usage/core/BUILD.gn
+++ b/components/data_usage/core/BUILD.gn
@@ -8,6 +8,7 @@ source_set("core") {
"data_use.h",
"data_use_aggregator.cc",
"data_use_aggregator.h",
+ "data_use_amortizer.h",
"data_use_annotator.h",
]
deps = [
diff --git a/components/data_usage/core/data_use_aggregator.cc b/components/data_usage/core/data_use_aggregator.cc
index 091a900..47c7649 100644
--- a/components/data_usage/core/data_use_aggregator.cc
+++ b/components/data_usage/core/data_use_aggregator.cc
@@ -6,11 +6,9 @@
#include "base/bind.h"
#include "base/callback.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop/message_loop.h"
-#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "components/data_usage/core/data_use.h"
+#include "components/data_usage/core/data_use_amortizer.h"
#include "components/data_usage/core/data_use_annotator.h"
#include "net/base/load_timing_info.h"
#include "net/base/network_change_notifier.h"
@@ -22,12 +20,11 @@
namespace data_usage {
-DataUseAggregator::DataUseAggregator(scoped_ptr<DataUseAnnotator> annotator)
+DataUseAggregator::DataUseAggregator(scoped_ptr<DataUseAnnotator> annotator,
+ scoped_ptr<DataUseAmortizer> amortizer)
: annotator_(annotator.Pass()),
+ amortizer_(amortizer.Pass()),
connection_type_(net::NetworkChangeNotifier::GetConnectionType()),
- off_the_record_tx_bytes_since_last_flush_(0),
- off_the_record_rx_bytes_since_last_flush_(0),
- is_flush_pending_(false),
weak_ptr_factory_(this) {
#if defined(OS_ANDROID)
mcc_mnc_ = net::android::GetTelephonySimOperator();
@@ -63,20 +60,24 @@ void DataUseAggregator::ReportDataUse(net::URLRequest* request,
connection_type_, mcc_mnc_, tx_bytes, rx_bytes));
if (!annotator_) {
- AppendDataUse(data_use.Pass());
+ PassDataUseToAmortizer(data_use.Pass());
return;
}
+ // TODO(sclittle): Instead of binding a new callback every time, re-use the
+ // same callback every time.
annotator_->Annotate(
request, data_use.Pass(),
- base::Bind(&DataUseAggregator::AppendDataUse, GetWeakPtr()));
+ base::Bind(&DataUseAggregator::PassDataUseToAmortizer, GetWeakPtr()));
}
void DataUseAggregator::ReportOffTheRecordDataUse(int64_t tx_bytes,
int64_t rx_bytes) {
DCHECK(thread_checker_.CalledOnValidThread());
- off_the_record_tx_bytes_since_last_flush_ += tx_bytes;
- off_the_record_rx_bytes_since_last_flush_ += rx_bytes;
+ if (!amortizer_)
+ return;
+
+ amortizer_->OnExtraBytes(tx_bytes, rx_bytes);
}
base::WeakPtr<DataUseAggregator> DataUseAggregator::GetWeakPtr() {
@@ -99,50 +100,33 @@ void DataUseAggregator::SetMccMncForTests(const std::string& mcc_mnc) {
mcc_mnc_ = mcc_mnc;
}
-void DataUseAggregator::AppendDataUse(scoped_ptr<DataUse> data_use) {
+void DataUseAggregator::PassDataUseToAmortizer(scoped_ptr<DataUse> data_use) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(data_use);
- // As an optimization, attempt to combine the newly reported data use with the
- // most recent buffered data use, if the annotations on the data use are the
- // same.
- if (!buffered_data_use_.empty() &&
- buffered_data_use_.back()->CanCombineWith(*data_use)) {
- buffered_data_use_.back()->tx_bytes += data_use->tx_bytes;
- buffered_data_use_.back()->rx_bytes += data_use->rx_bytes;
- } else {
- buffered_data_use_.push_back(data_use.Pass());
+ if (!amortizer_) {
+ OnAmortizationComplete(data_use.Pass());
+ return;
}
- if (!is_flush_pending_) {
- // Post a flush operation to happen in the future, so that the
- // DataUseAggregator has a chance to batch together some data use before
- // notifying observers.
- base::MessageLoop::current()->task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&DataUseAggregator::FlushBufferedDataUse, GetWeakPtr()));
- is_flush_pending_ = true;
- }
+ // TODO(sclittle): Instead of binding a new callback every time, re-use the
+ // same callback every time.
+ amortizer_->AmortizeDataUse(
+ data_use.Pass(),
+ base::Bind(&DataUseAggregator::OnAmortizationComplete, GetWeakPtr()));
}
-void DataUseAggregator::FlushBufferedDataUse() {
+void DataUseAggregator::OnAmortizationComplete(
+ scoped_ptr<DataUse> amortized_data_use) {
DCHECK(thread_checker_.CalledOnValidThread());
- // TODO(sclittle): Amortize data use on supported platforms before notifying
- // observers.
-
// Pass Observers a sequence of const DataUse pointers instead of using the
// buffer directly in order to prevent Observers from modifying the DataUse
// objects.
- std::vector<const DataUse*> const_sequence(buffered_data_use_.begin(),
- buffered_data_use_.end());
+ // TODO(sclittle): Change the observer interface to take in a const DataUse&.
+ std::vector<const DataUse*> const_sequence(1, amortized_data_use.get());
DCHECK(!ContainsValue(const_sequence, nullptr));
FOR_EACH_OBSERVER(Observer, observer_list_, OnDataUse(const_sequence));
-
- buffered_data_use_.clear();
- off_the_record_tx_bytes_since_last_flush_ = 0;
- off_the_record_rx_bytes_since_last_flush_ = 0;
- is_flush_pending_ = false;
}
} // namespace data_usage
diff --git a/components/data_usage/core/data_use_aggregator.h b/components/data_usage/core/data_use_aggregator.h
index 36d8ef0..50bb36e 100644
--- a/components/data_usage/core/data_use_aggregator.h
+++ b/components/data_usage/core/data_use_aggregator.h
@@ -12,7 +12,6 @@
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
-#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/threading/thread_checker.h"
@@ -24,6 +23,7 @@ class URLRequest;
namespace data_usage {
+class DataUseAmortizer;
class DataUseAnnotator;
struct DataUse;
@@ -41,9 +41,12 @@ class DataUseAggregator
const std::vector<const DataUse*>& data_use_sequence) = 0;
};
- // Constructs a new DataUseAggregator with the given |annotator|. A NULL
- // annotator will be treated as a no-op annotator.
- explicit DataUseAggregator(scoped_ptr<DataUseAnnotator> annotator);
+ // Constructs a new DataUseAggregator with the given |annotator| and
+ // |amortizer|. A NULL |annotator| will be treated as a no-op annotator, and a
+ // NULL |amortizer| will be treated as a no-op amortizer.
+ DataUseAggregator(scoped_ptr<DataUseAnnotator> annotator,
+ scoped_ptr<DataUseAmortizer> amortizer);
+
~DataUseAggregator() override;
void AddObserver(Observer* observer);
@@ -73,20 +76,18 @@ class DataUseAggregator
void SetMccMncForTests(const std::string& mcc_mnc);
private:
- // Appends |data_use| to the buffer of unreported data use and prepares to
- // notify observers.
- void AppendDataUse(scoped_ptr<DataUse> data_use);
+ // Passes |data_use| to |amortizer_| if it exists, or calls
+ // OnAmortizationComplete directly if |amortizer_| doesn't exist.
+ void PassDataUseToAmortizer(scoped_ptr<DataUse> data_use);
- // Flush any buffered data use and notify observers.
- void FlushBufferedDataUse();
+ // Notifies observers with the data use from |amortized_data_use|.
+ void OnAmortizationComplete(scoped_ptr<DataUse> amortized_data_use);
base::ThreadChecker thread_checker_;
scoped_ptr<DataUseAnnotator> annotator_;
+ scoped_ptr<DataUseAmortizer> amortizer_;
base::ObserverList<Observer> observer_list_;
- // Buffer of unreported data use.
- ScopedVector<DataUse> buffered_data_use_;
-
// Current connection type as notified by NetworkChangeNotifier.
net::NetworkChangeNotifier::ConnectionType connection_type_;
@@ -95,15 +96,6 @@ class DataUseAggregator
// even if the current active network is not a cellular network.
std::string mcc_mnc_;
- // The total amount of off-the-record data usage that has happened since the
- // last time the buffer was flushed.
- int64_t off_the_record_tx_bytes_since_last_flush_;
- int64_t off_the_record_rx_bytes_since_last_flush_;
-
- // Indicates if a FlushBufferedDataUse() callback has been posted to run later
- // on the IO thread.
- bool is_flush_pending_;
-
base::WeakPtrFactory<DataUseAggregator> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(DataUseAggregator);
diff --git a/components/data_usage/core/data_use_aggregator_unittest.cc b/components/data_usage/core/data_use_aggregator_unittest.cc
index f0d5f44..325065c 100644
--- a/components/data_usage/core/data_use_aggregator_unittest.cc
+++ b/components/data_usage/core/data_use_aggregator_unittest.cc
@@ -15,6 +15,7 @@
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "components/data_usage/core/data_use.h"
+#include "components/data_usage/core/data_use_amortizer.h"
#include "components/data_usage/core/data_use_annotator.h"
#include "net/base/load_timing_info.h"
#include "net/base/network_change_notifier.h"
@@ -38,8 +39,9 @@ base::TimeTicks GetRequestStart(const net::URLRequest& request) {
// Test class that can set the network operator's MCCMNC.
class TestDataUseAggregator : public DataUseAggregator {
public:
- TestDataUseAggregator(scoped_ptr<DataUseAnnotator> annotator)
- : DataUseAggregator(annotator.Pass()) {}
+ TestDataUseAggregator(scoped_ptr<DataUseAnnotator> annotator,
+ scoped_ptr<DataUseAmortizer> amortizer)
+ : DataUseAggregator(annotator.Pass(), amortizer.Pass()) {}
~TestDataUseAggregator() override {}
@@ -102,6 +104,25 @@ class FakeDataUseAnnotator : public DataUseAnnotator {
DISALLOW_COPY_AND_ASSIGN(FakeDataUseAnnotator);
};
+// Test DataUseAmortizer that doubles the bytes of all DataUse objects it sees.
+class DoublingAmortizer : public DataUseAmortizer {
+ public:
+ DoublingAmortizer() {}
+ ~DoublingAmortizer() override {}
+
+ void AmortizeDataUse(scoped_ptr<DataUse> data_use,
+ const AmortizationCompleteCallback& callback) override {
+ data_use->tx_bytes *= 2;
+ data_use->rx_bytes *= 2;
+ callback.Run(data_use.Pass());
+ }
+
+ void OnExtraBytes(int64_t extra_tx_bytes, int64_t extra_rx_bytes) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DoublingAmortizer);
+};
+
// A network delegate that reports all received and sent network bytes to a
// DataUseAggregator.
class ReportingNetworkDelegate : public net::NetworkDelegateImpl {
@@ -124,6 +145,9 @@ class ReportingNetworkDelegate : public net::NetworkDelegateImpl {
typedef std::map<const net::URLRequest*, DataUseContext> DataUseContextMap;
+ // Constructs a ReportingNetworkDelegate. |fake_data_use_annotator| can be
+ // NULL, indicating that no annotator is in use and no requests should be
+ // annotated with tab IDs.
ReportingNetworkDelegate(
TestDataUseAggregator* data_use_aggregator,
FakeDataUseAnnotator* fake_data_use_annotator,
@@ -147,7 +171,8 @@ class ReportingNetworkDelegate : public net::NetworkDelegateImpl {
? DataUseContext()
: data_use_context_it->second;
- fake_data_use_annotator_->set_tab_id(data_use_context.tab_id);
+ if (fake_data_use_annotator_)
+ fake_data_use_annotator_->set_tab_id(data_use_context.tab_id);
if (test_network_change_notifier_->GetCurrentConnectionType() !=
data_use_context.connection_type) {
@@ -211,23 +236,37 @@ class TestObserver : public DataUseAggregator::Observer {
class DataUseAggregatorTest : public testing::Test {
public:
- DataUseAggregatorTest()
- : fake_data_use_annotator_(new FakeDataUseAnnotator()),
- data_use_aggregator_(
- scoped_ptr<DataUseAnnotator>(fake_data_use_annotator_)),
- test_network_change_notifier_(&data_use_aggregator_),
- reporting_network_delegate_(&data_use_aggregator_,
- fake_data_use_annotator_,
- &test_network_change_notifier_),
- context_(true),
- test_observer_(&data_use_aggregator_) {
- context_.set_client_socket_factory(&mock_socket_factory_);
- context_.set_network_delegate(&reporting_network_delegate_);
- context_.Init();
- }
-
+ DataUseAggregatorTest() {}
~DataUseAggregatorTest() override {}
+ void Initialize(scoped_ptr<FakeDataUseAnnotator> annotator,
+ scoped_ptr<DataUseAmortizer> amortizer) {
+ // Destroy objects that have dependencies on other objects here in the
+ // reverse order that they are created.
+ context_.reset();
+ reporting_network_delegate_.reset();
+ mock_socket_factory_.reset();
+ test_network_change_notifier_.reset();
+ test_observer_.reset();
+
+ // Initialize testing objects.
+ FakeDataUseAnnotator* fake_data_use_annotator = annotator.get();
+ data_use_aggregator_.reset(
+ new TestDataUseAggregator(annotator.Pass(), amortizer.Pass()));
+ test_observer_.reset(new TestObserver(data_use_aggregator_.get()));
+ test_network_change_notifier_.reset(
+ new TestNetworkChangeNotifier(data_use_aggregator_.get()));
+ mock_socket_factory_.reset(new net::MockClientSocketFactory());
+ reporting_network_delegate_.reset(new ReportingNetworkDelegate(
+ data_use_aggregator_.get(), fake_data_use_annotator,
+ test_network_change_notifier_.get()));
+
+ context_.reset(new net::TestURLRequestContext(true));
+ context_->set_client_socket_factory(mock_socket_factory_.get());
+ context_->set_network_delegate(reporting_network_delegate_.get());
+ context_->Init();
+ }
+
scoped_ptr<net::URLRequest> ExecuteRequest(
const GURL& url,
const GURL& first_party_for_cookies,
@@ -239,18 +278,18 @@ class DataUseAggregatorTest : public testing::Test {
net::MockRead(net::SYNCHRONOUS, net::OK),
};
net::StaticSocketDataProvider socket(reads, arraysize(reads), nullptr, 0);
- mock_socket_factory_.AddSocketDataProvider(&socket);
+ mock_socket_factory_->AddSocketDataProvider(&socket);
net::TestDelegate delegate;
scoped_ptr<net::URLRequest> request =
- context_.CreateRequest(url, net::IDLE, &delegate);
+ context_->CreateRequest(url, net::IDLE, &delegate);
request->set_first_party_for_cookies(first_party_for_cookies);
ReportingNetworkDelegate::DataUseContextMap data_use_context_map;
data_use_context_map[request.get()] =
ReportingNetworkDelegate::DataUseContext(tab_id, connection_type,
mcc_mnc);
- reporting_network_delegate_.set_data_use_context_map(data_use_context_map);
+ reporting_network_delegate_->set_data_use_context_map(data_use_context_map);
request->Start();
loop_.RunUntilIdle();
@@ -259,179 +298,132 @@ class DataUseAggregatorTest : public testing::Test {
}
ReportingNetworkDelegate* reporting_network_delegate() {
- return &reporting_network_delegate_;
+ return reporting_network_delegate_.get();
}
- DataUseAggregator* data_use_aggregator() { return &data_use_aggregator_; }
+ DataUseAggregator* data_use_aggregator() {
+ return data_use_aggregator_.get();
+ }
net::MockClientSocketFactory* mock_socket_factory() {
- return &mock_socket_factory_;
+ return mock_socket_factory_.get();
}
- net::TestURLRequestContext* context() { return &context_; }
+ net::TestURLRequestContext* context() { return context_.get(); }
- TestObserver* test_observer() { return &test_observer_; }
+ TestObserver* test_observer() { return test_observer_.get(); }
private:
base::MessageLoopForIO loop_;
- // Weak, owned by |data_use_aggregator_|.
- FakeDataUseAnnotator* fake_data_use_annotator_;
- TestDataUseAggregator data_use_aggregator_;
- TestNetworkChangeNotifier test_network_change_notifier_;
- net::MockClientSocketFactory mock_socket_factory_;
- ReportingNetworkDelegate reporting_network_delegate_;
- net::TestURLRequestContext context_;
- TestObserver test_observer_;
+ scoped_ptr<TestDataUseAggregator> data_use_aggregator_;
+ scoped_ptr<TestObserver> test_observer_;
+ scoped_ptr<TestNetworkChangeNotifier> test_network_change_notifier_;
+ scoped_ptr<net::MockClientSocketFactory> mock_socket_factory_;
+ scoped_ptr<ReportingNetworkDelegate> reporting_network_delegate_;
+ scoped_ptr<net::TestURLRequestContext> context_;
DISALLOW_COPY_AND_ASSIGN(DataUseAggregatorTest);
};
TEST_F(DataUseAggregatorTest, ReportDataUse) {
- const int32_t kFooTabId = 10;
- const net::NetworkChangeNotifier::ConnectionType kFooConnectionType =
- net::NetworkChangeNotifier::CONNECTION_2G;
- const std::string kFooMccMnc = "foo_mcc_mnc";
- scoped_ptr<net::URLRequest> foo_request =
- ExecuteRequest(GURL("http://foo.com"), GURL("http://foofirstparty.com"),
- kFooTabId, kFooConnectionType, kFooMccMnc);
-
- const int32_t kBarTabId = 20;
- const net::NetworkChangeNotifier::ConnectionType kBarConnectionType =
- net::NetworkChangeNotifier::CONNECTION_WIFI;
- const std::string kBarMccMnc = "bar_mcc_mnc";
- scoped_ptr<net::URLRequest> bar_request =
- ExecuteRequest(GURL("http://bar.com"), GURL("http://barfirstparty.com"),
- kBarTabId, kBarConnectionType, kBarMccMnc);
-
- auto data_use_it = test_observer()->observed_data_use().begin();
-
- // First, the |foo_request| data use should have happened.
- int64_t observed_foo_tx_bytes = 0, observed_foo_rx_bytes = 0;
- while (data_use_it != test_observer()->observed_data_use().end() &&
- data_use_it->url == GURL("http://foo.com")) {
- EXPECT_EQ(GetRequestStart(*foo_request), data_use_it->request_start);
- EXPECT_EQ(GURL("http://foofirstparty.com"),
- data_use_it->first_party_for_cookies);
- EXPECT_EQ(kFooTabId, data_use_it->tab_id);
- EXPECT_EQ(kFooConnectionType, data_use_it->connection_type);
- EXPECT_EQ(kFooMccMnc, data_use_it->mcc_mnc);
-
- observed_foo_tx_bytes += data_use_it->tx_bytes;
- observed_foo_rx_bytes += data_use_it->rx_bytes;
- ++data_use_it;
- }
- EXPECT_EQ(foo_request->GetTotalSentBytes(), observed_foo_tx_bytes);
- EXPECT_EQ(foo_request->GetTotalReceivedBytes(), observed_foo_rx_bytes);
-
- // Then, the |bar_request| data use should have happened.
- int64_t observed_bar_tx_bytes = 0, observed_bar_rx_bytes = 0;
- while (data_use_it != test_observer()->observed_data_use().end()) {
- EXPECT_EQ(GURL("http://bar.com"), data_use_it->url);
- EXPECT_EQ(GetRequestStart(*bar_request), data_use_it->request_start);
- EXPECT_EQ(GURL("http://barfirstparty.com"),
- data_use_it->first_party_for_cookies);
- EXPECT_EQ(kBarTabId, data_use_it->tab_id);
- EXPECT_EQ(kBarConnectionType, data_use_it->connection_type);
- EXPECT_EQ(kBarMccMnc, data_use_it->mcc_mnc);
-
- observed_bar_tx_bytes += data_use_it->tx_bytes;
- observed_bar_rx_bytes += data_use_it->rx_bytes;
- ++data_use_it;
- }
- EXPECT_EQ(bar_request->GetTotalSentBytes(), observed_bar_tx_bytes);
- EXPECT_EQ(bar_request->GetTotalReceivedBytes(), observed_bar_rx_bytes);
-}
-
-TEST_F(DataUseAggregatorTest, ReportCombinedDataUse) {
- // Set up the |foo_request|.
- net::MockRead foo_reads[] = {
- net::MockRead(net::SYNCHRONOUS, "HTTP/1.1 200 OK\r\n\r\n"),
- net::MockRead(net::SYNCHRONOUS, "hello world"),
- net::MockRead(net::SYNCHRONOUS, net::OK),
- };
- net::StaticSocketDataProvider foo_socket(foo_reads, arraysize(foo_reads),
- nullptr, 0);
- mock_socket_factory()->AddSocketDataProvider(&foo_socket);
-
- net::TestDelegate foo_delegate;
- scoped_ptr<net::URLRequest> foo_request = context()->CreateRequest(
- GURL("http://foo.com"), net::IDLE, &foo_delegate);
- foo_request->set_first_party_for_cookies(GURL("http://foofirstparty.com"));
-
- // Set up the |bar_request|.
- net::MockRead bar_reads[] = {
- net::MockRead(net::SYNCHRONOUS, "HTTP/1.1 200 OK\r\n\r\n"),
- net::MockRead(net::SYNCHRONOUS, "hello world"),
- net::MockRead(net::SYNCHRONOUS, net::OK),
+ const struct {
+ bool use_annotator;
+ bool use_amortizer;
+ bool expect_tab_ids;
+ int64_t expected_amortization_multiple;
+ } kTestCases[] = {
+ {false, false, false, 1},
+ {false, true, false, 2},
+ {true, false, true, 1},
+ {true, true, true, 2},
};
- net::StaticSocketDataProvider bar_socket(bar_reads, arraysize(bar_reads),
- nullptr, 0);
- mock_socket_factory()->AddSocketDataProvider(&bar_socket);
-
- net::TestDelegate bar_delegate;
- scoped_ptr<net::URLRequest> bar_request = context()->CreateRequest(
- GURL("http://bar.com"), net::IDLE, &bar_delegate);
- bar_request->set_first_party_for_cookies(GURL("http://barfirstparty.com"));
-
- // Set up the network delegate to assign tab IDs and connection types for each
- // request.
- const int32_t kFooTabId = 10;
- const net::NetworkChangeNotifier::ConnectionType kFooConnectionType =
- net::NetworkChangeNotifier::CONNECTION_2G;
- const std::string kFooMccMnc = "foo_mcc_mnc";
- const int32_t kBarTabId = 20;
- const net::NetworkChangeNotifier::ConnectionType kBarConnectionType =
- net::NetworkChangeNotifier::CONNECTION_WIFI;
- const std::string kBarMccMnc = "bar_mcc_mnc";
-
- ReportingNetworkDelegate::DataUseContextMap data_use_context_map;
- data_use_context_map[foo_request.get()] =
- ReportingNetworkDelegate::DataUseContext(kFooTabId, kFooConnectionType,
- kFooMccMnc);
- data_use_context_map[bar_request.get()] =
- ReportingNetworkDelegate::DataUseContext(kBarTabId, kBarConnectionType,
- kBarMccMnc);
- reporting_network_delegate()->set_data_use_context_map(data_use_context_map);
-
- // Run the requests.
- foo_request->Start();
- bar_request->Start();
- base::MessageLoop::current()->RunUntilIdle();
- // The observer should have been notified once with a DataUse element for each
- // request.
- EXPECT_EQ(1, test_observer()->on_data_use_called_count());
- EXPECT_EQ(static_cast<size_t>(2),
- test_observer()->observed_data_use().size());
-
- // All of the |foo_request| DataUse should have been combined into a single
- // DataUse element.
- const DataUse& foo_data_use = test_observer()->observed_data_use().front();
- EXPECT_EQ(GURL("http://foo.com"), foo_data_use.url);
- EXPECT_EQ(GetRequestStart(*foo_request), foo_data_use.request_start);
- EXPECT_EQ(GURL("http://foofirstparty.com"),
- foo_data_use.first_party_for_cookies);
- EXPECT_EQ(kFooTabId, foo_data_use.tab_id);
- EXPECT_EQ(kFooConnectionType, foo_data_use.connection_type);
- EXPECT_EQ(kFooMccMnc, foo_data_use.mcc_mnc);
- EXPECT_EQ(foo_request->GetTotalSentBytes(), foo_data_use.tx_bytes);
- EXPECT_EQ(foo_request->GetTotalReceivedBytes(), foo_data_use.rx_bytes);
-
- // All of the |bar_request| DataUse should have been combined into a single
- // DataUse element.
- const DataUse& bar_data_use = test_observer()->observed_data_use().back();
- EXPECT_EQ(GURL("http://bar.com"), bar_data_use.url);
- EXPECT_EQ(GetRequestStart(*bar_request), bar_data_use.request_start);
- EXPECT_EQ(GURL("http://barfirstparty.com"),
- bar_data_use.first_party_for_cookies);
- EXPECT_EQ(kBarTabId, bar_data_use.tab_id);
- EXPECT_EQ(kBarConnectionType, bar_data_use.connection_type);
- EXPECT_EQ(kBarMccMnc, bar_data_use.mcc_mnc);
- EXPECT_EQ(bar_request->GetTotalSentBytes(), bar_data_use.tx_bytes);
- EXPECT_EQ(bar_request->GetTotalReceivedBytes(), bar_data_use.rx_bytes);
+ for (const auto& test_case : kTestCases) {
+ scoped_ptr<FakeDataUseAnnotator> annotator(
+ test_case.use_annotator ? new FakeDataUseAnnotator() : nullptr);
+ scoped_ptr<DataUseAmortizer> amortizer(
+ test_case.use_amortizer ? new DoublingAmortizer() : nullptr);
+
+ Initialize(annotator.Pass(), amortizer.Pass());
+
+ const int32_t kFooTabId = 10;
+ const net::NetworkChangeNotifier::ConnectionType kFooConnectionType =
+ net::NetworkChangeNotifier::CONNECTION_2G;
+ const std::string kFooMccMnc = "foo_mcc_mnc";
+ scoped_ptr<net::URLRequest> foo_request =
+ ExecuteRequest(GURL("http://foo.com"), GURL("http://foofirstparty.com"),
+ kFooTabId, kFooConnectionType, kFooMccMnc);
+
+ const int32_t kBarTabId = 20;
+ const net::NetworkChangeNotifier::ConnectionType kBarConnectionType =
+ net::NetworkChangeNotifier::CONNECTION_WIFI;
+ const std::string kBarMccMnc = "bar_mcc_mnc";
+ scoped_ptr<net::URLRequest> bar_request =
+ ExecuteRequest(GURL("http://bar.com"), GURL("http://barfirstparty.com"),
+ kBarTabId, kBarConnectionType, kBarMccMnc);
+
+ auto data_use_it = test_observer()->observed_data_use().begin();
+
+ // First, the |foo_request| data use should have happened.
+ int64_t observed_foo_tx_bytes = 0, observed_foo_rx_bytes = 0;
+ while (data_use_it != test_observer()->observed_data_use().end() &&
+ data_use_it->url == GURL("http://foo.com")) {
+ EXPECT_EQ(GetRequestStart(*foo_request), data_use_it->request_start);
+ EXPECT_EQ(GURL("http://foofirstparty.com"),
+ data_use_it->first_party_for_cookies);
+
+ if (test_case.expect_tab_ids)
+ EXPECT_EQ(kFooTabId, data_use_it->tab_id);
+ else
+ EXPECT_EQ(-1, data_use_it->tab_id);
+
+ EXPECT_EQ(kFooConnectionType, data_use_it->connection_type);
+ EXPECT_EQ(kFooMccMnc, data_use_it->mcc_mnc);
+
+ observed_foo_tx_bytes += data_use_it->tx_bytes;
+ observed_foo_rx_bytes += data_use_it->rx_bytes;
+ ++data_use_it;
+ }
+ EXPECT_EQ(foo_request->GetTotalSentBytes() *
+ test_case.expected_amortization_multiple,
+ observed_foo_tx_bytes);
+ EXPECT_EQ(foo_request->GetTotalReceivedBytes() *
+ test_case.expected_amortization_multiple,
+ observed_foo_rx_bytes);
+
+ // Then, the |bar_request| data use should have happened.
+ int64_t observed_bar_tx_bytes = 0, observed_bar_rx_bytes = 0;
+ while (data_use_it != test_observer()->observed_data_use().end()) {
+ EXPECT_EQ(GURL("http://bar.com"), data_use_it->url);
+ EXPECT_EQ(GetRequestStart(*bar_request), data_use_it->request_start);
+ EXPECT_EQ(GURL("http://barfirstparty.com"),
+ data_use_it->first_party_for_cookies);
+
+ if (test_case.expect_tab_ids)
+ EXPECT_EQ(kBarTabId, data_use_it->tab_id);
+ else
+ EXPECT_EQ(-1, data_use_it->tab_id);
+
+ EXPECT_EQ(kBarConnectionType, data_use_it->connection_type);
+ EXPECT_EQ(kBarMccMnc, data_use_it->mcc_mnc);
+
+ observed_bar_tx_bytes += data_use_it->tx_bytes;
+ observed_bar_rx_bytes += data_use_it->rx_bytes;
+ ++data_use_it;
+ }
+ EXPECT_EQ(bar_request->GetTotalSentBytes() *
+ test_case.expected_amortization_multiple,
+ observed_bar_tx_bytes);
+ EXPECT_EQ(bar_request->GetTotalReceivedBytes() *
+ test_case.expected_amortization_multiple,
+ observed_bar_rx_bytes);
+ }
}
TEST_F(DataUseAggregatorTest, ReportOffTheRecordDataUse) {
+ Initialize(scoped_ptr<FakeDataUseAnnotator>(new FakeDataUseAnnotator()),
+ scoped_ptr<DataUseAmortizer>(new DoublingAmortizer()));
+
// Off the record data use should not be reported to observers.
data_use_aggregator()->ReportOffTheRecordDataUse(1000, 1000);
base::MessageLoop::current()->RunUntilIdle();
diff --git a/components/data_usage/core/data_use_amortizer.h b/components/data_usage/core/data_use_amortizer.h
new file mode 100644
index 0000000..8cdd08f
--- /dev/null
+++ b/components/data_usage/core/data_use_amortizer.h
@@ -0,0 +1,42 @@
+// 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.
+
+#ifndef COMPONENTS_DATA_USAGE_CORE_DATA_USE_AMORTIZER_H_
+#define COMPONENTS_DATA_USAGE_CORE_DATA_USE_AMORTIZER_H_
+
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace data_usage {
+
+struct DataUse;
+
+// Class that takes in DataUse and amortizes any extra data usage overhead
+// across DataUse objects.
+class DataUseAmortizer {
+ public:
+ typedef base::Callback<void(scoped_ptr<DataUse>)>
+ AmortizationCompleteCallback;
+
+ virtual ~DataUseAmortizer() {}
+
+ // Amortizes overhead into |data_use|, then passes the it to |callback| once
+ // amortization is complete. Amortizers that perform buffering may combine
+ // together |data_use| objects with the same |callback| if the |data_use|
+ // objects are identical in all ways but their byte counts.
+ virtual void AmortizeDataUse(
+ scoped_ptr<DataUse> data_use,
+ const AmortizationCompleteCallback& callback) = 0;
+
+ // Notifies the DataUseAmortizer that some extra bytes have been transferred
+ // that aren't associated with any DataUse objects (e.g. off-the-record
+ // traffic).
+ virtual void OnExtraBytes(int64_t extra_tx_bytes, int64_t extra_rx_bytes) = 0;
+};
+
+} // namespace data_usage
+
+#endif // COMPONENTS_DATA_USAGE_CORE_DATA_USE_AMORTIZER_H_