Explorar el Código

Add high watermark telemetry to stream stats

tags/v0.9.0
Jan Svabenik hace 1 mes
padre
commit
1becfa5e0c
Se han modificado 3 ficheros con 49 adiciones y 0 borrados
  1. +6
    -0
      docs/API.md
  2. +20
    -0
      internal/audio/stream.go
  3. +23
    -0
      internal/audio/stream_test.go

+ 6
- 0
docs/API.md Ver fichero

@@ -299,6 +299,8 @@ Requires `--audio-stdin`, `--audio-http`, or another configured stream source to
"capacity": 131072,
"buffered": 0.09,
"bufferedDurationSeconds": 0.27,
"highWatermark": 15000,
"highWatermarkDurationSeconds": 0.34,
"written": 890000,
"underruns": 0,
"overflows": 0
@@ -368,6 +370,8 @@ The stream uses a lock-free ring buffer (default: 2 seconds at input rate). Buff
"capacity": 131072,
"buffered": 0.09,
"bufferedDurationSeconds": 0.27,
"highWatermark": 15000,
"highWatermarkDurationSeconds": 0.34,
"written": 890000,
"underruns": 0,
"overflows": 0
@@ -379,5 +383,7 @@ The stream uses a lock-free ring buffer (default: 2 seconds at input rate). Buff
- **overflows**: Audio arrived faster than DSP consumed (data dropped)
- **buffered**: Fill ratio (0.0 = empty, 1.0 = full)
- **bufferedDurationSeconds**: Approximate seconds of audio queued in the buffer (`available` frames divided by the sample rate)
- **highWatermark**: Highest observed buffer occupancy (frames) since the buffer was created
- **highWatermarkDurationSeconds**: Equivalent peak time (`highWatermark` frames divided by the sample rate)

When no audio is streaming, the transmitter falls back to the configured tone generator or silence.

+ 20
- 0
internal/audio/stream.go Ver fichero

@@ -24,6 +24,7 @@ type StreamSource struct {
Underruns atomic.Uint64
Overflows atomic.Uint64
Written atomic.Uint64
highWatermark atomic.Int64
}

// NewStreamSource creates a ring buffer with the given capacity (rounded up
@@ -54,6 +55,7 @@ func (s *StreamSource) WriteFrame(f Frame) bool {
s.ring[int(wp)&s.mask] = f
s.writePos.Add(1)
s.Written.Add(1)
s.updateHighWatermark()
return true
}

@@ -114,11 +116,14 @@ func (s *StreamSource) Stats() StreamStats {
if s.size > 0 {
buffered = float64(available) / float64(s.size)
}
highWatermark := int(s.highWatermark.Load())
return StreamStats{
Available: available,
Capacity: s.size,
Buffered: buffered,
BufferedDurationSeconds: s.bufferedDurationSeconds(available),
HighWatermark: highWatermark,
HighWatermarkDurationSeconds: s.bufferedDurationSeconds(highWatermark),
Written: s.Written.Load(),
Underruns: s.Underruns.Load(),
Overflows: s.Overflows.Load(),
@@ -131,6 +136,8 @@ type StreamStats struct {
Capacity int `json:"capacity"`
Buffered float64 `json:"buffered"`
BufferedDurationSeconds float64 `json:"bufferedDurationSeconds"`
HighWatermark int `json:"highWatermark"`
HighWatermarkDurationSeconds float64 `json:"highWatermarkDurationSeconds"`
Written uint64 `json:"written"`
Underruns uint64 `json:"underruns"`
Overflows uint64 `json:"overflows"`
@@ -143,6 +150,19 @@ func (s *StreamSource) bufferedDurationSeconds(available int) float64 {
return float64(available) / float64(s.SampleRate)
}

func (s *StreamSource) updateHighWatermark() {
available := s.Available()
for {
prev := s.highWatermark.Load()
if int64(available) <= prev {
return
}
if s.highWatermark.CompareAndSwap(prev, int64(available)) {
return
}
}
}

// --- StreamResampler ---

// StreamResampler wraps a StreamSource and rate-converts from the stream's


+ 23
- 0
internal/audio/stream_test.go Ver fichero

@@ -221,6 +221,29 @@ func TestStreamSource_StatsBufferedDuration(t *testing.T) {
}
}

func TestStreamSource_StatsHighWatermark(t *testing.T) {
rate := 44100
s := NewStreamSource(64, rate)
for i := 0; i < 12; i++ {
s.WriteFrame(NewFrame(0, 0))
}
for i := 0; i < 5; i++ {
s.ReadFrame()
}
stats := s.Stats()
if stats.HighWatermark != 12 {
t.Fatalf("expected high watermark 12, got %d", stats.HighWatermark)
}
expected := float64(stats.HighWatermark) / float64(rate)
if math.Abs(stats.HighWatermarkDurationSeconds-expected) > 1e-9 {
t.Fatalf("high watermark duration %.9f != %.9f", stats.HighWatermarkDurationSeconds, expected)
}
if stats.HighWatermark < stats.Available {
t.Fatalf("high watermark %d < available %d", stats.HighWatermark, stats.Available)
}
}


// --- StreamResampler tests ---

func TestStreamResampler_1to1(t *testing.T) {


Cargando…
Cancelar
Guardar