Define and test the BBR steady-state queue time budget

This commit is contained in:
evgeny committed 2026-10-23 01:00:36 +02:00
1 parent 5f406c2a41
commit 4035794631
2 files changed
+49 -1

No files matched your search

+3
View File
@@ -23,6 +23,7 @@ extern "C" {
#define BBR_STARTUP_CWND_GAIN (BBR_UNIT * 2)
#define BBR_DRAIN_GAIN (BBR_UNIT * 1000 / 2885)
#define BBR_CWND_GAIN (BBR_UNIT * 2)
#define BBR_QUEUE_BUDGET_US 20000 // целевая добавочная очередь после STARTUP; минимум 4*MSS сохраняется
#define BBR_PROBE_RTT_CWND_GAIN (BBR_UNIT * 1 / 2)
#define BBR_CWND_MIN_TARGET 4
#define BBR_FULL_BW_THRESH (BBR_UNIT * 5 / 4)
@@ -68,6 +69,8 @@ struct bbr {
uint32_t inflight_hi;
uint32_t bw_latest;
uint32_t inflight_latest;
uint32_t queue_budget_us; // целевое время очереди в PROBE_BW, мкс; init задаёт 20мс, 0 отключает ограничение
uint32_t queue_cwnd_cap; // последний предел DATA: max(4*MSS, BW*(min_rtt+budget)); 0 — ограничение не активно
uint8_t mode;
uint8_t cycle_idx;
uint16_t pacing_gain;
+46 -1
View File
@@ -50,7 +50,7 @@ static int test_byte_rate(void) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BBR byte sample: MSS=%u delivered=10000 interval_us=10000 pace=%u cwnd=%u",
sizes[i], pacing, cwnd);
if (pacing != 990000) FAIL("1MB/s delivery must produce 990000B/s cruise pacing");
if (cwnd < 40000 || cwnd > 41000) FAIL("2*BDP plus ACK aggregation must be 40000..41000 bytes");
if (cwnd != 40000) FAIL("20ms queue budget at 20ms RTT must cap the window at 40000 bytes");
}
PASS(); return 0;
}
@@ -97,6 +97,7 @@ static int test_probe_growth_bytes(void) {
static int test_probe_packet_budget(void) {
MSEG("PROBE_UP reserves two MSS; Reno rounds use packets");
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.queue_budget_us = 0; // здесь проверяем отдельный резерв PROBE_UP без ограничения по времени очереди
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP;
uint32_t cwnd = 100000, pacing = 0;
struct bbr_rate_sample rs = { .delivered = 10000, .interval_us = 10000, .rtt_us = 20000,
@@ -116,6 +117,7 @@ static int test_probe_packet_budget(void) {
static int test_large_ack_aggregation(void) {
MSEG("ACK aggregation above 64KiB keeps byte precision");
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.queue_budget_us = 0; // размер ACK-резерва проверяется отдельно от политики времени очереди
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.cycle_mstamp_tb = s.now_tb; s.probe_wait_us = 2000000;
uint32_t cwnd = 300000, pacing = 0;
@@ -205,6 +207,7 @@ static int test_cwnd_bounded(void)
const uint32_t caps[] = {4000, 8000}, expected[] = {5600, 8000};
for (unsigned i = 0; i < 2; i++) {
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.queue_budget_us = 0;
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP;
s.inflight_hi = s.inflight_lo = caps[i]; g_cwnd = 14000; g_pacing = 0;
s.now_tb += 5000; run_ack(&s, 1400, 5000, 0, 0, 14000);
@@ -261,6 +264,7 @@ static int test_probe_rtt(void)
static int test_probe_rtt_lifecycle(void) {
MSEG("PROBE_RTT half-BDP drain, hold and window restore");
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.queue_budget_us = 0; // восстановление исходного окна проверяем без последующего ограничения бюджета
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_CRUISE;
s.bw_hi[0] = BW_UNIT; s.min_rtt_us = s.probe_rtt_min_us = 200000; s.has_seen_rtt = 1;
s.now_tb += BBR_PROBE_RTT_WIN_MS * 10 + 1;
@@ -284,6 +288,45 @@ static int test_probe_rtt_lifecycle(void) {
PASS(); return 0;
}
/* Бюджет включает все резервы окна и не зависит от удлинения текущего RTT очередью. */
static int test_queue_budget(void) {
MSEG("queue time budget includes ACK and probing reserves");
const uint32_t rtts[] = {20000, 200000, 20000, 20000};
const uint32_t budgets[] = {20000, 20000, 5000, 0};
const uint32_t expected[] = {40000, 220000, 25000, 43800};
for (unsigned i = 0; i < 4; i++) {
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.queue_budget_us = budgets[i]; s.mode = BBR_PROBE_BW; s.full_bw_reached = 1;
s.cycle_idx = BBR_BW_PROBE_UP; s.min_rtt_us = s.probe_rtt_min_us = rtts[i]; s.has_seen_rtt = 1;
s.bw_hi[0] = BW_UNIT;
uint32_t cwnd = 1000000, pacing = 0;
struct bbr_rate_sample rs = { .delivered = 10000, .interval_us = 10000, .rtt_us = rtts[i] * 2,
.acked_sacked = 1000, .tx_in_flight = 1000000 };
bbr_main(&s, &rs, &cwnd, &pacing, 1400, 100000, 0);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Queue budget: min_rtt=%u budget=%u cwnd=%u expected=%u",
rtts[i], budgets[i], cwnd, expected[i]);
if (cwnd != expected[i]) FAIL("queue budget must bound the entire window using min_rtt");
}
PASS(); return 0;
}
static int test_queue_budget_floor_and_startup(void) {
MSEG("queue budget preserves minimum window and STARTUP");
struct bbr s = { .now_tb = 10000 }; bbr_init(&s);
s.mode = BBR_PROBE_BW; s.full_bw_reached = 1; s.cycle_idx = BBR_BW_PROBE_UP;
uint32_t cwnd = 1000000, pacing = 0;
struct bbr_rate_sample rs = { .delivered = 100, .interval_us = 100000, .rtt_us = 20000,
.acked_sacked = 100, .tx_in_flight = 1000000 };
bbr_main(&s, &rs, &cwnd, &pacing, 1400, 100000, 0);
if (cwnd != 5600) FAIL("minimum four-MSS window must survive a tiny estimated BDP");
memset(&s, 0, sizeof(s)); s.now_tb = 10000; bbr_init(&s);
cwnd = 5600; pacing = 0; rs.delivered = rs.acked_sacked = 5600;
rs.interval_us = rs.rtt_us = 200000; rs.tx_in_flight = 5600;
bbr_main(&s, &rs, &cwnd, &pacing, 1400, 5600, 1);
if (s.mode != BBR_STARTUP || cwnd != 11200) FAIL("queue budget must not prevent STARTUP window growth");
PASS(); return 0;
}
int main(void)
{
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
@@ -302,6 +345,8 @@ int main(void)
test_fast_path();
test_probe_rtt();
test_probe_rtt_lifecycle();
test_queue_budget();
test_queue_budget_floor_and_startup();
printf("\n=== Results: %d/%d passed, %d failed ===\n", test_passed, test_total, test_failed);
return test_failed ? 1 : 0;
}