Browse Source

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
etcp-inflight-fix
Evgeny 4 months ago
parent
commit
4f0baa37db
  1. 19
      src/control_server.c
  2. 5
      src/etcp.c
  3. 20
      src/etcp_bbr.c
  4. 1
      src/etcp_connections.c
  5. 1
      src/etcp_connections.h
  6. 5
      tests/Makefile.am
  7. 9
      tests/bbr_integration/run.sh
  8. 492
      tests/bbr_integration/test_bbr_integration.c
  9. 56
      tools/etcpmon/etcpmon_client.c
  10. 16
      tools/etcpmon/etcpmon_client.h
  11. 30
      tools/etcpmon/etcpmon_graph.c
  12. 23
      tools/etcpmon/etcpmon_gui.c
  13. 14
      tools/etcpmon/etcpmon_protocol.h

19
src/control_server.c

@ -1078,6 +1078,25 @@ static void send_metrics(struct control_server* server, struct control_client* c
link_info[i].inflight_bytes = link->inflight_bytes; link_info[i].inflight_bytes = link->inflight_bytes;
link_info[i].inflight_packets = link->inflight_packets; link_info[i].inflight_packets = link->inflight_packets;
link_info[i].inflight_lim_bytes = link->inflight_lim_bytes; link_info[i].inflight_lim_bytes = link->inflight_lim_bytes;
/* BBR fields */
if (link->bbr) {
link_info[i].bbr_mode = link->bbr->mode;
link_info[i].bbr_cycle_idx = link->bbr->cycle_idx;
link_info[i].bbr_full_bw_reached = link->bbr->full_bw_reached;
link_info[i].bbr_loss_in_round = link->bbr->loss_in_round;
link_info[i].bbr_pacing_rate = link->bbr_pacing_rate;
link_info[i].bbr_min_rtt_us = link->bbr->min_rtt_us;
link_info[i].bbr_pacing_gain = link->bbr->pacing_gain;
link_info[i].bbr_inflight_hi = link->bbr->inflight_hi;
link_info[i].bbr_inflight_lo = link->bbr->inflight_lo;
/* bw_hi/lo: convert from BW_UNIT scale (pkts/usec<<24) to bytes/sec */
uint32_t max_bw = link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1];
link_info[i].bbr_bw_hi = max_bw ? (uint32_t)((uint64_t)max_bw * link->mtu * 1000000ULL / 16777216ULL) : 0;
link_info[i].bbr_bw_lo = link->bbr->bw_lo != ~0U ? (uint32_t)((uint64_t)link->bbr->bw_lo * link->mtu * 1000000ULL / 16777216ULL) : 0;
} else {
memset(&link_info[i].bbr_mode, 0, 11 * 4 + 2 + 2); // zero out all BBR fields
}
link = link->next; link = link->next;
} }

5
src/etcp.c

@ -920,6 +920,8 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
// Always subtract from previous last_link (if any) – this handles retransmission // Always subtract from previous last_link (if any) – this handles retransmission
if (inf_pkt->last_link) { if (inf_pkt->last_link) {
inf_pkt->last_link->total_retransmissions++; inf_pkt->last_link->total_retransmissions++;
inf_pkt->last_link->bbr_loss_since_ack += inf_pkt->ll.len;
bbr_note_loss(inf_pkt->last_link->bbr);
inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len; inf_pkt->last_link->inflight_bytes -= inf_pkt->ll.len;
inf_pkt->last_link->inflight_packets--; inf_pkt->last_link->inflight_packets--;
if (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link); if (link->send_blocked_inflight && link->inflight_bytes < link->inflight_lim_bytes) loadbalancer_link_ready(link);
@ -1227,7 +1229,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
.acked_sacked = acked_pkt->ll.len, .acked_sacked = acked_pkt->ll.len,
.prior_delivered = (uint32_t)acked_pkt->delivered_at_send, .prior_delivered = (uint32_t)acked_pkt->delivered_at_send,
.tx_in_flight = acked_pkt->inflight_at_send, .tx_in_flight = acked_pkt->inflight_at_send,
.lost = 0, .lost = (int)link->bbr_loss_since_ack,
.is_app_limited = acked_pkt->is_app_limited, .is_app_limited = acked_pkt->is_app_limited,
}; };
link->delivered_bytes += rs.delivered; link->delivered_bytes += rs.delivered;
@ -1245,6 +1247,7 @@ void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t d
link->send_blocked_inflight = 0; link->send_blocked_inflight = 0;
loadbalancer_link_ready(link); loadbalancer_link_ready(link);
} }
link->bbr_loss_since_ack = 0;
} }

20
src/etcp_bbr.c

