mirror of https://github.com/Chocobozzz/PeerTube
Introduce bufferStalled playback metric
parent
f2887d29b8
commit
62bf86c186
|
@ -18,6 +18,8 @@ class MetricsPlugin extends Plugin {
|
|||
private resolutionChanges = 0
|
||||
private errors = 0
|
||||
|
||||
private bufferStalled = 0
|
||||
|
||||
private p2pEnabled: boolean
|
||||
private p2pPeers = 0
|
||||
|
||||
|
@ -33,6 +35,7 @@ class MetricsPlugin extends Plugin {
|
|||
this.trackBytes()
|
||||
this.trackResolutionChange()
|
||||
this.trackErrors()
|
||||
this.trackBufferStalled()
|
||||
|
||||
this.one('play', () => {
|
||||
this.player.on('video-change', () => {
|
||||
|
@ -56,6 +59,7 @@ class MetricsPlugin extends Plugin {
|
|||
|
||||
this.resolutionChanges = 0
|
||||
this.errors = 0
|
||||
this.bufferStalled = 0
|
||||
|
||||
this.lastPlayerNetworkInfo = undefined
|
||||
|
||||
|
@ -107,6 +111,7 @@ class MetricsPlugin extends Plugin {
|
|||
resolutionChanges: this.resolutionChanges,
|
||||
|
||||
errors: this.errors,
|
||||
bufferStalled: this.bufferStalled,
|
||||
|
||||
downloadedBytesHTTP: this.downloadedBytesHTTP,
|
||||
|
||||
|
@ -128,6 +133,8 @@ class MetricsPlugin extends Plugin {
|
|||
|
||||
this.errors = 0
|
||||
|
||||
this.bufferStalled = 0
|
||||
|
||||
const headers = new Headers({ 'Content-type': 'application/json; charset=UTF-8' })
|
||||
|
||||
return fetch(this.options_.metricsUrl(), { method: 'POST', body: JSON.stringify(body), headers })
|
||||
|
@ -161,9 +168,19 @@ class MetricsPlugin extends Plugin {
|
|||
|
||||
private trackErrors () {
|
||||
this.player.on('error', () => {
|
||||
debugLogger('Adding player error')
|
||||
|
||||
this.errors++
|
||||
})
|
||||
}
|
||||
|
||||
private trackBufferStalled () {
|
||||
this.player.on('buffer-stalled', () => {
|
||||
debugLogger('Adding buffer stalled')
|
||||
|
||||
this.bufferStalled++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerPlugin('metrics', MetricsPlugin)
|
||||
|
|
|
@ -159,6 +159,15 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
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 () {
|
||||
|
|
|
@ -71,15 +71,11 @@ class Logger {
|
|||
console.error('Cannot set tokens to client log sender.', { err })
|
||||
}
|
||||
|
||||
try {
|
||||
fetch(serverUrl + '/api/v1/server/logs/client', {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Cannot send client warn/error to server.', err)
|
||||
}
|
||||
fetch(serverUrl + '/api/v1/server/logs/client', {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload)
|
||||
}).catch(err => console.error('Cannot send client warn/error to server.', err))
|
||||
}
|
||||
|
||||
private buildServerLogPayload (level: Extract<LoggerLevel, 'warn' | 'error'>, message: LoggerMessage, meta?: LoggerMeta) {
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface PlaybackMetricCreate {
|
|||
resolutionChanges: number
|
||||
|
||||
errors: number
|
||||
bufferStalled: number
|
||||
|
||||
downloadedBytesP2P: number
|
||||
downloadedBytesHTTP: number
|
||||
|
|
|
@ -48,6 +48,7 @@ describe('Test metrics API validators', function () {
|
|||
downloadedBytesP2P: 0,
|
||||
downloadedBytesHTTP: 0,
|
||||
uploadedBytesP2P: 0,
|
||||
bufferStalled: 0,
|
||||
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 () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { HttpStatusCode, PlaybackMetricCreate, VideoPrivacy, VideoResolution } from '@peertube/peertube-models'
|
||||
import { HttpStatusCode, VideoPrivacy, VideoResolution } from '@peertube/peertube-models'
|
||||
import {
|
||||
cleanupTests,
|
||||
createSingleServer,
|
||||
|
@ -9,8 +8,9 @@ import {
|
|||
PeerTubeServer,
|
||||
setAccessTokensToServers
|
||||
} 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 { expect } from 'chai'
|
||||
|
||||
describe('Open Telemetry', function () {
|
||||
let server: PeerTubeServer
|
||||
|
@ -73,6 +73,7 @@ describe('Open Telemetry', function () {
|
|||
downloadedBytesHTTP: 0,
|
||||
uploadedBytesP2P: 5,
|
||||
p2pPeers: 1,
|
||||
bufferStalled: 2,
|
||||
p2pEnabled: false,
|
||||
videoId: video.uuid
|
||||
}
|
||||
|
@ -91,7 +92,7 @@ describe('Open Telemetry', function () {
|
|||
const video = await server.videos.quickUpload({ name: 'video' })
|
||||
|
||||
const metrics = {
|
||||
playerMode: 'p2p-media-loader',
|
||||
playerMode: 'p2p-media-loader' as 'p2p-media-loader',
|
||||
resolution: VideoResolution.H_1080P,
|
||||
fps: 30,
|
||||
resolutionChanges: 1,
|
||||
|
@ -100,9 +101,10 @@ describe('Open Telemetry', function () {
|
|||
downloadedBytesHTTP: 0,
|
||||
uploadedBytesP2P: 5,
|
||||
p2pPeers: 7,
|
||||
bufferStalled: 8,
|
||||
p2pEnabled: false,
|
||||
videoId: video.uuid
|
||||
} as PlaybackMetricCreate
|
||||
}
|
||||
|
||||
await server.metrics.addPlaybackMetric({ metrics })
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import { PlaybackMetricCreate } from '@peertube/peertube-models'
|
|||
export class PlaybackMetrics {
|
||||
private errorsCounter: Counter
|
||||
private resolutionChangesCounter: Counter
|
||||
private bufferStalledCounter: Counter
|
||||
|
||||
private downloadedBytesP2PCounter: Counter
|
||||
private uploadedBytesP2PCounter: Counter
|
||||
|
@ -29,6 +30,10 @@ export class PlaybackMetrics {
|
|||
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', {
|
||||
description: 'Downloaded bytes with HTTP by PeerTube player.'
|
||||
})
|
||||
|
@ -75,6 +80,10 @@ export class PlaybackMetrics {
|
|||
|
||||
this.uploadedBytesP2PCounter.add(metrics.uploadedBytesP2P, attributes)
|
||||
|
||||
if (metrics.bufferStalled) {
|
||||
this.bufferStalledCounter.add(metrics.bufferStalled, attributes)
|
||||
}
|
||||
|
||||
if (metrics.p2pPeers) {
|
||||
this.peersP2PPeersGaugeBuffer.push({
|
||||
value: metrics.p2pPeers,
|
||||
|
|
|
@ -26,6 +26,10 @@ const addPlaybackMetricValidator = [
|
|||
body('resolutionChanges')
|
||||
.isInt({ min: 0 }),
|
||||
|
||||
body('bufferStalled')
|
||||
.optional()
|
||||
.isInt({ min: 0 }),
|
||||
|
||||
body('errors')
|
||||
.isInt({ min: 0 }),
|
||||
|
||||
|
|
|
@ -10987,6 +10987,9 @@ components:
|
|||
resolutionChanges:
|
||||
type: number
|
||||
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:
|
||||
type: number
|
||||
description: How many errors occurred since the last metric creation
|
||||
|
|
Loading…
Reference in New Issue