|
|
|
@@ -69,27 +69,28 @@ func durationMs(ns uint64) float64 { |
|
|
|
} |
|
|
|
|
|
|
|
type EngineStats struct { |
|
|
|
State string `json:"state"` |
|
|
|
ChunksProduced uint64 `json:"chunksProduced"` |
|
|
|
TotalSamples uint64 `json:"totalSamples"` |
|
|
|
Underruns uint64 `json:"underruns"` |
|
|
|
LateBuffers uint64 `json:"lateBuffers,omitempty"` |
|
|
|
LastError string `json:"lastError,omitempty"` |
|
|
|
UptimeSeconds float64 `json:"uptimeSeconds"` |
|
|
|
MaxCycleMs float64 `json:"maxCycleMs,omitempty"` |
|
|
|
MaxGenerateMs float64 `json:"maxGenerateMs,omitempty"` |
|
|
|
MaxUpsampleMs float64 `json:"maxUpsampleMs,omitempty"` |
|
|
|
MaxWriteMs float64 `json:"maxWriteMs,omitempty"` |
|
|
|
Queue output.QueueStats `json:"queue"` |
|
|
|
RuntimeIndicator RuntimeIndicator `json:"runtimeIndicator"` |
|
|
|
RuntimeAlert string `json:"runtimeAlert,omitempty"` |
|
|
|
LastFault *FaultEvent `json:"lastFault,omitempty"` |
|
|
|
DegradedTransitions uint64 `json:"degradedTransitions"` |
|
|
|
MutedTransitions uint64 `json:"mutedTransitions"` |
|
|
|
FaultedTransitions uint64 `json:"faultedTransitions"` |
|
|
|
FaultCount uint64 `json:"faultCount"` |
|
|
|
FaultHistory []FaultEvent `json:"faultHistory,omitempty"` |
|
|
|
TransitionHistory []RuntimeTransition `json:"transitionHistory,omitempty"` |
|
|
|
State string `json:"state"` |
|
|
|
RuntimeStateDurationSeconds float64 `json:"runtimeStateDurationSeconds"` |
|
|
|
ChunksProduced uint64 `json:"chunksProduced"` |
|
|
|
TotalSamples uint64 `json:"totalSamples"` |
|
|
|
Underruns uint64 `json:"underruns"` |
|
|
|
LateBuffers uint64 `json:"lateBuffers,omitempty"` |
|
|
|
LastError string `json:"lastError,omitempty"` |
|
|
|
UptimeSeconds float64 `json:"uptimeSeconds"` |
|
|
|
MaxCycleMs float64 `json:"maxCycleMs,omitempty"` |
|
|
|
MaxGenerateMs float64 `json:"maxGenerateMs,omitempty"` |
|
|
|
MaxUpsampleMs float64 `json:"maxUpsampleMs,omitempty"` |
|
|
|
MaxWriteMs float64 `json:"maxWriteMs,omitempty"` |
|
|
|
Queue output.QueueStats `json:"queue"` |
|
|
|
RuntimeIndicator RuntimeIndicator `json:"runtimeIndicator"` |
|
|
|
RuntimeAlert string `json:"runtimeAlert,omitempty"` |
|
|
|
LastFault *FaultEvent `json:"lastFault,omitempty"` |
|
|
|
DegradedTransitions uint64 `json:"degradedTransitions"` |
|
|
|
MutedTransitions uint64 `json:"mutedTransitions"` |
|
|
|
FaultedTransitions uint64 `json:"faultedTransitions"` |
|
|
|
FaultCount uint64 `json:"faultCount"` |
|
|
|
FaultHistory []FaultEvent `json:"faultHistory,omitempty"` |
|
|
|
TransitionHistory []RuntimeTransition `json:"transitionHistory,omitempty"` |
|
|
|
} |
|
|
|
|
|
|
|
type RuntimeIndicator string |
|
|
|
@@ -158,10 +159,11 @@ type Engine struct { |
|
|
|
transitionHistoryMu sync.Mutex |
|
|
|
transitionHistory []RuntimeTransition |
|
|
|
|
|
|
|
degradedTransitions atomic.Uint64 |
|
|
|
mutedTransitions atomic.Uint64 |
|
|
|
faultedTransitions atomic.Uint64 |
|
|
|
faultEvents atomic.Uint64 |
|
|
|
degradedTransitions atomic.Uint64 |
|
|
|
mutedTransitions atomic.Uint64 |
|
|
|
faultedTransitions atomic.Uint64 |
|
|
|
faultEvents atomic.Uint64 |
|
|
|
runtimeStateEnteredAt atomic.Uint64 |
|
|
|
|
|
|
|
// Live config: pending frequency change, applied between chunks |
|
|
|
pendingFreq atomic.Pointer[float64] |
|
|
|
@@ -415,27 +417,28 @@ func (e *Engine) Stats() EngineStats { |
|
|
|
ri := runtimeIndicator(queue.Health, hasRecentLateBuffers) |
|
|
|
lastFault := e.lastFaultEvent() |
|
|
|
return EngineStats{ |
|
|
|
State: string(e.currentRuntimeState()), |
|
|
|
ChunksProduced: e.chunksProduced.Load(), |
|
|
|
TotalSamples: e.totalSamples.Load(), |
|
|
|
Underruns: e.underruns.Load(), |
|
|
|
LateBuffers: lateBuffers, |
|
|
|
LastError: errVal, |
|
|
|
UptimeSeconds: uptime, |
|
|
|
MaxCycleMs: durationMs(e.maxCycleNs.Load()), |
|
|
|
MaxGenerateMs: durationMs(e.maxGenerateNs.Load()), |
|
|
|
MaxUpsampleMs: durationMs(e.maxUpsampleNs.Load()), |
|
|
|
MaxWriteMs: durationMs(e.maxWriteNs.Load()), |
|
|
|
Queue: queue, |
|
|
|
RuntimeIndicator: ri, |
|
|
|
RuntimeAlert: runtimeAlert(queue.Health, hasRecentLateBuffers), |
|
|
|
LastFault: lastFault, |
|
|
|
DegradedTransitions: e.degradedTransitions.Load(), |
|
|
|
MutedTransitions: e.mutedTransitions.Load(), |
|
|
|
FaultedTransitions: e.faultedTransitions.Load(), |
|
|
|
FaultCount: e.faultEvents.Load(), |
|
|
|
FaultHistory: e.FaultHistory(), |
|
|
|
TransitionHistory: e.TransitionHistory(), |
|
|
|
State: string(e.currentRuntimeState()), |
|
|
|
RuntimeStateDurationSeconds: e.runtimeStateDurationSeconds(), |
|
|
|
ChunksProduced: e.chunksProduced.Load(), |
|
|
|
TotalSamples: e.totalSamples.Load(), |
|
|
|
Underruns: e.underruns.Load(), |
|
|
|
LateBuffers: lateBuffers, |
|
|
|
LastError: errVal, |
|
|
|
UptimeSeconds: uptime, |
|
|
|
MaxCycleMs: durationMs(e.maxCycleNs.Load()), |
|
|
|
MaxGenerateMs: durationMs(e.maxGenerateNs.Load()), |
|
|
|
MaxUpsampleMs: durationMs(e.maxUpsampleNs.Load()), |
|
|
|
MaxWriteMs: durationMs(e.maxWriteNs.Load()), |
|
|
|
Queue: queue, |
|
|
|
RuntimeIndicator: ri, |
|
|
|
RuntimeAlert: runtimeAlert(queue.Health, hasRecentLateBuffers), |
|
|
|
LastFault: lastFault, |
|
|
|
DegradedTransitions: e.degradedTransitions.Load(), |
|
|
|
MutedTransitions: e.mutedTransitions.Load(), |
|
|
|
FaultedTransitions: e.faultedTransitions.Load(), |
|
|
|
FaultCount: e.faultEvents.Load(), |
|
|
|
FaultHistory: e.FaultHistory(), |
|
|
|
TransitionHistory: e.TransitionHistory(), |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@@ -613,9 +616,10 @@ func cloneFrame(src *output.CompositeFrame) *output.CompositeFrame { |
|
|
|
} |
|
|
|
|
|
|
|
func (e *Engine) setRuntimeState(state RuntimeState) { |
|
|
|
now := time.Now() |
|
|
|
prev := e.currentRuntimeState() |
|
|
|
if prev != state { |
|
|
|
e.recordRuntimeTransition(prev, state) |
|
|
|
e.recordRuntimeTransition(prev, state, now) |
|
|
|
switch state { |
|
|
|
case RuntimeStateDegraded: |
|
|
|
e.degradedTransitions.Add(1) |
|
|
|
@@ -624,6 +628,9 @@ func (e *Engine) setRuntimeState(state RuntimeState) { |
|
|
|
case RuntimeStateFaulted: |
|
|
|
e.faultedTransitions.Add(1) |
|
|
|
} |
|
|
|
e.runtimeStateEnteredAt.Store(uint64(now.UnixNano())) |
|
|
|
} else if e.runtimeStateEnteredAt.Load() == 0 { |
|
|
|
e.runtimeStateEnteredAt.Store(uint64(now.UnixNano())) |
|
|
|
} |
|
|
|
e.runtimeState.Store(state) |
|
|
|
} |
|
|
|
@@ -637,6 +644,13 @@ func (e *Engine) currentRuntimeState() RuntimeState { |
|
|
|
return RuntimeStateIdle |
|
|
|
} |
|
|
|
|
|
|
|
func (e *Engine) runtimeStateDurationSeconds() float64 { |
|
|
|
if ts := e.runtimeStateEnteredAt.Load(); ts != 0 { |
|
|
|
return time.Since(time.Unix(0, int64(ts))).Seconds() |
|
|
|
} |
|
|
|
return 0 |
|
|
|
} |
|
|
|
|
|
|
|
func (e *Engine) hasRecentLateBuffers() bool { |
|
|
|
lateAlertAt := e.lateBufferAlertAt.Load() |
|
|
|
if lateAlertAt == 0 { |
|
|
|
@@ -670,9 +684,12 @@ func (e *Engine) TransitionHistory() []RuntimeTransition { |
|
|
|
return history |
|
|
|
} |
|
|
|
|
|
|
|
func (e *Engine) recordRuntimeTransition(from, to RuntimeState) { |
|
|
|
func (e *Engine) recordRuntimeTransition(from, to RuntimeState, when time.Time) { |
|
|
|
if when.IsZero() { |
|
|
|
when = time.Now() |
|
|
|
} |
|
|
|
ev := RuntimeTransition{ |
|
|
|
Time: time.Now(), |
|
|
|
Time: when, |
|
|
|
From: from, |
|
|
|
To: to, |
|
|
|
Severity: runtimeStateSeverity(to), |
|
|
|
|