8 changed files with 209 additions and 55 deletions
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
package metrics |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sort" |
||||
|
||||
"github.com/owncast/owncast/core/data" |
||||
"github.com/owncast/owncast/models" |
||||
) |
||||
|
||||
var errorMessages = map[string]string{ |
||||
"LOWSPEED": "%d of %d clients (%d%%) are consuming video slower than, or too close to your bitrate of %d kbps.", |
||||
"PLAYBACK_ERRORS": "%d of %d clients (%d%%) are experiencing different, unspecified, playback issues.", |
||||
} |
||||
|
||||
// GetStreamHealthOverview will return the stream health overview.
|
||||
func GetStreamHealthOverview() *models.StreamHealthOverview { |
||||
return metrics.streamHealthOverview |
||||
} |
||||
|
||||
func generateStreamHealthOverview() { |
||||
overview := models.StreamHealthOverview{ |
||||
Healthy: true, |
||||
HealthyPercentage: 100, |
||||
} |
||||
|
||||
defer func() { |
||||
metrics.streamHealthOverview = &overview |
||||
}() |
||||
|
||||
type singleVariant struct { |
||||
isVideoPassthrough bool |
||||
bitrate int |
||||
} |
||||
|
||||
outputVariants := data.GetStreamOutputVariants() |
||||
|
||||
streamSortVariants := make([]singleVariant, len(outputVariants)) |
||||
for i, variant := range outputVariants { |
||||
variantSort := singleVariant{ |
||||
bitrate: variant.VideoBitrate, |
||||
isVideoPassthrough: variant.IsVideoPassthrough, |
||||
} |
||||
streamSortVariants[i] = variantSort |
||||
} |
||||
|
||||
sort.Slice(streamSortVariants, func(i, j int) bool { |
||||
if streamSortVariants[i].isVideoPassthrough && !streamSortVariants[j].isVideoPassthrough { |
||||
return true |
||||
} |
||||
|
||||
if !streamSortVariants[i].isVideoPassthrough && streamSortVariants[j].isVideoPassthrough { |
||||
return false |
||||
} |
||||
|
||||
return streamSortVariants[i].bitrate > streamSortVariants[j].bitrate |
||||
}) |
||||
|
||||
lowestSupportedBitrate := float64(streamSortVariants[0].bitrate) |
||||
totalNumberOfClients := len(windowedBandwidths) |
||||
|
||||
if totalNumberOfClients == 0 { |
||||
return |
||||
} |
||||
|
||||
// Determine healthy status based on bandwidth speeds of clients.
|
||||
unhealthyClientCount := 0 |
||||
for _, speed := range windowedBandwidths { |
||||
if int(speed) < int(lowestSupportedBitrate*1.1) { |
||||
unhealthyClientCount++ |
||||
} |
||||
} |
||||
if unhealthyClientCount > 0 { |
||||
overview.Message = fmt.Sprintf(errorMessages["LOWSPEED"], unhealthyClientCount, totalNumberOfClients, int((float64(unhealthyClientCount)/float64(totalNumberOfClients))*100), int(lowestSupportedBitrate)) |
||||
} |
||||
|
||||
// If bandwidth is ok, determine healthy status based on error
|
||||
// counts of clients.
|
||||
if unhealthyClientCount == 0 { |
||||
for _, errors := range windowedErrorCounts { |
||||
unhealthyClientCount += int(errors) |
||||
} |
||||
if unhealthyClientCount > 0 { |
||||
overview.Message = fmt.Sprintf(errorMessages["PLAYBACK_ERRORS"], unhealthyClientCount, totalNumberOfClients, int((float64(unhealthyClientCount)/float64(totalNumberOfClients))*100)) |
||||
} |
||||
} |
||||
|
||||
if unhealthyClientCount == 0 { |
||||
return |
||||
} |
||||
|
||||
percentUnhealthy := 100 - ((float64(unhealthyClientCount) / float64(totalNumberOfClients)) * 100) |
||||
overview.HealthyPercentage = int(percentUnhealthy) |
||||
overview.Healthy = overview.HealthyPercentage > 95 |
||||
} |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
package models |
||||
|
||||
// StreamHealthOverview represents an overview of the current stream health.
|
||||
type StreamHealthOverview struct { |
||||
Healthy bool `json:"healthy"` |
||||
HealthyPercentage int `json:"healthPercentage"` |
||||
Message string `json:"message"` |
||||
} |
||||
Loading…
Reference in new issue