mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(tx): тихий детектор голодания очереди TX
Всплеск, рождённый нашим трактом, невозможен без одного события: очередь DUC
простаивает дольше подушки отправителя (DUC_FIFO_THROTTLE = 2000 отсчётов при
192 кГц = 10.42 мс). Съём 2026-08-21 это показал прямо: шесть всплесков — шесть
простоев 10.3-23.5 мс, а паузы 8 мс и короче не дали ни одного. Значит счётчик
таких простоев — полный детектор «наш/не наш», и оператору больше не нужно
называть интервал на глаз.
Новый юнит TxHealth.pas. Включён всегда: переменной окружения нет намеренно —
прибор, который надо не забыть включить, к моменту дефекта выключен.
* поток отправителя DUC мерит каждый простой очереди на передаче — одно чтение
монотонных часов на ПЕРЕХОД, ни файлов, ни локов (обе ветки, win и unix);
* планировщик TCI публикует рядом состояние пейсинга (аванс, долг, в полёте,
квант, шаг, период пачек клиента) — простыми записями, без лока: числа
диагностические, а лок в потоке отправителя DUC недопустим для тайминга;
* сетевой поток считает бит HPS_FIFO_EMPTY из HP-статуса радио — поле ГЛУБИНЫ
FIFO на этой прошивке статично (256) и веры ему нет, а бит мы не читали вовсе;
* ★журнал пишет ТОЛЬКО потребитель — таймер UI (100 мс) и цикл демона через
TxHealthService. В тракте передачи диска нет по построению.
Последнее — правило, оплаченное регрессом: прошлый прибор (TxTrace) сбрасывал
дамп синхронно в хвосте SetMOX, и эти десятки миллисекунд закрывали гонку в
пейсинге TCI (лечение — fa38fa7). Инструмент чинил дефект собой.
Журнал ~/.config/ewsdr/txhealth.log, строка на передачу: «чисто» либо «ОПАСНО
(первое на N мс)» плюс подробности с контекстом пейсинга. В статусной строке
UI — «·сухо N» за сеанс, чтобы не помнить, какая посылка была плохой.
Стенд: секция G в test/tci (порог подушки, сводка, момент от фронта PTT,
контекст рядом с событием, молчание вне передачи) — 276/276. Диск стенд не
трогает намеренно: берёт сводки через TxHealthTake/TxHealthDetail, мимо
TxHealthService, иначе писал бы в настоящий журнал пользователя.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bkwwyj7xVRrqnSVEseTRfV
This commit is contained in:
+22
-1
@@ -19,7 +19,7 @@ uses
|
||||
{$ELSE}
|
||||
Sockets, BaseUnix,
|
||||
{$ENDIF}
|
||||
HPSDRProtocol, SyncObjs, Settings, RadioBackend;
|
||||
HPSDRProtocol, SyncObjs, Settings, RadioBackend, TxHealth, PlatformUtils;
|
||||
|
||||
const
|
||||
DUC_TX_QUEUE_SIZE = 256; // power of two, bounded latency on TX underrun/overrun
|
||||
@@ -497,6 +497,7 @@ const
|
||||
DUC_WIN_SPIN_US = 350; // final part of 1.25 ms interval: avoid Sleep jitter
|
||||
var
|
||||
Pkt: TDUCIQPacket;
|
||||
DryFrom: Int64; // детектор: начало простоя очереди, мкс (см. TxHealth)
|
||||
{$IFDEF WINDOWS}
|
||||
QPCFreq: Int64;
|
||||
QPCValue: Int64;
|
||||
@@ -551,11 +552,15 @@ begin
|
||||
if not QueryPerformanceFrequency(QPCFreq) or (QPCFreq <= 0) then
|
||||
QPCFreq := 1000;
|
||||
NextDueUs := ClockUs;
|
||||
DryFrom := 0;
|
||||
|
||||
while not Terminated do
|
||||
begin
|
||||
if not FNet.DequeueDUCIQ(Pkt) then
|
||||
begin
|
||||
// Детектор голодания: очередь пуста на передаче. Стоимость — одно чтение
|
||||
// монотонных часов на ПЕРЕХОД, ни файлов, ни локов (см. TxHealth.pas).
|
||||
if FNet.FIsTransmitting and (DryFrom = 0) then DryFrom := MonotonicUs;
|
||||
RTLEventWaitFor(FNet.FDUCIQSem, 20);
|
||||
NowUs := ClockUs;
|
||||
if NextDueUs < NowUs - DUC_PACKET_INTERVAL_US then
|
||||
@@ -568,6 +573,12 @@ begin
|
||||
if not FNet.FIsTransmitting then
|
||||
Continue;
|
||||
|
||||
if DryFrom <> 0 then
|
||||
begin
|
||||
TxHealthDrought(DryFrom, MonotonicUs, FNet.FDUCIQCount);
|
||||
DryFrom := 0;
|
||||
end;
|
||||
|
||||
FNet.PackSeqBytes(Pkt.Seq, FNet.NextSeq(FNet.FSeqDUCIQ));
|
||||
FNet.DoSendTo(FNet.FSocket, Pkt, SizeOf(Pkt), FNet.FDevice.IPAddress,
|
||||
FNet.FPortDUCIQ);
|
||||
@@ -580,12 +591,16 @@ begin
|
||||
{$ELSE}
|
||||
VirtualSamples := 0;
|
||||
LastTick := GetTickCount64;
|
||||
DryFrom := 0;
|
||||
|
||||
while not Terminated do
|
||||
begin
|
||||
if not FNet.DequeueDUCIQ(Pkt) then
|
||||
begin
|
||||
DrainVirtualFIFO;
|
||||
// Детектор голодания: очередь пуста на передаче. Стоимость — одно чтение
|
||||
// монотонных часов на ПЕРЕХОД, ни файлов, ни локов (см. TxHealth.pas).
|
||||
if FNet.FIsTransmitting and (DryFrom = 0) then DryFrom := MonotonicUs;
|
||||
// Очередь пуста. НЕ инжектируем нулевые пакеты — 1.25мс тишины прямо
|
||||
// в поток сэмплов = разрыв данных = всплеск на водопаде/в эфире. Как
|
||||
// dl1ycf pihpsdr (txiq_thread: пустая очередь → просто sem_wait): ждём
|
||||
@@ -604,6 +619,12 @@ begin
|
||||
if not FNet.FIsTransmitting then
|
||||
Continue;
|
||||
|
||||
if DryFrom <> 0 then
|
||||
begin
|
||||
TxHealthDrought(DryFrom, MonotonicUs, FNet.FDUCIQCount);
|
||||
DryFrom := 0;
|
||||
end;
|
||||
|
||||
FNet.PackSeqBytes(Pkt.Seq, FNet.NextSeq(FNet.FSeqDUCIQ));
|
||||
FNet.DoSendTo(FNet.FSocket, Pkt, SizeOf(Pkt), FNet.FDevice.IPAddress,
|
||||
FNet.FPortDUCIQ);
|
||||
|
||||
Reference in New Issue
Block a user