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

Unified Diff: components/data_usage/android/traffic_stats_amortizer_unittest.cc

Issue 1390993005: Amortize data usage using TrafficStats on Android. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@data_use_buffering
Patch Set: Fixed nit Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/data_usage/android/traffic_stats_amortizer_unittest.cc
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 0000000000000000000000000000000000000000..b6b7f56dd7d9082b60ab22f94b270f3ba6a95353
--- /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

Powered by Google App Engine
This is Rietveld 408576698