Add p2p info to metrics
This commit is contained in:
parent
b63c607b92
commit
c6867725fb
15 changed files with 141 additions and 73 deletions
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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: {
|
||||
p2p: this.options.p2pEnabled
|
||||
? {
|
||||
downloadSpeed: p2pDownloadSpeed,
|
||||
uploadSpeed: p2pUploadSpeed,
|
||||
numPeers: this.statsP2PBytes.numPeers,
|
||||
peersWithWebSeed: this.statsP2PBytes.peersWithWebSeed,
|
||||
peersP2POnly: this.statsP2PBytes.peersP2POnly,
|
||||
downloaded: this.statsP2PBytes.totalDownload,
|
||||
uploaded: this.statsP2PBytes.totalUpload
|
||||
},
|
||||
bandwidthEstimate: (this.hlsjs as any).bandwidthEstimate / 8
|
||||
}
|
||||
: undefined
|
||||
} as PlayerNetworkInfo)
|
||||
}, 1000)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -35,6 +35,10 @@ smtp:
|
|||
log:
|
||||
level: 'debug'
|
||||
|
||||
open_telemetry:
|
||||
metrics:
|
||||
enabled: true
|
||||
|
||||
contact_form:
|
||||
enabled: true
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,11 @@ const addPlaybackMetricValidator = [
|
|||
.optional()
|
||||
.isInt({ min: 0 }),
|
||||
|
||||
body('totalPeers')
|
||||
body('p2pPeers')
|
||||
.optional()
|
||||
.isInt({ min: 0 }),
|
||||
|
||||
body('p2pEnabled')
|
||||
.optional()
|
||||
.isBoolean(),
|
||||
|
||||
body('playerMode')
|
||||
|
|
|
@ -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' }
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -6,8 +6,8 @@ export interface PlaybackMetricCreate {
|
|||
resolution?: VideoResolution
|
||||
fps?: number
|
||||
|
||||
p2pEnabled?: boolean
|
||||
totalPeers?: number
|
||||
p2pEnabled: boolean
|
||||
p2pPeers?: number
|
||||
|
||||
resolutionChanges: number
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue