| // 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 <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram_base.h" |
| #include "base/run_loop.h" |
| #include "base/test/histogram_tester.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; |
| |
| const char kPreAmortizationTxHistogram[] = |
| "TrafficStatsAmortizer.PreAmortizationRunDataUseBytes.Tx"; |
| const char kPreAmortizationRxHistogram[] = |
| "TrafficStatsAmortizer.PreAmortizationRunDataUseBytes.Rx"; |
| const char kPostAmortizationTxHistogram[] = |
| "TrafficStatsAmortizer.PostAmortizationRunDataUseBytes.Tx"; |
| const char kPostAmortizationRxHistogram[] = |
| "TrafficStatsAmortizer.PostAmortizationRunDataUseBytes.Rx"; |
| const char kAmortizationDelayHistogram[] = |
| "TrafficStatsAmortizer.AmortizationDelay"; |
| const char kBufferSizeOnFlushHistogram[] = |
| "TrafficStatsAmortizer.BufferSizeOnFlush"; |
| const char kConcurrentTabs[] = "TrafficStatsAmortizer.ConcurrentTabs"; |
| |
| // The maximum sample value that can be recorded in a histogram. |
| const base::HistogramBase::Sample kMaxRecordableSample = |
| base::HistogramBase::kSampleType_MAX - 1; |
| |
| // Converts a |delay| into a histogram sample of the number of milliseconds in |
| // the delay. |
| base::HistogramBase::Sample GetDelaySample(const base::TimeDelta& delay) { |
| return static_cast<base::HistogramBase::Sample>(delay.InMilliseconds()); |
| } |
| |
| // Synthesizes a fake std::unique_ptr<DataUse> with the given |url|, |tab_id|, |
| // |tx_bytes| and |rx_bytes|, using arbitrary values for all other fields. |
| std::unique_ptr<DataUse> CreateDataUseWithURLAndTab(const GURL& url, |
| int32_t tab_id, |
| int64_t tx_bytes, |
| int64_t rx_bytes) { |
| return std::unique_ptr<DataUse>( |
| new DataUse(url, base::TimeTicks() /* request_start */, |
| GURL("http://examplefirstparty.com"), tab_id, |
| net::NetworkChangeNotifier::CONNECTION_2G, "example_mcc_mnc", |
| tx_bytes, rx_bytes)); |
| } |
| |
| // Synthesizes a fake std::unique_ptr<DataUse> with the given |url|, |tx_bytes| |
| // and |
| // |rx_bytes|, using arbitrary values for all other fields. |
| std::unique_ptr<DataUse> CreateDataUseWithURL(const GURL& url, |
| int64_t tx_bytes, |
| int64_t rx_bytes) { |
| return CreateDataUseWithURLAndTab(url, 10, tx_bytes, rx_bytes); |
| } |
| |
| // Synthesizes a fake std::unique_ptr<DataUse> with the given |tab_id|, |
| // |tx_bytes| |
| // and |rx_bytes|, using arbitrary values for all other fields. |
| std::unique_ptr<DataUse> CreateDataUseWithTab(int32_t tab_id, |
| int64_t tx_bytes, |
| int64_t rx_bytes) { |
| return CreateDataUseWithURLAndTab(GURL("http://example.com"), tab_id, |
| tx_bytes, rx_bytes); |
| } |
| |
| // Synthesizes a fake std::unique_ptr<DataUse> with the given |tx_bytes| and |
| // |rx_bytes|, using arbitrary values for all other fields. |
| std::unique_ptr<DataUse> CreateDataUse(int64_t tx_bytes, int64_t rx_bytes) { |
| return CreateDataUseWithURL(GURL("http://example.com"), tx_bytes, rx_bytes); |
| } |
| |
| // Appends |data_use| to |data_use_sequence|. |data_use_sequence| must not be |
| // NULL. |
| void AppendDataUseToSequence( |
| std::vector<std::unique_ptr<DataUse>>* data_use_sequence, |
| std::unique_ptr<DataUse> data_use) { |
| data_use_sequence->push_back(std::move(data_use)); |
| } |
| |
| // 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( |
| std::unique_ptr<base::TickClock> tick_clock, |
| std::unique_ptr<base::Timer> traffic_stats_query_timer) |
| : TrafficStatsAmortizer(std::move(tick_clock), |
| std::move(traffic_stats_query_timer), |
| 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_(std::unique_ptr<base::TickClock>(test_tick_clock_), |
| std::unique_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(std::unique_ptr<DataUse> expected, |
| std::unique_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( |
| std::unique_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) { |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 100, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 1000, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 100, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 1000, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeDataUse) { |
| // Simulate the first amortization run. |
| { |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 150, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 1500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 150, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 1500, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 2, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| // Simulate the second amortization run. |
| { |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 150, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 1500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 300, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 3000, 1); |
| histogram_tester.ExpectUniqueSample( |
| kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay + kTrafficStatsQueryDelay / 2), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 2, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithExtraBytes) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 50, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 100, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 1000, 1); |
| histogram_tester.ExpectUniqueSample( |
| kAmortizationDelayHistogram, GetDelaySample(kTrafficStatsQueryDelay), 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithNegativeOverhead) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 50, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 25, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 250, 1); |
| histogram_tester.ExpectUniqueSample( |
| kAmortizationDelayHistogram, GetDelaySample(kTrafficStatsQueryDelay), 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithMaxIntByteCounts) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| // Byte count samples should be capped at the maximum Sample value that's |
| // valid to be recorded. |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample( |
| kAmortizationDelayHistogram, GetDelaySample(kTrafficStatsQueryDelay), 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithMaxIntScaleFactor) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| // Post-amortization byte count samples should be capped at the maximum Sample |
| // value that's valid to be recorded. |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample( |
| kAmortizationDelayHistogram, GetDelaySample(kTrafficStatsQueryDelay), 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroScaleFactor) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| // 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()); |
| |
| // Pre-amortization byte count samples should be capped at the maximum Sample |
| // value that's valid to be recorded. |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, |
| kMaxRecordableSample, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample( |
| kAmortizationDelayHistogram, GetDelaySample(kTrafficStatsQueryDelay), 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroPreAmortizationBytes) { |
| SkipFirstAmortizationRun(); |
| |
| { |
| base::HistogramTester histogram_tester; |
| |
| // Both byte counts should stay 0, even though TrafficStats saw bytes, which |
| // should be reflected in the next amortization run instead. |
| amortizer()->AmortizeDataUse(CreateDataUse(0, 0), |
| ExpectDataUseCallback(CreateDataUse(0, 0))); |
| // Add the TrafficStats byte counts for this and the next amortization run. |
| amortizer()->AddTrafficStats(100, 1000); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| EXPECT_EQ(1, data_use_callback_call_count()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| |
| // Both byte counts should double, even though the TrafficStats byte counts |
| // actually updated during the previous amortization run. |
| amortizer()->AmortizeDataUse( |
| CreateDataUse(50, 500), |
| ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| EXPECT_EQ(2, data_use_callback_call_count()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 50, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 100, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 1000, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroTxPreAmortizationBytes) { |
| SkipFirstAmortizationRun(); |
| |
| { |
| base::HistogramTester histogram_tester; |
| |
| // The count of transmitted bytes starts at 0, so it should stay 0, and be |
| // amortized in the next amortization run instead. |
| amortizer()->AmortizeDataUse(CreateDataUse(0, 500), |
| ExpectDataUseCallback(CreateDataUse(0, 1000))); |
| // Add the TrafficStats byte counts for this and the next amortization run. |
| amortizer()->AddTrafficStats(100, 1000); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| EXPECT_EQ(1, data_use_callback_call_count()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 1000, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| |
| // The count of transmitted bytes should double, even though they actually |
| // updated during the previous amortization run. |
| amortizer()->AmortizeDataUse(CreateDataUse(50, 0), |
| ExpectDataUseCallback(CreateDataUse(100, 0))); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| EXPECT_EQ(2, data_use_callback_call_count()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 50, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 100, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroRxPreAmortizationBytes) { |
| SkipFirstAmortizationRun(); |
| |
| { |
| base::HistogramTester histogram_tester; |
| |
| // The count of received bytes starts at 0, so it should stay 0, and be |
| // amortized in the next amortization run instead. |
| amortizer()->AmortizeDataUse(CreateDataUse(50, 0), |
| ExpectDataUseCallback(CreateDataUse(100, 0))); |
| // Add the TrafficStats byte counts for this and the next amortization run. |
| amortizer()->AddTrafficStats(100, 1000); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| EXPECT_EQ(1, data_use_callback_call_count()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 50, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 100, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| |
| // The count of received bytes should double, even though they actually |
| // updated during the previous amortization run. |
| amortizer()->AmortizeDataUse(CreateDataUse(0, 500), |
| ExpectDataUseCallback(CreateDataUse(0, 1000))); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| EXPECT_EQ(2, data_use_callback_call_count()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 1000, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kTrafficStatsQueryDelay), |
| 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxDelay) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| // 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, kTrafficStatsQueryDelay); |
| |
| // 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()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 50, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 500, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 100, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 1000, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, |
| GetDelaySample(kMaxAmortizationDelay), 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 1, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxBufferSize) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| // 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))); |
| } |
| |
| const int kExpectedBufSize = static_cast<int>(kMaxDataUseBufferSize + 1); |
| EXPECT_EQ(kExpectedBufSize, data_use_callback_call_count()); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, |
| 50 * kExpectedBufSize, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, |
| 500 * kExpectedBufSize, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, |
| 100 * kExpectedBufSize, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, |
| 1000 * kExpectedBufSize, 1); |
| histogram_tester.ExpectUniqueSample(kAmortizationDelayHistogram, 0, 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, |
| kExpectedBufSize, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, AmortizeCombinedDataUse) { |
| SkipFirstAmortizationRun(); |
| base::HistogramTester histogram_tester; |
| |
| const GURL foo_url("http://foo.com"); |
| const GURL bar_url("http://bar.com"); |
| |
| std::vector<std::unique_ptr<DataUse>> baz_sequence; |
| const DataUseAmortizer::AmortizationCompleteCallback baz_callback = |
| base::Bind(&AppendDataUseToSequence, &baz_sequence); |
| |
| std::vector<std::unique_ptr<DataUse>> qux_sequence; |
| const DataUseAmortizer::AmortizationCompleteCallback qux_callback = |
| base::Bind(&AppendDataUseToSequence, &qux_sequence); |
| |
| // Byte counts should double, with some DataUse objects combined together. |
| |
| // Two consecutive DataUse objects that are identical except for byte counts |
| // and with the same callback should be combined. |
| amortizer()->AmortizeDataUse(CreateDataUseWithURL(foo_url, 50, 500), |
| baz_callback); |
| amortizer()->AmortizeDataUse(CreateDataUseWithURL(foo_url, 100, 1000), |
| baz_callback); |
| |
| // This DataUse object should not be combined with the previous one because it |
| // has a different URL. |
| amortizer()->AmortizeDataUse(CreateDataUseWithURL(bar_url, 50, 500), |
| baz_callback); |
| |
| // This DataUse object should not be combined with the previous one because it |
| // has a different callback. |
| amortizer()->AmortizeDataUse(CreateDataUseWithURL(bar_url, 50, 500), |
| qux_callback); |
| |
| // This DataUse object should not be combined with the previous foo/baz |
| // DataUse objects because other DataUse objects were reported in-between. |
| amortizer()->AmortizeDataUse(CreateDataUseWithURL(foo_url, 50, 500), |
| baz_callback); |
| |
| // Simulate that TrafficStats saw double the number of reported bytes across |
| // all reported DataUse. |
| amortizer()->AddTrafficStats(600, 6000); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| |
| EXPECT_EQ(3U, baz_sequence.size()); |
| ExpectDataUse(CreateDataUseWithURL(foo_url, 300, 3000), |
| std::move(baz_sequence[0])); |
| ExpectDataUse(CreateDataUseWithURL(bar_url, 100, 1000), |
| std::move(baz_sequence[1])); |
| ExpectDataUse(CreateDataUseWithURL(foo_url, 100, 1000), |
| std::move(baz_sequence[2])); |
| |
| EXPECT_EQ(1U, qux_sequence.size()); |
| ExpectDataUse(CreateDataUseWithURL(bar_url, 100, 1000), |
| std::move(qux_sequence[0])); |
| |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 300, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 3000, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 600, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 6000, 1); |
| histogram_tester.ExpectUniqueSample( |
| kAmortizationDelayHistogram, GetDelaySample(kTrafficStatsQueryDelay), 1); |
| histogram_tester.ExpectUniqueSample(kBufferSizeOnFlushHistogram, 4, 1); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 1, 1); |
| } |
| |
| TEST_F(TrafficStatsAmortizerTest, ConcurrentTabsHistogram) { |
| SkipFirstAmortizationRun(); |
| |
| { |
| // Test data usage reported multiple times for two tabs. |
| base::HistogramTester histogram_tester; |
| amortizer()->SetNextTrafficStats(true, 0, 0); |
| amortizer()->AmortizeDataUse( |
| CreateDataUseWithTab(1, 50, 500), |
| ExpectDataUseCallback(CreateDataUseWithTab(1, 100, 1000))); |
| amortizer()->AmortizeDataUse( |
| CreateDataUseWithTab(2, 100, 1000), |
| ExpectDataUseCallback(CreateDataUseWithTab(2, 200, 2000))); |
| amortizer()->AmortizeDataUse( |
| CreateDataUseWithTab(1, 50, 500), |
| ExpectDataUseCallback(CreateDataUseWithTab(1, 100, 1000))); |
| amortizer()->AmortizeDataUse( |
| CreateDataUseWithTab(2, 100, 1000), |
| ExpectDataUseCallback(CreateDataUseWithTab(2, 200, 2000))); |
| amortizer()->SetNextTrafficStats(true, 600, 6000); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, 2, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationTxHistogram, 300, 1); |
| histogram_tester.ExpectUniqueSample(kPreAmortizationRxHistogram, 3000, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationTxHistogram, 600, 1); |
| histogram_tester.ExpectUniqueSample(kPostAmortizationRxHistogram, 6000, 1); |
| } |
| |
| // Test data usage for 1-5 tabs. |
| for (int32_t total_tabs = 1; total_tabs <= 5; ++total_tabs) { |
| base::HistogramTester histogram_tester; |
| |
| for (int32_t i = 1; i <= total_tabs; ++i) { |
| amortizer()->AmortizeDataUse( |
| CreateDataUseWithTab(i, 100, 1000), |
| ExpectDataUseCallback(CreateDataUseWithTab(i, 200, 2000))); |
| } |
| amortizer()->AddTrafficStats(total_tabs * 200, total_tabs * 2000); |
| AdvanceTime(kTrafficStatsQueryDelay); |
| histogram_tester.ExpectUniqueSample(kConcurrentTabs, total_tabs, 1); |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace android |
| } // namespace data_usage |