/* Copyright (c) 2017 - 2021 LiteSpeed Technologies Inc. See LICENSE. */ /* * Based on Google code released under BSD license here: * https://groups.google.com/forum/#!topic/bbr-dev/3RTgkzi5ZD8 */ /* * Copyright 2017, Google Inc. * * Use of this source code is governed by the following BSD-style license: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Kathleen Nichols' algorithm for tracking the minimum (or maximum) * value of a data stream over some fixed time interval. (E.g., * the minimum RTT over the past five minutes.) It uses constant * space and constant time per update yet almost always delivers * the same minimum as an implementation that has to keep all the * data in the window. * * The algorithm keeps track of the best, 2nd best & 3rd best min * values, maintaining an invariant that the measurement time of * the n'th best >= n-1'th best. It also makes sure that the three * values are widely separated in the time window since that bounds * the worse case error when that data is monotonically increasing * over the window. * * Upon getting a new min, we can forget everything earlier because * it has no value - the new min is <= everything else in the window * by definition and it'samples the most recent. So we restart fresh on * every new min and overwrites 2nd & 3rd choices. The same property * holds for 2nd & 3rd best. */ #include #include #include "lsquic_minmax.h" /* As time advances, update the 1st, 2nd, and 3rd choices. */ static void minmax_subwin_update (struct minmax *minmax, const struct minmax_sample *sample) { uint64_t dt = sample->time - minmax->samples[0].time; if (dt > minmax->window) { /* * Passed entire window without a new sample so make 2nd * choice the new sample & 3rd choice the new 2nd choice. * we may have to iterate this since our 2nd choice * may also be outside the window (we checked on entry * that the third choice was in the window). */ minmax->samples[0] = minmax->samples[1]; minmax->samples[1] = minmax->samples[2]; minmax->samples[2] = *sample; if (sample->time - minmax->samples[0].time > minmax->window) { minmax->samples[0] = minmax->samples[1]; minmax->samples[1] = minmax->samples[2]; minmax->samples[2] = *sample; } } else if (minmax->samples[1].time == minmax->samples[0].time && dt > minmax->window / 4) { /* * We've passed a quarter of the window without a new sample * so take a 2nd choice from the 2nd quarter of the window. */ minmax->samples[2] = minmax->samples[1] = *sample; } else if (minmax->samples[2].time == minmax->samples[1].time && dt > minmax->window / 2) { /* * We've passed half the window without finding a new sample * so take a 3rd choice from the last half of the window */ minmax->samples[2] = *sample; } } /* Check if new measurement updates the 1st, 2nd or 3rd choice max. */ void lsquic_minmax_update_max (struct minmax *minmax, uint64_t now, uint64_t meas) { struct minmax_sample sample = { .time = now, .value = meas }; if (minmax->samples[0].value == 0 /* uninitialized */ || sample.value >= minmax->samples[0].value /* found new max? */ || sample.time - minmax->samples[2].time > minmax->window) /* nothing left in window? */ { minmax_reset(minmax, sample); /* forget earlier samples */ return; } if (sample.value >= minmax->samples[1].value) minmax->samples[2] = minmax->samples[1] = sample; else if (sample.value >= minmax->samples[2].value) minmax->samples[2] = sample; minmax_subwin_update(minmax, &sample); } /* Check if new measurement updates the 1st, 2nd or 3rd choice min. */ void lsquic_minmax_update_min (struct minmax *minmax, uint64_t now, uint64_t meas) { struct minmax_sample sample = { .time = now, .value = meas }; if (minmax->samples[0].value == 0 /* uninitialized */ || sample.value <= minmax->samples[0].value /* found new min? */ || sample.time - minmax->samples[2].time > minmax->window) /* nothing left in window? */ { minmax_reset(minmax, sample); /* forget earlier samples */ return; } if (sample.value <= minmax->samples[1].value) minmax->samples[2] = minmax->samples[1] = sample; else if (sample.value <= minmax->samples[2].value) minmax->samples[2] = sample; minmax_subwin_update(minmax, &sample); }