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.