playback.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. package metrics
  2. import (
  3. "math"
  4. "time"
  5. "github.com/owncast/owncast/core"
  6. "github.com/owncast/owncast/utils"
  7. )
  8. // Playback error counts reported since the last time we collected metrics.
  9. var (
  10. windowedErrorCounts = map[string]float64{}
  11. windowedQualityVariantChanges = map[string]float64{}
  12. windowedBandwidths = map[string]float64{}
  13. windowedLatencies = map[string]float64{}
  14. windowedDownloadDurations = map[string]float64{}
  15. )
  16. func handlePlaybackPolling() {
  17. metrics.m.Lock()
  18. defer metrics.m.Unlock()
  19. // Make sure this is fired first before all the values get cleared below.
  20. if _getStatus().Online {
  21. generateStreamHealthOverview()
  22. }
  23. collectPlaybackErrorCount()
  24. collectLatencyValues()
  25. collectSegmentDownloadDuration()
  26. collectLowestBandwidth()
  27. collectQualityVariantChanges()
  28. }
  29. // RegisterPlaybackErrorCount will add to the windowed playback error count.
  30. func RegisterPlaybackErrorCount(clientID string, count float64) {
  31. metrics.m.Lock()
  32. defer metrics.m.Unlock()
  33. windowedErrorCounts[clientID] = count
  34. // windowedErrorCounts = append(windowedErrorCounts, count)
  35. }
  36. // RegisterQualityVariantChangesCount will add to the windowed quality variant
  37. // change count.
  38. func RegisterQualityVariantChangesCount(clientID string, count float64) {
  39. metrics.m.Lock()
  40. defer metrics.m.Unlock()
  41. windowedQualityVariantChanges[clientID] = count
  42. }
  43. // RegisterPlayerBandwidth will add to the windowed playback bandwidth.
  44. func RegisterPlayerBandwidth(clientID string, kbps float64) {
  45. metrics.m.Lock()
  46. defer metrics.m.Unlock()
  47. windowedBandwidths[clientID] = kbps
  48. }
  49. // RegisterPlayerLatency will add to the windowed player latency values.
  50. func RegisterPlayerLatency(clientID string, seconds float64) {
  51. metrics.m.Lock()
  52. defer metrics.m.Unlock()
  53. windowedLatencies[clientID] = seconds
  54. }
  55. // RegisterPlayerSegmentDownloadDuration will add to the windowed player segment
  56. // download duration values.
  57. func RegisterPlayerSegmentDownloadDuration(clientID string, seconds float64) {
  58. metrics.m.Lock()
  59. defer metrics.m.Unlock()
  60. windowedDownloadDurations[clientID] = seconds
  61. }
  62. // collectPlaybackErrorCount will take all of the error counts each individual
  63. // player reported and average them into a single metric. This is done so
  64. // one person with bad connectivity doesn't make it look like everything is
  65. // horrible for everyone.
  66. func collectPlaybackErrorCount() {
  67. valueSlice := utils.Float64MapToSlice(windowedErrorCounts)
  68. count := utils.Sum(valueSlice)
  69. windowedErrorCounts = map[string]float64{}
  70. metrics.errorCount = append(metrics.errorCount, TimestampedValue{
  71. Time: time.Now(),
  72. Value: count,
  73. })
  74. if len(metrics.errorCount) > maxCollectionValues {
  75. metrics.errorCount = metrics.errorCount[1:]
  76. }
  77. // Save to Prometheus collector.
  78. playbackErrorCount.Set(count)
  79. }
  80. func collectSegmentDownloadDuration() {
  81. median := 0.0
  82. max := 0.0
  83. min := 0.0
  84. valueSlice := utils.Float64MapToSlice(windowedDownloadDurations)
  85. if len(valueSlice) > 0 {
  86. median = utils.Median(valueSlice)
  87. min, max = utils.MinMax(valueSlice)
  88. windowedDownloadDurations = map[string]float64{}
  89. }
  90. metrics.medianSegmentDownloadSeconds = append(metrics.medianSegmentDownloadSeconds, TimestampedValue{
  91. Time: time.Now(),
  92. Value: median,
  93. })
  94. if len(metrics.medianSegmentDownloadSeconds) > maxCollectionValues {
  95. metrics.medianSegmentDownloadSeconds = metrics.medianSegmentDownloadSeconds[1:]
  96. }
  97. metrics.minimumSegmentDownloadSeconds = append(metrics.minimumSegmentDownloadSeconds, TimestampedValue{
  98. Time: time.Now(),
  99. Value: min,
  100. })
  101. if len(metrics.minimumSegmentDownloadSeconds) > maxCollectionValues {
  102. metrics.minimumSegmentDownloadSeconds = metrics.minimumSegmentDownloadSeconds[1:]
  103. }
  104. metrics.maximumSegmentDownloadSeconds = append(metrics.maximumSegmentDownloadSeconds, TimestampedValue{
  105. Time: time.Now(),
  106. Value: max,
  107. })
  108. if len(metrics.maximumSegmentDownloadSeconds) > maxCollectionValues {
  109. metrics.maximumSegmentDownloadSeconds = metrics.maximumSegmentDownloadSeconds[1:]
  110. }
  111. }
  112. // GetMedianDownloadDurationsOverTime will return a window of durations errors over time.
  113. func GetMedianDownloadDurationsOverTime() []TimestampedValue {
  114. return metrics.medianSegmentDownloadSeconds
  115. }
  116. // GetMaximumDownloadDurationsOverTime will return a maximum durations errors over time.
  117. func GetMaximumDownloadDurationsOverTime() []TimestampedValue {
  118. return metrics.maximumSegmentDownloadSeconds
  119. }
  120. // GetMinimumDownloadDurationsOverTime will return a maximum durations errors over time.
  121. func GetMinimumDownloadDurationsOverTime() []TimestampedValue {
  122. return metrics.minimumSegmentDownloadSeconds
  123. }
  124. // GetPlaybackErrorCountOverTime will return a window of playback errors over time.
  125. func GetPlaybackErrorCountOverTime() []TimestampedValue {
  126. return metrics.errorCount
  127. }
  128. func collectLatencyValues() {
  129. median := 0.0
  130. min := 0.0
  131. max := 0.0
  132. valueSlice := utils.Float64MapToSlice(windowedLatencies)
  133. windowedLatencies = map[string]float64{}
  134. if len(valueSlice) > 0 {
  135. median = utils.Median(valueSlice)
  136. min, max = utils.MinMax(valueSlice)
  137. windowedLatencies = map[string]float64{}
  138. }
  139. metrics.medianLatency = append(metrics.medianLatency, TimestampedValue{
  140. Time: time.Now(),
  141. Value: median,
  142. })
  143. if len(metrics.medianLatency) > maxCollectionValues {
  144. metrics.medianLatency = metrics.medianLatency[1:]
  145. }
  146. metrics.minimumLatency = append(metrics.minimumLatency, TimestampedValue{
  147. Time: time.Now(),
  148. Value: min,
  149. })
  150. if len(metrics.minimumLatency) > maxCollectionValues {
  151. metrics.minimumLatency = metrics.minimumLatency[1:]
  152. }
  153. metrics.maximumLatency = append(metrics.maximumLatency, TimestampedValue{
  154. Time: time.Now(),
  155. Value: max,
  156. })
  157. if len(metrics.maximumLatency) > maxCollectionValues {
  158. metrics.maximumLatency = metrics.maximumLatency[1:]
  159. }
  160. }
  161. // GetMedianLatencyOverTime will return the median latency values over time.
  162. func GetMedianLatencyOverTime() []TimestampedValue {
  163. if len(metrics.medianLatency) == 0 {
  164. return []TimestampedValue{}
  165. }
  166. return metrics.medianLatency
  167. }
  168. // GetMinimumLatencyOverTime will return the min latency values over time.
  169. func GetMinimumLatencyOverTime() []TimestampedValue {
  170. if len(metrics.minimumLatency) == 0 {
  171. return []TimestampedValue{}
  172. }
  173. return metrics.minimumLatency
  174. }
  175. // GetMaximumLatencyOverTime will return the max latency values over time.
  176. func GetMaximumLatencyOverTime() []TimestampedValue {
  177. if len(metrics.maximumLatency) == 0 {
  178. return []TimestampedValue{}
  179. }
  180. return metrics.maximumLatency
  181. }
  182. // collectLowestBandwidth will collect the bandwidth currently collected
  183. // so we can report to the streamer the worst possible streaming condition
  184. // being experienced.
  185. func collectLowestBandwidth() {
  186. min := 0.0
  187. median := 0.0
  188. max := 0.0
  189. valueSlice := utils.Float64MapToSlice(windowedBandwidths)
  190. if len(windowedBandwidths) > 0 {
  191. min, max = utils.MinMax(valueSlice)
  192. min = math.Round(min)
  193. max = math.Round(max)
  194. median = utils.Median(valueSlice)
  195. windowedBandwidths = map[string]float64{}
  196. }
  197. metrics.lowestBitrate = append(metrics.lowestBitrate, TimestampedValue{
  198. Time: time.Now(),
  199. Value: math.Round(min),
  200. })
  201. if len(metrics.lowestBitrate) > maxCollectionValues {
  202. metrics.lowestBitrate = metrics.lowestBitrate[1:]
  203. }
  204. metrics.medianBitrate = append(metrics.medianBitrate, TimestampedValue{
  205. Time: time.Now(),
  206. Value: math.Round(median),
  207. })
  208. if len(metrics.medianBitrate) > maxCollectionValues {
  209. metrics.medianBitrate = metrics.medianBitrate[1:]
  210. }
  211. metrics.highestBitrate = append(metrics.highestBitrate, TimestampedValue{
  212. Time: time.Now(),
  213. Value: math.Round(max),
  214. })
  215. if len(metrics.highestBitrate) > maxCollectionValues {
  216. metrics.highestBitrate = metrics.highestBitrate[1:]
  217. }
  218. }
  219. // GetSlowestDownloadRateOverTime will return the collected lowest bandwidth values
  220. // over time.
  221. func GetSlowestDownloadRateOverTime() []TimestampedValue {
  222. if len(metrics.lowestBitrate) == 0 {
  223. return []TimestampedValue{}
  224. }
  225. return metrics.lowestBitrate
  226. }
  227. // GetMedianDownloadRateOverTime will return the collected median bandwidth values.
  228. func GetMedianDownloadRateOverTime() []TimestampedValue {
  229. if len(metrics.medianBitrate) == 0 {
  230. return []TimestampedValue{}
  231. }
  232. return metrics.medianBitrate
  233. }
  234. // GetMaximumDownloadRateOverTime will return the collected maximum bandwidth values.
  235. func GetMaximumDownloadRateOverTime() []TimestampedValue {
  236. if len(metrics.maximumLatency) == 0 {
  237. return []TimestampedValue{}
  238. }
  239. return metrics.maximumLatency
  240. }
  241. // GetMinimumDownloadRateOverTime will return the collected minimum bandwidth values.
  242. func GetMinimumDownloadRateOverTime() []TimestampedValue {
  243. if len(metrics.minimumLatency) == 0 {
  244. return []TimestampedValue{}
  245. }
  246. return metrics.minimumLatency
  247. }
  248. // GetMaxDownloadRateOverTime will return the collected highest bandwidth values.
  249. func GetMaxDownloadRateOverTime() []TimestampedValue {
  250. if len(metrics.highestBitrate) == 0 {
  251. return []TimestampedValue{}
  252. }
  253. return metrics.highestBitrate
  254. }
  255. func collectQualityVariantChanges() {
  256. valueSlice := utils.Float64MapToSlice(windowedQualityVariantChanges)
  257. count := utils.Sum(valueSlice)
  258. windowedQualityVariantChanges = map[string]float64{}
  259. metrics.qualityVariantChanges = append(metrics.qualityVariantChanges, TimestampedValue{
  260. Time: time.Now(),
  261. Value: count,
  262. })
  263. }
  264. // GetQualityVariantChangesOverTime will return the collected quality variant
  265. // changes.
  266. func GetQualityVariantChangesOverTime() []TimestampedValue {
  267. return metrics.qualityVariantChanges
  268. }
  269. // GetPlaybackMetricsRepresentation returns what percentage of all known players
  270. // the metrics represent.
  271. func GetPlaybackMetricsRepresentation() int {
  272. totalPlayerCount := len(core.GetActiveViewers())
  273. representation := utils.IntPercentage(len(windowedBandwidths), totalPlayerCount)
  274. return representation
  275. }