2022-03-25 09:06:47 +03:00
package metrics
import (
"fmt"
"sort"
2022-03-28 02:27:38 +03:00
"github.com/owncast/owncast/core"
2022-03-25 09:06:47 +03:00
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
2022-03-26 23:01:23 +03:00
"github.com/owncast/owncast/utils"
2022-03-25 09:06:47 +03:00
)
2022-03-27 02:13:06 +03:00
const (
2022-04-10 09:02:50 +03:00
healthyPercentageMinValue = 75
maxCPUUsage = 90
minClientCountForDetails = 3
2022-03-27 02:13:06 +03:00
)
2022-03-25 09:06:47 +03:00
// GetStreamHealthOverview will return the stream health overview.
func GetStreamHealthOverview ( ) * models . StreamHealthOverview {
return metrics . streamHealthOverview
}
func generateStreamHealthOverview ( ) {
2022-04-10 09:02:50 +03:00
// Determine what percentage of total players are represented in our overview.
totalPlayerCount := len ( core . GetActiveViewers ( ) )
if totalPlayerCount == 0 {
metrics . streamHealthOverview = nil
return
2022-03-25 09:06:47 +03:00
}
2022-04-10 09:02:50 +03:00
pct := getClientErrorHeathyPercentage ( )
if pct == - 1 {
metrics . streamHealthOverview = nil
return
}
overview := & models . StreamHealthOverview {
Healthy : pct > healthyPercentageMinValue ,
HealthyPercentage : pct ,
Message : getStreamHealthOverviewMessage ( ) ,
2022-03-27 02:13:06 +03:00
}
2022-04-07 10:14:23 +03:00
if totalPlayerCount > 0 && len ( windowedBandwidths ) > 0 {
representation := utils . IntPercentage ( len ( windowedBandwidths ) , totalPlayerCount )
overview . Representation = representation
}
2022-04-10 09:02:50 +03:00
2022-03-27 02:13:06 +03:00
metrics . streamHealthOverview = overview
}
2022-03-25 09:06:47 +03:00
2022-04-10 09:02:50 +03:00
func getStreamHealthOverviewMessage ( ) string {
if message := wastefulBitrateOverviewMessage ( ) ; message != "" {
return message
} else if message := cpuUsageHealthOverviewMessage ( ) ; message != "" {
return message
} else if message := networkSpeedHealthOverviewMessage ( ) ; message != "" {
return message
} else if message := errorCountHealthOverviewMessage ( ) ; message != "" {
return message
}
return ""
}
func networkSpeedHealthOverviewMessage ( ) string {
2022-03-25 09:06:47 +03:00
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
} )
2022-04-10 09:02:50 +03:00
lowestSupportedBitrate := float64 ( streamSortVariants [ len ( streamSortVariants ) - 1 ] . bitrate )
2022-03-25 09:06:47 +03:00
totalNumberOfClients := len ( windowedBandwidths )
if totalNumberOfClients == 0 {
2022-04-10 09:02:50 +03:00
return ""
2022-03-25 09:06:47 +03:00
}
// Determine healthy status based on bandwidth speeds of clients.
unhealthyClientCount := 0
2022-03-27 02:13:06 +03:00
2022-03-25 09:06:47 +03:00
for _ , speed := range windowedBandwidths {
if int ( speed ) < int ( lowestSupportedBitrate * 1.1 ) {
unhealthyClientCount ++
}
}
if unhealthyClientCount == 0 {
2022-04-10 09:02:50 +03:00
return ""
2022-03-25 09:06:47 +03:00
}
2022-04-10 09:02:50 +03:00
return fmt . Sprintf ( "%d of %d viewers (%d%%) are consuming video slower than, or too close to your bitrate of %d kbps." , unhealthyClientCount , totalNumberOfClients , int ( ( float64 ( unhealthyClientCount ) / float64 ( totalNumberOfClients ) ) * 100 ) , int ( lowestSupportedBitrate ) )
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
// wastefulBitrateOverviewMessage attempts to determine if a streamer is sending to
// Owncast at a bitrate higher than they're streaming to their viewers leading
// to wasted CPU by having to compress it.
func wastefulBitrateOverviewMessage ( ) string {
2022-03-27 02:13:06 +03:00
if len ( metrics . CPUUtilizations ) < 2 {
2022-04-10 09:02:50 +03:00
return ""
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
// Only return an alert if the CPU usage is around the max cpu threshold.
2022-03-27 02:13:06 +03:00
recentCPUUses := metrics . CPUUtilizations [ len ( metrics . CPUUtilizations ) - 2 : ]
values := make ( [ ] float64 , len ( recentCPUUses ) )
for i , val := range recentCPUUses {
values [ i ] = val . Value
}
recentCPUUse := utils . Avg ( values )
2022-04-10 09:02:50 +03:00
if recentCPUUse < maxCPUUsage - 10 {
return ""
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
currentBroadcast := core . GetCurrentBroadcast ( )
if currentBroadcast == nil {
return ""
}
currentBroadcaster := core . GetBroadcaster ( )
if currentBroadcast == nil {
return ""
}
if currentBroadcaster . StreamDetails . AudioBitrate == 0 {
return ""
}
inboundBitrate := currentBroadcaster . StreamDetails . VideoBitrate
maxBitrate := 0
if inboundBitrate > maxBitrate {
return fmt . Sprintf ( "You're broadcasting to Owncast at %dkbps but only sending to your viewers at %dkbps, requiring unnecessary work to be performed. You may want to decrease what you're sending to Owncast or increase what you send to your viewers to match." , inboundBitrate , maxBitrate )
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
return ""
}
func cpuUsageHealthOverviewMessage ( ) string {
if len ( metrics . CPUUtilizations ) < 2 {
return ""
}
2022-03-27 02:13:06 +03:00
2022-04-10 09:02:50 +03:00
recentCPUUses := metrics . CPUUtilizations [ len ( metrics . CPUUtilizations ) - 2 : ]
values := make ( [ ] float64 , len ( recentCPUUses ) )
for i , val := range recentCPUUses {
values [ i ] = val . Value
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
recentCPUUse := utils . Avg ( values )
if recentCPUUse < maxCPUUsage {
return ""
}
return fmt . Sprintf ( "The CPU usage on your server is over %d%%. This may cause video to be provided slower than necessary, causing buffering for your viewers. Consider increasing the resources available or reducing the number of output variants you made available." , maxCPUUsage )
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
func errorCountHealthOverviewMessage ( ) string {
2022-03-27 02:13:06 +03:00
totalNumberOfClients := len ( windowedBandwidths )
if totalNumberOfClients == 0 {
2022-04-10 09:02:50 +03:00
return ""
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
clientsWithErrors := getClientsWithErrorsCount ( )
2022-03-26 23:01:23 +03:00
2022-03-27 02:13:06 +03:00
if clientsWithErrors == 0 {
2022-04-10 09:02:50 +03:00
return ""
2022-03-25 09:06:47 +03:00
}
2022-04-07 10:14:23 +03:00
// Only return these detailed values and messages if we feel we have enough
// clients to be able to make a reasonable assessment. This is an arbitrary
// number but 1 out of 1 isn't helpful.
2022-03-27 02:13:06 +03:00
2022-04-07 10:14:23 +03:00
if totalNumberOfClients >= minClientCountForDetails {
healthyPercentage := utils . IntPercentage ( clientsWithErrors , totalNumberOfClients )
isUsingPassthrough := false
outputVariants := data . GetStreamOutputVariants ( )
for _ , variant := range outputVariants {
if variant . IsVideoPassthrough {
isUsingPassthrough = true
}
2022-03-31 00:02:49 +03:00
}
2022-04-07 10:14:23 +03:00
if isUsingPassthrough {
2022-04-10 09:02:50 +03:00
return fmt . Sprintf ( "%d of %d viewers (%d%%) are experiencing errors. You're currently using a video passthrough output, often known for causing playback issues for people. It is suggested you turn it off." , clientsWithErrors , totalNumberOfClients , healthyPercentage )
2022-04-07 10:14:23 +03:00
}
2022-04-10 09:02:50 +03:00
2022-04-26 00:09:06 +03:00
currentBroadcast := core . GetCurrentBroadcast ( )
if currentBroadcast != nil && currentBroadcast . LatencyLevel . SecondsPerSegment < 3 {
return fmt . Sprintf ( "%d of %d viewers (%d%%) may be experiencing some issues. You may want to increase your latency buffer level in your video configuration to see if it helps." , clientsWithErrors , totalNumberOfClients , healthyPercentage )
}
2022-04-10 09:02:50 +03:00
return fmt . Sprintf ( "%d of %d viewers (%d%%) may be experiencing some issues." , clientsWithErrors , totalNumberOfClients , healthyPercentage )
2022-03-31 00:02:49 +03:00
}
2022-04-07 10:14:23 +03:00
2022-04-10 09:02:50 +03:00
return ""
}
func getClientsWithErrorsCount ( ) int {
clientsWithErrors := 0
for _ , errors := range windowedErrorCounts {
if errors > 0 {
clientsWithErrors ++
}
}
return clientsWithErrors
}
func getClientErrorHeathyPercentage ( ) int {
totalNumberOfClients := len ( windowedErrorCounts )
if totalNumberOfClients == 0 {
return - 1
}
clientsWithErrors := getClientsWithErrorsCount ( )
if clientsWithErrors == 0 {
return 100
2022-03-27 02:13:06 +03:00
}
2022-04-10 09:02:50 +03:00
pct := 100 - utils . IntPercentage ( clientsWithErrors , totalNumberOfClients )
return pct
2022-03-25 09:06:47 +03:00
}