summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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_