85 lines
2.7 KiB
Go
85 lines
2.7 KiB
Go
package credits
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Whether the server is too busy to be reading media speculatively.
|
||
|
|
//
|
||
|
|
// Deliberately not a telemetry subsystem. The gateway already knows the only thing that
|
||
|
|
// actually matters here — how many televisions are playing something right now — because
|
||
|
|
// every one of them reports playback started, progress and stopped. A scan competes with
|
||
|
|
// those streams for the same disk and the same Emby, so the count is a better predictor of
|
||
|
|
// harm than a CPU figure would be, and it costs nothing to keep.
|
||
|
|
|
||
|
|
const (
|
||
|
|
// busyPlaybacks is how many concurrent streams make speculative scanning unwelcome. Two
|
||
|
|
// televisions is an ordinary evening in a household and the NAS copes; a third is the
|
||
|
|
// point at which adding ranged reads of a fourth file stops being free.
|
||
|
|
busyPlaybacks = 2
|
||
|
|
|
||
|
|
// playbackStale is how long a playback is believed without a progress report. Reports
|
||
|
|
// arrive every ten seconds, so a minute of silence means the television went away
|
||
|
|
// without saying so — a crash, a power cut, a network drop. Without this the gauge would
|
||
|
|
// latch busy for ever on one lost stop report and speculative scanning would never run
|
||
|
|
// again until the container restarted.
|
||
|
|
playbackStale = 90 * time.Second
|
||
|
|
)
|
||
|
|
|
||
|
|
// PlaybackLoad counts what is playing, from the reports televisions already send.
|
||
|
|
type PlaybackLoad struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
active map[string]time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewPlaybackLoad() *PlaybackLoad {
|
||
|
|
return &PlaybackLoad{active: map[string]time.Time{}}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Playing records that a stream is alive. Called from playback started and from each
|
||
|
|
// progress report, which is what makes the staleness sweep work: a live stream keeps
|
||
|
|
// refreshing its own timestamp.
|
||
|
|
func (l *PlaybackLoad) Playing(sessionKey string) {
|
||
|
|
if l == nil || sessionKey == "" {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
l.mu.Lock()
|
||
|
|
l.active[sessionKey] = time.Now()
|
||
|
|
l.mu.Unlock()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Stopped records that a stream ended.
|
||
|
|
func (l *PlaybackLoad) Stopped(sessionKey string) {
|
||
|
|
if l == nil || sessionKey == "" {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
l.mu.Lock()
|
||
|
|
delete(l.active, sessionKey)
|
||
|
|
l.mu.Unlock()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Busy is the one question the scheduler asks.
|
||
|
|
func (l *PlaybackLoad) Busy(context.Context) bool {
|
||
|
|
return l.Count() >= busyPlaybacks
|
||
|
|
}
|
||
|
|
|
||
|
|
// Count is the live stream count, sweeping anything that stopped reporting. The sweep is
|
||
|
|
// here rather than on a ticker because this is the only reader, and a background goroutine
|
||
|
|
// to expire at most a handful of map entries would cost more than it tidies.
|
||
|
|
func (l *PlaybackLoad) Count() int {
|
||
|
|
if l == nil {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
cutoff := time.Now().Add(-playbackStale)
|
||
|
|
l.mu.Lock()
|
||
|
|
defer l.mu.Unlock()
|
||
|
|
for key, seen := range l.active {
|
||
|
|
if seen.Before(cutoff) {
|
||
|
|
delete(l.active, key)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return len(l.active)
|
||
|
|
}
|