diff --git a/client/src/assets/player/shared/metrics/metrics-plugin.ts b/client/src/assets/player/shared/metrics/metrics-plugin.ts index 15824f236..8ea6e13ee 100644 --- a/client/src/assets/player/shared/metrics/metrics-plugin.ts +++ b/client/src/assets/player/shared/metrics/metrics-plugin.ts @@ -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) diff --git a/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts index 5e6409959..591497df5 100644 --- a/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts @@ -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 () { diff --git a/client/src/root-helpers/logger.ts b/client/src/root-helpers/logger.ts index 108228d84..37e5fc3c1 100644 --- a/client/src/root-helpers/logger.ts +++ b/client/src/root-helpers/logger.ts @@ -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, message: LoggerMessage, meta?: LoggerMeta) { diff --git a/packages/models/src/metrics/playback-metric-create.model.ts b/packages/models/src/metrics/playback-metric-create.model.ts index 3ae91b295..f00fa75cf 100644 --- a/packages/models/src/metrics/playback-metric-create.model.ts +++ b/packages/models/src/metrics/playback-metric-create.model.ts @@ -12,6 +12,7 @@ export interface PlaybackMetricCreate { resolutionChanges: number errors: number + bufferStalled: number downloadedBytesP2P: number downloadedBytesHTTP: number diff --git a/packages/tests/src/api/check-params/metrics.ts b/packages/tests/src/api/check-params/metrics.ts index cda854554..0dfae26d0 100644 --- a/packages/tests/src/api/check-params/metrics.ts +++ b/packages/tests/src/api/check-params/metrics.ts @@ -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, diff --git a/packages/tests/src/api/server/open-telemetry.ts b/packages/tests/src/api/server/open-telemetry.ts index 87b44d6f1..2bcf2a39f 100644 --- a/packages/tests/src/api/server/open-telemetry.ts +++ b/packages/tests/src/api/server/open-telemetry.ts @@ -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 }) diff --git a/server/core/lib/opentelemetry/metric-helpers/playback-metrics.ts b/server/core/lib/opentelemetry/metric-helpers/playback-metrics.ts index ec139f331..3511f1fba 100644 --- a/server/core/lib/opentelemetry/metric-helpers/playback-metrics.ts +++ b/server/core/lib/opentelemetry/metric-helpers/playback-metrics.ts @@ -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, diff --git a/server/core/middlewares/validators/metrics.ts b/server/core/middlewares/validators/metrics.ts index 8832b80d2..77dd988d9 100644 --- a/server/core/middlewares/validators/metrics.ts +++ b/server/core/middlewares/validators/metrics.ts @@ -26,6 +26,10 @@ const addPlaybackMetricValidator = [ body('resolutionChanges') .isInt({ min: 0 }), + body('bufferStalled') + .optional() + .isInt({ min: 0 }), + body('errors') .isInt({ min: 0 }), diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index b8be67a5d..54f97cd49 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -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