litespeed-quic/test/unittests/test_minmax.c

454 lines
11 KiB
C
Raw Normal View History

/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */
/* Tests adopted from Chromium windowed_filter_test.cc */
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include "lsquic_minmax.h"
/* Convert milliseconds to lsquic_time_t, which is microseconds */
#define ms(val) ((val) * 1000)
static void
init_minmax (struct minmax *minmax)
{
minmax_init(minmax, ms(99));
}
// Sets up windowed_min_rtt_ to have the following values:
// Best = 20ms, recorded at 25ms
// Second best = 30ms, recorded at 50ms
// Third best = 50ms, recorded at 100ms
static void
init_min_filter (struct minmax *minmax)
{
uint64_t now, rtt;
unsigned i;
now = 0;
rtt = ms(10);
for (i = 0; i < 5; ++i)
{
minmax_upmin(minmax, now, rtt);
now += ms(25);
rtt += ms(10);
}
assert(ms(20) == minmax_get_idx(minmax, 0));
assert(ms(30) == minmax_get_idx(minmax, 1));
assert(ms(50) == minmax_get_idx(minmax, 2));
}
// Sets up windowed_max_bw_ to have the following values:
// Best = 900 bps, recorded at 25ms
// Second best = 800 bps, recorded at 50ms
// Third best = 600 bps, recorded at 100ms
static void
init_max_filter (struct minmax *minmax)
{
uint64_t now, bw;
unsigned i;
now = 0;
bw = 1000;
for (i = 0; i < 5; ++i)
{
minmax_upmax(minmax, now, bw);
now += ms(25);
bw -= 100;
}
assert(900 == minmax_get_idx(minmax, 0));
assert(800 == minmax_get_idx(minmax, 1));
assert(600 == minmax_get_idx(minmax, 2));
}
// Test helper function: updates the filter with a lot of small values in order
// to ensure that it is not susceptible to noise.
static void
update_with_irrelevant_samples (struct minmax *minmax, uint64_t max_value,
uint64_t time)
{
uint64_t i;
for (i = 0; i < 1000; ++i)
minmax_upmax(minmax, time, i % max_value);
}
static void
test_uninitialized_estimates (void)
{
struct minmax minmax;
init_minmax(&minmax);
assert(0 == minmax_get_idx(&minmax, 0));
assert(0 == minmax_get_idx(&minmax, 1));
assert(0 == minmax_get_idx(&minmax, 2));
}
static void
test_monotonically_increasing_min (void)
{
struct minmax minmax;
uint64_t rtt, now;
unsigned i;
rtt = ms(10);
now = 0;
init_minmax(&minmax);
minmax_upmin(&minmax, now, rtt);
assert(ms(10) == minmax_get(&minmax));
// Gradually increase the rtt samples and ensure the windowed min rtt starts
// rising.
for (i = 0; i < 6; ++i)
{
now += ms(25);
rtt += ms(10);
minmax_upmin(&minmax, now, rtt);
if (i < 3)
assert(minmax_get(&minmax) == ms(10));
else if (i == 3)
assert(minmax_get(&minmax) == ms(20));
else if (i == 4)
assert(minmax_get(&minmax) == ms(30));
else
assert(minmax_get(&minmax) == ms(50));
}
}
static void
test_monotonically_decreasing_max (void)
{
struct minmax minmax;
uint64_t bw, now;
unsigned i;
bw = 1000;
now = 0;
init_minmax(&minmax);
minmax_upmax(&minmax, now, bw);
assert(1000 == minmax_get(&minmax));
// Gradually decrease the bw samples and ensure the windowed max bw starts
// decreasing.
for (i = 0; i < 6; ++i)
{
now += ms(25);
bw -= 100;
minmax_upmax(&minmax, now, bw);
if (i < 3)
assert(minmax_get(&minmax) == 1000);
else if (i == 3)
assert(minmax_get(&minmax) == 900);
else if (i == 4)
assert(minmax_get(&minmax) == 800);
else
assert(minmax_get(&minmax) == 600);
}
}
static void
sample_changes_third_best_min (void)
{
struct minmax minmax;
uint64_t rtt, now;
init_minmax(&minmax);
init_min_filter(&minmax);
rtt = minmax_get_idx(&minmax, 2);
rtt -= ms(5);
now = ms(101);
minmax_upmin(&minmax, now, rtt);
assert(rtt == minmax_get_idx(&minmax, 2));
assert(ms(30) == minmax_get_idx(&minmax, 1));
assert(ms(20) == minmax_get_idx(&minmax, 0));
}
static void
sample_changes_third_best_max (void)
{
struct minmax minmax;
uint64_t bw, now;
init_minmax(&minmax);
init_max_filter(&minmax);
bw = minmax_get_idx(&minmax, 2);
bw += 50;
now = ms(101);
minmax_upmax(&minmax, now, bw);
assert(bw == minmax_get_idx(&minmax, 2));
assert(800 == minmax_get_idx(&minmax, 1));
assert(900 == minmax_get_idx(&minmax, 0));
}
// RTT sample lower than the second-choice min sets that and also
// the third-choice min.
static void
sample_changes_second_best_min (void)
{
struct minmax minmax;
uint64_t rtt, now;
init_minmax(&minmax);
init_min_filter(&minmax);
rtt = minmax_get_idx(&minmax, 1);
rtt -= ms(5);
now = ms(101);
minmax_upmin(&minmax, now, rtt);
assert(rtt == minmax_get_idx(&minmax, 2));
assert(rtt == minmax_get_idx(&minmax, 1));
assert(ms(20) == minmax_get_idx(&minmax, 0));
}
// BW sample higher than the second-choice max sets that and also
// the third-choice max.
static void
sample_changes_second_best_max (void)
{
struct minmax minmax;
uint64_t bw, now;
init_minmax(&minmax);
init_max_filter(&minmax);
bw = minmax_get_idx(&minmax, 1);
bw += 50;
now = ms(101);
minmax_upmax(&minmax, now, bw);
assert(bw == minmax_get_idx(&minmax, 2));
assert(bw == minmax_get_idx(&minmax, 1));
assert(900 == minmax_get_idx(&minmax, 0));
}
// RTT sample lower than the first-choice min-rtt sets that and also
// the second and third-choice mins.
static void
sample_changes_all_mins (void)
{
struct minmax minmax;
uint64_t rtt, now;
init_minmax(&minmax);
init_min_filter(&minmax);
rtt = minmax_get(&minmax);
rtt -= ms(5);
now = ms(101);
minmax_upmin(&minmax, now, rtt);
assert(rtt == minmax_get_idx(&minmax, 2));
assert(rtt == minmax_get_idx(&minmax, 1));
assert(rtt == minmax_get_idx(&minmax, 0));
}
// BW sample higher than the first-choice max sets that and also
// the second and third-choice maxs.
static void
sample_changes_all_maxs (void)
{
struct minmax minmax;
uint64_t bw, now;
init_minmax(&minmax);
init_max_filter(&minmax);
bw = minmax_get(&minmax);
bw += 50;
now = ms(101);
minmax_upmax(&minmax, now, bw);
assert(bw == minmax_get_idx(&minmax, 2));
assert(bw == minmax_get_idx(&minmax, 1));
assert(bw == minmax_get_idx(&minmax, 0));
}
// Best min sample was recorded at 25ms, so expiry time is 124ms.
static void
expire_best_min (void)
{
struct minmax minmax;
uint64_t rtt, now, old_2nd, old_3rd;
init_minmax(&minmax);
init_min_filter(&minmax);
old_3rd = minmax_get_idx(&minmax, 2);
old_2nd = minmax_get_idx(&minmax, 1);
rtt = old_3rd + ms(5);
now = ms(125);
minmax_upmin(&minmax, now, rtt);
assert(rtt == minmax_get_idx(&minmax, 2));
assert(old_3rd == minmax_get_idx(&minmax, 1));
assert(old_2nd == minmax_get(&minmax));
}
// Best max sample was recorded at 25ms, so expiry time is 124ms.
static void
expire_best_max (void)
{
struct minmax minmax;
uint64_t bw, now, old_2nd, old_3rd;
init_minmax(&minmax);
init_max_filter(&minmax);
old_3rd = minmax_get_idx(&minmax, 2);
old_2nd = minmax_get_idx(&minmax, 1);
bw = old_3rd - 50;
now = ms(125);
minmax_upmax(&minmax, now, bw);
assert(bw == minmax_get_idx(&minmax, 2));
assert(old_3rd == minmax_get_idx(&minmax, 1));
assert(old_2nd == minmax_get(&minmax));
}
// Second best min sample was recorded at 75ms, so expiry time is 174ms.
static void
expire_second_best_min (void)
{
struct minmax minmax;
uint64_t rtt, now, old_3rd;
init_minmax(&minmax);
init_min_filter(&minmax);
old_3rd = minmax_get_idx(&minmax, 2);
rtt = old_3rd + ms(5);
now = ms(175);
minmax_upmin(&minmax, now, rtt);
assert(rtt == minmax_get_idx(&minmax, 2));
assert(rtt == minmax_get_idx(&minmax, 1));
assert(old_3rd == minmax_get(&minmax));
}
// Second best max sample was recorded at 75ms, so expiry time is 174ms.
static void
expire_second_best_max (void)
{
struct minmax minmax;
uint64_t bw, now, old_3rd;
init_minmax(&minmax);
init_max_filter(&minmax);
old_3rd = minmax_get_idx(&minmax, 2);
bw = old_3rd - 50;
now = ms(175);
minmax_upmax(&minmax, now, bw);
assert(bw == minmax_get_idx(&minmax, 2));
assert(bw == minmax_get_idx(&minmax, 1));
assert(old_3rd == minmax_get(&minmax));
}
// Third best min sample was recorded at 100ms, so expiry time is 199ms.
static void
expire_all_mins (void)
{
struct minmax minmax;
uint64_t rtt, now, old_3rd;
init_minmax(&minmax);
init_min_filter(&minmax);
old_3rd = minmax_get_idx(&minmax, 2);
rtt = old_3rd + ms(5);
now = ms(200);
minmax_upmin(&minmax, now, rtt);
assert(rtt == minmax_get_idx(&minmax, 2));
assert(rtt == minmax_get_idx(&minmax, 1));
assert(rtt == minmax_get(&minmax));
}
// Third best max sample was recorded at 100ms, so expiry time is 199ms.
static void
expire_all_maxs (void)
{
struct minmax minmax;
uint64_t bw, now, old_3rd;
init_minmax(&minmax);
init_max_filter(&minmax);
old_3rd = minmax_get_idx(&minmax, 2);
bw = old_3rd - 50;
now = ms(200);
minmax_upmax(&minmax, now, bw);
assert(bw == minmax_get_idx(&minmax, 2));
assert(bw == minmax_get_idx(&minmax, 1));
assert(bw == minmax_get(&minmax));
}
// Test the windowed filter where the time used is an exact counter instead of a
// timestamp. This is useful if, for example, the time is measured in round
// trips.
static void
expire_counter_based_max (void)
{
struct minmax minmax;
// Create a window which starts at t = 0 and expires after two cycles.
minmax_init(&minmax, 2);
const uint64_t kBest = 50000;
// Insert 50000 at t = 1.
minmax_upmax(&minmax, 1, 50000);
assert(kBest == minmax_get(&minmax));
update_with_irrelevant_samples(&minmax, 20, 1);
assert(kBest == minmax_get(&minmax));
// Insert 40000 at t = 2. Nothing is expected to expire.
minmax_upmax(&minmax, 2, 40000);
assert(kBest == minmax_get(&minmax));
update_with_irrelevant_samples(&minmax, 20, 2);
assert(kBest == minmax_get(&minmax));
// Insert 30000 at t = 3. Nothing is expected to expire yet.
minmax_upmax(&minmax, 3, 30000);
assert(kBest == minmax_get(&minmax));
update_with_irrelevant_samples(&minmax, 20, 3);
assert(kBest == minmax_get(&minmax));
// Insert 20000 at t = 4. 50000 at t = 1 expires, so 40000 becomes the new
// maximum.
const uint64_t kNewBest = 40000;
minmax_upmax(&minmax, 4, 20000);
assert(kNewBest == minmax_get(&minmax));
update_with_irrelevant_samples(&minmax, 20, 4);
assert(kNewBest == minmax_get(&minmax));
}
int
main (void)
{
test_uninitialized_estimates();
test_monotonically_increasing_min();
test_monotonically_decreasing_max();
sample_changes_third_best_min();
sample_changes_third_best_max();
sample_changes_second_best_min();
sample_changes_second_best_max();
sample_changes_all_mins();
sample_changes_all_maxs();
expire_best_min();
expire_best_max();
expire_second_best_min();
expire_second_best_max();
expire_all_mins();
expire_all_maxs();
expire_counter_based_max();
return 0;
}