@ -65,8 +65,7 @@ static uint32_t bbr_bdp(const struct bbr* bbr, uint32_t bw, int gain, uint32_t m
static uint32_t bbr_quantization_budget(struct bbr* bbr, uint32_t cwnd, uint32_t mss) static uint32_t bbr_quantization_budget(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
{ {
(void)mss; cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET * mss ? cwnd : BBR_CWND_MIN_TARGET * mss);
cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET ? cwnd : BBR_CWND_MIN_TARGET);
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP) if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
cwnd += 2; cwnd += 2;
return cwnd; return cwnd;
@ -165,6 +164,8 @@ static void bbr_init_lower_bounds(struct bbr* bbr, int init_bw, uint32_t cwnd)
bbr->bw_lo = bbr_max_bw(bbr); bbr->bw_lo = bbr_max_bw(bbr);
if (bbr->inflight_lo == ~0U) if (bbr->inflight_lo == ~0U)
bbr->inflight_lo = cwnd; bbr->inflight_lo = cwnd;
if (bbr->inflight_hi == ~0U)
bbr->inflight_hi = (uint32_t)(cwnd > 4U * (uint32_t)bbr->init_cwnd ? cwnd : 4U * (uint32_t)bbr->init_cwnd);
} }
static void bbr_loss_lower_bounds(struct bbr* bbr) static void bbr_loss_lower_bounds(struct bbr* bbr)
@ -336,10 +337,10 @@ static void bbr_exit_probe_rtt(struct bbr* bbr)
} }
} }
static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr) static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr, uint32_t mss)
{ {
(void)bbr; (void)bbr;
return BBR_CWND_MIN_TARGET; return BBR_CWND_MIN_TARGET * mss;
} }
static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr) static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr)
@ -594,7 +595,7 @@ static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs
if (bbr->mode == BBR_PROBE_RTT) { if (bbr->mode == BBR_PROBE_RTT) {
if (!bbr->probe_rtt_done_stamp_tb && if (!bbr->probe_rtt_done_stamp_tb &&
inflight_packets <= bbr_probe_rtt_cwnd(bbr)) { inflight_packets <= bbr_probe_rtt_cwnd(bbr, mss)) {
bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U; bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U;
bbr->probe_rtt_round_done = 0; bbr->probe_rtt_round_done = 0;
bbr->next_rtt_delivered = bbr->delivered; bbr->next_rtt_delivered = bbr->delivered;
@ -755,12 +756,12 @@ static void bbr_set_cwnd(struct bbr* bbr, const struct bbr_rate_sample* rs,
bbr->try_fast_path = 1; bbr->try_fast_path = 1;
} }
local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET ? local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET * mss ?
local_cwnd : BBR_CWND_MIN_TARGET); local_cwnd : BBR_CWND_MIN_TARGET * mss);
done: done:
*cwnd = local_cwnd; *cwnd = local_cwnd;
if (bbr->mode == BBR_PROBE_RTT) { if (bbr->mode == BBR_PROBE_RTT) {
uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr); uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr, mss);
*cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd); *cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd);
} }
} }
@ -768,7 +769,6 @@ done:
static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, uint32_t mss) static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, uint32_t mss)
{ {
uint32_t cap = ~0U; uint32_t cap = ~0U;
(void)mss;
if (!bbr->initialized) if (!bbr->initialized)
return; return;
@ -780,7 +780,7 @@ static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, u
cap = bbr_inflight_with_headroom(bbr); cap = bbr_inflight_with_headroom(bbr);
cap = (uint32_t)(cap < bbr->inflight_lo ? cap : bbr->inflight_lo); cap = (uint32_t)(cap < bbr->inflight_lo ? cap : bbr->inflight_lo);
cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET ? cap : BBR_CWND_MIN_TARGET); cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET * mss ? cap : BBR_CWND_MIN_TARGET * mss);
*cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd); *cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd);
} }

1
src/etcp_connections.c

@ -791,6 +791,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
link->delivered_bytes = 0; link->delivered_bytes = 0;
link->last_ack_time_tb = get_time_tb(); link->last_ack_time_tb = get_time_tb();
link->bbr_pacing_rate = 0; link->bbr_pacing_rate = 0;
link->bbr_loss_since_ack = 0;
link->bbr = u_calloc(1, sizeof(struct bbr)); link->bbr = u_calloc(1, sizeof(struct bbr));
if (!link->bbr) { if (!link->bbr) {
u_free(link); u_free(link);

1
src/etcp_connections.h

@ -230,6 +230,7 @@ struct ETCP_LINK {
uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection) uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us) uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost)
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
void* send_hook_ctx; void* send_hook_ctx;

5
tests/Makefile.am

@ -2,6 +2,7 @@
# All available tests (check_PROGRAMS runs via automake check-TESTS) # All available tests (check_PROGRAMS runs via automake check-TESTS)
check_PROGRAMS = \ check_PROGRAMS = \
test_bbr_integration \
test_etcp_bbr \ test_etcp_bbr \
test_etcp_crypto \ test_etcp_crypto \
test_etcp_two_instances \ test_etcp_two_instances \
@ -348,6 +349,10 @@ test_bgp_route_exchange_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_
# test_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib # test_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
# test_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(COMMON_LIBS) # test_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(COMMON_LIBS)
test_bbr_integration_SOURCES = bbr_integration/test_bbr_integration.c
test_bbr_integration_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_bbr_integration_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
bench_timeout_heap_SOURCES = bench_timeout_heap.c bench_timeout_heap_SOURCES = bench_timeout_heap.c
bench_timeout_heap_CFLAGS = -I$(top_srcdir)/lib bench_timeout_heap_CFLAGS = -I$(top_srcdir)/lib
bench_timeout_heap_LDADD = $(COMMON_LIBS) bench_timeout_heap_LDADD = $(COMMON_LIBS)

