123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- package metrics
- import (
- "math"
- "time"
- "github.com/owncast/owncast/core"
- "github.com/owncast/owncast/utils"
- )
- // Playback error counts reported since the last time we collected metrics.
- var (
- windowedErrorCounts = map[string]float64{}
- windowedQualityVariantChanges = map[string]float64{}
- windowedBandwidths = map[string]float64{}
- windowedLatencies = map[string]float64{}
- windowedDownloadDurations = map[string]float64{}
- )
- func handlePlaybackPolling() {
- metrics.m.Lock()
- defer metrics.m.Unlock()
- // Make sure this is fired first before all the values get cleared below.
- if _getStatus().Online {
- generateStreamHealthOverview()
- }
- collectPlaybackErrorCount()
- collectLatencyValues()
- collectSegmentDownloadDuration()
- collectLowestBandwidth()
- collectQualityVariantChanges()
- }
- // RegisterPlaybackErrorCount will add to the windowed playback error count.
- func RegisterPlaybackErrorCount(clientID string, count float64) {
- metrics.m.Lock()
- defer metrics.m.Unlock()
- windowedErrorCounts[clientID] = count
- // windowedErrorCounts = append(windowedErrorCounts, count)
- }
- // RegisterQualityVariantChangesCount will add to the windowed quality variant
- // change count.
- func RegisterQualityVariantChangesCount(clientID string, count float64) {
- metrics.m.Lock()
- defer metrics.m.Unlock()
- windowedQualityVariantChanges[clientID] = count
- }
- // RegisterPlayerBandwidth will add to the windowed playback bandwidth.
- func RegisterPlayerBandwidth(clientID string, kbps float64) {
- metrics.m.Lock()
- defer metrics.m.Unlock()
- windowedBandwidths[clientID] = kbps
- }
- // RegisterPlayerLatency will add to the windowed player latency values.
- func RegisterPlayerLatency(clientID string, seconds float64) {
- metrics.m.Lock()
- defer metrics.m.Unlock()
- windowedLatencies[clientID] = seconds
- }
- // RegisterPlayerSegmentDownloadDuration will add to the windowed player segment
- // download duration values.
- func RegisterPlayerSegmentDownloadDuration(clientID string, seconds float64) {
- metrics.m.Lock()
- defer metrics.m.Unlock()
- windowedDownloadDurations[clientID] = seconds
- }
- // collectPlaybackErrorCount will take all of the error counts each individual
- // player reported and average them into a single metric. This is done so
- // one person with bad connectivity doesn't make it look like everything is
- // horrible for everyone.
- func collectPlaybackErrorCount() {
- valueSlice := utils.Float64MapToSlice(windowedErrorCounts)
- count := utils.Sum(valueSlice)
- windowedErrorCounts = map[string]float64{}
- metrics.errorCount = append(metrics.errorCount, TimestampedValue{
- Time: time.Now(),
- Value: count,
- })
- if len(metrics.errorCount) > maxCollectionValues {
- metrics.errorCount = metrics.errorCount[1:]
- }
- // Save to Prometheus collector.
- playbackErrorCount.Set(count)
- }
- func collectSegmentDownloadDuration() {
- median := 0.0
- max := 0.0
- min := 0.0
- valueSlice := utils.Float64MapToSlice(windowedDownloadDurations)
- if len(valueSlice) > 0 {
- median = utils.Median(valueSlice)
- min, max = utils.MinMax(valueSlice)
- windowedDownloadDurations = map[string]float64{}
- }
- metrics.medianSegmentDownloadSeconds = append(metrics.medianSegmentDownloadSeconds, TimestampedValue{
- Time: time.Now(),
- Value: median,
- })
- if len(metrics.medianSegmentDownloadSeconds) > maxCollectionValues {
- metrics.medianSegmentDownloadSeconds = metrics.medianSegmentDownloadSeconds[1:]
- }
- metrics.minimumSegmentDownloadSeconds = append(metrics.minimumSegmentDownloadSeconds, TimestampedValue{
- Time: time.Now(),
- Value: min,
- })
- if len(metrics.minimumSegmentDownloadSeconds) > maxCollectionValues {
- metrics.minimumSegmentDownloadSeconds = metrics.minimumSegmentDownloadSeconds[1:]
- }
- metrics.maximumSegmentDownloadSeconds = append(metrics.maximumSegmentDownloadSeconds, TimestampedValue{
- Time: time.Now(),
- Value: max,
- })
- if len(metrics.maximumSegmentDownloadSeconds) > maxCollectionValues {
- metrics.maximumSegmentDownloadSeconds = metrics.maximumSegmentDownloadSeconds[1:]
- }
- }
- // GetMedianDownloadDurationsOverTime will return a window of durations errors over time.
- func GetMedianDownloadDurationsOverTime() []TimestampedValue {
- return metrics.medianSegmentDownloadSeconds
- }
- // GetMaximumDownloadDurationsOverTime will return a maximum durations errors over time.
- func GetMaximumDownloadDurationsOverTime() []TimestampedValue {
- return metrics.maximumSegmentDownloadSeconds
- }
- // GetMinimumDownloadDurationsOverTime will return a maximum durations errors over time.
- func GetMinimumDownloadDurationsOverTime() []TimestampedValue {
- return metrics.minimumSegmentDownloadSeconds
- }
- // GetPlaybackErrorCountOverTime will return a window of playback errors over time.
- func GetPlaybackErrorCountOverTime() []TimestampedValue {
- return metrics.errorCount
- }
- func collectLatencyValues() {
- median := 0.0
- min := 0.0
- max := 0.0
- valueSlice := utils.Float64MapToSlice(windowedLatencies)
- windowedLatencies = map[string]float64{}
- if len(valueSlice) > 0 {
- median = utils.Median(valueSlice)
- min, max = utils.MinMax(valueSlice)
- windowedLatencies = map[string]float64{}
- }
- metrics.medianLatency = append(metrics.medianLatency, TimestampedValue{
- Time: time.Now(),
- Value: median,
- })
- if len(metrics.medianLatency) > maxCollectionValues {
- metrics.medianLatency = metrics.medianLatency[1:]
- }
- metrics.minimumLatency = append(metrics.minimumLatency, TimestampedValue{
- Time: time.Now(),
- Value: min,
- })
- if len(metrics.minimumLatency) > maxCollectionValues {
- metrics.minimumLatency = metrics.minimumLatency[1:]
- }
- metrics.maximumLatency = append(metrics.maximumLatency, TimestampedValue{
- Time: time.Now(),
- Value: max,
- })
- if len(metrics.maximumLatency) > maxCollectionValues {
- metrics.maximumLatency = metrics.maximumLatency[1:]
- }
- }
- // GetMedianLatencyOverTime will return the median latency values over time.
- func GetMedianLatencyOverTime() []TimestampedValue {
- if len(metrics.medianLatency) == 0 {
- return []TimestampedValue{}
- }
- return metrics.medianLatency
- }
- // GetMinimumLatencyOverTime will return the min latency values over time.
- func GetMinimumLatencyOverTime() []TimestampedValue {
- if len(metrics.minimumLatency) == 0 {
- return []TimestampedValue{}
- }
- return metrics.minimumLatency
- }
- // GetMaximumLatencyOverTime will return the max latency values over time.
- func GetMaximumLatencyOverTime() []TimestampedValue {
- if len(metrics.maximumLatency) == 0 {
- return []TimestampedValue{}
- }
- return metrics.maximumLatency
- }
- // collectLowestBandwidth will collect the bandwidth currently collected
- // so we can report to the streamer the worst possible streaming condition
- // being experienced.
- func collectLowestBandwidth() {
- min := 0.0
- median := 0.0
- max := 0.0
- valueSlice := utils.Float64MapToSlice(windowedBandwidths)
- if len(windowedBandwidths) > 0 {
- min, max = utils.MinMax(valueSlice)
- min = math.Round(min)
- max = math.Round(max)
- median = utils.Median(valueSlice)
- windowedBandwidths = map[string]float64{}
- }
- metrics.lowestBitrate = append(metrics.lowestBitrate, TimestampedValue{
- Time: time.Now(),
- Value: math.Round(min),
- })
- if len(metrics.lowestBitrate) > maxCollectionValues {
- metrics.lowestBitrate = metrics.lowestBitrate[1:]
- }
- metrics.medianBitrate = append(metrics.medianBitrate, TimestampedValue{
- Time: time.Now(),
- Value: math.Round(median),
- })
- if len(metrics.medianBitrate) > maxCollectionValues {
- metrics.medianBitrate = metrics.medianBitrate[1:]
- }
- metrics.highestBitrate = append(metrics.highestBitrate, TimestampedValue{
- Time: time.Now(),
- Value: math.Round(max),
- })
- if len(metrics.highestBitrate) > maxCollectionValues {
- metrics.highestBitrate = metrics.highestBitrate[1:]
- }
- }
- // GetSlowestDownloadRateOverTime will return the collected lowest bandwidth values
- // over time.
- func GetSlowestDownloadRateOverTime() []TimestampedValue {
- if len(metrics.lowestBitrate) == 0 {
- return []TimestampedValue{}
- }
- return metrics.lowestBitrate
- }
- // GetMedianDownloadRateOverTime will return the collected median bandwidth values.
- func GetMedianDownloadRateOverTime() []TimestampedValue {
- if len(metrics.medianBitrate) == 0 {
- return []TimestampedValue{}
- }
- return metrics.medianBitrate
- }
- // GetMaximumDownloadRateOverTime will return the collected maximum bandwidth values.
- func GetMaximumDownloadRateOverTime() []TimestampedValue {
- if len(metrics.maximumLatency) == 0 {
- return []TimestampedValue{}
- }
- return metrics.maximumLatency
- }
- // GetMinimumDownloadRateOverTime will return the collected minimum bandwidth values.
- func GetMinimumDownloadRateOverTime() []TimestampedValue {
- if len(metrics.minimumLatency) == 0 {
- return []TimestampedValue{}
- }
- return metrics.minimumLatency
- }
- // GetMaxDownloadRateOverTime will return the collected highest bandwidth values.
- func GetMaxDownloadRateOverTime() []TimestampedValue {
- if len(metrics.highestBitrate) == 0 {
- return []TimestampedValue{}
- }
- return metrics.highestBitrate
- }
- func collectQualityVariantChanges() {
- valueSlice := utils.Float64MapToSlice(windowedQualityVariantChanges)
- count := utils.Sum(valueSlice)
- windowedQualityVariantChanges = map[string]float64{}
- metrics.qualityVariantChanges = append(metrics.qualityVariantChanges, TimestampedValue{
- Time: time.Now(),
- Value: count,
- })
- }
- // GetQualityVariantChangesOverTime will return the collected quality variant
- // changes.
- func GetQualityVariantChangesOverTime() []TimestampedValue {
- return metrics.qualityVariantChanges
- }
- // GetPlaybackMetricsRepresentation returns what percentage of all known players
- // the metrics represent.
- func GetPlaybackMetricsRepresentation() int {
- totalPlayerCount := len(core.GetActiveViewers())
- representation := utils.IntPercentage(len(windowedBandwidths), totalPlayerCount)
- return representation
- }
|