blob: 00b6af18654e539e669508d8143406ec13b712d6 [file] [log] [blame]
// 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