blob: 653cf255324c993132ff696f6bee6578b821e19f [file] [log] [blame]
// Copyright 2016 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 "net/nqe/observation_buffer.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/macros.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "net/nqe/network_quality_estimator_params.h"
#include "net/nqe/network_quality_observation.h"
#include "net/nqe/network_quality_observation_source.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace nqe {
namespace internal {
namespace {
// Verify that the buffer size is never exceeded.
TEST(NetworkQualityObservationBufferTest, BoundedBuffer) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer observation_buffer(&params, &tick_clock, 1.0, 1.0);
const base::TimeTicks now =
base::TimeTicks() + base::TimeDelta::FromSeconds(1);
for (int i = 1; i <= 1000; ++i) {
observation_buffer.AddObservation(
Observation(i, now, INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_TCP));
// The number of entries should be at most the maximum buffer size.
EXPECT_GE(300u, observation_buffer.Size());
}
}
// Verify that the percentiles are monotonically non-decreasing when a weight is
// applied.
TEST(NetworkQualityObservationBufferTest, GetPercentileWithWeights) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer observation_buffer(&params, &tick_clock, 0.98, 1.0);
const base::TimeTicks now = tick_clock.NowTicks();
for (int i = 1; i <= 100; ++i) {
tick_clock.Advance(base::TimeDelta::FromSeconds(1));
observation_buffer.AddObservation(
Observation(i, tick_clock.NowTicks(), INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_TCP));
}
EXPECT_EQ(100U, observation_buffer.Size());
int32_t result_lowest = INT32_MAX;
int32_t result_highest = INT32_MIN;
for (int i = 1; i <= 100; ++i) {
size_t observations_count = 0;
// Verify that i'th percentile is more than i-1'th percentile.
base::Optional<int32_t> result_i = observation_buffer.GetPercentile(
now, INT32_MIN, i, &observations_count);
EXPECT_EQ(100u, observations_count);
ASSERT_TRUE(result_i.has_value());
result_lowest = std::min(result_lowest, result_i.value());
result_highest = std::max(result_highest, result_i.value());
base::Optional<int32_t> result_i_1 = observation_buffer.GetPercentile(
now, INT32_MIN, i - 1, &observations_count);
EXPECT_EQ(100u, observations_count);
ASSERT_TRUE(result_i_1.has_value());
EXPECT_LE(result_i_1.value(), result_i.value());
}
EXPECT_LT(result_lowest, result_highest);
}
// Verifies that the percentiles are correctly computed. All observations have
// the same timestamp.
TEST(NetworkQualityObservationBufferTest, PercentileSameTimestamps) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer buffer(&params, &tick_clock, 0.5, 1.0);
ASSERT_EQ(0u, buffer.Size());
ASSERT_LT(0u, buffer.Capacity());
const base::TimeTicks now = tick_clock.NowTicks();
size_t observations_count = 0;
// Percentiles should be unavailable when no observations are available.
EXPECT_FALSE(
buffer
.GetPercentile(base::TimeTicks(), INT32_MIN, 50,
&observations_count)
.has_value());
EXPECT_EQ(0u, observations_count);
// Insert samples from {1,2,3,..., 100}. First insert odd samples, then even
// samples. This helps in verifying that the order of samples does not matter.
for (int i = 1; i <= 99; i += 2) {
buffer.AddObservation(Observation(i, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
EXPECT_TRUE(buffer.GetPercentile(base::TimeTicks(), INT32_MIN, 50, nullptr)
.has_value());
ASSERT_EQ(static_cast<size_t>(i / 2 + 1), buffer.Size());
}
for (int i = 2; i <= 100; i += 2) {
buffer.AddObservation(Observation(i, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
EXPECT_TRUE(buffer.GetPercentile(base::TimeTicks(), INT32_MIN, 50, nullptr)
.has_value());
ASSERT_EQ(static_cast<size_t>(i / 2 + 50), buffer.Size());
}
ASSERT_EQ(100u, buffer.Size());
for (int i = 0; i <= 100; ++i) {
// Checks if the difference between actual result and the computed result is
// less than 1. This is required because computed percentiles may be
// slightly different from what is expected due to floating point
// computation errors and integer rounding off errors.
base::Optional<int32_t> result = buffer.GetPercentile(
base::TimeTicks(), INT32_MIN, i, &observations_count);
EXPECT_EQ(100u, observations_count);
EXPECT_TRUE(result.has_value());
EXPECT_NEAR(result.value(), i, 1.0);
}
EXPECT_FALSE(
buffer
.GetPercentile(now + base::TimeDelta::FromSeconds(1), INT32_MIN, 50,
&observations_count)
.has_value());
EXPECT_EQ(0u, observations_count);
// Percentiles should be unavailable when no observations are available.
buffer.Clear();
EXPECT_FALSE(
buffer
.GetPercentile(base::TimeTicks(), INT32_MIN, 50,
&observations_count)
.has_value());
EXPECT_EQ(0u, observations_count);
}
// Verifies that the percentiles are correctly computed. Observations have
// different timestamps with half the observations being very old and the rest
// of them being very recent. Percentiles should factor in recent observations
// much more heavily than older samples.
TEST(NetworkQualityObservationBufferTest, PercentileDifferentTimestamps) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer buffer(&params, &tick_clock, 0.5, 1.0);
const base::TimeTicks now = tick_clock.NowTicks();
const base::TimeTicks very_old = now - base::TimeDelta::FromDays(7);
size_t observations_count;
// Network quality should be unavailable when no observations are available.
EXPECT_FALSE(
buffer
.GetPercentile(base::TimeTicks(), INT32_MIN, 50,
&observations_count)
.has_value());
EXPECT_EQ(0u, observations_count);
// First 50 samples have very old timestamps.
for (int i = 1; i <= 50; ++i) {
buffer.AddObservation(Observation(i, very_old, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
}
// Next 50 (i.e., from 51 to 100) have recent timestamps.
for (int i = 51; i <= 100; ++i) {
buffer.AddObservation(Observation(i, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
}
// Older samples have very little weight. So, all percentiles are >= 51
// (lowest value among recent observations).
for (int i = 1; i < 100; ++i) {
// Checks if the difference between the two integers is less than 1. This is
// required because computed percentiles may be slightly different from
// what is expected due to floating point computation errors and integer
// rounding off errors.
base::Optional<int32_t> result =
buffer.GetPercentile(very_old, INT32_MIN, i, &observations_count);
EXPECT_TRUE(result.has_value());
EXPECT_NEAR(result.value(), 51 + 0.49 * i, 1);
EXPECT_EQ(100u, observations_count);
}
EXPECT_FALSE(buffer.GetPercentile(now + base::TimeDelta::FromSeconds(1),
INT32_MIN, 50, &observations_count));
EXPECT_EQ(0u, observations_count);
}
// Verifies that the percentiles are correctly computed. All observations have
// same timestamp with half the observations taken at low RSSI, and half the
// observations with high RSSI. Percentiles should be computed based on the
// current RSSI and the RSSI of the observations.
TEST(NetworkQualityObservationBufferTest, PercentileDifferentRSSI) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer buffer(&params, &tick_clock, 1.0, 0.25);
const base::TimeTicks now = tick_clock.NowTicks();
int32_t high_rssi = 4;
int32_t low_rssi = 0;
// Network quality should be unavailable when no observations are available.
EXPECT_FALSE(buffer.GetPercentile(base::TimeTicks(), INT32_MIN, 50, nullptr)
.has_value());
// First 50 samples have very low RSSI.
for (int i = 1; i <= 50; ++i) {
buffer.AddObservation(
Observation(i, now, low_rssi, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
}
// Next 50 (i.e., from 51 to 100) have high RSSI.
for (int i = 51; i <= 100; ++i) {
buffer.AddObservation(Observation(i, now, high_rssi,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
}
// When the current RSSI is |high_rssi|, higher weight should be assigned
// to observations that were taken at |high_rssi|.
for (int i = 1; i < 100; ++i) {
base::Optional<int32_t> result =
buffer.GetPercentile(now, high_rssi, i, nullptr);
EXPECT_TRUE(result.has_value());
EXPECT_NEAR(result.value(), 51 + 0.49 * i, 2);
}
// When the current RSSI is |low_rssi|, higher weight should be assigned
// to observations that were taken at |low_rssi|.
for (int i = 1; i < 100; ++i) {
base::Optional<int32_t> result =
buffer.GetPercentile(now, low_rssi, i, nullptr);
EXPECT_TRUE(result.has_value());
EXPECT_NEAR(result.value(), i / 2, 2);
}
}
// Verifies that the percentiles are correctly computed when some of the
// observation sources are disallowed. All observations have the same timestamp.
TEST(NetworkQualityObservationBufferTest, RemoveObservations) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer buffer(&params, &tick_clock, 0.5, 1.0);
const base::TimeTicks now = tick_clock.NowTicks();
// Insert samples from {1,2,3,..., 100}. First insert odd samples, then even
// samples. This helps in verifying that the order of samples does not matter.
for (int i = 1; i <= 99; i += 2) {
buffer.AddObservation(Observation(i, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
}
EXPECT_EQ(50u, buffer.Size());
// Add samples for TCP and QUIC observations which should not be taken into
// account when computing the percentile.
for (int i = 1; i <= 99; i += 2) {
buffer.AddObservation(Observation(10000, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_TCP));
buffer.AddObservation(Observation(10000, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_QUIC));
}
EXPECT_EQ(150u, buffer.Size());
for (int i = 2; i <= 100; i += 2) {
buffer.AddObservation(Observation(i, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
}
EXPECT_EQ(200u, buffer.Size());
bool deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_MAX] = {
false};
// Since all entries in |deleted_observation_sources| are set to false, no
// observations should be deleted.
buffer.RemoveObservationsWithSource(deleted_observation_sources);
EXPECT_EQ(200u, buffer.Size());
// 50 TCP and 50 QUIC observations should be deleted.
deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_TCP] = true;
deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_QUIC] = true;
buffer.RemoveObservationsWithSource(deleted_observation_sources);
EXPECT_EQ(100u, buffer.Size());
for (int i = 0; i <= 100; ++i) {
// Checks if the difference between the two integers is less than 1. This is
// required because computed percentiles may be slightly different from
// what is expected due to floating point computation errors and integer
// rounding off errors.
base::Optional<int32_t> result =
buffer.GetPercentile(base::TimeTicks(), INT32_MIN, i,
nullptr);
EXPECT_TRUE(result.has_value());
EXPECT_NEAR(result.value(), i, 1);
}
deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP] = true;
buffer.RemoveObservationsWithSource(deleted_observation_sources);
EXPECT_EQ(0u, buffer.Size());
}
TEST(NetworkQualityObservationBufferTest, TestGetMedianRTTSince) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer buffer(&params, &tick_clock, 0.5, 1.0);
base::TimeTicks now = tick_clock.NowTicks();
base::TimeTicks old = now - base::TimeDelta::FromMilliseconds(1);
ASSERT_NE(old, now);
// First sample has very old timestamp.
buffer.AddObservation(
Observation(1, old, INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
buffer.AddObservation(Observation(100, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP));
const struct {
base::TimeTicks start_timestamp;
bool expect_network_quality_available;
base::TimeDelta expected_url_request_rtt;
} tests[] = {
{now + base::TimeDelta::FromSeconds(10), false,
base::TimeDelta::FromMilliseconds(0)},
{now, true, base::TimeDelta::FromMilliseconds(100)},
{now - base::TimeDelta::FromMicroseconds(500), true,
base::TimeDelta::FromMilliseconds(100)},
};
for (const auto& test : tests) {
base::Optional<int32_t> url_request_rtt =
buffer.GetPercentile(test.start_timestamp, INT32_MIN, 50, nullptr);
EXPECT_EQ(test.expect_network_quality_available,
url_request_rtt.has_value());
if (test.expect_network_quality_available) {
EXPECT_EQ(test.expected_url_request_rtt.InMillisecondsF(),
url_request_rtt.value());
}
}
}
// Test that time filtering works and the remote hosts are split correctly.
TEST(NetworkQualityObservationBufferTest,
RestGetPercentileForEachRemoteHostSinceTimeStamp) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
const uint64_t new_host = 0x101010UL;
const int32_t new_host_observation = 1000;
const size_t new_host_num_obs = 10;
const uint64_t old_host = 0x202020UL;
const int32_t old_host_observation = 2000;
const size_t old_host_num_obs = 20;
ObservationBuffer buffer(&params, &tick_clock, 0.5, 1.0);
base::TimeTicks now = tick_clock.NowTicks();
for (unsigned int i = 0; i < old_host_num_obs; ++i) {
buffer.AddObservation(Observation(
old_host_observation, now - base::TimeDelta::FromSeconds(100),
INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP, old_host));
}
for (unsigned int i = 0; i < new_host_num_obs; ++i) {
buffer.AddObservation(Observation(new_host_observation, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP,
new_host));
}
std::map<uint64_t, int32_t> host_keyed_percentiles;
std::map<uint64_t, size_t> host_keyed_counts;
buffer.GetPercentileForEachHostWithCounts(
now - base::TimeDelta::FromSeconds(50), 50, base::nullopt,
&host_keyed_percentiles, &host_keyed_counts);
EXPECT_EQ(1u, host_keyed_percentiles.size());
EXPECT_EQ(1u, host_keyed_counts.size());
EXPECT_EQ(new_host_observation, host_keyed_percentiles[new_host]);
EXPECT_EQ(new_host_num_obs, host_keyed_counts[new_host]);
host_keyed_percentiles.clear();
host_keyed_counts.clear();
buffer.GetPercentileForEachHostWithCounts(
now - base::TimeDelta::FromSeconds(150), 50, base::nullopt,
&host_keyed_percentiles, &host_keyed_counts);
EXPECT_EQ(2u, host_keyed_percentiles.size());
EXPECT_EQ(2u, host_keyed_counts.size());
EXPECT_EQ(new_host_observation, host_keyed_percentiles[new_host]);
EXPECT_EQ(new_host_num_obs, host_keyed_counts[new_host]);
EXPECT_EQ(old_host_observation, host_keyed_percentiles[old_host]);
EXPECT_EQ(old_host_num_obs, host_keyed_counts[old_host]);
}
// Test that the result is split correctly for multiple remote hosts and that
// the count for each host is correct.
TEST(NetworkQualityObservationBufferTest,
RestGetPercentileForEachRemoteHostCounts) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer buffer(&params, &tick_clock, 0.5, 1.0);
base::TimeTicks now = tick_clock.NowTicks();
const size_t num_remote_hosts = 5;
// Add |2*i| observations having value |4*i| for host |i|.
for (unsigned int host_index = 1; host_index <= num_remote_hosts;
++host_index) {
for (unsigned int count = 1; count <= 2 * host_index; ++count) {
buffer.AddObservation(Observation(4 * host_index, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP,
static_cast<uint64_t>(host_index)));
}
}
std::map<uint64_t, int32_t> host_keyed_percentiles;
std::map<uint64_t, size_t> host_keyed_counts;
buffer.GetPercentileForEachHostWithCounts(
base::TimeTicks(), 50, base::nullopt, &host_keyed_percentiles,
&host_keyed_counts);
EXPECT_EQ(num_remote_hosts, host_keyed_percentiles.size());
EXPECT_EQ(num_remote_hosts, host_keyed_counts.size());
for (unsigned int host_index = 1; host_index <= num_remote_hosts;
++host_index) {
EXPECT_EQ(2u * host_index,
host_keyed_counts[static_cast<uint64_t>(host_index)]);
EXPECT_EQ(static_cast<int32_t>(4 * host_index),
host_keyed_percentiles[static_cast<uint64_t>(host_index)]);
}
}
// Test that the percentiles are computed correctly for different remote hosts.
TEST(NetworkQualityObservationBufferTest,
RestGetPercentileForEachRemoteHostComputation) {
std::map<std::string, std::string> variation_params;
NetworkQualityEstimatorParams params(variation_params);
base::SimpleTestTickClock tick_clock;
tick_clock.Advance(base::TimeDelta::FromMinutes(1));
ObservationBuffer buffer(&params, &tick_clock, 0.5, 1.0);
base::TimeTicks now = tick_clock.NowTicks();
const size_t num_hosts = 3;
// For three different remote hosts, add observations such that the 50
// percentiles are different.
for (unsigned int host_index = 1; host_index <= num_hosts; host_index++) {
// Add |20 * host_index + 1| observations for host |host_index|.
for (unsigned int observation_value = 90 * host_index;
observation_value <= 110 * host_index; observation_value++) {
buffer.AddObservation(Observation(observation_value, now, INT32_MIN,
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP,
static_cast<uint64_t>(host_index)));
}
}
std::map<uint64_t, int32_t> host_keyed_percentiles;
std::map<uint64_t, size_t> host_keyed_counts;
// Test the computation of the median.
buffer.GetPercentileForEachHostWithCounts(
base::TimeTicks(), 50, base::nullopt, &host_keyed_percentiles,
&host_keyed_counts);
EXPECT_EQ(num_hosts, host_keyed_percentiles.size());
EXPECT_EQ(num_hosts, host_keyed_counts.size());
// The median must be equal to |100 * i| and the count must be equal to
// |20 * i + 1| for host |i|.
for (unsigned int host_index = 1; host_index <= num_hosts; host_index++) {
EXPECT_EQ(100u * host_index,
static_cast<uint32_t>(
host_keyed_percentiles[static_cast<uint64_t>(host_index)]));
EXPECT_EQ(static_cast<size_t>(20 * host_index + 1),
host_keyed_counts[static_cast<uint64_t>(host_index)]);
}
// Test the computation of 0th percentile.
buffer.GetPercentileForEachHostWithCounts(base::TimeTicks(), 0, base::nullopt,
&host_keyed_percentiles,
&host_keyed_counts);
EXPECT_EQ(num_hosts, host_keyed_percentiles.size());
EXPECT_EQ(num_hosts, host_keyed_counts.size());
// The 0 percentile must be equal to |90 * i| and the count must be equal to
// |20 * i| for host |i|.
for (unsigned int host_index = 1; host_index <= num_hosts; host_index++) {
EXPECT_EQ(90u * host_index,
static_cast<uint32_t>(
host_keyed_percentiles[static_cast<uint64_t>(host_index)]));
EXPECT_EQ(static_cast<size_t>(20 * host_index + 1),
host_keyed_counts[static_cast<uint64_t>(host_index)]);
}
// Test the computation of 100th percentile.
buffer.GetPercentileForEachHostWithCounts(
base::TimeTicks(), 100, base::nullopt, &host_keyed_percentiles,
&host_keyed_counts);
EXPECT_EQ(num_hosts, host_keyed_percentiles.size());
EXPECT_EQ(num_hosts, host_keyed_counts.size());
// The 0 percentile must be equal to |90 * i| and the count must be equal to
// |20 * i| for host |i|.
for (int host_index = 1; host_index <= 3; host_index++) {
EXPECT_EQ(110 * host_index,
host_keyed_percentiles[static_cast<uint64_t>(host_index)]);
EXPECT_EQ(static_cast<size_t>(20 * host_index + 1),
host_keyed_counts[static_cast<uint64_t>(host_index)]);
}
}
} // namespace
} // namespace internal
} // namespace nqe
} // namespace net