Files
VerseVDI-Data-Plane/gateway/telemetry.go
sechmachine 122080ab34
Verify Data Plane / gateway (push) Failing after 3m59s
fix(gateway): recover bounded pacing debt
2026-08-09 20:54:15 +07:00

211 lines
5.6 KiB
Go

package gateway
import (
"context"
"sync"
"sync/atomic"
"time"
)
type Metrics struct {
ActiveSessions atomic.Int64
AdmittedSessions atomic.Uint64
AdmissionRejects atomic.Uint64
Reconnects atomic.Uint64
DrainTransitions atomic.Uint64
MediaDrops atomic.Uint64
MediaPackets atomic.Uint64
MediaBytes atomic.Uint64
QueueDelayNanos atomic.Uint64
ProcessingDelayNanos atomic.Uint64
ProcessingSamples atomic.Uint64
PacingDelayNanos atomic.Uint64
ProviderErrors atomic.Uint64
InputRejected atomic.Uint64
ControlRTTNanos atomic.Uint64
ControlJitterNanos atomic.Uint64
ControlLossPPM atomic.Uint64
PendingReliable atomic.Uint64
ProviderState atomic.Uint64
}
type MetricsSnapshot struct {
ActiveSessions int64
AdmittedSessions uint64
AdmissionRejects uint64
Reconnects uint64
DrainTransitions uint64
MediaDrops uint64
MediaPackets uint64
MediaBytes uint64
QueueDelayNanos uint64
ProcessingDelayNanos uint64
ProcessingSamples uint64
PacingDelayNanos uint64
ProviderErrors uint64
InputRejected uint64
ControlRTTNanos uint64
ControlJitterNanos uint64
ControlLossPPM uint64
PendingReliable uint64
ProviderState uint64
}
func (m *Metrics) Snapshot() MetricsSnapshot {
return MetricsSnapshot{
ActiveSessions: m.ActiveSessions.Load(),
AdmittedSessions: m.AdmittedSessions.Load(),
AdmissionRejects: m.AdmissionRejects.Load(),
Reconnects: m.Reconnects.Load(),
DrainTransitions: m.DrainTransitions.Load(),
MediaDrops: m.MediaDrops.Load(),
MediaPackets: m.MediaPackets.Load(),
MediaBytes: m.MediaBytes.Load(),
QueueDelayNanos: m.QueueDelayNanos.Load(),
ProcessingDelayNanos: m.ProcessingDelayNanos.Load(),
ProcessingSamples: m.ProcessingSamples.Load(),
PacingDelayNanos: m.PacingDelayNanos.Load(),
ProviderErrors: m.ProviderErrors.Load(),
InputRejected: m.InputRejected.Load(),
ControlRTTNanos: m.ControlRTTNanos.Load(),
ControlJitterNanos: m.ControlJitterNanos.Load(),
ControlLossPPM: m.ControlLossPPM.Load(),
PendingReliable: m.PendingReliable.Load(),
ProviderState: m.ProviderState.Load(),
}
}
func (m *Metrics) observeProviderTelemetry(telemetry ProviderTelemetry) {
if m == nil {
return
}
m.ControlRTTNanos.Store(uint64(telemetry.ControlRTT))
m.ControlJitterNanos.Store(uint64(telemetry.ControlJitter))
m.PendingReliable.Store(telemetry.PendingReliable)
if telemetry.ReliableSent == 0 {
m.ControlLossPPM.Store(0)
} else {
m.ControlLossPPM.Store(telemetry.ReliableRetransmits * 1_000_000 / telemetry.ReliableSent)
}
}
func (m *Metrics) observeProviderState(state string) {
if m == nil {
return
}
switch state {
case ProviderStateStarting:
m.ProviderState.Store(1)
case ProviderStateReady:
m.ProviderState.Store(2)
case ProviderStateDisconnected:
m.ProviderState.Store(3)
case ProviderStateTerminating:
m.ProviderState.Store(4)
case ProviderStateTerminated:
m.ProviderState.Store(5)
case ProviderStateCleanup:
m.ProviderState.Store(6)
case ProviderStateFailed:
m.ProviderState.Store(7)
default:
m.ProviderState.Store(0)
}
}
// fairPacer is the gateway's one shared, equal-tier media scheduler. Each
// session can hold only its existing bounded provider media channel while it
// waits for the next reservation, so a slow client cannot grow a global queue.
type fairPacer struct {
mu sync.Mutex
bytesPerSecond int64
flows map[string]fairPacerFlow
reservations atomic.Uint64
}
type fairPacerFlow struct {
next time.Time
lastSeen time.Time
debt time.Duration
}
const fairPacerMaximumCatchup = 5 * time.Millisecond
func newFairPacer(kbps int64) *fairPacer {
pacer := &fairPacer{flows: make(map[string]fairPacerFlow)}
pacer.setKbps(kbps)
return pacer
}
func (p *fairPacer) setKbps(kbps int64) {
if p == nil {
return
}
p.mu.Lock()
if kbps > 0 {
p.bytesPerSecond = kbps * 1000 / 8
} else {
p.bytesPerSecond = 0
}
p.mu.Unlock()
}
func (p *fairPacer) remove(flow string) {
if p == nil || flow == "" {
return
}
p.mu.Lock()
delete(p.flows, flow)
p.mu.Unlock()
}
func (p *fairPacer) reserveAt(now time.Time, flow string, bytes int) time.Time {
if p == nil || flow == "" || bytes < 1 {
return now
}
p.mu.Lock()
defer p.mu.Unlock()
if p.bytesPerSecond < 1 {
return now
}
for key, state := range p.flows {
if now.Sub(state.lastSeen) > time.Second {
delete(p.flows, key)
}
}
state := p.flows[flow]
state.lastSeen = now
p.flows[flow] = state
base := state.next
if base.IsZero() {
base = now
} else if lag := now.Sub(base); lag > fairPacerMaximumCatchup {
base = now.Add(-fairPacerMaximumCatchup)
state.debt = min(state.debt+lag-fairPacerMaximumCatchup, nativeApolloVideoQueueLatency-fairPacerMaximumCatchup)
}
numerator := int64(bytes) * int64(len(p.flows)) * int64(time.Second)
delay := time.Duration((numerator + p.bytesPerSecond - 1) / p.bytesPerSecond)
if repayment := min(delay/21, state.debt); repayment > 0 {
delay -= repayment
state.debt -= repayment
}
state.next = base.Add(delay)
p.flows[flow] = state
return state.next
}
func (p *fairPacer) wait(ctx context.Context, flow string, bytes int) error {
target := p.reserveAt(time.Now(), flow, bytes)
p.reservations.Add(1)
if delay := time.Until(target); delay > 0 {
timer := time.NewTimer(delay)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
}
}
return nil
}