Add isolated slow-duplex measurements and record BBR diagnosis
This commit is contained in:
1 parent
a198cfb9e3
commit
f8a2ecaabd
2 files changed
+83
-12
No files matched your search
@@ -3,6 +3,62 @@ ETCP: независимая обратная связь каналов и до
|
||||
|
||||
Статус
|
||||
|
||||
24.10.2026, продолжение после push: отдельно исследован одиночный дуплекс2Мбит/с.
|
||||
Все прежние15 локальных коммитов вошли в origin/master после rebase; патчи сохранены.
|
||||
Из-за параллельных изменений основного каталога продолжение изолировано в ветке
|
||||
etcp/solo-duplex-20261024, база a198cfb9. Протокол ETCP/BBR на этом этапе не изменён.
|
||||
|
||||
Добавлен нативный сценарий duplex-slow: один20мс/2Мбит/с путь, два насыщенных отправителя.
|
||||
Он использует прежние проверки полезных/свежих байтов, очереди, retries и полного drain.
|
||||
UTUN_MULTIPATH_SAMPLE_MS=1000..60000 задаёт только длительность steady скоростных замеров;
|
||||
по умолчанию прежние4с, warmup3с и отказные интервалы сохранены. Пороги не снижены.
|
||||
Таблица скоростных профилей теперь содержит count/duplex вместе с настройками;
|
||||
выбор режима/числа путей больше не зависит от порядкового номера строки.
|
||||
Для единственного пути замер не повторяется как фиктивный агрегат.
|
||||
Длинный замер дополняет короткие повторы: разовый PASS одного из них задачу не закрывает.
|
||||
|
||||
Точные ACK/TX-события буферизованы отдельным диагностическим observer в /tmp,
|
||||
без синхронного I/O внутри замера и без изменения BBR. Контрольный20-секундный steady:
|
||||
полезная скорость1.726/1.658Мбит/с, retries=0; пик очереди23.7/23.0мс.
|
||||
Сторона0 провела около4.49с в PROBE_DOWN, сторона1 —6.43с из20с.
|
||||
В этой фазе pacing около1.39Мбит/с при доступной полосе2Мбит/с.
|
||||
|
||||
Разложены7763 действующих DATA ACK без повторов: прямой путь DATA,
|
||||
ожидание ACK у получателя, обратный путь ACK. Пример side=1, seq1084, t=5.830182с:
|
||||
DATA15.909мс + ожидание ACK0.029мс + обратный путь32.219мс = raw RTT48.157мс.
|
||||
В BBR переданы48.128мс после вычитания ожидания, minRTT25.451мс.
|
||||
PROBE_DOWN длится142.4мс; DATA-flight после ACK7644байта, цель осушения5915байт.
|
||||
При почти свободном прямом пути выход из DOWN продолжает ждать уменьшения
|
||||
неподтверждённого flight, который также зависит от встречной очереди ACK.
|
||||
По7763 точкам проверена сумма трёх частей и соответствие RTT после вычитания hold.
|
||||
Это подтверждает вклад обратной очереди; не доказывает, что он единственный.
|
||||
|
||||
Проверенные прототипы остаются только в /tmp, в рабочий BBR не переносились:
|
||||
- округление цели DOWN до MSS, округление Reno-раундов, их комбинация:
|
||||
есть FAIL повторных коротких прогонов; один успешный прогон не воспроизводится устойчиво;
|
||||
- добавление2мс ACK-резерва: короткий прогон1.754/1.674 FAIL, длинный1.732/1.757 PASS;
|
||||
- уменьшение очереди до min(20мс,minRTT/4): длинный1.257/1.036 FAIL;
|
||||
задержка ниже, но утилизация ухудшилась, также пострадал дуплексный агрегат;
|
||||
- временный предел окна DOWN существующей целью осушения: DOWN сократился до6..22мс,
|
||||
длинный1.754/1.764 PASS, короткий1.752/1.656 FAIL;
|
||||
- округление верхнего предела очереди до MSS, в том числе вместе с пределом DOWN:
|
||||
короткие1.734/1.681 и1.721/1.664 FAIL; длинный1.752/1.752 PASS.
|
||||
Следовательно, ни фиксированный ACK-резерв, ни округление, ни одно сокращение DOWN
|
||||
не подтверждены как самостоятельное полное исправление. Повышать minRTT/порог задержки,
|
||||
отключать PROBE_RTT или понижать порог скорости для получения PASS оснований нет.
|
||||
Далее нужны сопоставленные причины остановки DATA в CRUISE/REFILL после короткого DOWN:
|
||||
окно физического flight, общий admission budget и pacer; затем один причинный фикс
|
||||
с отдельными модельными регрессиями, короткими повторами и длинным сетевым замером.
|
||||
|
||||
Повтор текущего кода из корня:
|
||||
UTUN_MULTIPATH_DIAG=/tmp/solo.csv ./tests/test_etcp_multipath duplex-slow
|
||||
UTUN_MULTIPATH_SAMPLE_MS=20000 UTUN_MULTIPATH_DIAG=/tmp/solo-long.csv ./tests/test_etcp_multipath duplex-slow
|
||||
python3 tools/etcp_diag_report.py /tmp/solo-long.csv /tmp/solo-long.html
|
||||
Для длинного замера использовать обычные100мс срезы:10мс/60с может исчерпать буфер.
|
||||
Артефакты контрольного RTT-разложения: /tmp/etcp-solo-feedback-{model,tx,diag}.csv
|
||||
и /tmp/etcp-solo-feedback.log; observer/build: /tmp/etcp-solo-bbr-probe.c/.mk.
|
||||
Прототипы: /tmp/etcp-solo-bbr-control.c, /tmp/etcp-solo-*-model.csv и соответствующие .log.
|
||||
|
||||
24.10.2026: исправлены возраст RTT-фильтра, сроки PROBE_RTT и замораживание DATA-измерений при росте RTT.
|
||||
min_rtt_us теперь копируется вместе с probe_rtt_min_stamp_tb. Повторное использование
|
||||
сохранённого короткого минимума не ставит текущее время на старое измерение.
|
||||
|
||||
+27
-12
@@ -64,6 +64,7 @@ static struct profile profiles[PATHS];
|
||||
static struct trace traces[2][TRACE_COUNT];
|
||||
static struct path_stats traffic[2][PATHS];
|
||||
static unsigned path_count, failures, enabled_mask, bidirectional, low_load, measuring;
|
||||
static unsigned sample_ms = SAMPLE_MS; // UTUN_MULTIPATH_SAMPLE_MS=1000..60000; только скоростные замеры
|
||||
static uint64_t started, next_message[2], measure_start, fault_start;
|
||||
static uint32_t fault_seq;
|
||||
static unsigned fault_trace_count;
|
||||
@@ -666,7 +667,7 @@ static uint64_t measure_rate(const struct profile* settings, unsigned count, uns
|
||||
setup(settings, count, mask); low_load = 0;
|
||||
run_for(WARMUP_MS, 1, "warmup"); begin_measure();
|
||||
uint64_t before[2] = {ep[0].bytes, ep[1].bytes};
|
||||
run_for(SAMPLE_MS, 1, "steady");
|
||||
run_for(sample_ms, 1, "steady");
|
||||
uint64_t elapsed = get_time_us() - measure_start, rate = 0;
|
||||
for (unsigned side = 0; side <= bidirectional; side++) {
|
||||
uint64_t useful = (ep[1-side].bytes-before[1-side]) * 8000 / elapsed, capacity = 0, unique_total = 0;
|
||||
@@ -862,6 +863,15 @@ int main(int argc, char** argv) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); test_clock_init(10);
|
||||
log_bbr_samples = getenv("UTUN_BBR_SAMPLES") != NULL;
|
||||
if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG);
|
||||
const char* duration = getenv("UTUN_MULTIPATH_SAMPLE_MS");
|
||||
if (duration) {
|
||||
char* end = NULL; errno = 0;
|
||||
unsigned long ms = strtoul(duration, &end, 10);
|
||||
if (errno || end == duration || *end || ms < 1000 || ms > 60000) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "Invalid UTUN_MULTIPATH_SAMPLE_MS='%s': expected 1000..60000", duration); return 1;
|
||||
}
|
||||
sample_ms = ms;
|
||||
}
|
||||
CHECK(socket_platform_init() == 0);
|
||||
const char* output = getenv("UTUN_MULTIPATH_CSV");
|
||||
if (output) { csv = fopen(output, "w"); CHECK(csv); fprintf(csv,
|
||||
@@ -902,22 +912,27 @@ int main(int argc, char** argv) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Multipath diagnostics: period_us=%u file=%s; snapshots buffered until drain",
|
||||
diagnostic_period_us, detail);
|
||||
}
|
||||
struct profile matrix[][3] = {
|
||||
{{20,8000},{20,8000}}, {{20,2000},{20,8000}}, {{20,8000},{200,8000}},
|
||||
{{20,2000},{200,8000}}, {{20,8000},{200,2000}}, {{20,2000},{80,8000},{200,2000}}, {{20,2000},{80,8000}}
|
||||
const struct { const char* name; unsigned count, duplex; struct profile paths[PATHS]; } matrix[] = {
|
||||
{"equal",2,0,{{20,8000},{20,8000}}}, {"bandwidth",2,0,{{20,2000},{20,8000}}},
|
||||
{"rtt",2,0,{{20,8000},{200,8000}}}, {"fast-distant",2,0,{{20,2000},{200,8000}}},
|
||||
{"slow-distant",2,0,{{20,8000},{200,2000}}}, {"three",3,0,{{20,2000},{80,8000},{200,2000}}},
|
||||
{"bidirectional",2,1,{{20,2000},{80,8000}}}, {"duplex-slow",1,1,{{20,2000}}}
|
||||
};
|
||||
const char* names[] = {"equal", "bandwidth", "rtt", "fast-distant", "slow-distant", "three", "bidirectional"};
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Multipath rate measurement: warmup_ms=%u sample_ms=%u; goodput threshold=85%%",
|
||||
WARMUP_MS, sample_ms);
|
||||
size_t baseline = u_get_allocated_count();
|
||||
unsigned selected = 0;
|
||||
for (unsigned n = 0; n < sizeof(names)/sizeof(names[0]); n++) {
|
||||
if (argc > 1 && strcmp(argv[1], names[n])) continue;
|
||||
for (unsigned n = 0; n < sizeof(matrix)/sizeof(matrix[0]); n++) {
|
||||
if (argc > 1 && strcmp(argv[1], matrix[n].name)) continue;
|
||||
selected++;
|
||||
bidirectional = n == 6;
|
||||
case_name = names[n]; unsigned count = n == 5 ? 3 : 2;
|
||||
bidirectional = matrix[n].duplex;
|
||||
case_name = matrix[n].name; unsigned count = matrix[n].count;
|
||||
uint64_t solo_sum = 0;
|
||||
for (unsigned p = 0; p < count; p++) solo_sum += measure_rate(matrix[n], count, 1u << p);
|
||||
uint64_t aggregate = measure_rate(matrix[n], count, (1u << count)-1);
|
||||
expect(aggregate >= solo_sum * 90 / 100, "aggregation >=90% sum of solo rates", aggregate, solo_sum * 90 / 100);
|
||||
for (unsigned p = 0; p < count; p++) solo_sum += measure_rate(matrix[n].paths, count, 1u << p);
|
||||
if (count > 1) {
|
||||
uint64_t aggregate = measure_rate(matrix[n].paths, count, (1u << count)-1);
|
||||
expect(aggregate >= solo_sum * 90 / 100, "aggregation >=90% sum of solo rates", aggregate, solo_sum * 90 / 100);
|
||||
}
|
||||
CHECK(u_get_allocated_count() == baseline);
|
||||
}
|
||||
struct { const char* name; unsigned count, broken, directions, sparse, ms; enum dummynet_mode mode; } faults[] = {
|
||||
|
||||
Reference in new issue
Block a user