OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "components/data_usage/android/traffic_stats_amortizer.h" |
| 6 |
| 7 #include <stdint.h> |
| 8 |
| 9 #include <string> |
| 10 |
| 11 #include "base/bind.h" |
| 12 #include "base/macros.h" |
| 13 #include "base/memory/scoped_ptr.h" |
| 14 #include "base/message_loop/message_loop.h" |
| 15 #include "base/run_loop.h" |
| 16 #include "base/test/simple_test_tick_clock.h" |
| 17 #include "base/time/tick_clock.h" |
| 18 #include "base/time/time.h" |
| 19 #include "base/timer/mock_timer.h" |
| 20 #include "base/timer/timer.h" |
| 21 #include "components/data_usage/core/data_use.h" |
| 22 #include "components/data_usage/core/data_use_amortizer.h" |
| 23 #include "net/base/network_change_notifier.h" |
| 24 #include "testing/gtest/include/gtest/gtest.h" |
| 25 #include "url/gurl.h" |
| 26 |
| 27 namespace data_usage { |
| 28 namespace android { |
| 29 |
| 30 namespace { |
| 31 |
| 32 // The delay between receiving DataUse and querying TrafficStats byte counts for |
| 33 // amortization. |
| 34 const base::TimeDelta kTrafficStatsQueryDelay = |
| 35 base::TimeDelta::FromMilliseconds(50); |
| 36 |
| 37 // The longest amount of time that an amortization run can be delayed for. |
| 38 const base::TimeDelta kMaxAmortizationDelay = |
| 39 base::TimeDelta::FromMilliseconds(200); |
| 40 |
| 41 // The maximum allowed size of the DataUse buffer. |
| 42 const size_t kMaxDataUseBufferSize = 8; |
| 43 |
| 44 // Synthesizes a fake scoped_ptr<DataUse> with the given |tx_bytes| and |
| 45 // |rx_bytes|, using arbitrary values for all other fields. |
| 46 scoped_ptr<DataUse> CreateDataUse(int64_t tx_bytes, int64_t rx_bytes) { |
| 47 return scoped_ptr<DataUse>(new DataUse( |
| 48 GURL("http://example.com"), base::TimeTicks() /* request_start */, |
| 49 GURL("http://examplefirstparty.com"), 10 /* tab_id */, |
| 50 net::NetworkChangeNotifier::CONNECTION_2G, "example_mcc_mnc", tx_bytes, |
| 51 rx_bytes)); |
| 52 } |
| 53 |
| 54 // Class that represents a base::MockTimer with an attached base::TickClock, so |
| 55 // that it can update its |desired_run_time()| according to the current time |
| 56 // when the timer is reset. |
| 57 class MockTimerWithTickClock : public base::MockTimer { |
| 58 public: |
| 59 MockTimerWithTickClock(bool retain_user_task, |
| 60 bool is_repeating, |
| 61 base::TickClock* tick_clock) |
| 62 : base::MockTimer(retain_user_task, is_repeating), |
| 63 tick_clock_(tick_clock) {} |
| 64 |
| 65 ~MockTimerWithTickClock() override {} |
| 66 |
| 67 void Reset() override { |
| 68 base::MockTimer::Reset(); |
| 69 set_desired_run_time(tick_clock_->NowTicks() + GetCurrentDelay()); |
| 70 } |
| 71 |
| 72 private: |
| 73 base::TickClock* tick_clock_; |
| 74 |
| 75 DISALLOW_COPY_AND_ASSIGN(MockTimerWithTickClock); |
| 76 }; |
| 77 |
| 78 // A TrafficStatsAmortizer for testing that allows for tests to simulate the |
| 79 // byte counts returned from TrafficStats. |
| 80 class TestTrafficStatsAmortizer : public TrafficStatsAmortizer { |
| 81 public: |
| 82 TestTrafficStatsAmortizer(scoped_ptr<base::TickClock> tick_clock, |
| 83 scoped_ptr<base::Timer> traffic_stats_query_timer) |
| 84 : TrafficStatsAmortizer(tick_clock.Pass(), |
| 85 traffic_stats_query_timer.Pass(), |
| 86 kTrafficStatsQueryDelay, |
| 87 kMaxAmortizationDelay, |
| 88 kMaxDataUseBufferSize), |
| 89 next_traffic_stats_available_(false), |
| 90 next_traffic_stats_tx_bytes_(-1), |
| 91 next_traffic_stats_rx_bytes_(-1) {} |
| 92 |
| 93 ~TestTrafficStatsAmortizer() override {} |
| 94 |
| 95 void SetNextTrafficStats(bool available, int64_t tx_bytes, int64_t rx_bytes) { |
| 96 next_traffic_stats_available_ = available; |
| 97 next_traffic_stats_tx_bytes_ = tx_bytes; |
| 98 next_traffic_stats_rx_bytes_ = rx_bytes; |
| 99 } |
| 100 |
| 101 void AddTrafficStats(int64_t tx_bytes, int64_t rx_bytes) { |
| 102 next_traffic_stats_tx_bytes_ += tx_bytes; |
| 103 next_traffic_stats_rx_bytes_ += rx_bytes; |
| 104 } |
| 105 |
| 106 protected: |
| 107 bool QueryTrafficStats(int64_t* tx_bytes, int64_t* rx_bytes) const override { |
| 108 *tx_bytes = next_traffic_stats_tx_bytes_; |
| 109 *rx_bytes = next_traffic_stats_rx_bytes_; |
| 110 return next_traffic_stats_available_; |
| 111 } |
| 112 |
| 113 private: |
| 114 bool next_traffic_stats_available_; |
| 115 int64_t next_traffic_stats_tx_bytes_; |
| 116 int64_t next_traffic_stats_rx_bytes_; |
| 117 |
| 118 DISALLOW_COPY_AND_ASSIGN(TestTrafficStatsAmortizer); |
| 119 }; |
| 120 |
| 121 class TrafficStatsAmortizerTest : public testing::Test { |
| 122 public: |
| 123 TrafficStatsAmortizerTest() |
| 124 : test_tick_clock_(new base::SimpleTestTickClock()), |
| 125 mock_timer_(new MockTimerWithTickClock(false, false, test_tick_clock_)), |
| 126 amortizer_(scoped_ptr<base::TickClock>(test_tick_clock_), |
| 127 scoped_ptr<base::Timer>(mock_timer_)), |
| 128 data_use_callback_call_count_(0) {} |
| 129 |
| 130 ~TrafficStatsAmortizerTest() override { |
| 131 EXPECT_FALSE(mock_timer_->IsRunning()); |
| 132 } |
| 133 |
| 134 // Simulates the passage of time by |delta|, firing timers when appropriate. |
| 135 void AdvanceTime(const base::TimeDelta& delta) { |
| 136 const base::TimeTicks end_time = test_tick_clock_->NowTicks() + delta; |
| 137 base::RunLoop().RunUntilIdle(); |
| 138 |
| 139 while (test_tick_clock_->NowTicks() < end_time) { |
| 140 PumpMockTimer(); |
| 141 |
| 142 // If |mock_timer_| is scheduled to fire in the future before |end_time|, |
| 143 // advance to that time. |
| 144 if (mock_timer_->IsRunning() && |
| 145 mock_timer_->desired_run_time() < end_time) { |
| 146 test_tick_clock_->Advance(mock_timer_->desired_run_time() - |
| 147 test_tick_clock_->NowTicks()); |
| 148 } else { |
| 149 // Otherwise, advance to |end_time|. |
| 150 test_tick_clock_->Advance(end_time - test_tick_clock_->NowTicks()); |
| 151 } |
| 152 } |
| 153 PumpMockTimer(); |
| 154 } |
| 155 |
| 156 // Skip the first amortization run where TrafficStats byte count deltas are |
| 157 // unavailable, for convenience. |
| 158 void SkipFirstAmortizationRun() { |
| 159 // The initial values of TrafficStats shouldn't matter. |
| 160 amortizer()->SetNextTrafficStats(true, 0, 0); |
| 161 |
| 162 // Do the first amortization run with TrafficStats unavailable. |
| 163 amortizer()->OnExtraBytes(100, 1000); |
| 164 AdvanceTime(kTrafficStatsQueryDelay); |
| 165 EXPECT_EQ(0, data_use_callback_call_count()); |
| 166 } |
| 167 |
| 168 // Expects that |expected| and |actual| are equivalent. |
| 169 void ExpectDataUse(scoped_ptr<DataUse> expected, scoped_ptr<DataUse> actual) { |
| 170 ++data_use_callback_call_count_; |
| 171 |
| 172 // Have separate checks for the |tx_bytes| and |rx_bytes|, since those are |
| 173 // calculated with floating point arithmetic. |
| 174 EXPECT_DOUBLE_EQ(static_cast<double>(expected->tx_bytes), |
| 175 static_cast<double>(actual->tx_bytes)); |
| 176 EXPECT_DOUBLE_EQ(static_cast<double>(expected->rx_bytes), |
| 177 static_cast<double>(actual->rx_bytes)); |
| 178 |
| 179 // Copy the byte counts over from |expected| just in case they're only |
| 180 // slightly different due to floating point error, so that this doesn't |
| 181 // cause the equality comparison below to fail. |
| 182 actual->tx_bytes = expected->tx_bytes; |
| 183 actual->rx_bytes = expected->rx_bytes; |
| 184 EXPECT_EQ(*expected, *actual); |
| 185 } |
| 186 |
| 187 // Creates an ExpectDataUse callback, as a convenience. |
| 188 DataUseAmortizer::AmortizationCompleteCallback ExpectDataUseCallback( |
| 189 scoped_ptr<DataUse> expected) { |
| 190 return base::Bind(&TrafficStatsAmortizerTest::ExpectDataUse, |
| 191 base::Unretained(this), base::Passed(&expected)); |
| 192 } |
| 193 |
| 194 base::TimeTicks NowTicks() const { return test_tick_clock_->NowTicks(); } |
| 195 |
| 196 TestTrafficStatsAmortizer* amortizer() { return &amortizer_; } |
| 197 |
| 198 int data_use_callback_call_count() const { |
| 199 return data_use_callback_call_count_; |
| 200 } |
| 201 |
| 202 private: |
| 203 // Pumps |mock_timer_|, firing it while it's scheduled to run now or in the |
| 204 // past. After calling this, |mock_timer_| is either not running or is |
| 205 // scheduled to run in the future. |
| 206 void PumpMockTimer() { |
| 207 // Fire the |mock_timer_| if the time has come up. Use a while loop in case |
| 208 // the fired task started the timer again to fire immediately. |
| 209 while (mock_timer_->IsRunning() && |
| 210 mock_timer_->desired_run_time() <= test_tick_clock_->NowTicks()) { |
| 211 mock_timer_->Fire(); |
| 212 base::RunLoop().RunUntilIdle(); |
| 213 } |
| 214 } |
| 215 |
| 216 base::MessageLoop message_loop_; |
| 217 |
| 218 // Weak, owned by |amortizer_|. |
| 219 base::SimpleTestTickClock* test_tick_clock_; |
| 220 |
| 221 // Weak, owned by |amortizer_|. |
| 222 MockTimerWithTickClock* mock_timer_; |
| 223 |
| 224 TestTrafficStatsAmortizer amortizer_; |
| 225 |
| 226 // The number of times ExpectDataUse has been called. |
| 227 int data_use_callback_call_count_; |
| 228 |
| 229 DISALLOW_COPY_AND_ASSIGN(TrafficStatsAmortizerTest); |
| 230 }; |
| 231 |
| 232 TEST_F(TrafficStatsAmortizerTest, AmortizeWithTrafficStatsAlwaysUnavailable) { |
| 233 amortizer()->SetNextTrafficStats(false, -1, -1); |
| 234 // Do it three times for good measure. |
| 235 for (int i = 0; i < 3; ++i) { |
| 236 // Extra bytes should be ignored since TrafficStats are unavailable. |
| 237 amortizer()->OnExtraBytes(1337, 9001); |
| 238 // The original DataUse should be unchanged. |
| 239 amortizer()->AmortizeDataUse( |
| 240 CreateDataUse(100, 1000), |
| 241 ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| 242 |
| 243 AdvanceTime(kTrafficStatsQueryDelay); |
| 244 EXPECT_EQ(i + 1, data_use_callback_call_count()); |
| 245 } |
| 246 } |
| 247 |
| 248 TEST_F(TrafficStatsAmortizerTest, AmortizeDataUse) { |
| 249 // The initial values of TrafficStats shouldn't matter. |
| 250 amortizer()->SetNextTrafficStats(true, 1337, 9001); |
| 251 |
| 252 // The first amortization run should not change any byte counts because |
| 253 // there's no TrafficStats delta to work with. |
| 254 amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| 255 ExpectDataUseCallback(CreateDataUse(50, 500))); |
| 256 amortizer()->AmortizeDataUse(CreateDataUse(100, 1000), |
| 257 ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| 258 AdvanceTime(kTrafficStatsQueryDelay); |
| 259 EXPECT_EQ(2, data_use_callback_call_count()); |
| 260 |
| 261 // This amortization run, tx_bytes and rx_bytes should be doubled. |
| 262 amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| 263 ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| 264 AdvanceTime(kTrafficStatsQueryDelay / 2); |
| 265 |
| 266 // Another DataUse is reported before the amortizer queries TrafficStats. |
| 267 amortizer()->AmortizeDataUse(CreateDataUse(100, 1000), |
| 268 ExpectDataUseCallback(CreateDataUse(200, 2000))); |
| 269 AdvanceTime(kTrafficStatsQueryDelay / 2); |
| 270 |
| 271 // Then, the TrafficStats values update with the new bytes. The second run |
| 272 // callbacks should not have been called yet. |
| 273 amortizer()->AddTrafficStats(300, 3000); |
| 274 EXPECT_EQ(2, data_use_callback_call_count()); |
| 275 |
| 276 // The callbacks should fire once kTrafficStatsQueryDelay has passed since the |
| 277 // DataUse was passed to the amortizer. |
| 278 AdvanceTime(kTrafficStatsQueryDelay / 2); |
| 279 EXPECT_EQ(4, data_use_callback_call_count()); |
| 280 } |
| 281 |
| 282 TEST_F(TrafficStatsAmortizerTest, AmortizeWithExtraBytes) { |
| 283 SkipFirstAmortizationRun(); |
| 284 |
| 285 // Byte counts should double. |
| 286 amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| 287 ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| 288 amortizer()->OnExtraBytes(500, 5000); |
| 289 amortizer()->AddTrafficStats(1100, 11000); |
| 290 AdvanceTime(kTrafficStatsQueryDelay); |
| 291 EXPECT_EQ(1, data_use_callback_call_count()); |
| 292 } |
| 293 |
| 294 TEST_F(TrafficStatsAmortizerTest, AmortizeWithNegativeOverhead) { |
| 295 SkipFirstAmortizationRun(); |
| 296 |
| 297 // Byte counts should halve. |
| 298 amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| 299 ExpectDataUseCallback(CreateDataUse(25, 250))); |
| 300 amortizer()->AddTrafficStats(25, 250); |
| 301 AdvanceTime(kTrafficStatsQueryDelay); |
| 302 EXPECT_EQ(1, data_use_callback_call_count()); |
| 303 } |
| 304 |
| 305 TEST_F(TrafficStatsAmortizerTest, AmortizeWithMaxIntByteCounts) { |
| 306 SkipFirstAmortizationRun(); |
| 307 |
| 308 // Byte counts should be unchanged. |
| 309 amortizer()->AmortizeDataUse( |
| 310 CreateDataUse(INT64_MAX, INT64_MAX), |
| 311 ExpectDataUseCallback(CreateDataUse(INT64_MAX, INT64_MAX))); |
| 312 amortizer()->SetNextTrafficStats(true, INT64_MAX, INT64_MAX); |
| 313 AdvanceTime(kTrafficStatsQueryDelay); |
| 314 EXPECT_EQ(1, data_use_callback_call_count()); |
| 315 } |
| 316 |
| 317 TEST_F(TrafficStatsAmortizerTest, AmortizeWithMaxIntScaleFactor) { |
| 318 SkipFirstAmortizationRun(); |
| 319 |
| 320 // Byte counts should be scaled up to INT64_MAX. |
| 321 amortizer()->AmortizeDataUse( |
| 322 CreateDataUse(1, 1), |
| 323 ExpectDataUseCallback(CreateDataUse(INT64_MAX, INT64_MAX))); |
| 324 amortizer()->SetNextTrafficStats(true, INT64_MAX, INT64_MAX); |
| 325 AdvanceTime(kTrafficStatsQueryDelay); |
| 326 EXPECT_EQ(1, data_use_callback_call_count()); |
| 327 } |
| 328 |
| 329 TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroScaleFactor) { |
| 330 SkipFirstAmortizationRun(); |
| 331 |
| 332 // Byte counts should be scaled down to 0. |
| 333 amortizer()->AmortizeDataUse(CreateDataUse(INT64_MAX, INT64_MAX), |
| 334 ExpectDataUseCallback(CreateDataUse(0, 0))); |
| 335 amortizer()->SetNextTrafficStats(true, 0, 0); |
| 336 AdvanceTime(kTrafficStatsQueryDelay); |
| 337 EXPECT_EQ(1, data_use_callback_call_count()); |
| 338 } |
| 339 |
| 340 TEST_F(TrafficStatsAmortizerTest, AmortizeWithZeroPreAmortizationBytes) { |
| 341 SkipFirstAmortizationRun(); |
| 342 |
| 343 // Both byte counts should stay 0, even though TrafficStats saw bytes. |
| 344 amortizer()->AmortizeDataUse(CreateDataUse(0, 0), |
| 345 ExpectDataUseCallback(CreateDataUse(0, 0))); |
| 346 amortizer()->AddTrafficStats(100, 1000); |
| 347 AdvanceTime(kTrafficStatsQueryDelay); |
| 348 EXPECT_EQ(1, data_use_callback_call_count()); |
| 349 |
| 350 // This time, only TX bytes are 0, so RX bytes should double, but TX bytes |
| 351 // should stay 0. |
| 352 amortizer()->AmortizeDataUse(CreateDataUse(0, 500), |
| 353 ExpectDataUseCallback(CreateDataUse(0, 1000))); |
| 354 amortizer()->AddTrafficStats(100, 1000); |
| 355 AdvanceTime(kTrafficStatsQueryDelay); |
| 356 EXPECT_EQ(2, data_use_callback_call_count()); |
| 357 |
| 358 // This time, only RX bytes are 0, so TX bytes should double, but RX bytes |
| 359 // should stay 0. |
| 360 amortizer()->AmortizeDataUse(CreateDataUse(50, 0), |
| 361 ExpectDataUseCallback(CreateDataUse(100, 0))); |
| 362 amortizer()->AddTrafficStats(100, 1000); |
| 363 AdvanceTime(kTrafficStatsQueryDelay); |
| 364 EXPECT_EQ(3, data_use_callback_call_count()); |
| 365 } |
| 366 |
| 367 TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxDelay) { |
| 368 SkipFirstAmortizationRun(); |
| 369 |
| 370 // Byte counts should double. |
| 371 amortizer()->AddTrafficStats(1000, 10000); |
| 372 amortizer()->AmortizeDataUse(CreateDataUse(50, 500), |
| 373 ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| 374 |
| 375 // kSmallDelay is a delay that's shorter than the delay before TrafficStats |
| 376 // would be queried, where kMaxAmortizationDelay is a multiple of kSmallDelay. |
| 377 const base::TimeDelta kSmallDelay = kMaxAmortizationDelay / 10; |
| 378 EXPECT_LT(kSmallDelay, kMaxAmortizationDelay); |
| 379 |
| 380 // Simulate multiple cases of extra bytes being reported, each before |
| 381 // TrafficStats would be queried, until kMaxAmortizationDelay has elapsed. |
| 382 AdvanceTime(kSmallDelay); |
| 383 for (int64_t i = 0; i < kMaxAmortizationDelay / kSmallDelay - 1; ++i) { |
| 384 EXPECT_EQ(0, data_use_callback_call_count()); |
| 385 amortizer()->OnExtraBytes(50, 500); |
| 386 AdvanceTime(kSmallDelay); |
| 387 } |
| 388 |
| 389 // The final time, the amortizer should have given up on waiting to query |
| 390 // TrafficStats and just have amortized as soon as it hit the deadline of |
| 391 // kMaxAmortizationDelay. |
| 392 EXPECT_EQ(1, data_use_callback_call_count()); |
| 393 } |
| 394 |
| 395 TEST_F(TrafficStatsAmortizerTest, AmortizeAtMaxBufferSize) { |
| 396 SkipFirstAmortizationRun(); |
| 397 |
| 398 // Report (max buffer size + 1) consecutive DataUse objects, which will be |
| 399 // amortized immediately once the buffer exceeds maximum size. |
| 400 amortizer()->AddTrafficStats(100 * (kMaxDataUseBufferSize + 1), |
| 401 1000 * (kMaxDataUseBufferSize + 1)); |
| 402 for (size_t i = 0; i < kMaxDataUseBufferSize + 1; ++i) { |
| 403 EXPECT_EQ(0, data_use_callback_call_count()); |
| 404 amortizer()->AmortizeDataUse( |
| 405 CreateDataUse(50, 500), |
| 406 ExpectDataUseCallback(CreateDataUse(100, 1000))); |
| 407 } |
| 408 |
| 409 EXPECT_EQ(static_cast<int>(kMaxDataUseBufferSize + 1), |
| 410 data_use_callback_call_count()); |
| 411 } |
| 412 |
| 413 } // namespace |
| 414 |
| 415 } // namespace android |
| 416 } // namespace data_usage |
OLD | NEW |