Expand upon playback metrics

This commit is contained in:
Gabe Kangas 2022-03-16 22:49:27 -07:00
parent d84da617b7
commit 6479220e78
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
4 changed files with 227 additions and 44 deletions

View File

@ -13,13 +13,22 @@ import (
// GetVideoPlaybackMetrics returns video playback metrics. // GetVideoPlaybackMetrics returns video playback metrics.
func GetVideoPlaybackMetrics(w http.ResponseWriter, r *http.Request) { func GetVideoPlaybackMetrics(w http.ResponseWriter, r *http.Request) {
type response struct { type response struct {
Errors []metrics.TimestampedValue `json:"errors"` Errors []metrics.TimestampedValue `json:"errors"`
QualityVariantChanges []metrics.TimestampedValue `json:"qualityVariantChanges"` QualityVariantChanges []metrics.TimestampedValue `json:"qualityVariantChanges"`
Latency []metrics.TimestampedValue `json:"latency"`
SegmentDownloadDuration []metrics.TimestampedValue `json:"segmentDownloadDuration"` HighestLatency []metrics.TimestampedValue `json:"highestLatency"`
SlowestDownloadRate []metrics.TimestampedValue `json:"minPlayerBitrate"` MedianLatency []metrics.TimestampedValue `json:"medianLatency"`
AvailableBitrates []int `json:"availableBitrates"` LowestLatency []metrics.TimestampedValue `json:"lowestLatency"`
SegmentLength int `json:"segmentLength"`
MedianDownloadDuration []metrics.TimestampedValue `json:"medianSegmentDownloadDuration"`
MaximumDownloadDuration []metrics.TimestampedValue `json:"maximumSegmentDownloadDuration"`
MinimumDownloadDuration []metrics.TimestampedValue `json:"minimumSegmentDownloadDuration"`
SlowestDownloadRate []metrics.TimestampedValue `json:"minPlayerBitrate"`
MedianDownloadRate []metrics.TimestampedValue `json:"medianPlayerBitrate"`
HighestDownloadRater []metrics.TimestampedValue `json:"maxPlayerBitrate"`
AvailableBitrates []int `json:"availableBitrates"`
SegmentLength int `json:"segmentLength"`
} }
availableBitrates := []int{} availableBitrates := []int{}
@ -37,18 +46,32 @@ func GetVideoPlaybackMetrics(w http.ResponseWriter, r *http.Request) {
} }
errors := metrics.GetPlaybackErrorCountOverTime() errors := metrics.GetPlaybackErrorCountOverTime()
latency := metrics.GetLatencyOverTime() medianLatency := metrics.GetMedianLatencyOverTime()
durations := metrics.GetDownloadDurationsOverTime() minimumLatency := metrics.GetMinimumLatencyOverTime()
maximumLatency := metrics.GetMaximumLatencyOverTime()
medianDurations := metrics.GetMedianDownloadDurationsOverTime()
maximumDurations := metrics.GetMaximumDownloadDurationsOverTime()
minimumDurations := metrics.GetMinimumDownloadDurationsOverTime()
minPlayerBitrate := metrics.GetSlowestDownloadRateOverTime() minPlayerBitrate := metrics.GetSlowestDownloadRateOverTime()
medianPlayerBitrate := metrics.GetMedianDownloadRateOverTime()
maxPlayerBitrate := metrics.GetMaxDownloadRateOverTime()
qualityVariantChanges := metrics.GetQualityVariantChangesOverTime() qualityVariantChanges := metrics.GetQualityVariantChangesOverTime()
resp := response{ resp := response{
AvailableBitrates: availableBitrates, AvailableBitrates: availableBitrates,
Errors: errors, Errors: errors,
Latency: latency, MedianLatency: medianLatency,
HighestLatency: maximumLatency,
LowestLatency: minimumLatency,
SegmentLength: segmentLength, SegmentLength: segmentLength,
SegmentDownloadDuration: durations, MedianDownloadDuration: medianDurations,
MaximumDownloadDuration: maximumDurations,
MinimumDownloadDuration: minimumDurations,
SlowestDownloadRate: minPlayerBitrate, SlowestDownloadRate: minPlayerBitrate,
MedianDownloadRate: medianPlayerBitrate,
HighestDownloadRater: maxPlayerBitrate,
QualityVariantChanges: qualityVariantChanges, QualityVariantChanges: qualityVariantChanges,
} }

View File

@ -20,14 +20,24 @@ const (
// CollectedMetrics stores different collected + timestamped values. // CollectedMetrics stores different collected + timestamped values.
type CollectedMetrics struct { type CollectedMetrics struct {
CPUUtilizations []TimestampedValue `json:"cpu"` CPUUtilizations []TimestampedValue `json:"cpu"`
RAMUtilizations []TimestampedValue `json:"memory"` RAMUtilizations []TimestampedValue `json:"memory"`
DiskUtilizations []TimestampedValue `json:"disk"` DiskUtilizations []TimestampedValue `json:"disk"`
errorCount []TimestampedValue `json:"-"`
lowestBitrate []TimestampedValue `json:"-"` errorCount []TimestampedValue `json:"-"`
segmentDownloadSeconds []TimestampedValue `json:"-"` lowestBitrate []TimestampedValue `json:"-"`
averageLatency []TimestampedValue `json:"-"` medianBitrate []TimestampedValue `json:"-"`
qualityVariantChanges []TimestampedValue `json:"-"` highestBitrate []TimestampedValue `json:"-"`
medianSegmentDownloadSeconds []TimestampedValue `json:"-"`
maximumSegmentDownloadSeconds []TimestampedValue `json:"-"`
minimumSegmentDownloadSeconds []TimestampedValue `json:"-"`
minimumLatency []TimestampedValue `json:"-"`
maximumLatency []TimestampedValue `json:"-"`
medianLatency []TimestampedValue `json:"-"`
qualityVariantChanges []TimestampedValue `json:"-"`
} }
// Metrics is the shared Metrics instance. // Metrics is the shared Metrics instance.

View File

@ -65,25 +65,57 @@ func collectPlaybackErrorCount() {
} }
func collectSegmentDownloadDuration() { func collectSegmentDownloadDuration() {
val := 0.0 median := 0.0
max := 0.0
min := 0.0
if len(windowedDownloadDurations) > 0 { if len(windowedDownloadDurations) > 0 {
val = utils.Avg(windowedDownloadDurations) median = utils.Median(windowedDownloadDurations)
min, max = utils.MinMax(windowedDownloadDurations)
windowedDownloadDurations = []float64{} windowedDownloadDurations = []float64{}
} }
metrics.segmentDownloadSeconds = append(metrics.segmentDownloadSeconds, TimestampedValue{
metrics.medianSegmentDownloadSeconds = append(metrics.medianSegmentDownloadSeconds, TimestampedValue{
Time: time.Now(), Time: time.Now(),
Value: val, Value: median,
}) })
if len(metrics.segmentDownloadSeconds) > maxCollectionValues { if len(metrics.medianSegmentDownloadSeconds) > maxCollectionValues {
metrics.segmentDownloadSeconds = metrics.segmentDownloadSeconds[1:] 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:]
} }
} }
// GetDownloadDurationsOverTime will return a window of durations errors over time. // GetMedianDownloadDurationsOverTime will return a window of durations errors over time.
func GetDownloadDurationsOverTime() []TimestampedValue { func GetMedianDownloadDurationsOverTime() []TimestampedValue {
return metrics.segmentDownloadSeconds 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. // GetPlaybackErrorCountOverTime will return a window of playback errors over time.
@ -92,53 +124,113 @@ func GetPlaybackErrorCountOverTime() []TimestampedValue {
} }
func collectLatencyValues() { func collectLatencyValues() {
val := 0.0 median := 0.0
min := 0.0
max := 0.0
if len(windowedLatencies) > 0 { if len(windowedLatencies) > 0 {
val = utils.Avg(windowedLatencies) median = utils.Median(windowedLatencies)
val = math.Round(val) min, max = utils.MinMax(windowedLatencies)
windowedLatencies = []float64{} windowedLatencies = []float64{}
} }
metrics.averageLatency = append(metrics.averageLatency, TimestampedValue{ metrics.medianLatency = append(metrics.medianLatency, TimestampedValue{
Time: time.Now(), Time: time.Now(),
Value: val, Value: median,
}) })
if len(metrics.averageLatency) > maxCollectionValues { if len(metrics.medianLatency) > maxCollectionValues {
metrics.averageLatency = metrics.averageLatency[1:] 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:]
} }
} }
// GetLatencyOverTime will return the min, max and avg latency values over time. // GetMedianLatencyOverTime will return the median latency values over time.
func GetLatencyOverTime() []TimestampedValue { func GetMedianLatencyOverTime() []TimestampedValue {
if len(metrics.averageLatency) == 0 { if len(metrics.medianLatency) == 0 {
return []TimestampedValue{} return []TimestampedValue{}
} }
return metrics.averageLatency return metrics.medianLatency
} }
// collectLowestBandwidth will collect the lowest bandwidth currently collected // 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 // so we can report to the streamer the worst possible streaming condition
// being experienced. // being experienced.
func collectLowestBandwidth() { func collectLowestBandwidth() {
val := 0.0 min := 0.0
median := 0.0
max := 0.0
if len(windowedBandwidths) > 0 { if len(windowedBandwidths) > 0 {
val, _ = utils.MinMax(windowedBandwidths) min, max = utils.MinMax(windowedBandwidths)
val = math.Round(val) min = math.Round(min)
max = math.Round(max)
median = utils.Median(windowedBandwidths)
windowedBandwidths = []float64{} windowedBandwidths = []float64{}
} }
metrics.lowestBitrate = append(metrics.lowestBitrate, TimestampedValue{ metrics.lowestBitrate = append(metrics.lowestBitrate, TimestampedValue{
Time: time.Now(), Time: time.Now(),
Value: math.Round(val), Value: math.Round(min),
}) })
if len(metrics.lowestBitrate) > maxCollectionValues { if len(metrics.lowestBitrate) > maxCollectionValues {
metrics.lowestBitrate = metrics.lowestBitrate[1:] 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 // GetSlowestDownloadRateOverTime will return the collected lowest bandwidth values
@ -151,6 +243,38 @@ func GetSlowestDownloadRateOverTime() []TimestampedValue {
return metrics.lowestBitrate 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() { func collectQualityVariantChanges() {
count := utils.Sum(windowedQualityVariantChanges) count := utils.Sum(windowedQualityVariantChanges)
windowedQualityVariantChanges = []float64{} windowedQualityVariantChanges = []float64{}

View File

@ -82,3 +82,29 @@ func MinMax(array []float64) (float64, float64) {
} }
return min, max return min, max
} }
func mean(input []float64) float64 {
sum := Sum(input)
return sum / float64(len(input))
}
// Median gets the median number in a slice of numbers.
func Median(input []float64) float64 {
if len(input) == 1 {
return input[0]
}
c := make([]float64, len(input))
copy(c, input)
var median float64
l := len(c)
if l%2 == 0 {
median = mean(c[l/2-1 : l/2+1])
} else {
median = c[l/2]
}
return median
}