chore(tx): сняты зонды трассировки — пейсинг проверен на железе

Всплесков на передаче нет, инструмент своё отработал и больше не нужен в
горячем пути. Снято ровно то, что ставил d209697, и ничего сверх:

* удалён юнит TxTrace.pas (кольцо + сброс дампа + TraceInit);
* TCIAdapter: точки съёма маркеров и TX-аудио, поля FTxTraceLast/FTxTraceAudio;
* HPSDRNetwork: DUC-DRY/DUC-RUN и переменная DryFrom (жила только для них),
  вместе с ними ушли из uses TxTrace и PlatformUtils — их добавлял тот же
  коммит, MonotonicUs в этом файле больше нигде не встречается;
* RadioController: PTT-метка в SetMOX и сброс дампа на снятии PTT;
* TraceInit из ewsdr.lpr и ewsdrd.lpr.

Каждая удалённая строка стояла под `if TraceOn` — вычислений, от которых
зависит тракт, в них не было. ★PlatformUtils.MonotonicUs НЕ тронут: на нём
абсолютные дедлайны планировщика TX, это не отладка.

Скрипт разбора test/hpsdr/txtrace_scan.py оставлен, формат дампа не менялся;
в README стенда записан рецепт возврата зондов из d209697.

Проверено: lazbuild --ws=qt6 и build-ewsdrd.sh собираются, test/tci 260/260,
test/cat 50/50 — те же числа, что и с зондами.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bkwwyj7xVRrqnSVEseTRfV
This commit is contained in:
2026-08-24 17:52:23 +03:00
co-authored by Claude Opus 5
parent d2096976df
commit 166fc65fc8
7 changed files with 17 additions and 185 deletions
+1 -15
View File
@@ -19,7 +19,7 @@ uses
{$ELSE}
Sockets, BaseUnix,
{$ENDIF}
HPSDRProtocol, SyncObjs, Settings, RadioBackend, TxTrace, PlatformUtils;
HPSDRProtocol, SyncObjs, Settings, RadioBackend;
const
DUC_TX_QUEUE_SIZE = 256; // power of two, bounded latency on TX underrun/overrun
@@ -509,7 +509,6 @@ var
NowTick: QWord;
ElapsedMs: QWord;
WaitMs: Integer;
DryFrom: Int64; // приборы: начало простоя очереди, мкс
{$ENDIF}
{$IFDEF WINDOWS}
@@ -581,18 +580,12 @@ begin
{$ELSE}
VirtualSamples := 0;
LastTick := GetTickCount64;
DryFrom := 0;
while not Terminated do
begin
if not FNet.DequeueDUCIQ(Pkt) then
begin
DrainVirtualFIFO;
if TraceOn and FNet.FIsTransmitting and (DryFrom = 0) then
begin
DryFrom := MonotonicUs;
TR(trkDucDry, VirtualSamples, FNet.FDUCIQCount);
end;
// Очередь пуста. НЕ инжектируем нулевые пакеты — 1.25мс тишины прямо
// в поток сэмплов = разрыв данных = всплеск на водопаде/в эфире. Как
// dl1ycf pihpsdr (txiq_thread: пустая очередь → просто sem_wait): ждём
@@ -611,13 +604,6 @@ begin
if not FNet.FIsTransmitting then
Continue;
if TraceOn and (DryFrom <> 0) then
begin
TR(trkDucRun, Integer(MonotonicUs - DryFrom), VirtualSamples,
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);
+1 -5
View File
@@ -51,7 +51,7 @@ uses
HPSDRProtocol, HPSDRNetwork, RadioBackend, PlutoBackend, IIOBindings,
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC, DMRDecoder,
Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore, CWMorse, CWKeyer,
CWDecoder, TxTrace;
CWDecoder;
// Панадаптеры (этап 3): потолок MAX_PANS живёт в WDSPEngine (общий для
// движка/контроллера/UI). Фактический лимит бэкенда — Caps.MaxPans (Pluto=1).
@@ -6303,9 +6303,6 @@ begin
// прекратить — ключ обязан отпуститься немедленно.
if (not On_) and CWXBusy then CWXAbort;
FTransmitting := On_;
if TraceOn and (On_ <> WasTransmitting) then
if Assigned(FDSPEngine) then TR(trkPtt, Ord(On_), Ord(FDSPEngine.TXMicSource))
else TR(trkPtt, Ord(On_), -1);
// 2TON: прямое снятие MOX (кнопкой/CAT/web) гасит и двухтональник.
if (not On_) and FTwoTone then
begin
@@ -6446,7 +6443,6 @@ begin
// (оператор держит T/R руками) — вооружение обязано пойти за этой кнопкой.
SyncCWKeyer;
Changed(rfTransmitting);
if TraceOn and WasTransmitting and (not On_) then TraceDump('tx');
end;
procedure TRadioController.SetTune(On_: Boolean);
+1 -21
View File
@@ -44,7 +44,7 @@ interface
uses
Classes, SysUtils, DateUtils, Math, SyncObjs,
RadioController, RadioBackend, WDSPEngine, Settings, PlatformUtils,
DXSpotStore, TCIProtocol, TCIServer, TCIStreams, TxTrace;
DXSpotStore, TCIProtocol, TCIServer, TCIStreams;
const
// ★Приёмник TCI: 0 — главный тракт (каналы A/B = VFO A/B), N — слайс СЛОТА
@@ -242,8 +242,6 @@ type
FTxLeadKeep: Integer; // ★он же, но ПЕРЕЖИВАЮЩИЙ конец передачи
FTxLeadRate: Integer; // частота, при которой аванс измерен
FTxLeadLowUs: Int64; // с какого момента оценка держится НИЖЕ аванса
FTxTraceLast: Int64; // приборы: время прошлого маркера
FTxTraceAudio: Int64; // приборы: время прошлого блока аудио
FTxLastRxUs: Int64; // когда пришёл прошлый блок (границы пачек)
// Рабочие буферы разбора TX-блока. Полем, а не на стеке: развёрнутый в
// 48 кГц блок — это сотни килобайт, и класть их в стек потока клиента
@@ -3754,7 +3752,6 @@ begin
TCIFillHeader(H, tstTXChrono, Rx, Rate, ST, Q, Chans);
Client.SendBinNow(H);
if TraceOn then TR(trkNote, -4, Q, Rx);
end;
procedure TTCIAdapter.TxPublishLead(Rate: Integer);
@@ -3843,7 +3840,6 @@ begin
try
if not Active then
begin
if TraceOn and FTxRunning then TR(trkNote, -3, Round(FTxInFlight), 0);
FTxRunning := False;
FServer.TxTickPeriodMs := 0;
Exit;
@@ -3886,7 +3882,6 @@ begin
(TCI_TX_LEAD_MAX_MS * Rate) div 1000);
if LeadWant > FTxLead then
begin
if TraceOn then TR(trkNote, -2, LeadWant, FTxLead);
FTxLead := LeadWant;
FTxLeadLowUs := 0;
TxPublishLead(Rate);
@@ -3899,7 +3894,6 @@ begin
if FTxLeadLowUs = 0 then FTxLeadLowUs := NowUs
else if NowUs - FTxLeadLowUs > TCI_TX_LEAD_DOWN_MS * 1000 then
begin
if TraceOn then TR(trkNote, -2, LeadWant, FTxLead);
FTxLead := LeadWant;
FTxLeadLowUs := NowUs;
TxPublishLead(Rate);
@@ -3946,7 +3940,6 @@ begin
begin
// Прощаем ровно один квант и начинаем выдержку заново: поздний ответ
// безопасен, он уведёт долг в минус и сам притормозит выдачу.
if TraceOn then TR(trkNote, -1, Round(FTxInFlight), FTxWindowQ);
FTxInFlight := Max(0, FTxInFlight - Q);
// ★Срок сдвигаем НА выдержку, а не на «сейчас». Зависших кредитов может
// быть несколько, и прощение по одному за выдержку живого времени
@@ -3984,13 +3977,6 @@ begin
FTxInFlight := FTxInFlight + Q;
TxNoteRequest(NowUs);
Inc(Sent);
if TraceOn then
begin
if FTxTraceLast = 0 then FTxTraceLast := NowUs;
TR(trkChrono, Q, Round(FTxOwed - FTxInFlight),
Integer(NowUs - FTxTraceLast));
FTxTraceLast := NowUs;
end;
end;
finally
FTxLock.Leave;
@@ -4104,12 +4090,6 @@ begin
FTxProgressUs := NowUs;
FTxArmed := True;
TxNoteReply(NowUs);
if TraceOn then
begin
if FTxTraceAudio = 0 then FTxTraceAudio := NowUs;
TR(trkTxAudio, Integer(NowUs - FTxTraceAudio), Integer(H.DataLength), k);
FTxTraceAudio := NowUs;
end;
if (FTxInterp = nil) or (FTxInRate <> Rate) then
begin
-139
View File
@@ -1,139 +0,0 @@
unit TxTrace;
{
TxTrace.pas — кольцевая трассировка TX-тракта.
★Временный диагностический юнит. Выключен по умолчанию, включается
переменной окружения EWSDR_TXTRACE=1; при выключенной трассировке TR() —
одна проверка Boolean. Никакого ввода-вывода в горячем пути: кольцо
сбрасывается на диск в конце каждой передачи.
Зачем он снова здесь: всплески на передаче вернулись после того, как первая
редакция зондов была удалена, и без них видно только то, что видит глаз на
водопаде. Прибор смотрит ровно на подачу TX-аудио по TCI и на очередь DUC.
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
const
trkPtt = 0; // A=1 старт передачи / 0 конец; B=источник модуляции
trkChrono = 7; // A=запрошено кадров; B=непокрытый долг; C=пауза от прошлого маркера, мкс
trkTxAudio = 8; // A=пауза от прошлого блока, мкс; B=length в шапке; C=кадров после интерполяции
trkDucDry = 10; // A=виртуальный уровень FIFO радио; B=глубина очереди
trkDucRun = 11; // A=длительность простоя очереди, мкс; B=уровень FIFO; C=глубина очереди
trkNote = 12; // A=-1 прощение кредита, -2 смена аванса, -3 в полёте на снятии PTT, -4 предварительный маркер
var
TraceOn: Boolean = False;
procedure TraceInit;
procedure TR(Kind: Integer; A: Integer = 0; B: Integer = 0; C: Integer = 0);
function TraceDump(const Reason: string): string;
implementation
uses
SysUtils, Math, PlatformUtils;
const
TRACE_SIZE = 16384;
TRACE_MASK = TRACE_SIZE - 1;
type
TTraceRec = record
T: Int64;
Kind: Integer;
A, B, C: Integer;
end;
var
Ring: array[0..TRACE_SIZE - 1] of TTraceRec;
RingSeq: LongInt = 0;
Epoch: Int64 = 0;
const
TRACE_NAMES: array[0..12] of string = (
'PTT', 'RECV-GAP', 'HANDLER', 'HP-SYNC', 'WIDEBAND', 'TX-TICK',
'TX-PHASE', 'CHRONO', 'TX-AUDIO', 'PULL-MIC', 'DUC-DRY', 'DUC-RUN', 'NOTE');
procedure TraceInit;
var
S: string;
begin
S := LowerCase(Trim(GetEnvironmentVariable('EWSDR_TXTRACE')));
TraceOn := (S = '1') or (S = 'true') or (S = 'yes') or (S = 'on');
Epoch := MonotonicUs;
end;
procedure TR(Kind: Integer; A: Integer = 0; B: Integer = 0; C: Integer = 0);
var
Idx: LongInt;
P: ^TTraceRec;
begin
if not TraceOn then Exit;
Idx := InterlockedIncrement(RingSeq) - 1;
P := @Ring[Idx and TRACE_MASK];
P^.T := MonotonicUs;
P^.Kind := Kind;
P^.A := A;
P^.B := B;
P^.C := C;
end;
function Slug(const S: string): string;
var i: Integer;
begin
Result := '';
for i := 1 to Length(S) do
if S[i] in ['a'..'z', 'A'..'Z', '0'..'9', '-', '_'] then
Result := Result + S[i];
if Result <> '' then Result := '-' + Copy(Result, 1, 24);
end;
function TraceDump(const Reason: string): string;
var
F: TextFile;
i, First: LongInt;
Total: LongInt;
R: TTraceRec;
Name: string;
begin
Result := '';
if not TraceOn then Exit;
Total := RingSeq;
if Total <= 0 then Exit;
Result := GetAppCfgDir + 'txtrace-' +
FormatDateTime('yyyymmdd-hhnnss', Now) + Slug(Reason) + '.txt';
AssignFile(F, Result);
try
Rewrite(F);
try
WriteLn(F, '# ewsdr TX trace, повод: ', Reason);
WriteLn(F, '# всего событий: ', Total, ', в кольце: ',
Min(Total, TRACE_SIZE));
WriteLn(F, '# t_ms событие A B C');
First := Total - Min(Total, TRACE_SIZE);
for i := First to Total - 1 do
begin
R := Ring[i and TRACE_MASK];
if (R.Kind < Low(TRACE_NAMES)) or (R.Kind > High(TRACE_NAMES)) then
Name := '?'
else
Name := TRACE_NAMES[R.Kind];
WriteLn(F, Format('%12.3f %-9s %9d %9d %9d',
[(R.T - Epoch) / 1000.0, Name, R.A, R.B, R.C]));
end;
finally
CloseFile(F);
end;
except
on E: Exception do Result := '';
end;
end;
end.
+1 -2
View File
@@ -18,12 +18,11 @@ uses
Forms, LazOpenGLContextEx,
MainForm, AudioOutput, DeviceForm, FlatButton, FreqDisplay, VfoOverlay,
WDSP, WDSPEngine, WebPageHtml, WebServer, WebUtils, WinFirewall, WsClient,
Settings, RadioController, TxTrace;
Settings, RadioController;
{$R *.res}
begin
TraceInit;
EnableProcessDpiAwareness;
RequireDerivedFormResource := True;
Application.Title:='EWSDR';
+1 -2
View File
@@ -28,7 +28,7 @@ uses
{$IFDEF UNIX}cthreads, BaseUnix,{$ENDIF}
Classes, SysUtils, Math,
RadioController, WebServer, WebAdapter, WDSPEngine,
HPSDRNetwork, Settings, WDSP, PlatformUtils, DeviceStore, BoardUtils, TxTrace;
HPSDRNetwork, Settings, WDSP, PlatformUtils, DeviceStore, BoardUtils;
const
PUSH_INTERVAL_MS = 50; // период исходящего web-зеркала (как GUI-таймер)
@@ -490,7 +490,6 @@ begin
end;
begin
TraceInit;
OnGetApplicationName := @DaemonAppName; // ДО создания контроллера/настроек!
Log('EWSDR daemon starting (headless).');
GHost := THeadlessHost.Create;
+12 -1
View File
@@ -107,8 +107,19 @@ TX голодала, в эфире дыры. Тут же диапазон глу
## Приборы внутри процесса: кольцевая трассировка TX-тракта
★**Сняты из кода 2026-08-24** — пейсинг TX проверен на железе, всплесков нет, и
инструмент больше не нужен в горячем пути. Последняя редакция зондов лежит в
коммите `d209697`; вернуть их целиком:
git show d209697 -- TxTrace.pas | git apply # сам юнит
git show <коммит снятия> | git apply -R # точки съёма и TraceInit
Скрипт разбора (`txtrace_scan.py`) оставлен: формат дампа не менялся, и при
возврате зондов он работает как прежде. Ниже — описание прибора на случай
возврата.
Дамп на проводе видит ПАУЗУ в потоке DUC, но не видит, кто её сделал. Для этого
в код вписано кольцо трассировки (`TxTrace.pas`), выключенное по умолчанию.
в код вписывалось кольцо трассировки (`TxTrace.pas`), выключенное по умолчанию.
Включение — переменной окружения, кольцо сбрасывается на диск в конце КАЖДОЙ
передачи (после снятия PTT, чтобы файл не задержал выход из эфира):