Introduce bufferStalled playback metric
This commit is contained in:
parent
f2887d29b8
commit
62bf86c186
|
@ -18,6 +18,8 @@ class MetricsPlugin extends Plugin {
|
||||||
private resolutionChanges = 0
|
private resolutionChanges = 0
|
||||||
private errors = 0
|
private errors = 0
|
||||||
|
|
||||||
|
private bufferStalled = 0
|
||||||
|
|
||||||
private p2pEnabled: boolean
|
private p2pEnabled: boolean
|
||||||
private p2pPeers = 0
|
private p2pPeers = 0
|
||||||
|
|
||||||
|
@ -33,6 +35,7 @@ class MetricsPlugin extends Plugin {
|
||||||
this.trackBytes()
|
this.trackBytes()
|
||||||
this.trackResolutionChange()
|
this.trackResolutionChange()
|
||||||
this.trackErrors()
|
this.trackErrors()
|
||||||
|
this.trackBufferStalled()
|
||||||
|
|
||||||
this.one('play', () => {
|
this.one('play', () => {
|
||||||
this.player.on('video-change', () => {
|
this.player.on('video-change', () => {
|
||||||
|
@ -56,6 +59,7 @@ class MetricsPlugin extends Plugin {
|
||||||
|
|
||||||
this.resolutionChanges = 0
|
this.resolutionChanges = 0
|
||||||
this.errors = 0
|
this.errors = 0
|
||||||
|
this.bufferStalled = 0
|
||||||
|
|
||||||
this.lastPlayerNetworkInfo = undefined
|
this.lastPlayerNetworkInfo = undefined
|
||||||
|
|
||||||
|
@ -107,6 +111,7 @@ class MetricsPlugin extends Plugin {
|
||||||
resolutionChanges: this.resolutionChanges,
|
resolutionChanges: this.resolutionChanges,
|
||||||
|
|
||||||
errors: this.errors,
|
errors: this.errors,
|
||||||
|
bufferStalled: this.bufferStalled,
|
||||||
|
|
||||||
downloadedBytesHTTP: this.downloadedBytesHTTP,
|
downloadedBytesHTTP: this.downloadedBytesHTTP,
|
||||||
|
|
||||||
|
@ -128,6 +133,8 @@ class MetricsPlugin extends Plugin {
|
||||||
|
|
||||||
this.errors = 0
|
this.errors = 0
|
||||||
|
|
||||||
|
this.bufferStalled = 0
|
||||||
|
|
||||||
const headers = new Headers({ 'Content-type': 'application/json; charset=UTF-8' })
|
const headers = new Headers({ 'Content-type': 'application/json; charset=UTF-8' })
|
||||||
|
|
||||||
return fetch(this.options_.metricsUrl(), { method: 'POST', body: JSON.stringify(body), headers })
|
return fetch(this.options_.metricsUrl(), { method: 'POST', body: JSON.stringify(body), headers })
|
||||||
|
@ -161,9 +168,19 @@ class MetricsPlugin extends Plugin {
|
||||||
|
|
||||||
private trackErrors () {
|
private trackErrors () {
|
||||||
this.player.on('error', () => {
|
this.player.on('error', () => {
|
||||||
|
debugLogger('Adding player error')
|
||||||
|
|
||||||
this.errors++
|
this.errors++
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private trackBufferStalled () {
|
||||||
|
this.player.on('buffer-stalled', () => {
|
||||||
|
debugLogger('Adding buffer stalled')
|
||||||
|
|
||||||
|
this.bufferStalled++
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
videojs.registerPlugin('metrics', MetricsPlugin)
|
videojs.registerPlugin('metrics', MetricsPlugin)
|
||||||
|
|
|
@ -159,6 +159,15 @@ class P2pMediaLoaderPlugin extends Plugin {
|
||||||
this.player.trigger('video-ratio-changed', { ratio: level.width / level.height })
|
this.player.trigger('video-ratio-changed', { ratio: level.width / level.height })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Track buffer issues
|
||||||
|
this.hlsjs.on(Hlsjs.Events.ERROR, (_event, errorData) => {
|
||||||
|
if (errorData.type !== Hlsjs.ErrorTypes.MEDIA_ERROR) return
|
||||||
|
|
||||||
|
if (errorData.details === Hlsjs.ErrorDetails.BUFFER_STALLED_ERROR) {
|
||||||
|
this.player.trigger('buffer-stalled')
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private runStats () {
|
private runStats () {
|
||||||
|
|
|
@ -71,15 +71,11 @@ class Logger {
|
||||||
console.error('Cannot set tokens to client log sender.', { err })
|
console.error('Cannot set tokens to client log sender.', { err })
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
fetch(serverUrl + '/api/v1/server/logs/client', {
|
fetch(serverUrl + '/api/v1/server/logs/client', {
|
||||||
headers,
|
headers,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
})
|
}).catch(err => console.error('Cannot send client warn/error to server.', err))
|
||||||
} catch (err) {
|
|
||||||
console.error('Cannot send client warn/error to server.', err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildServerLogPayload (level: Extract<LoggerLevel, 'warn' | 'error'>, message: LoggerMessage, meta?: LoggerMeta) {
|
private buildServerLogPayload (level: Extract<LoggerLevel, 'warn' | 'error'>, message: LoggerMessage, meta?: LoggerMeta) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ export interface PlaybackMetricCreate {
|
||||||
resolutionChanges: number
|
resolutionChanges: number
|
||||||
|
|
||||||
errors: number
|
errors: number
|
||||||
|
bufferStalled: number
|
||||||
|
|
||||||
downloadedBytesP2P: number
|
downloadedBytesP2P: number
|
||||||
downloadedBytesHTTP: number
|
downloadedBytesHTTP: number
|
||||||
|
|
|
@ -48,6 +48,7 @@ describe('Test metrics API validators', function () {
|
||||||
downloadedBytesP2P: 0,
|
downloadedBytesP2P: 0,
|
||||||
downloadedBytesHTTP: 0,
|
downloadedBytesHTTP: 0,
|
||||||
uploadedBytesP2P: 0,
|
uploadedBytesP2P: 0,
|
||||||
|
bufferStalled: 0,
|
||||||
videoId: videoUUID
|
videoId: videoUUID
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -174,6 +175,14 @@ describe('Test metrics API validators', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid bufferStalled', async function () {
|
||||||
|
await makePostBodyRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
fields: { ...baseParams, bufferStalled: 'toto' }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('Should fail with a bad video id', async function () {
|
it('Should fail with a bad video id', async function () {
|
||||||
await makePostBodyRequest({
|
await makePostBodyRequest({
|
||||||
url: server.url,
|
url: server.url,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
import { expect } from 'chai'
|
import { HttpStatusCode, VideoPrivacy, VideoResolution } from '@peertube/peertube-models'
|
||||||
import { HttpStatusCode, PlaybackMetricCreate, VideoPrivacy, VideoResolution } from '@peertube/peertube-models'
|
|
||||||
import {
|
import {
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createSingleServer,
|
createSingleServer,
|
||||||
|
@ -9,8 +8,9 @@ import {
|
||||||
PeerTubeServer,
|
PeerTubeServer,
|
||||||
setAccessTokensToServers
|
setAccessTokensToServers
|
||||||
} from '@peertube/peertube-server-commands'
|
} from '@peertube/peertube-server-commands'
|
||||||
import { expectLogDoesNotContain, expectLogContain } from '@tests/shared/checks.js'
|
import { expectLogContain, expectLogDoesNotContain } from '@tests/shared/checks.js'
|
||||||
import { MockHTTP } from '@tests/shared/mock-servers/mock-http.js'
|
import { MockHTTP } from '@tests/shared/mock-servers/mock-http.js'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
|
||||||
describe('Open Telemetry', function () {
|
describe('Open Telemetry', function () {
|
||||||
let server: PeerTubeServer
|
let server: PeerTubeServer
|
||||||
|
@ -73,6 +73,7 @@ describe('Open Telemetry', function () {
|
||||||
downloadedBytesHTTP: 0,
|
downloadedBytesHTTP: 0,
|
||||||
uploadedBytesP2P: 5,
|
uploadedBytesP2P: 5,
|
||||||
p2pPeers: 1,
|
p2pPeers: 1,
|
||||||
|
bufferStalled: 2,
|
||||||
p2pEnabled: false,
|
p2pEnabled: false,
|
||||||
videoId: video.uuid
|
videoId: video.uuid
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ describe('Open Telemetry', function () {
|
||||||
const video = await server.videos.quickUpload({ name: 'video' })
|
const video = await server.videos.quickUpload({ name: 'video' })
|
||||||
|
|
||||||
const metrics = {
|
const metrics = {
|
||||||
playerMode: 'p2p-media-loader',
|
playerMode: 'p2p-media-loader' as 'p2p-media-loader',
|
||||||
resolution: VideoResolution.H_1080P,
|
resolution: VideoResolution.H_1080P,
|
||||||
fps: 30,
|
fps: 30,
|
||||||
resolutionChanges: 1,
|
resolutionChanges: 1,
|
||||||
|
@ -100,9 +101,10 @@ describe('Open Telemetry', function () {
|
||||||
downloadedBytesHTTP: 0,
|
downloadedBytesHTTP: 0,
|
||||||
uploadedBytesP2P: 5,
|
uploadedBytesP2P: 5,
|
||||||
p2pPeers: 7,
|
p2pPeers: 7,
|
||||||
|
bufferStalled: 8,
|
||||||
p2pEnabled: false,
|
p2pEnabled: false,
|
||||||
videoId: video.uuid
|
videoId: video.uuid
|
||||||
} as PlaybackMetricCreate
|
}
|
||||||
|
|
||||||
await server.metrics.addPlaybackMetric({ metrics })
|
await server.metrics.addPlaybackMetric({ metrics })
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { PlaybackMetricCreate } from '@peertube/peertube-models'
|
||||||
export class PlaybackMetrics {
|
export class PlaybackMetrics {
|
||||||
private errorsCounter: Counter
|
private errorsCounter: Counter
|
||||||
private resolutionChangesCounter: Counter
|
private resolutionChangesCounter: Counter
|
||||||
|
private bufferStalledCounter: Counter
|
||||||
|
|
||||||
private downloadedBytesP2PCounter: Counter
|
private downloadedBytesP2PCounter: Counter
|
||||||
private uploadedBytesP2PCounter: Counter
|
private uploadedBytesP2PCounter: Counter
|
||||||
|
@ -29,6 +30,10 @@ export class PlaybackMetrics {
|
||||||
description: 'Resolution changes collected from PeerTube player.'
|
description: 'Resolution changes collected from PeerTube player.'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.bufferStalledCounter = this.meter.createCounter('peertube_playback_buffer_stalled_count', {
|
||||||
|
description: 'Number of times playback is stuck because buffer is running out of data, collected from PeerTube player.'
|
||||||
|
})
|
||||||
|
|
||||||
this.downloadedBytesHTTPCounter = this.meter.createCounter('peertube_playback_http_downloaded_bytes', {
|
this.downloadedBytesHTTPCounter = this.meter.createCounter('peertube_playback_http_downloaded_bytes', {
|
||||||
description: 'Downloaded bytes with HTTP by PeerTube player.'
|
description: 'Downloaded bytes with HTTP by PeerTube player.'
|
||||||
})
|
})
|
||||||
|
@ -75,6 +80,10 @@ export class PlaybackMetrics {
|
||||||
|
|
||||||
this.uploadedBytesP2PCounter.add(metrics.uploadedBytesP2P, attributes)
|
this.uploadedBytesP2PCounter.add(metrics.uploadedBytesP2P, attributes)
|
||||||
|
|
||||||
|
if (metrics.bufferStalled) {
|
||||||
|
this.bufferStalledCounter.add(metrics.bufferStalled, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
if (metrics.p2pPeers) {
|
if (metrics.p2pPeers) {
|
||||||
this.peersP2PPeersGaugeBuffer.push({
|
this.peersP2PPeersGaugeBuffer.push({
|
||||||
value: metrics.p2pPeers,
|
value: metrics.p2pPeers,
|
||||||
|
|
|
@ -26,6 +26,10 @@ const addPlaybackMetricValidator = [
|
||||||
body('resolutionChanges')
|
body('resolutionChanges')
|
||||||
.isInt({ min: 0 }),
|
.isInt({ min: 0 }),
|
||||||
|
|
||||||
|
body('bufferStalled')
|
||||||
|
.optional()
|
||||||
|
.isInt({ min: 0 }),
|
||||||
|
|
||||||
body('errors')
|
body('errors')
|
||||||
.isInt({ min: 0 }),
|
.isInt({ min: 0 }),
|
||||||
|
|
||||||
|
|
|
@ -10987,6 +10987,9 @@ components:
|
||||||
resolutionChanges:
|
resolutionChanges:
|
||||||
type: number
|
type: number
|
||||||
description: How many resolution changes occurred since the last metric creation
|
description: How many resolution changes occurred since the last metric creation
|
||||||
|
bufferStalled:
|
||||||
|
type: number
|
||||||
|
description: How many times buffer has been stalled since the last metric creation
|
||||||
errors:
|
errors:
|
||||||
type: number
|
type: number
|
||||||
description: How many errors occurred since the last metric creation
|
description: How many errors occurred since the last metric creation
|
||||||
|
|
Loading…
Reference in New Issue