diff options
-rw-r--r-- | chrome/browser/BUILD.gn | 1 | ||||
-rw-r--r-- | chrome/browser/DEPS | 2 | ||||
-rw-r--r-- | chrome/browser/android/data_usage/external_data_use_observer_unittest.cc | 4 | ||||
-rw-r--r-- | chrome/browser/io_thread.cc | 10 | ||||
-rw-r--r-- | chrome/browser/net/chrome_network_delegate_unittest.cc | 4 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 1 | ||||
-rw-r--r-- | components/components_tests.gyp | 2 | ||||
-rw-r--r-- | components/data_usage.gypi | 23 | ||||
-rw-r--r-- | components/data_usage/android/BUILD.gn | 34 | ||||
-rw-r--r-- | components/data_usage/android/DEPS | 5 | ||||
-rw-r--r-- | components/data_usage/android/traffic_stats_amortizer.cc | 292 | ||||
-rw-r--r-- | components/data_usage/android/traffic_stats_amortizer.h | 163 | ||||
-rw-r--r-- | components/data_usage/android/traffic_stats_amortizer_unittest.cc | 416 | ||||
-rw-r--r-- | components/data_usage/core/BUILD.gn | 1 | ||||
-rw-r--r-- | components/data_usage/core/data_use_aggregator.cc | 66 | ||||
-rw-r--r-- | components/data_usage/core/data_use_aggregator.h | 34 | ||||
-rw-r--r-- | components/data_usage/core/data_use_aggregator_unittest.cc | 340 | ||||
-rw-r--r-- | components/data_usage/core/data_use_amortizer.h | 42 |
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( + ¤t_traffic_stats_tx_bytes, ¤t_traffic_stats_rx_bytes); + + if (are_current_traffic_stats_available && + are_last_amortization_traffic_stats_available_ && + !buffered_data_use_.empty()) { + // These TrafficStats byte counts are guaranteed to increase monotonically + // since device boot. + DCHECK_GE(current_traffic_stats_tx_bytes, + last_amortization_traffic_stats_tx_bytes_); + DCHECK_GE(current_traffic_stats_rx_bytes, + last_amortization_traffic_stats_rx_bytes_); + + 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_ |