9
tests/bbr_integration/run.sh

@ -0,0 +1,9 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/../../"
echo "=== Building BBR integration test ==="
./build.sh -j4
echo ""
echo "=== Running BBR integration test ==="
cd build/tests
./test_bbr_integration

492
tests/bbr_integration/test_bbr_integration.c

@ -0,0 +1,492 @@
/**
* @file test_bbr_integration.c
* @brief BBR integration test — max-speed traffic under emulated network conditions
*
* Network: 50ms delay, 5% loss, 1 Mbit shaper, ~100KB queue
* Duration: 10 seconds, metrics every 100ms
*
* Architecture (single uasync, single thread):
* Sender(client:20000) → Dummynet(:20001) → Receiver(server:20002)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/memory_pool.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include "../lib/mem.h"
#include "../src/dummynet.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/etcp.h"
#include "../src/etcp_api.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_bbr.h"
#include "../src/secure_channel.h"
#include "../src/config_updater.h"
#include "../src/routing.h"
#include "../src/crc32.h"
/* ===== Test constants ===== */
#define DN_PORT 21001
#define SRV_PORT 21002
#define CLI_PORT 21000
#define PAYLOAD_SIZE 1200
#define TEST_DURATION_MS 15000
#define METRICS_TB 1000 /* 100ms in 0.1ms units */
#define SEND_TIMER_TB 1 /* 0.1ms re-schedule */
#define BURST_MAX 64
#define EMU_DELAY_MS 50
#define EMU_JITTER_MS 12
#define EMU_BW_KBPS 1000
#define EMU_LOSS_PERM 50 /* 5% */
#define EMU_QUEUE_PKTS 65 /* ~100KB at MTU 1400-1600 */
/* ===== BBR name helpers ===== */
static const char* bbr_mode_name(uint8_t m) {
switch (m) {
case BBR_STARTUP: return "STRTUP";
case BBR_DRAIN: return "DRAIN ";
case BBR_PROBE_BW: return "PROBW ";
case BBR_PROBE_RTT: return "PRRTT ";
default: return "??????";
}
}
static const char* bbr_cycle_name(uint8_t m, uint8_t c) {
if (m != BBR_PROBE_BW) return " --";
switch (c) {
case BBR_BW_PROBE_UP: return " UP";
case BBR_BW_PROBE_DOWN: return " DN";
case BBR_BW_PROBE_CRUISE: return " CR";
case BBR_BW_PROBE_REFILL: return " RF";
default: return " ??";
}
}
/* ===== Test context ===== */
struct test_ctx {
struct UASYNC* ua;
struct UTUN_INSTANCE* sender;
struct UTUN_INSTANCE* receiver;
struct dummynet* dn;
int test_done;
uint64_t bytes_sent;
uint64_t bytes_received;
uint64_t last_bytes_received;
uint64_t start_time_us;
FILE* log_file;
void* metrics_timer;
};
/* ===== Time helper ===== */
static uint64_t now_us(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
}
/* ===== Instance creation ===== */
static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
const char* priv_hex, const char* pub_hex) {
struct UTUN_INSTANCE* inst = u_calloc(1, sizeof(*inst));
if (!inst) return NULL;
inst->ua = u;
inst->node_id = node_id;
if (sc_init_local_keys(&inst->my_keys, pub_hex, priv_hex) != SC_OK) { u_free(inst); return NULL; }
inst->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET));
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE);
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE);
if (!inst->ack_pool || !inst->data_pool || !inst->pkt_pool) { u_free(inst); return NULL; }
struct utun_config* cfg = u_calloc(1, sizeof(*cfg));
if (!cfg) { u_free(inst); return NULL; }
strncpy(cfg->global.my_public_key_hex, pub_hex, MAX_KEY_LEN - 1);
strncpy(cfg->global.my_private_key_hex, priv_hex, MAX_KEY_LEN - 1);
cfg->global.my_node_id = node_id;
cfg->global.mtu = 1400;
cfg->global.keepalive_timeout = 5000;
cfg->global.keepalive_interval = 500;
cfg->global.allowed_keys_allow_all = 1;
inst->config = cfg;
return inst;
}
static int add_server(struct UTUN_INSTANCE* inst, const char* name, int port) {
struct CFG_SERVER* srv = u_calloc(1, sizeof(*srv));
if (!srv) return -1;
strncpy(srv->name, name, MAX_CONN_NAME_LEN - 1);
srv->ip.ss_family = AF_INET;
((struct sockaddr_in*)&srv->ip)->sin_addr.s_addr = inet_addr("127.0.0.1");
((struct sockaddr_in*)&srv->ip)->sin_port = htons(port);
srv->type = CFG_SERVER_TYPE_PUBLIC;
srv->next = inst->config->servers;
inst->config->servers = srv;
return 0;
}
static int add_client(struct UTUN_INSTANCE* inst, const char* peer_pubkey) {
struct CFG_CLIENT* cli = u_calloc(1, sizeof(*cli));
if (!cli) return -1;
strncpy(cli->name, "peer", MAX_CONN_NAME_LEN - 1);
strncpy(cli->peer_public_key_hex, peer_pubkey, MAX_KEY_LEN - 1);
cli->keepalive = 1;
cli->next = inst->config->clients;
inst->config->clients = cli;
return 0;
}
static struct CFG_CLIENT_LINK* add_link(struct CFG_CLIENT* cli, struct CFG_SERVER* local_srv, int remote_port) {
struct CFG_CLIENT_LINK* link = u_calloc(1, sizeof(*link));
if (!link) return NULL;
link->remote_addr.ss_family = AF_INET;
((struct sockaddr_in*)&link->remote_addr)->sin_addr.s_addr = inet_addr("127.0.0.1");
((struct sockaddr_in*)&link->remote_addr)->sin_port = htons(remote_port);
link->local_srv = local_srv;
struct CFG_CLIENT_LINK** tail = &cli->links;
while (*tail) tail = &(*tail)->next;
*tail = link;
return link;
}
/* ===== Callbacks ===== */
static void on_recv(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct test_ctx* ctx = (struct test_ctx*)conn->instance->etcp_new_conn_arg;
if (entry) { ctx->bytes_received += entry->len; queue_entry_free(entry); }
}
static void send_burst(struct test_ctx* ctx);
static void send_timer_cb(void* arg) {
send_burst((struct test_ctx*)arg);
}
static void send_burst(struct test_ctx* ctx) {
if (ctx->test_done) return;
struct ETCP_CONN* conn = ctx->sender->connections;
if (!conn || !conn->initialized) return;
int sent = 0;
while (sent < BURST_MAX) {
struct ll_entry* e = ll_alloc_lldgram(1 + PAYLOAD_SIZE);
if (!e) break;
e->dgram[0] = ETCP_ID_DATA;
e->len = 1 + PAYLOAD_SIZE;
if (etcp_send(conn, e) == 0) { ctx->bytes_sent += PAYLOAD_SIZE; sent++; }
else { queue_entry_free(e); break; }
}
if (!ctx->test_done)
uasync_set_timeout(ctx->ua, SEND_TIMER_TB, ctx, send_timer_cb, "bbr_send");
}
static void print_metrics(struct test_ctx* ctx) {
double time_s = (double)(now_us() - ctx->start_time_us) / 1000000.0;
uint64_t dr = ctx->bytes_received - ctx->last_bytes_received;
ctx->last_bytes_received = ctx->bytes_received;
double tput_kbps = (double)dr * 8.0 / 100.0; /* dr * 8 / (0.1s * 1000) */
const struct dummynet_stats* ds = dummynet_get_stats(ctx->dn, DUMMYNET_FORWARD);
int qpkt = dummynet_get_queue_size(ctx->dn, DUMMYNET_FORWARD);
uint64_t dropq = ds ? ds->dropped : 0;
uint64_t dropl = ds ? ds->lost : 0;
const char* lk_status = " ??";
uint32_t lk_reinit = 0;
uint32_t lk_rtrns = 0;
double bw_latest_k = 0;
double bw_lo_k = 0;
int round_start = 0;
double deliv_k = 0;
int full_bw = 0;
int loss_in_rnd = 0;
const char* mode_str = " ----";
const char* cyc_str = " --";
double pgain = 0;
double infl_kb = 0;
double lim_kb = 0;
double inhi_kb = 0;
double inlo_kb = 0;
double pace_kbps = 0;
double rtt_ms = 0;
if (ctx->sender->connections && ctx->sender->connections->links) {
struct ETCP_LINK* l = ctx->sender->connections->links;
struct ETCP_CONN* c = ctx->sender->connections;
lk_status = l->link_status ? " UP" : " DN";
lk_reinit = c->reinit_count;
lk_rtrns = l->total_retransmissions;
if (l->initialized && l->bbr) {
struct bbr* b = l->bbr;
mode_str = bbr_mode_name(b->mode);
cyc_str = bbr_cycle_name(b->mode, b->cycle_idx);
pgain = (double)b->pacing_gain / BBR_UNIT;
infl_kb = (double)l->inflight_bytes / 1024.0;
lim_kb = (double)l->inflight_lim_bytes / 1024.0;
inhi_kb = (double)b->inflight_hi / 1024.0;
inlo_kb = (double)b->inflight_lo / 1024.0;
pace_kbps = (double)l->bbr_pacing_rate / 125.0;
rtt_ms = (double)b->min_rtt_us / 1000.0;
bw_latest_k = (double)b->bw_latest / 125.0;
bw_lo_k = (double)b->bw_lo / 125.0;
round_start = b->round_start;
deliv_k = (double)b->delivered / 1024.0;
full_bw = b->full_bw_reached;
loss_in_rnd = b->loss_in_round;
}
}
static int header_printed = 0;
if (!header_printed) {
printf("=== Legend: BWlst=bw_latest(kbps) Rnd=round_start(*) DelK=delivered(KB) BWlo=bw_lo(kbps) ===\n");
printf("%6s %7s %4s %5s %5s %3s %4s %4s %7s %3s %6s %7s %6s %4s %5s %7s %7s %7s %6s %7s %7s %4s\n",
"Time", "TputK", "Qpkt", "DnQov", "DnQls",
"LkS", "Rein", "Rtrn",
"BWlstK", "Rnd", "DelvK", "BWloK",
"Mode", "Cyc", "Pgain", "InflKB", "LimKB",
"PaceKb", "RTTms", "InHiKB", "InLoKB", "LRnd");
printf("------ ------- ---- ----- ----- --- ---- ---- ------- --- ------ ------- ------ ---- ----- ------- ------- ------- ------ ------- ------- ----\n");
if (ctx->log_file) {
fprintf(ctx->log_file, "=== Legend: BWlst=bw_latest(kbps) Rnd=round_start(*) DelK=delivered(KB) BWlo=bw_lo(kbps) ===\n");
fprintf(ctx->log_file, "%6s %7s %4s %5s %5s %3s %4s %4s %7s %3s %6s %7s %6s %4s %5s %7s %7s %7s %6s %7s %7s %4s\n",
"Time", "TputK", "Qpkt", "DnQov", "DnQls",
"LkS", "Rein", "Rtrn",
"BWlstK", "Rnd", "DelvK", "BWloK",
"Mode", "Cyc", "Pgain", "InflKB", "LimKB",
"PaceKb", "RTTms", "InHiKB", "InLoKB", "LRnd");
fprintf(ctx->log_file, "------ ------- ---- ----- ----- --- ---- ---- ------- --- ------ ------- ------ ---- ----- ------- ------- ------- ------ ------- ------- ----\n");
}
header_printed = 1;
}
printf("%6.1f %7.0f %4d %5llu %5llu %3s %4u %4u %7.0f %3s %6.0f %7.1f %6s %4s %5.2f %7.1f %7.1f %7.0f %6.1f %7.1f %7.1f %4d\n",
time_s, tput_kbps, qpkt,
(unsigned long long)dropq, (unsigned long long)dropl,
lk_status, lk_reinit, lk_rtrns,
bw_latest_k, round_start ? " *" : " ", deliv_k, bw_lo_k,
mode_str, cyc_str, pgain, infl_kb, lim_kb,
pace_kbps, rtt_ms, inhi_kb, inlo_kb, loss_in_rnd);
fflush(stdout);
if (ctx->log_file) {
fprintf(ctx->log_file, "%6.1f %7.0f %4d %5llu %5llu %3s %4u %4u %7.0f %3s %6.0f %7.1f %6s %4s %5.2f %7.1f %7.1f %7.0f %6.1f %7.1f %7.1f %4d\n",
time_s, tput_kbps, qpkt,
(unsigned long long)dropq, (unsigned long long)dropl,
lk_status, lk_reinit, lk_rtrns,
bw_latest_k, round_start ? " *" : " ", deliv_k, bw_lo_k,
mode_str, cyc_str, pgain, infl_kb, lim_kb,
pace_kbps, rtt_ms, inhi_kb, inlo_kb, loss_in_rnd);
fflush(ctx->log_file);
}
}
static void metrics_timer_cb(void* arg) {
struct test_ctx* ctx = (struct test_ctx*)arg;
if (ctx->test_done) return;
print_metrics(ctx);
ctx->metrics_timer = uasync_set_timeout(ctx->ua, METRICS_TB, ctx, metrics_timer_cb, "bbr_metrics");
}
/* ===== Main ===== */
int main(void) {
printf("=== BBR Integration Test ===\n");
printf("Network emulation: %ums delay, %u%% loss, %u kbps shaper, %u pkt queue\n\n",
EMU_DELAY_MS, EMU_LOSS_PERM / 10, EMU_BW_KBPS, EMU_QUEUE_PKTS);
srand((unsigned)time(NULL));
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
socket_platform_init();
crc32_init();
struct test_ctx ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.ua = uasync_create();
if (!ctx.ua) { printf("ERROR: uasync_create failed\n"); return 1; }
const char* s_priv =
"67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb";
const char* s_pub =
"1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17"
"c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9";
const char* c_priv =
"4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2";
const char* c_pub =
"c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3"
"c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71";
printf("Creating instances...\n");
ctx.sender = create_instance(ctx.ua, 0x1111111111111111ULL, c_priv, c_pub);
ctx.receiver = create_instance(ctx.ua, 0x2222222222222222ULL, s_priv, s_pub);
if (!ctx.sender || !ctx.receiver) { printf("ERROR: create_instance failed\n"); return 1; }
ctx.sender->etcp_new_conn_arg = &ctx;
ctx.receiver->etcp_new_conn_arg = &ctx;
if (add_server(ctx.receiver, "srv", SRV_PORT) < 0 ||
add_server(ctx.sender, "local", CLI_PORT) < 0 ||
add_client(ctx.sender, s_pub) < 0) {
printf("ERROR: config setup failed\n"); return 1;
}
add_link(ctx.sender->config->clients, ctx.sender->config->servers, DN_PORT);
printf("Init receiver...\n");
if (utun_instance_init(ctx.receiver) < 0) { printf("ERROR: receiver init failed\n"); return 1; }
printf("Init sender...\n");
if (utun_instance_init(ctx.sender) < 0) { printf("ERROR: sender init failed\n"); return 1; }
etcp_bind(ctx.receiver, ETCP_ID_DATA, on_recv);
etcp_set_new_conn_cbk(ctx.receiver, NULL, &ctx);
printf("Creating dummynet on port %d ...\n", DN_PORT);
ctx.dn = dummynet_create(ctx.ua, "127.0.0.1", DN_PORT);
if (!ctx.dn) { printf("ERROR: dummynet_create failed\n"); return 1; }
dummynet_set_direction(ctx.dn, DUMMYNET_FORWARD,
EMU_DELAY_MS, EMU_JITTER_MS,
EMU_BW_KBPS, EMU_QUEUE_PKTS, EMU_LOSS_PERM,
"127.0.0.1", SRV_PORT);
dummynet_set_direction(ctx.dn, DUMMYNET_BACKWARD,
5, 1, 0, 200, 0,
"127.0.0.1", CLI_PORT);
/* Wait for connection */
printf("Waiting for connection...\n");
uint64_t t0 = now_us();
while ((now_us() - t0) < 10000000ULL) {
uasync_poll(ctx.ua, 1);
if (ctx.sender->connections && ctx.sender->connections->links) {
struct ETCP_LINK* l = ctx.sender->connections->links;
if (l->initialized && l->link_status == 1) break;
}
}
printf("Connection established, stabilizing...\n");
t0 = now_us();
while ((now_us() - t0) < 500000ULL) uasync_poll(ctx.ua, 1);
/* Start test */
printf("\n=== Starting traffic (%d seconds) ===\n\n", TEST_DURATION_MS / 1000);
ctx.log_file = fopen("test_bbr_integration.log", "w");
if (ctx.log_file)
fprintf(ctx.log_file, "# BBR Integration Test — %ums delay %u%% loss %ukbps\n\n",
EMU_DELAY_MS, EMU_LOSS_PERM / 10, EMU_BW_KBPS);
ctx.start_time_us = now_us();
ctx.last_bytes_received = 0;
ctx.metrics_timer = uasync_set_timeout(ctx.ua, METRICS_TB, &ctx, metrics_timer_cb, "bbr_metrics");
send_burst(&ctx);
/* Main loop */
uint64_t last_report = 0;
while (!ctx.test_done) {
uasync_poll(ctx.ua, 10);
uint64_t elapsed = (now_us() - ctx.start_time_us) / 1000;
if (elapsed >= TEST_DURATION_MS) ctx.test_done = 1;
if (elapsed - last_report >= 2000) {
last_report = elapsed;
const struct dummynet_stats* ds = dummynet_get_stats(ctx.dn, DUMMYNET_FORWARD);
const char* lks = "?";
uint32_t rein = 0, rtrns = 0;
if (ctx.sender->connections && ctx.sender->connections->links) {
lks = ctx.sender->connections->links->link_status ? "UP" : "DN";
rein = ctx.sender->connections->reinit_count;
rtrns = ctx.sender->connections->links->total_retransmissions;
}
printf(" t=%lus Lk=%s Rein=%u Rtrns=%u recv=%lu dn_tx=%llu dn_lost=%llu\n",
(unsigned long)elapsed, lks, rein, rtrns, (unsigned long)ctx.bytes_received,
ds ? (unsigned long long)ds->sent : 0ULL,
ds ? (unsigned long long)ds->lost : 0ULL);
}
}
/* Drain */
printf("\nDraining...\n");
ctx.test_done = 1;
t0 = now_us();
while ((now_us() - t0) < 2000000ULL) uasync_poll(ctx.ua, 10);
print_metrics(&ctx);
/* Final summary */
uint64_t end_us = now_us();
double dur_s = (double)(end_us - ctx.start_time_us) / 1000000.0;
printf("\n=== BBR Integration Test Results ===\n");
printf("Duration: %.1f s\n", dur_s);
printf("Bytes received: %lu\n", (unsigned long)ctx.bytes_received);
printf("Avg throughput: %.0f kbps\n",
(double)ctx.bytes_received * 8.0 / dur_s / 1000.0);
const struct dummynet_stats* ds = dummynet_get_stats(ctx.dn, DUMMYNET_FORWARD);
const struct dummynet_stats* ds_bk = dummynet_get_stats(ctx.dn, DUMMYNET_BACKWARD);
if (ds)
printf("Dummynet forward: rx=%llu tx=%llu lost=%llu dropped_ovf=%llu\n",
(unsigned long long)ds->recv, (unsigned long long)ds->sent,
(unsigned long long)ds->lost, (unsigned long long)ds->dropped);
if (ds_bk)
printf("Dummynet backward: rx=%llu tx=%llu lost=%llu dropped_ovf=%llu\n",
(unsigned long long)ds_bk->recv, (unsigned long long)ds_bk->sent,
(unsigned long long)ds_bk->lost, (unsigned long long)ds_bk->dropped);
if (ctx.sender->connections) {
struct ETCP_CONN* c = ctx.sender->connections;
printf("ETCP: reinit=%u reset=%u links_up=%u\n",
c->reinit_count, c->reset_count, c->links_up);
if (c->links) {
struct ETCP_LINK* l = c->links;
int lnum = 0;
while (l) {
lnum++;
printf(" Link%d: status=%s state=%u rKeep=%u sKeep=%u retrans=%lu"
" inflight=%u/%u lim=%u kB rtt=%u ms\n",
lnum, l->link_status ? "UP" : "DN", l->link_state,
l->recv_keepalive, l->remote_keepalive,
(unsigned long)l->total_retransmissions,
l->inflight_bytes, l->inflight_packets, l->inflight_lim_bytes / 1024,
l->rtt_last);
if (l->bbr)
printf(" BBR: mode=%d cycle=%d full_bw=%d loss_rnd=%d"
" minrtt=%.1fms bw_hi=%.0fK bw_lo=%.0fK pace=%.0fK infl_hi=%.1fK infl_lo=%.1fK\n",
l->bbr->mode, l->bbr->cycle_idx, l->bbr->full_bw_reached, l->bbr->loss_in_round,
(double)l->bbr->min_rtt_us / 1000.0,
(double)l->bbr->bw_hi[0] / 125.0, (double)l->bbr->bw_lo / 125.0,
(double)l->bbr_pacing_rate / 125.0,
(double)l->bbr->inflight_hi / 1024.0, (double)l->bbr->inflight_lo / 1024.0);
l = l->next;
}
}
}
int pass = (ctx.bytes_received > 50000); /* at least 50KB delivered */
printf("\n[%s]\n", pass ? "PASS" : "FAIL");
if (ctx.log_file) {
fprintf(ctx.log_file, "\n=== Results ===\n");
fprintf(ctx.log_file, "Duration: %.1fs BytesRecv: %lu Throughput: %.0f kbps\n",
dur_s, (unsigned long)ctx.bytes_received,
(double)ctx.bytes_received * 8.0 / dur_s / 1000.0);
if (ds)
fprintf(ctx.log_file, "DN_FWD: rx=%llu tx=%llu lost=%llu ovf=%llu\n",
(unsigned long long)ds->recv, (unsigned long long)ds->sent,
(unsigned long long)ds->lost, (unsigned long long)ds->dropped);
if (ds_bk)
fprintf(ctx.log_file, "DN_BCK: rx=%llu tx=%llu lost=%llu ovf=%llu\n",
(unsigned long long)ds_bk->recv, (unsigned long long)ds_bk->sent,
(unsigned long long)ds_bk->lost, (unsigned long long)ds_bk->dropped);
fclose(ctx.log_file);
printf("Log written to test_bbr_integration.log\n");
}
/* Cleanup */
dummynet_destroy(ctx.dn);
utun_instance_destroy(ctx.sender);
utun_instance_destroy(ctx.receiver);
uasync_destroy(ctx.ua, 1);
return pass ? 0 : 1;
}

