Add p2p info to metrics

This commit is contained in:
Chocobozzz 2023-07-21 11:42:52 +02:00
parent b63c607b92
commit c6867725fb
No known key found for this signature in database
GPG key ID: 583A612D890159BE
15 changed files with 141 additions and 73 deletions

View file

@ -203,7 +203,7 @@ export class PeerTubePlayer {
this.player.one('error', () => handleError())
this.player.on('p2p-info', (_, data: PlayerNetworkInfo) => {
this.player.on('network-info', (_, data: PlayerNetworkInfo) => {
if (data.source !== 'p2p-media-loader' || isNaN(data.bandwidthEstimate)) return
saveAverageBandwidth(data.bandwidthEstimate)

View file

@ -39,15 +39,14 @@ class P2PInfoButton extends Button {
subDivP2P.appendChild(peersText)
const subDivHttp = videojs.dom.createEl('div', { className: 'vjs-peertube-hidden' }) as HTMLElement
const subDivHttpText = videojs.dom.createEl('span', {
className: 'http-fallback',
textContent: 'HTTP'
})
const subDivHttpText = videojs.dom.createEl('span', { className: 'http-fallback' })
subDivHttp.appendChild(subDivHttpText)
div.appendChild(subDivHttp)
this.player_.on('p2p-info', (_event: any, data: PlayerNetworkInfo) => {
this.player_.on('network-info', (_event: any, data: PlayerNetworkInfo) => {
if (!data.p2p) return
subDivP2P.className = 'vjs-peertube-displayed'
subDivHttp.className = 'vjs-peertube-hidden'
@ -58,7 +57,7 @@ class P2PInfoButton extends Button {
const uploadSpeed = bytes(p2pStats.uploadSpeed)
const totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded)
const totalUploaded = bytes(p2pStats.uploaded)
const numPeers = p2pStats.numPeers
const numPeers = p2pStats.peersWithWebSeed
subDivP2P.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n'
@ -85,8 +84,13 @@ class P2PInfoButton extends Button {
subDivP2P.className = 'vjs-peertube-displayed'
})
this.player_.on('http-info', (_event, data: PlayerNetworkInfo) => {
// We are in HTTP fallback
this.player_.on('network-info', (_event, data: PlayerNetworkInfo) => {
if (data.p2p) return
if (data.source === 'web-video') subDivHttpText.textContent = 'HTTP'
else if (data.source === 'p2p-media-loader') subDivHttpText.textContent = 'HLS'
// We are in HTTP mode
subDivHttp.className = 'vjs-peertube-displayed'
subDivP2P.className = 'vjs-peertube-hidden'

View file

@ -19,7 +19,7 @@ class MetricsPlugin extends Plugin {
private errors = 0
private p2pEnabled: boolean
private totalPeers = 0
private p2pPeers = 0
private lastPlayerNetworkInfo: PlayerNetworkInfo
@ -111,12 +111,12 @@ class MetricsPlugin extends Plugin {
errors: this.errors,
downloadedBytesP2P: this.downloadedBytesP2P,
downloadedBytesHTTP: this.downloadedBytesHTTP,
downloadedBytesP2P: this.downloadedBytesP2P,
uploadedBytesP2P: this.uploadedBytesP2P,
totalPeers: this.totalPeers,
p2pPeers: this.p2pPeers,
p2pEnabled: this.p2pEnabled,
videoId: this.options_.videoUUID()
@ -139,23 +139,14 @@ class MetricsPlugin extends Plugin {
}
private trackBytes () {
this.player.on('p2p-info', (_event, data: PlayerNetworkInfo) => {
this.player.on('network-info', (_event, data: PlayerNetworkInfo) => {
this.downloadedBytesHTTP += Math.round(data.http.downloaded - (this.lastPlayerNetworkInfo?.http.downloaded || 0))
this.downloadedBytesP2P += Math.round(data.p2p.downloaded - (this.lastPlayerNetworkInfo?.p2p.downloaded || 0))
this.downloadedBytesP2P += Math.round((data.p2p?.downloaded || 0) - (this.lastPlayerNetworkInfo?.p2p?.downloaded || 0))
this.uploadedBytesP2P += Math.round(data.p2p.uploaded - (this.lastPlayerNetworkInfo?.p2p.uploaded || 0))
this.uploadedBytesP2P += Math.round((data.p2p?.uploaded || 0) - (this.lastPlayerNetworkInfo?.p2p?.uploaded || 0))
this.totalPeers = data.p2p.numPeers
this.p2pEnabled = true
this.lastPlayerNetworkInfo = data
})
this.player.on('http-info', (_event, data: PlayerNetworkInfo) => {
this.downloadedBytesHTTP += Math.round(data.http.downloaded - (this.lastPlayerNetworkInfo?.http.downloaded || 0))
this.totalPeers = 0
this.p2pEnabled = false
this.p2pPeers = data.p2p?.peersP2POnly
this.p2pEnabled = !!data.p2p
this.lastPlayerNetworkInfo = data
})

View file

@ -16,7 +16,8 @@ class P2pMediaLoaderPlugin extends Plugin {
private statsP2PBytes = {
pendingDownload: [] as number[],
pendingUpload: [] as number[],
numPeers: 0,
peersWithWebSeed: 0,
peersP2POnly: 0,
totalDownload: 0,
totalUpload: 0
}
@ -113,7 +114,7 @@ class P2pMediaLoaderPlugin extends Plugin {
this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl)
})
this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls()
this.statsP2PBytes.peersWithWebSeed = 1 + this.options.redundancyUrlManager.countBaseUrls()
this.runStats()
@ -138,8 +139,14 @@ class P2pMediaLoaderPlugin extends Plugin {
this.statsP2PBytes.totalUpload += bytes
})
this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--)
this.p2pEngine.on(Events.PeerConnect, () => {
this.statsP2PBytes.peersWithWebSeed++
this.statsP2PBytes.peersP2POnly++
})
this.p2pEngine.on(Events.PeerClose, () => {
this.statsP2PBytes.peersWithWebSeed--
this.statsP2PBytes.peersP2POnly--
})
this.networkInfoInterval = setInterval(() => {
const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload)
@ -151,20 +158,23 @@ class P2pMediaLoaderPlugin extends Plugin {
this.statsP2PBytes.pendingUpload = []
this.statsHTTPBytes.pendingDownload = []
return this.player.trigger('p2p-info', {
return this.player.trigger('network-info', {
source: 'p2p-media-loader',
bandwidthEstimate: (this.hlsjs as any).bandwidthEstimate / 8,
http: {
downloadSpeed: httpDownloadSpeed,
downloaded: this.statsHTTPBytes.totalDownload
},
p2p: {
downloadSpeed: p2pDownloadSpeed,
uploadSpeed: p2pUploadSpeed,
numPeers: this.statsP2PBytes.numPeers,
downloaded: this.statsP2PBytes.totalDownload,
uploaded: this.statsP2PBytes.totalUpload
},
bandwidthEstimate: (this.hlsjs as any).bandwidthEstimate / 8
p2p: this.options.p2pEnabled
? {
downloadSpeed: p2pDownloadSpeed,
uploadSpeed: p2pUploadSpeed,
peersWithWebSeed: this.statsP2PBytes.peersWithWebSeed,
peersP2POnly: this.statsP2PBytes.peersP2POnly,
downloaded: this.statsP2PBytes.totalDownload,
uploaded: this.statsP2PBytes.totalUpload
}
: undefined
} as PlayerNetworkInfo)
}, 1000)
}

View file

@ -44,6 +44,8 @@ export class HLSOptionsBuilder {
requiresUserAuth: this.options.requiresUserAuth,
videoFileToken: this.options.videoFileToken,
p2pEnabled: this.options.p2pEnabled,
redundancyUrlManager,
type: 'application/x-mpegURL',
src: this.options.hls.playlistUrl,

View file

@ -63,8 +63,7 @@ class StatsCard extends Component {
private liveLatency: InfoElement
private onP2PInfoHandler: (_event: any, data: EventPlayerNetworkInfo) => void
private onHTTPInfoHandler: (_event: any, data: EventPlayerNetworkInfo) => void
private onNetworkInfoHandler: (_event: any, data: EventPlayerNetworkInfo) => void
createEl () {
this.containerEl = videojs.dom.createEl('div', {
@ -89,33 +88,26 @@ class StatsCard extends Component {
this.populateInfoBlocks()
this.onP2PInfoHandler = (_event, data) => {
this.onNetworkInfoHandler = (_event, data) => {
this.mode = data.source
const p2pStats = data.p2p
const httpStats = data.http
this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ')
this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed).join(' ')
this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ')
this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded).join(' ')
this.playerNetworkInfo.numPeers = p2pStats.numPeers
this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s'
this.playerNetworkInfo.downloadSpeed = bytes((p2pStats?.downloadSpeed || 0) + (httpStats.downloadSpeed || 0)).join(' ')
this.playerNetworkInfo.uploadSpeed = bytes(p2pStats?.uploadSpeed || 0).join(' ')
this.playerNetworkInfo.totalDownloaded = bytes((p2pStats?.downloaded || 0) + httpStats.downloaded).join(' ')
this.playerNetworkInfo.totalUploaded = bytes(p2pStats?.uploaded || 0).join(' ')
this.playerNetworkInfo.numPeers = p2pStats?.peersWithWebSeed
if (data.source === 'p2p-media-loader') {
this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s'
this.playerNetworkInfo.downloadedFromServer = bytes(httpStats.downloaded).join(' ')
this.playerNetworkInfo.downloadedFromPeers = bytes(p2pStats.downloaded).join(' ')
this.playerNetworkInfo.downloadedFromPeers = bytes(p2pStats?.downloaded || 0).join(' ')
}
}
this.onHTTPInfoHandler = (_event, data) => {
this.mode = data.source
this.playerNetworkInfo.totalDownloaded = bytes(data.http.downloaded).join(' ')
}
this.player().on('p2p-info', this.onP2PInfoHandler)
this.player().on('http-info', this.onHTTPInfoHandler)
this.player().on('network-info', this.onNetworkInfoHandler)
return this.containerEl
}
@ -123,8 +115,7 @@ class StatsCard extends Component {
dispose () {
if (this.updateInterval) clearInterval(this.updateInterval)
this.player().off('p2p-info', this.onP2PInfoHandler)
this.player().off('http-info', this.onHTTPInfoHandler)
this.player().off('network-info', this.onNetworkInfoHandler)
super.dispose()
}

View file

@ -175,7 +175,7 @@ class WebVideoPlugin extends Plugin {
private setupNetworkInfoInterval () {
this.networkInfoInterval = setInterval(() => {
return this.player.trigger('http-info', {
return this.player.trigger('network-info', {
source: 'web-video',
http: {
downloaded: this.player.bufferedPercent() * this.currentVideoFile.size

View file

@ -184,6 +184,8 @@ type P2PMediaLoaderPluginOptions = {
type: string
src: string
p2pEnabled: boolean
loader: P2PMediaLoader
segmentValidator: SegmentValidator
@ -240,9 +242,12 @@ type PlayerNetworkInfo = {
p2p?: {
downloadSpeed: number
uploadSpeed: number
downloaded: number
uploaded: number
numPeers: number
peersWithWebSeed: number
peersP2POnly: number
}
// In bytes

View file

@ -35,6 +35,10 @@ smtp:
log:
level: 'debug'
open_telemetry:
metrics:
enabled: true
contact_form:
enabled: true

View file

@ -1,4 +1,4 @@
import { Counter, Histogram, Meter } from '@opentelemetry/api'
import { Counter, Meter } from '@opentelemetry/api'
import { MVideoImmutable } from '@server/types/models'
import { PlaybackMetricCreate } from '@shared/models'
@ -11,7 +11,10 @@ export class PlaybackMetrics {
private downloadedBytesHTTPCounter: Counter
private peersP2PPeers: Histogram
private peersP2PPeersGaugeBuffer: {
value: number
attributes: any
}[] = []
constructor (private readonly meter: Meter) {
@ -37,8 +40,14 @@ export class PlaybackMetrics {
description: 'Uploaded bytes with P2P by PeerTube player.'
})
this.peersP2PPeers = this.meter.createHistogram('peertube_playback_p2p_peers', {
this.meter.createObservableGauge('peertube_playback_p2p_peers', {
description: 'Total P2P peers connected to the PeerTube player.'
}).addCallback(observableResult => {
for (const gauge of this.peersP2PPeersGaugeBuffer) {
observableResult.observe(gauge.value, gauge.attributes)
}
this.peersP2PPeersGaugeBuffer = []
})
}
@ -66,6 +75,11 @@ export class PlaybackMetrics {
this.uploadedBytesP2PCounter.add(metrics.uploadedBytesP2P, attributes)
if (metrics.totalPeers) this.peersP2PPeers.record(metrics.totalPeers, attributes)
if (metrics.p2pPeers) {
this.peersP2PPeersGaugeBuffer.push({
value: metrics.p2pPeers,
attributes
})
}
}
}

View file

@ -13,12 +13,11 @@ const addPlaybackMetricValidator = [
.optional()
.isInt({ min: 0 }),
body('totalPeers')
body('p2pPeers')
.optional()
.isInt({ min: 0 }),
body('p2pEnabled')
.optional()
.isBoolean(),
body('playerMode')

View file

@ -38,6 +38,7 @@ describe('Test metrics API validators', function () {
fps: 30,
resolutionChanges: 1,
errors: 2,
p2pEnabled: true,
downloadedBytesP2P: 0,
downloadedBytesHTTP: 0,
uploadedBytesP2P: 0,
@ -145,7 +146,13 @@ describe('Test metrics API validators', function () {
})
})
it('Should fail with an invalid p2pEnabled', async function () {
it('Should fail with a missing/invalid p2pEnabled', async function () {
await makePostBodyRequest({
url: server.url,
path,
fields: omit(baseParams, [ 'p2pEnabled' ])
})
await makePostBodyRequest({
url: server.url,
path,
@ -157,7 +164,7 @@ describe('Test metrics API validators', function () {
await makePostBodyRequest({
url: server.url,
path,
fields: { ...baseParams, totalPeers: 'toto' }
fields: { ...baseParams, p2pPeers: 'toto' }
})
})

View file

@ -2,7 +2,7 @@
import { expect } from 'chai'
import { expectLogContain, expectLogDoesNotContain, MockHTTP } from '@server/tests/shared'
import { HttpStatusCode, VideoPrivacy, VideoResolution } from '@shared/models'
import { HttpStatusCode, PlaybackMetricCreate, VideoPrivacy, VideoResolution } from '@shared/models'
import { cleanupTests, createSingleServer, makeRawRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
describe('Open Telemetry', function () {
@ -62,14 +62,49 @@ describe('Open Telemetry', function () {
downloadedBytesP2P: 0,
downloadedBytesHTTP: 0,
uploadedBytesP2P: 5,
totalPeers: 1,
p2pPeers: 1,
p2pEnabled: false,
videoId: video.uuid
}
})
const res = await makeRawRequest({ url: metricsUrl, expectedStatus: HttpStatusCode.OK_200 })
expect(res.text).to.contain('peertube_playback_http_downloaded_bytes_total{')
expect(res.text).to.contain('peertube_playback_p2p_peers{')
expect(res.text).to.contain('p2pEnabled="false"')
})
it('Should take the last playback metric', async function () {
await setAccessTokensToServers([ server ])
const video = await server.videos.quickUpload({ name: 'video' })
const metrics = {
playerMode: 'p2p-media-loader',
resolution: VideoResolution.H_1080P,
fps: 30,
resolutionChanges: 1,
errors: 2,
downloadedBytesP2P: 0,
downloadedBytesHTTP: 0,
uploadedBytesP2P: 5,
p2pPeers: 7,
p2pEnabled: false,
videoId: video.uuid
} as PlaybackMetricCreate
await server.metrics.addPlaybackMetric({ metrics })
metrics.p2pPeers = 42
await server.metrics.addPlaybackMetric({ metrics })
const res = await makeRawRequest({ url: metricsUrl, expectedStatus: HttpStatusCode.OK_200 })
// eslint-disable-next-line max-len
const label = `{videoOrigin="local",playerMode="p2p-media-loader",resolution="1080",fps="30",p2pEnabled="false",videoUUID="${video.uuid}"}`
expect(res.text).to.contain(`peertube_playback_p2p_peers${label} 42`)
expect(res.text).to.not.contain(`peertube_playback_p2p_peers${label} 7`)
})
it('Should disable http request duration metrics', async function () {

View file

@ -6,8 +6,8 @@ export interface PlaybackMetricCreate {
resolution?: VideoResolution
fps?: number
p2pEnabled?: boolean
totalPeers?: number
p2pEnabled: boolean
p2pPeers?: number
resolutionChanges: number

View file

@ -9528,6 +9528,11 @@ components:
fps:
type: number
description: Current player video fps
p2pEnabled:
type: boolean
p2pPeers:
type: number
description: P2P peers connected (doesn't include WebSeed peers)
resolutionChanges:
type: number
description: How many resolution changes occured since the last metric creation
@ -9555,6 +9560,7 @@ components:
- downloadedBytesP2P
- downloadedBytesHTTP
- uploadedBytesP2P
- p2pEnabled
- videoId
RunnerRegistrationToken: