bbr: исправить передачу потерь в BBR + пол для cwnd на уровне BBR_CWND_MIN_TARGET*mss

- etcp.c: rs->lost из link->bbr_loss_since_ack (был хардкод 0), вызов bbr_note_loss при ретрансмите
- etcp_connections.h: поле bbr_loss_since_ack для per-link учёта потерь в байтах
- etcp_bbr.c: везде где BBR_CWND_MIN_TARGET — умножить на mss (юнит-мизматч пакеты/байты)
  - bbr_quantization_budget, bbr_set_cwnd, bbr_bound_cwnd_for_inflight_model, bbr_probe_rtt_cwnd
  - bbr_init_lower_bounds: инициализация inflight_hi из cwnd (был ~0U навсегда)
- control_server.c + etcpmon: экспорт BBR-метрик (bw_hi/lo, inflight_hi/lo, режим, потери)
- tests/bbr_integration: интеграционный тест BBR (15с, 50ms/5%loss/1Mbit) с таблицей метрик
- устранён death spiral BBR при высоких потерях: cwnd теперь не падает ниже 4*mss
This commit is contained in:
Evgeny committed 2026-06-01 17:50:16 +03:00
1 parent 6c79939db3
commit 4f0baa37db
13 files changed
+615 -78

No files matched your search

