mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
Посреди передачи из MSHV на водопаде появлялись всплески своего сигнала. Цепочка: очередь DUC пустеет дольше подушки отправителя (DUC_FIFO_THROTTLE = 2000 отсчётов = 10.4 мс) → FIFO радио сохнет → модуляция обрывается → в эфире остаётся голая несущая на частоте гетеродина DUC, в стороне от тона ровно на звуковой сдвиг. Доказано pcap-съёмом: шесть всплесков в дампе — ровно столько, сколько видел оператор, и каждый стоит за паузой 10.3-23.5 мс, а паузы 8 мс и короче не дали ни одного. Виноват не клиент и не блокировка UI, а зернистость НАШЕГО запроса. Слой первый: PushTxChrono жил на общем тике сервера 20 мс, а просил блок клиента целиком (2048 отсчётов = 42.7 мс) — маркер выходил через два или три тика, то есть через 40 или 60 мс. Слой второй: MSHV отвечает пачками по 4-5 блоков раз в ~44 мс (STREAM_C = 4096 при 96 кГц), и мелкий квант этого не лечит — нужен запас не меньше пачки. Сделано: * квант запроса = один блок TXA (512 отсчётов движка), а не блок клиента; * свой поток-планировщик TxTickLoop с АБСОЛЮТНЫМИ дедлайнами (опоздание одного пробуждения не сдвигает сетку); общий тик маркеров больше не шлёт; * бухгалтерия Owed/InFlight в кадрах на канал, гасится по k до интерполятора; потолок долга обязан быть выше окна в полёте (TCI_TX_OWED_HEADROOM_Q), иначе связывающим становится он и подача падает до 58% реального времени при полностью исправном клиенте; * SendBinNow: маркеры пишутся в сокет напрямую под FWriteLock, минуя очередь (та выпускается лишь на пробуждении потока клиента, TCI_POLL_MS = 20 мс — вдвое больше кванта, и подача снова рвалась); * FReapLock: планировщик TX — новый поток, а правило «клиента освобождает только тик-поток» держалось на том, что им же он и пользуется; * аванс под зернистость клиента: измеряется по ПЕРИОДУ между пачками (размер пачки зависит от того, сколько мы запросили ⇒ положительная обратная связь), переживает конец передачи, умеет уменьшаться по выдержке TCI_TX_LEAD_DOWN_MS, потолок TCI_TX_LEAD_MAX_MS; * старт передачи: KickTxTick будит планировщика на фронте PTT, TxPreWarm шлёт один маркер ДО SetMOX (41 мс раздумий клиента накладываются на нашу же подготовку тракта) под гейтом «передатчик свободен и чужого источника нет», PrimeDUCIQ для источника TCI растянут до Max(6096, аванс×4) — путь микрофона радио, CW и web не затронут; * посев аванса TCI_TX_LEAD_DEF_MS = 50 мс, пока про клиента ничего не известно: обучение к первому осушению физически не успевает. Монотонные часы одного источника для всех потоков — PlatformUtils.MonotonicUs (абсолютные дедлайны не терпят часов, способных прыгнуть от NTP). На железе: опасных осушений посреди передачи НОЛЬ (было 12 за 11 с), четыре передачи из пяти вообще без единого, включая старт; прогон 15:21 чист везде, в том числе на первой передаче после подключения. Всплесков оператор больше не видит. Приборы: TxTrace.pas (EWSDR_TXTRACE=1) и стенд test/hpsdr — кольцевой tcpdump capture.sh, разбор дампа pcap_tx_scan.py, разбор трассы txtrace_scan.py. Стенд test/tci: часть F «Пейсинг TX», 260 проверок, провалов нет; живой клиент с рампой, RTT и потерями — test/tci/tx_chrono_bench.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014gmVQnna1i4EbSZ2VGm6KD
67 lines
2.6 KiB
Bash
Executable File
67 lines
2.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Съём трафика радио openHPSDR для разбора всплесков на передаче.
|
|
#
|
|
# sudo test/hpsdr/capture.sh [-i bridge0] [-h 172.16.2.200] [-d /tmp/hpsdrcap]
|
|
#
|
|
# Пишет кольцевой буфер pcap (по умолчанию 24 x 150 МБ ~= 3.6 ГБ, это ~10-15 мин
|
|
# полного дуплекса) плюс лог счётчиков UDP-потерь ядра раз в секунду.
|
|
# Услышал/увидел всплеск — ЗАСЕКИ ВРЕМЯ и жми Ctrl-C: последние файлы кольца
|
|
# и есть нужный кусок.
|
|
|
|
set -euo pipefail
|
|
|
|
IFACE=bridge0
|
|
RADIO=172.16.2.200
|
|
OUTDIR=/tmp/hpsdrcap
|
|
FILES=24
|
|
SIZE=150
|
|
|
|
while getopts "i:h:d:n:s:" opt; do
|
|
case "$opt" in
|
|
i) IFACE=$OPTARG ;;
|
|
h) RADIO=$OPTARG ;;
|
|
d) OUTDIR=$OPTARG ;;
|
|
n) FILES=$OPTARG ;;
|
|
s) SIZE=$OPTARG ;;
|
|
*) echo "usage: $0 [-i iface] [-h radio_ip] [-d outdir] [-n ring_files] [-s mb_per_file]" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if [ "$(id -u)" != 0 ]; then
|
|
echo "нужен root (tcpdump): sudo $0 ..." >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$OUTDIR"
|
|
STAMP=$(date +%Y%m%d-%H%M%S)
|
|
echo "iface=$IFACE radio=$RADIO out=$OUTDIR/hpsdr-$STAMP-NNN.pcap ring=${FILES}x${SIZE}MB"
|
|
echo "старт: $(date -Ins)"
|
|
|
|
# Счётчики потерь ядра. Если растут — пакеты терялись НЕ на проводе, а в сокете
|
|
# приложения (мал rcvbuf / поток приёма не успевает), и в pcap всё будет чисто.
|
|
# drops=N в skmem — счётчик отброшенных ядром датаграмм этого сокета.
|
|
(
|
|
while :; do
|
|
printf '%s' "$(date +%s.%N)"
|
|
awk '/^Udp:/{getline; printf " InErrors=%s RcvbufErrors=%s", $4, $6; exit}' /proc/net/snmp
|
|
ss -uanm 2>/dev/null | awk '
|
|
/^(UNCONN|ESTAB)/ { port=$4; rq=$2; next }
|
|
/skmem:/ {
|
|
rb=0; d=0;
|
|
if (match($0, /rb[0-9]+/)) rb = substr($0, RSTART+2, RLENGTH-2);
|
|
if (match($0, /,d[0-9]+\)/)) d = substr($0, RSTART+2, RLENGTH-3);
|
|
if (rb+0 > 1000000) printf " sock=%s rq=%s drops=%s", port, rq, d;
|
|
}'
|
|
echo
|
|
sleep 1
|
|
done
|
|
) > "$OUTDIR/hpsdr-$STAMP.drops" &
|
|
DROPPID=$!
|
|
trap 'kill $DROPPID 2>/dev/null || true' EXIT
|
|
|
|
# Без exec: exec заменил бы процесс оболочки и trap EXIT не сработал бы,
|
|
# оставив сборщик .drops сиротой навсегда.
|
|
tcpdump -i "$IFACE" -s 0 -B 65536 -n --time-stamp-precision=nano \
|
|
-W "$FILES" -C "$SIZE" -w "$OUTDIR/hpsdr-$STAMP.pcap" \
|
|
"udp and host $RADIO"
|