56
tools/etcpmon/etcpmon_client.c

@ -864,53 +864,23 @@ void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon
struct metrics_history* h = &client->history; struct metrics_history* h = &client->history;
int idx = h->head; 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) { /* BBR per-link metrics (use first link as representative) */
fprintf(client->log_file, "add_to_history: rtt_last=%u rtt_avg10=%u rtt_avg100=%u jitter=%u\n", if (client->last_links_count > 0 && client->last_links) {
metrics->etcp.rtt_last, metrics->etcp.rtt_avg_10, metrics->etcp.rtt_avg_100, metrics->etcp.jitter); struct etcpmon_link_metrics* l = &client->last_links[0];
fflush(client->log_file); 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; h->head = (h->head + 1) % GRAPH_HISTORY_SIZE;
if (h->count < GRAPH_HISTORY_SIZE) { if (h->count < GRAPH_HISTORY_SIZE) h->count++;
h->count++; }
}
if (client->log_file) { if (client->log_file) {
fprintf(client->log_file, "history updated: count=%d head=%d\n", h->count, h->head); fprintf(client->log_file, "history updated: count=%d head=%d\n", h->count, h->head);

16
tools/etcpmon/etcpmon_client.h

@ -21,14 +21,14 @@ extern "C" {
/* Graph metrics types */ /* Graph metrics types */
typedef enum { typedef enum {
GRAPH_METRIC_RTT_LAST = 0, GRAPH_METRIC_INFLIGHT_BYTES = 0,
GRAPH_METRIC_RTT_AVG10, GRAPH_METRIC_INFLIGHT_LIM,
GRAPH_METRIC_RTT_AVG100, GRAPH_METRIC_MIN_RTT,
GRAPH_METRIC_JITTER, GRAPH_METRIC_BW_HI,
GRAPH_METRIC_RETRANS, GRAPH_METRIC_PACING_RATE,
GRAPH_METRIC_ACKS, GRAPH_METRIC_BW_LO,
GRAPH_METRIC_INFLIGHT, GRAPH_METRIC_INFLIGHT_HI,
GRAPH_METRIC_BYTES_SENT, GRAPH_METRIC_BBR_MODE,
GRAPH_METRIC_COUNT GRAPH_METRIC_COUNT
} graph_metric_type_t; } graph_metric_type_t;

30
tools/etcpmon/etcpmon_graph.c

@ -15,12 +15,12 @@
#include <stdio.h> #include <stdio.h>
#include "etcpmon_gui.h" #include "etcpmon_gui.h"
const COLORREF graph_colors[GRAPH_METRICS_COUNT] = { 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,0), RGB(0,200,0), RGB(0,128,255), RGB(255,128,0),
RGB(255,0,255), RGB(0,255,255), RGB(255,128,0), RGB(128,0,255) RGB(160,0,200), RGB(0,180,180), RGB(255,0,128), RGB(200,200,0)
}; };
const char* graph_metric_names[GRAPH_METRICS_COUNT] = { const char* graph_metric_names[GRAPH_METRICS_COUNT] = {
"RTT Last","RTT Avg10","RTT Avg100","Jitter", "InflB","Cwnd","minRTT","bwHi",
"Retrns","ACKs","Inflght","Bytes/s" "Pace","bwLo","infHi","Mode"
}; };
static void RecalculateMinMax(struct metrics_history* hist, int vis, int oldest) 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; if (!app->hChannelValue[i]) continue;
float val = h->values[i][history_idx]; float val = h->values[i][history_idx];
char buf[32] = {0}; char buf[32] = {0};
if (i == GRAPH_METRIC_BYTES_SENT) { if (i == GRAPH_METRIC_BW_HI || i == GRAPH_METRIC_BW_LO || i == GRAPH_METRIC_PACING_RATE) {
if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/(1024.*1024.)); if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/1024.);
else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB/s", val/1024.); else snprintf(buf, sizeof(buf), "%.1f KB/s", val);
else snprintf(buf, sizeof(buf), "%.0f B/s", val); } else if (i == GRAPH_METRIC_INFLIGHT_BYTES || i == GRAPH_METRIC_INFLIGHT_LIM || i == GRAPH_METRIC_INFLIGHT_HI) {
} else if (i == GRAPH_METRIC_INFLIGHT) { if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB", val/(1024.*1024.));
if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.); else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B", val); else snprintf(buf, sizeof(buf), "%.0f B", val);
} else if (i == GRAPH_METRIC_RETRANS || i == GRAPH_METRIC_ACKS) { } else if (i == GRAPH_METRIC_MIN_RTT) {
snprintf(buf, sizeof(buf), "%.0f /s", val);
} else {
snprintf(buf, sizeof(buf), "%.1f ms", val); 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) if (strcmp(buf, last_text[i]) == 0)
continue; continue;

23
tools/etcpmon/etcpmon_gui.c

@ -239,8 +239,8 @@ static void CreateControls(struct etcpmon_app* app) {
y += GRAPH_HEIGHT + 8; y += GRAPH_HEIGHT + 8;
const char* channel_short_names[] = { const char* channel_short_names[] = {
"RTT-L", "RTT10", "RTT100", "Jitter", "InflB", "Cwnd", "minRTT", "bwHi",
"Retrns", "ACKs", "Inflght", "Bytes/s" "Pace", "bwLo", "infHi", "Mode"
}; };
int block_w = 200; 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_lim_bytes,
links[i].inflight_packets); 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)line1);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line2); SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line2);
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line3);
} }
InvalidateRect(app->hListLinks, NULL, FALSE); InvalidateRect(app->hListLinks, NULL, FALSE);
} }

14
tools/etcpmon/etcpmon_protocol.h

@ -237,6 +237,20 @@ struct etcpmon_link_metrics {
uint32_t inflight_bytes; uint32_t inflight_bytes;
uint32_t inflight_packets; uint32_t inflight_packets;
uint32_t inflight_lim_bytes; 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 */ /* TUN interface metrics */

Loading…
Cancel
Save