+13 -43
View File
@@ -864,53 +864,23 @@ void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon
struct metrics_history* h = &client->history;
int idx = h->head;
// for (int m = 0; m < GRAPH_METRICS_COUNT; m++) {
// float v = h->values[m][idx];
// if (v < h->min_val[m]) h->min_val[m] = v;
// if (v > h->max_val[m]) h->max_val[m] = v;
// }
if (client->log_file) {
fprintf(client->log_file, "add_to_history: rtt_last=%u rtt_avg10=%u rtt_avg100=%u jitter=%u\n",
metrics->etcp.rtt_last, metrics->etcp.rtt_avg_10, metrics->etcp.rtt_avg_100, metrics->etcp.jitter);
fflush(client->log_file);
/* BBR per-link metrics (use first link as representative) */
if (client->last_links_count > 0 && client->last_links) {
struct etcpmon_link_metrics* l = &client->last_links[0];
h->values[GRAPH_METRIC_INFLIGHT_BYTES][idx] = l->inflight_bytes / 1024.f;
h->values[GRAPH_METRIC_INFLIGHT_LIM][idx] = l->inflight_lim_bytes / 1024.f;
h->values[GRAPH_METRIC_MIN_RTT][idx] = l->bbr_min_rtt_us / 1000.f;
h->values[GRAPH_METRIC_BW_HI][idx] = l->bbr_bw_hi / 125.f;
h->values[GRAPH_METRIC_PACING_RATE][idx] = l->bbr_pacing_rate / 125.f;
h->values[GRAPH_METRIC_BW_LO][idx] = l->bbr_bw_lo / 125.f;
h->values[GRAPH_METRIC_INFLIGHT_HI][idx] = l->bbr_inflight_hi / 1024.f;
h->values[GRAPH_METRIC_BBR_MODE][idx] = (float)l->bbr_mode;
}
float scale = 0.1f; // to ms (protocol: 0.1ms units)
h->values[GRAPH_METRIC_RTT_LAST][idx] = (float)metrics->etcp.rtt_last * scale;
h->values[GRAPH_METRIC_RTT_AVG10][idx] = (float)metrics->etcp.rtt_avg_10 * scale;
h->values[GRAPH_METRIC_RTT_AVG100][idx] = (float)metrics->etcp.rtt_avg_100 * scale;
h->values[GRAPH_METRIC_JITTER][idx] = (float)metrics->etcp.jitter * scale;
uint32_t retrans_delta = 0;
uint32_t acks_delta = 0;
uint64_t bytes_delta = 0;
if (h->count > 0) {
if (metrics->etcp.retrans_count >= h->last_retrans) {
retrans_delta = metrics->etcp.retrans_count - h->last_retrans;
}
if (metrics->etcp.ack_count >= h->last_acks) {
acks_delta = metrics->etcp.ack_count - h->last_acks;
}
if (metrics->etcp.bytes_sent_total >= h->last_bytes_sent) {
bytes_delta = metrics->etcp.bytes_sent_total - h->last_bytes_sent;
}
}
h->last_retrans = metrics->etcp.retrans_count;
h->last_acks = metrics->etcp.ack_count;
h->last_bytes_sent = metrics->etcp.bytes_sent_total;
h->values[GRAPH_METRIC_RETRANS][idx] = (float)retrans_delta;
h->values[GRAPH_METRIC_ACKS][idx] = (float)acks_delta;
h->values[GRAPH_METRIC_INFLIGHT][idx] = (float)metrics->etcp.unacked_bytes;
h->values[GRAPH_METRIC_BYTES_SENT][idx] = (float)bytes_delta;
h->head = (h->head + 1) % GRAPH_HISTORY_SIZE;
if (h->count < GRAPH_HISTORY_SIZE) {
h->count++;
}
if (h->count < GRAPH_HISTORY_SIZE) h->count++;
}
if (client->log_file) {
fprintf(client->log_file, "history updated: count=%d head=%d\n", h->count, h->head);
+8 -8
View File
@@ -21,14 +21,14 @@ extern "C" {
/* Graph metrics types */
typedef enum {
GRAPH_METRIC_RTT_LAST = 0,
GRAPH_METRIC_RTT_AVG10,
GRAPH_METRIC_RTT_AVG100,
GRAPH_METRIC_JITTER,
GRAPH_METRIC_RETRANS,
GRAPH_METRIC_ACKS,
GRAPH_METRIC_INFLIGHT,
GRAPH_METRIC_BYTES_SENT,
GRAPH_METRIC_INFLIGHT_BYTES = 0,
GRAPH_METRIC_INFLIGHT_LIM,
GRAPH_METRIC_MIN_RTT,
GRAPH_METRIC_BW_HI,
GRAPH_METRIC_PACING_RATE,
GRAPH_METRIC_BW_LO,
GRAPH_METRIC_INFLIGHT_HI,
GRAPH_METRIC_BBR_MODE,
GRAPH_METRIC_COUNT
} graph_metric_type_t;
+18 -14
View File
@@ -15,12 +15,12 @@
#include <stdio.h>
#include "etcpmon_gui.h"
const COLORREF graph_colors[GRAPH_METRICS_COUNT] = {
RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), RGB(255,255,0),
RGB(255,0,255), RGB(0,255,255), RGB(255,128,0), RGB(128,0,255)
RGB(255,0,0), RGB(0,200,0), RGB(0,128,255), RGB(255,128,0),
RGB(160,0,200), RGB(0,180,180), RGB(255,0,128), RGB(200,200,0)
};
const char* graph_metric_names[GRAPH_METRICS_COUNT] = {
"RTT Last","RTT Avg10","RTT Avg100","Jitter",
"Retrns","ACKs","Inflght","Bytes/s"
"InflB","Cwnd","minRTT","bwHi",
"Pace","bwLo","infHi","Mode"
};
static void RecalculateMinMax(struct metrics_history* hist, int vis, int oldest)
{
@@ -165,17 +165,21 @@ void UpdateChannelValues(struct etcpmon_app* app, int history_idx)
if (!app->hChannelValue[i]) continue;
float val = h->values[i][history_idx];
char buf[32] = {0};
if (i == GRAPH_METRIC_BYTES_SENT) {
if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/(1024.*1024.));
else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB/s", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B/s", val);
} else if (i == GRAPH_METRIC_INFLIGHT) {
if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B", val);
} else if (i == GRAPH_METRIC_RETRANS || i == GRAPH_METRIC_ACKS) {
snprintf(buf, sizeof(buf), "%.0f /s", val);
} else {
if (i == GRAPH_METRIC_BW_HI || i == GRAPH_METRIC_BW_LO || i == GRAPH_METRIC_PACING_RATE) {
if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/1024.);
else snprintf(buf, sizeof(buf), "%.1f KB/s", val);
} else if (i == GRAPH_METRIC_INFLIGHT_BYTES || i == GRAPH_METRIC_INFLIGHT_LIM || i == GRAPH_METRIC_INFLIGHT_HI) {
if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB", val/(1024.*1024.));
else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B", val);
} else if (i == GRAPH_METRIC_MIN_RTT) {
snprintf(buf, sizeof(buf), "%.1f ms", val);
} else if (i == GRAPH_METRIC_BBR_MODE) {
static const char* mnames[] = {"STARTUP","DRAIN","PROBE_BW","PROBE_RTT"};
int m = (int)val; if (m < 0) m = 0; if (m > 3) m = 3;
snprintf(buf, sizeof(buf), "%s", mnames[m]);
} else {
snprintf(buf, sizeof(buf), "%.1f", val);
}
if (strcmp(buf, last_text[i]) == 0)
continue;
+21 -2
View File
@@ -239,8 +239,8 @@ static void CreateControls(struct etcpmon_app* app) {
y += GRAPH_HEIGHT + 8;
const char* channel_short_names[] = {
"RTT-L", "RTT10", "RTT100", "Jitter",
"Retrns", "ACKs", "Inflght", "Bytes/s"
"InflB", "Cwnd", "minRTT", "bwHi",
"Pace", "bwLo", "infHi", "Mode"
};
int block_w = 200;
@@ -1436,8 +1436,27 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
links[i].inflight_lim_bytes,
links[i].inflight_packets);
char line3[320];
const char* mode_names[] = {"STARTUP","DRAIN","PROBE_BW","PROBE_RTT"};
const char* cycle_names[] = {"UP","DOWN","CRUISE","REFILL"};
snprintf(line3, sizeof(line3),
" BBR: mode=%s cycle=%s full_bw=%s loss=%s pace=%.1f KB/s rtt=%u us "
"bwHi=%.1f KB/s bwLo=%.1f KB/s infHi=%u lo=%u gain=%.2f",
mode_names[links[i].bbr_mode > 3 ? 0 : links[i].bbr_mode],
cycle_names[links[i].bbr_cycle_idx > 3 ? 0 : links[i].bbr_cycle_idx],
links[i].bbr_full_bw_reached ? "yes" : "no",
links[i].bbr_loss_in_round ? "yes" : "no",
(float)links[i].bbr_pacing_rate / 125.0f,
links[i].bbr_min_rtt_us,
(float)links[i].bbr_bw_hi / 125.0f,
(float)links[i].bbr_bw_lo / 125.0f,
links[i].bbr_inflight_hi,
links[i].bbr_inflight_lo,
(float)links[i].bbr_pacing_gain / 256.0f);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line1);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line2);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line3);
}
InvalidateRect(app->hListLinks, NULL, FALSE);
}
+14
View File
@@ -237,6 +237,20 @@ struct etcpmon_link_metrics {
uint32_t inflight_bytes;
uint32_t inflight_packets;
uint32_t inflight_lim_bytes;
/* BBR congestion control metrics */
uint8_t bbr_mode; /* 0=STARTUP, 1=DRAIN, 2=PROBE_BW, 3=PROBE_RTT */
uint8_t bbr_cycle_idx; /* 0=UP, 1=DOWN, 2=CRUISE, 3=REFILL */
uint8_t bbr_full_bw_reached; /* startup completed? 0/1 */
uint8_t bbr_loss_in_round; /* loss in current round? 0/1 */
uint32_t bbr_pacing_rate; /* bytes/sec */
uint32_t bbr_min_rtt_us; /* min RTT (microseconds) */
uint32_t bbr_bw_hi; /* max bandwidth estimate (bytes/sec, converted from BW_UNIT scale) */
uint32_t bbr_bw_lo; /* conservative bandwidth estimate (bytes/sec) */
uint32_t bbr_inflight_hi; /* upper inflight bound (bytes) */
uint32_t bbr_inflight_lo; /* lower inflight bound (bytes) */
uint16_t bbr_pacing_gain; /* pacing gain (BBR_UNIT fixed-point: 710=2.77x, 192=0.75x) */
uint16_t pad_bbr; /* padding for 4-byte alignment */
};
/* TUN interface metrics */