Add playback metric endpoint sent to OTEL
This commit is contained in:
parent
0e6cd1c00f
commit
fd3c2e8705
|
@ -628,6 +628,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
: null,
|
||||
authorizationHeader: this.authService.getRequestHeaderValue(),
|
||||
|
||||
metricsUrl: environment.apiUrl + '/api/v1/metrics/playback',
|
||||
|
||||
embedUrl: video.embedUrl,
|
||||
embedTitle: video.name,
|
||||
instanceName: this.serverConfig.instance.name,
|
||||
|
|
|
@ -22,6 +22,7 @@ import './shared/playlist/playlist-plugin'
|
|||
import './shared/mobile/peertube-mobile-plugin'
|
||||
import './shared/mobile/peertube-mobile-buttons'
|
||||
import './shared/hotkeys/peertube-hotkeys-plugin'
|
||||
import './shared/metrics/metrics-plugin'
|
||||
import videojs from 'video.js'
|
||||
import { logger } from '@root-helpers/logger'
|
||||
import { PluginsManager } from '@root-helpers/plugins-manager'
|
||||
|
|
|
@ -87,9 +87,9 @@ class P2pInfoButton extends Button {
|
|||
const httpStats = data.http
|
||||
|
||||
const downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed)
|
||||
const uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed)
|
||||
const uploadSpeed = bytes(p2pStats.uploadSpeed)
|
||||
const totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded)
|
||||
const totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded)
|
||||
const totalUploaded = bytes(p2pStats.uploaded)
|
||||
const numPeers = p2pStats.numPeers
|
||||
|
||||
subDivWebtorrent.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n'
|
||||
|
|
|
@ -44,6 +44,14 @@ export class ManagerOptionsBuilder {
|
|||
'isLive',
|
||||
'videoUUID'
|
||||
])
|
||||
},
|
||||
metrics: {
|
||||
mode: this.mode,
|
||||
|
||||
...pick(commonOptions, [
|
||||
'metricsUrl',
|
||||
'videoUUID'
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './metrics-plugin'
|
|
@ -0,0 +1,128 @@
|
|||
import videojs from 'video.js'
|
||||
import { PlaybackMetricCreate } from '../../../../../../shared/models'
|
||||
import { MetricsPluginOptions, PlayerMode, PlayerNetworkInfo } from '../../types'
|
||||
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
|
||||
class MetricsPlugin extends Plugin {
|
||||
private readonly metricsUrl: string
|
||||
private readonly videoUUID: string
|
||||
private readonly mode: PlayerMode
|
||||
|
||||
private downloadedBytesP2P = 0
|
||||
private downloadedBytesHTTP = 0
|
||||
private uploadedBytesP2P = 0
|
||||
|
||||
private resolutionChanges = 0
|
||||
private errors = 0
|
||||
|
||||
private lastPlayerNetworkInfo: PlayerNetworkInfo
|
||||
|
||||
private metricsInterval: any
|
||||
|
||||
private readonly CONSTANTS = {
|
||||
METRICS_INTERVAL: 15000
|
||||
}
|
||||
|
||||
constructor (player: videojs.Player, options: MetricsPluginOptions) {
|
||||
super(player)
|
||||
|
||||
this.metricsUrl = options.metricsUrl
|
||||
this.videoUUID = options.videoUUID
|
||||
this.mode = options.mode
|
||||
|
||||
this.player.one('play', () => {
|
||||
this.runMetricsInterval()
|
||||
|
||||
this.trackBytes()
|
||||
this.trackResolutionChange()
|
||||
this.trackErrors()
|
||||
})
|
||||
}
|
||||
|
||||
dispose () {
|
||||
if (this.metricsInterval) clearInterval(this.metricsInterval)
|
||||
}
|
||||
|
||||
private runMetricsInterval () {
|
||||
this.metricsInterval = setInterval(() => {
|
||||
let resolution: number
|
||||
let fps: number
|
||||
|
||||
if (this.mode === 'p2p-media-loader') {
|
||||
const level = this.player.p2pMediaLoader().getCurrentLevel()
|
||||
if (!level) return
|
||||
|
||||
resolution = Math.min(level.height || 0, level.width || 0)
|
||||
|
||||
const framerate = level?.attrs['FRAME-RATE']
|
||||
fps = framerate
|
||||
? parseInt(framerate, 10)
|
||||
: undefined
|
||||
} else { // webtorrent
|
||||
const videoFile = this.player.webtorrent().getCurrentVideoFile()
|
||||
if (!videoFile) return
|
||||
|
||||
resolution = videoFile.resolution.id
|
||||
fps = videoFile.fps
|
||||
}
|
||||
|
||||
const body: PlaybackMetricCreate = {
|
||||
resolution,
|
||||
fps,
|
||||
|
||||
playerMode: this.mode,
|
||||
|
||||
resolutionChanges: this.resolutionChanges,
|
||||
|
||||
errors: this.errors,
|
||||
|
||||
downloadedBytesP2P: this.downloadedBytesP2P,
|
||||
downloadedBytesHTTP: this.downloadedBytesHTTP,
|
||||
|
||||
uploadedBytesP2P: this.uploadedBytesP2P,
|
||||
|
||||
videoId: this.videoUUID
|
||||
}
|
||||
|
||||
this.resolutionChanges = 0
|
||||
|
||||
this.downloadedBytesP2P = 0
|
||||
this.downloadedBytesHTTP = 0
|
||||
|
||||
this.uploadedBytesP2P = 0
|
||||
|
||||
this.errors = 0
|
||||
|
||||
const headers = new Headers({ 'Content-type': 'application/json; charset=UTF-8' })
|
||||
|
||||
return fetch(this.metricsUrl, { method: 'POST', body: JSON.stringify(body), headers })
|
||||
}, this.CONSTANTS.METRICS_INTERVAL)
|
||||
}
|
||||
|
||||
private trackBytes () {
|
||||
this.player.on('p2pInfo', (_event, data: PlayerNetworkInfo) => {
|
||||
this.downloadedBytesHTTP += data.http.downloaded - (this.lastPlayerNetworkInfo?.http.downloaded || 0)
|
||||
this.downloadedBytesP2P += data.p2p.downloaded - (this.lastPlayerNetworkInfo?.p2p.downloaded || 0)
|
||||
|
||||
this.uploadedBytesP2P += data.p2p.uploaded - (this.lastPlayerNetworkInfo?.p2p.uploaded || 0)
|
||||
|
||||
this.lastPlayerNetworkInfo = data
|
||||
})
|
||||
}
|
||||
|
||||
private trackResolutionChange () {
|
||||
this.player.on('engineResolutionChange', () => {
|
||||
this.resolutionChanges++
|
||||
})
|
||||
}
|
||||
|
||||
private trackErrors () {
|
||||
this.player.on('error', () => {
|
||||
this.errors++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerPlugin('metrics', MetricsPlugin)
|
||||
export { MetricsPlugin }
|
|
@ -2,10 +2,10 @@ import Hlsjs from 'hls.js'
|
|||
import videojs from 'video.js'
|
||||
import { Events, Segment } from '@peertube/p2p-media-loader-core'
|
||||
import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs'
|
||||
import { logger } from '@root-helpers/logger'
|
||||
import { timeToInt } from '@shared/core-utils'
|
||||
import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types'
|
||||
import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
|
||||
import { logger } from '@root-helpers/logger'
|
||||
|
||||
registerConfigPlugin(videojs)
|
||||
registerSourceHandler(videojs)
|
||||
|
@ -29,9 +29,7 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
}
|
||||
private statsHTTPBytes = {
|
||||
pendingDownload: [] as number[],
|
||||
pendingUpload: [] as number[],
|
||||
totalDownload: 0,
|
||||
totalUpload: 0
|
||||
totalDownload: 0
|
||||
}
|
||||
private startTime: number
|
||||
|
||||
|
@ -123,6 +121,8 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls()
|
||||
|
||||
this.runStats()
|
||||
|
||||
this.hlsjs.on(Hlsjs.Events.LEVEL_SWITCHED, () => this.player.trigger('engineResolutionChange'))
|
||||
}
|
||||
|
||||
private runStats () {
|
||||
|
@ -134,10 +134,13 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
})
|
||||
|
||||
this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, _segment, bytes: number) => {
|
||||
const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes
|
||||
if (method !== 'p2p') {
|
||||
logger.error(`Received upload from unknown method ${method}`)
|
||||
return
|
||||
}
|
||||
|
||||
elem.pendingUpload.push(bytes)
|
||||
elem.totalUpload += bytes
|
||||
this.statsP2PBytes.pendingUpload.push(bytes)
|
||||
this.statsP2PBytes.totalUpload += bytes
|
||||
})
|
||||
|
||||
this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
|
||||
|
@ -148,20 +151,16 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload)
|
||||
|
||||
const httpDownloadSpeed = this.arraySum(this.statsHTTPBytes.pendingDownload)
|
||||
const httpUploadSpeed = this.arraySum(this.statsHTTPBytes.pendingUpload)
|
||||
|
||||
this.statsP2PBytes.pendingDownload = []
|
||||
this.statsP2PBytes.pendingUpload = []
|
||||
this.statsHTTPBytes.pendingDownload = []
|
||||
this.statsHTTPBytes.pendingUpload = []
|
||||
|
||||
return this.player.trigger('p2pInfo', {
|
||||
source: 'p2p-media-loader',
|
||||
http: {
|
||||
downloadSpeed: httpDownloadSpeed,
|
||||
uploadSpeed: httpUploadSpeed,
|
||||
downloaded: this.statsHTTPBytes.totalDownload,
|
||||
uploaded: this.statsHTTPBytes.totalUpload
|
||||
downloaded: this.statsHTTPBytes.totalDownload
|
||||
},
|
||||
p2p: {
|
||||
downloadSpeed: p2pDownloadSpeed,
|
||||
|
|
|
@ -144,6 +144,8 @@ class PeerTubePlugin extends Plugin {
|
|||
this.listenFullScreenChange()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private runUserViewing () {
|
||||
let lastCurrentTime = this.startTime
|
||||
let lastViewEvent: VideoViewEvent
|
||||
|
@ -205,6 +207,8 @@ class PeerTubePlugin extends Plugin {
|
|||
return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private listenFullScreenChange () {
|
||||
this.player.on('fullscreenchange', () => {
|
||||
if (this.player.isFullscreen()) this.player.focus()
|
||||
|
|
|
@ -95,9 +95,9 @@ class StatsCard extends Component {
|
|||
const httpStats = data.http
|
||||
|
||||
this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ')
|
||||
this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed).join(' ')
|
||||
this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed).join(' ')
|
||||
this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ')
|
||||
this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ')
|
||||
this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded).join(' ')
|
||||
this.playerNetworkInfo.numPeers = p2pStats.numPeers
|
||||
this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s'
|
||||
|
||||
|
|
|
@ -204,6 +204,8 @@ class WebTorrentPlugin extends Plugin {
|
|||
}
|
||||
|
||||
this.updateVideoFile(newVideoFile, options)
|
||||
|
||||
this.player.trigger('engineResolutionChange')
|
||||
}
|
||||
|
||||
flushVideoFile (videoFile: VideoFile, destroyRenderer = true) {
|
||||
|
@ -506,9 +508,7 @@ class WebTorrentPlugin extends Plugin {
|
|||
source: 'webtorrent',
|
||||
http: {
|
||||
downloadSpeed: 0,
|
||||
uploadSpeed: 0,
|
||||
downloaded: 0,
|
||||
uploaded: 0
|
||||
downloaded: 0
|
||||
},
|
||||
p2p: {
|
||||
downloadSpeed: this.torrent.downloadSpeed,
|
||||
|
|
|
@ -59,6 +59,8 @@ export interface CommonOptions extends CustomizationOptions {
|
|||
videoViewUrl: string
|
||||
authorizationHeader?: string
|
||||
|
||||
metricsUrl: string
|
||||
|
||||
embedUrl: string
|
||||
embedTitle: string
|
||||
|
||||
|
|
|
@ -109,6 +109,12 @@ type PeerTubePluginOptions = {
|
|||
videoUUID: string
|
||||
}
|
||||
|
||||
type MetricsPluginOptions = {
|
||||
mode: PlayerMode
|
||||
metricsUrl: string
|
||||
videoUUID: string
|
||||
}
|
||||
|
||||
type PlaylistPluginOptions = {
|
||||
elements: VideoPlaylistElement[]
|
||||
|
||||
|
@ -165,6 +171,7 @@ type VideoJSPluginOptions = {
|
|||
playlist?: PlaylistPluginOptions
|
||||
|
||||
peertube: PeerTubePluginOptions
|
||||
metrics: MetricsPluginOptions
|
||||
|
||||
webtorrent?: WebtorrentPluginOptions
|
||||
|
||||
|
@ -197,9 +204,7 @@ type PlayerNetworkInfo = {
|
|||
|
||||
http: {
|
||||
downloadSpeed: number
|
||||
uploadSpeed: number
|
||||
downloaded: number
|
||||
uploaded: number
|
||||
}
|
||||
|
||||
p2p: {
|
||||
|
@ -227,6 +232,7 @@ export {
|
|||
ResolutionUpdateData,
|
||||
AutoResolutionUpdateData,
|
||||
PlaylistPluginOptions,
|
||||
MetricsPluginOptions,
|
||||
VideoJSCaption,
|
||||
PeerTubePluginOptions,
|
||||
WebtorrentPluginOptions,
|
||||
|
|
|
@ -203,6 +203,7 @@ export class PlayerManagerOptions {
|
|||
videoCaptions,
|
||||
inactivityTimeout: 2500,
|
||||
videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid),
|
||||
metricsUrl: window.location.origin + '/api/v1/metrics/playback',
|
||||
|
||||
videoShortUUID: video.shortUUID,
|
||||
videoUUID: video.uuid,
|
||||
|
|
|
@ -148,3 +148,8 @@ geo_ip:
|
|||
|
||||
video_studio:
|
||||
enabled: true
|
||||
|
||||
open_telemetry:
|
||||
metrics:
|
||||
prometheus_exporter:
|
||||
port: 9092
|
||||
|
|
10
package.json
10
package.json
|
@ -86,18 +86,18 @@
|
|||
"@babel/parser": "^7.17.8",
|
||||
"@node-oauth/oauth2-server": "^4.2.0",
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
"@opentelemetry/api-metrics": "^0.30.0",
|
||||
"@opentelemetry/api-metrics": "^0.31.0",
|
||||
"@opentelemetry/exporter-jaeger": "^1.3.1",
|
||||
"@opentelemetry/exporter-prometheus": "~0.30.0",
|
||||
"@opentelemetry/instrumentation": "^0.30.0",
|
||||
"@opentelemetry/exporter-prometheus": "~0.31.0",
|
||||
"@opentelemetry/instrumentation": "^0.31.0",
|
||||
"@opentelemetry/instrumentation-dns": "^0.29.0",
|
||||
"@opentelemetry/instrumentation-express": "^0.30.0",
|
||||
"@opentelemetry/instrumentation-fs": "^0.4.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.30.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.31.0",
|
||||
"@opentelemetry/instrumentation-pg": "^0.30.0",
|
||||
"@opentelemetry/instrumentation-redis-4": "^0.31.0",
|
||||
"@opentelemetry/resources": "^1.3.1",
|
||||
"@opentelemetry/sdk-metrics-base": "~0.30.0",
|
||||
"@opentelemetry/sdk-metrics-base": "~0.31.0",
|
||||
"@opentelemetry/sdk-trace-base": "^1.3.1",
|
||||
"@opentelemetry/sdk-trace-node": "^1.3.1",
|
||||
"@opentelemetry/semantic-conventions": "^1.3.1",
|
||||
|
|
|
@ -11,6 +11,7 @@ import { bulkRouter } from './bulk'
|
|||
import { configRouter } from './config'
|
||||
import { customPageRouter } from './custom-page'
|
||||
import { jobsRouter } from './jobs'
|
||||
import { metricsRouter } from './metrics'
|
||||
import { oauthClientsRouter } from './oauth-clients'
|
||||
import { overviewsRouter } from './overviews'
|
||||
import { pluginRouter } from './plugins'
|
||||
|
@ -18,9 +19,9 @@ import { searchRouter } from './search'
|
|||
import { serverRouter } from './server'
|
||||
import { usersRouter } from './users'
|
||||
import { videoChannelRouter } from './video-channel'
|
||||
import { videoChannelSyncRouter } from './video-channel-sync'
|
||||
import { videoPlaylistRouter } from './video-playlist'
|
||||
import { videosRouter } from './videos'
|
||||
import { videoChannelSyncRouter } from './video-channel-sync'
|
||||
|
||||
const apiRouter = express.Router()
|
||||
|
||||
|
@ -48,6 +49,7 @@ apiRouter.use('/video-channel-syncs', videoChannelSyncRouter)
|
|||
apiRouter.use('/video-playlists', videoPlaylistRouter)
|
||||
apiRouter.use('/videos', videosRouter)
|
||||
apiRouter.use('/jobs', jobsRouter)
|
||||
apiRouter.use('/metrics', metricsRouter)
|
||||
apiRouter.use('/search', searchRouter)
|
||||
apiRouter.use('/overviews', overviewsRouter)
|
||||
apiRouter.use('/plugins', pluginRouter)
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import express from 'express'
|
||||
import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
|
||||
import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models'
|
||||
import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares'
|
||||
|
||||
const metricsRouter = express.Router()
|
||||
|
||||
metricsRouter.post('/playback',
|
||||
asyncMiddleware(addPlaybackMetricValidator),
|
||||
addPlaybackMetric
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
metricsRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function addPlaybackMetric (req: express.Request, res: express.Response) {
|
||||
const body: PlaybackMetricCreate = req.body
|
||||
|
||||
OpenTelemetryMetrics.Instance.observePlaybackMetric(res.locals.onlyImmutableVideo, body)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
function isValidPlayerMode (value: any) {
|
||||
return value === 'webtorrent' || value === 'p2p-media-loader'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
isValidPlayerMode
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
export * from './lives-observers-builder'
|
||||
export * from './job-queue-observers-builder'
|
||||
export * from './nodejs-observers-builder'
|
||||
export * from './playback-metrics'
|
||||
export * from './stats-observers-builder'
|
||||
export * from './viewers-observers-builder'
|
||||
|
|
|
@ -2,7 +2,7 @@ import { readdir } from 'fs-extra'
|
|||
import { constants, PerformanceObserver } from 'perf_hooks'
|
||||
import * as process from 'process'
|
||||
import { Meter, ObservableResult } from '@opentelemetry/api-metrics'
|
||||
import { ExplicitBucketHistogramAggregation, MeterProvider } from '@opentelemetry/sdk-metrics-base'
|
||||
import { ExplicitBucketHistogramAggregation } from '@opentelemetry/sdk-metrics-base'
|
||||
import { View } from '@opentelemetry/sdk-metrics-base/build/src/view/View'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
|
||||
|
@ -12,7 +12,16 @@ import { logger } from '@server/helpers/logger'
|
|||
|
||||
export class NodeJSObserversBuilder {
|
||||
|
||||
constructor (private readonly meter: Meter, private readonly meterProvider: MeterProvider) {
|
||||
constructor (private readonly meter: Meter) {
|
||||
}
|
||||
|
||||
static getViews () {
|
||||
return [
|
||||
new View({
|
||||
aggregation: new ExplicitBucketHistogramAggregation([ 0.001, 0.01, 0.1, 1, 2, 5 ]),
|
||||
instrumentName: 'nodejs_gc_duration_seconds'
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
buildObservers () {
|
||||
|
@ -91,11 +100,6 @@ export class NodeJSObserversBuilder {
|
|||
[constants.NODE_PERFORMANCE_GC_WEAKCB]: 'weakcb'
|
||||
}
|
||||
|
||||
this.meterProvider.addView(
|
||||
new View({ aggregation: new ExplicitBucketHistogramAggregation([ 0.001, 0.01, 0.1, 1, 2, 5 ]) }),
|
||||
{ instrument: { name: 'nodejs_gc_duration_seconds' } }
|
||||
)
|
||||
|
||||
const histogram = this.meter.createHistogram('nodejs_gc_duration_seconds', {
|
||||
description: 'Garbage collection duration by kind, one of major, minor, incremental or weakcb'
|
||||
})
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import { Counter, Meter } from '@opentelemetry/api-metrics'
|
||||
import { MVideoImmutable } from '@server/types/models'
|
||||
import { PlaybackMetricCreate } from '@shared/models'
|
||||
|
||||
export class PlaybackMetrics {
|
||||
private errorsCounter: Counter
|
||||
private resolutionChangesCounter: Counter
|
||||
|
||||
private downloadedBytesP2PCounter: Counter
|
||||
private uploadedBytesP2PCounter: Counter
|
||||
|
||||
private downloadedBytesHTTPCounter: Counter
|
||||
|
||||
constructor (private readonly meter: Meter) {
|
||||
|
||||
}
|
||||
|
||||
buildCounters () {
|
||||
this.errorsCounter = this.meter.createCounter('peertube_playback_errors_count', {
|
||||
description: 'Errors collected from PeerTube player.'
|
||||
})
|
||||
|
||||
this.resolutionChangesCounter = this.meter.createCounter('peertube_playback_resolution_changes_count', {
|
||||
description: 'Resolution changes collected from PeerTube player.'
|
||||
})
|
||||
|
||||
this.downloadedBytesHTTPCounter = this.meter.createCounter('peertube_playback_http_downloaded_bytes', {
|
||||
description: 'Downloaded bytes with HTTP by PeerTube player.'
|
||||
})
|
||||
this.downloadedBytesP2PCounter = this.meter.createCounter('peertube_playback_p2p_downloaded_bytes', {
|
||||
description: 'Downloaded bytes with P2P by PeerTube player.'
|
||||
})
|
||||
|
||||
this.uploadedBytesP2PCounter = this.meter.createCounter('peertube_playback_p2p_uploaded_bytes', {
|
||||
description: 'Uploaded bytes with P2P by PeerTube player.'
|
||||
})
|
||||
}
|
||||
|
||||
observe (video: MVideoImmutable, metrics: PlaybackMetricCreate) {
|
||||
const attributes = {
|
||||
videoOrigin: video.remote
|
||||
? 'remote'
|
||||
: 'local',
|
||||
|
||||
playerMode: metrics.playerMode,
|
||||
|
||||
resolution: metrics.resolution + '',
|
||||
fps: metrics.fps + ''
|
||||
}
|
||||
|
||||
this.errorsCounter.add(metrics.errors, attributes)
|
||||
this.resolutionChangesCounter.add(metrics.resolutionChanges, attributes)
|
||||
|
||||
this.downloadedBytesHTTPCounter.add(metrics.downloadedBytesHTTP, attributes)
|
||||
this.downloadedBytesP2PCounter.add(metrics.downloadedBytesP2P, attributes)
|
||||
|
||||
this.uploadedBytesP2PCounter.add(metrics.uploadedBytesP2P, attributes)
|
||||
}
|
||||
}
|
|
@ -4,10 +4,13 @@ import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
|
|||
import { MeterProvider } from '@opentelemetry/sdk-metrics-base'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { MVideoImmutable } from '@server/types/models'
|
||||
import { PlaybackMetricCreate } from '@shared/models'
|
||||
import {
|
||||
JobQueueObserversBuilder,
|
||||
LivesObserversBuilder,
|
||||
NodeJSObserversBuilder,
|
||||
PlaybackMetrics,
|
||||
StatsObserversBuilder,
|
||||
ViewersObserversBuilder
|
||||
} from './metric-helpers'
|
||||
|
@ -20,6 +23,8 @@ class OpenTelemetryMetrics {
|
|||
|
||||
private onRequestDuration: (req: Request, res: Response) => void
|
||||
|
||||
private playbackMetrics: PlaybackMetrics
|
||||
|
||||
private constructor () {}
|
||||
|
||||
init (app: Application) {
|
||||
|
@ -41,7 +46,11 @@ class OpenTelemetryMetrics {
|
|||
|
||||
logger.info('Registering Open Telemetry metrics')
|
||||
|
||||
const provider = new MeterProvider()
|
||||
const provider = new MeterProvider({
|
||||
views: [
|
||||
...NodeJSObserversBuilder.getViews()
|
||||
]
|
||||
})
|
||||
|
||||
provider.addMetricReader(new PrometheusExporter({ port: CONFIG.OPEN_TELEMETRY.METRICS.PROMETHEUS_EXPORTER.PORT }))
|
||||
|
||||
|
@ -51,7 +60,10 @@ class OpenTelemetryMetrics {
|
|||
|
||||
this.buildRequestObserver()
|
||||
|
||||
const nodeJSObserversBuilder = new NodeJSObserversBuilder(this.meter, provider)
|
||||
this.playbackMetrics = new PlaybackMetrics(this.meter)
|
||||
this.playbackMetrics.buildCounters()
|
||||
|
||||
const nodeJSObserversBuilder = new NodeJSObserversBuilder(this.meter)
|
||||
nodeJSObserversBuilder.buildObservers()
|
||||
|
||||
const jobQueueObserversBuilder = new JobQueueObserversBuilder(this.meter)
|
||||
|
@ -67,6 +79,10 @@ class OpenTelemetryMetrics {
|
|||
viewersObserversBuilder.buildObservers()
|
||||
}
|
||||
|
||||
observePlaybackMetric (video: MVideoImmutable, metrics: PlaybackMetricCreate) {
|
||||
this.playbackMetrics.observe(video, metrics)
|
||||
}
|
||||
|
||||
private buildRequestObserver () {
|
||||
const requestDuration = this.meter.createHistogram('http_request_duration_ms', {
|
||||
unit: 'milliseconds',
|
||||
|
|
|
@ -10,6 +10,7 @@ export * from './express'
|
|||
export * from './feeds'
|
||||
export * from './follows'
|
||||
export * from './jobs'
|
||||
export * from './metrics'
|
||||
export * from './logs'
|
||||
export * from './oembed'
|
||||
export * from './pagination'
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import express from 'express'
|
||||
import { body } from 'express-validator'
|
||||
import { isValidPlayerMode } from '@server/helpers/custom-validators/metrics'
|
||||
import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { areValidationErrors, doesVideoExist } from './shared'
|
||||
|
||||
const addPlaybackMetricValidator = [
|
||||
body('resolution')
|
||||
.isInt({ min: 0 }).withMessage('Invalid resolution'),
|
||||
body('fps')
|
||||
.optional()
|
||||
.isInt({ min: 0 }).withMessage('Invalid fps'),
|
||||
body('playerMode')
|
||||
.custom(isValidPlayerMode).withMessage('Invalid playerMode'),
|
||||
|
||||
body('resolutionChanges')
|
||||
.isInt({ min: 0 }).withMessage('Invalid resolutionChanges'),
|
||||
|
||||
body('errors')
|
||||
.isInt({ min: 0 }).withMessage('Invalid errors'),
|
||||
|
||||
body('downloadedBytesP2P')
|
||||
.isInt({ min: 0 }).withMessage('Invalid downloadedBytesP2P'),
|
||||
body('downloadedBytesHTTP')
|
||||
.isInt({ min: 0 }).withMessage('Invalid downloadedBytesHTTP'),
|
||||
|
||||
body('uploadedBytesP2P')
|
||||
.isInt({ min: 0 }).withMessage('Invalid uploadedBytesP2P'),
|
||||
|
||||
body('videoId')
|
||||
.customSanitizer(toCompleteUUID)
|
||||
.optional()
|
||||
.custom(isIdOrUUIDValid),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking addPlaybackMetricValidator parameters.', { parameters: req.query })
|
||||
|
||||
if (!CONFIG.OPEN_TELEMETRY.METRICS.ENABLED) return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
|
||||
const body: PlaybackMetricCreate = req.body
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await doesVideoExist(body.videoId, res, 'only-immutable-attributes')) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
addPlaybackMetricValidator
|
||||
}
|
|
@ -10,6 +10,7 @@ import './follows'
|
|||
import './jobs'
|
||||
import './live'
|
||||
import './logs'
|
||||
import './metrics'
|
||||
import './my-user'
|
||||
import './plugins'
|
||||
import './redundancy'
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import 'mocha'
|
||||
import { omit } from 'lodash'
|
||||
import { HttpStatusCode, PlaybackMetricCreate, VideoResolution } from '@shared/models'
|
||||
import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
|
||||
|
||||
describe('Test metrics API validators', function () {
|
||||
let server: PeerTubeServer
|
||||
let videoUUID: string
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
before(async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
server = await createSingleServer(1, {
|
||||
open_telemetry: {
|
||||
metrics: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await setAccessTokensToServers([ server ])
|
||||
|
||||
const { uuid } = await server.videos.quickUpload({ name: 'video' })
|
||||
videoUUID = uuid
|
||||
})
|
||||
|
||||
describe('When adding playback metrics', function () {
|
||||
const path = '/api/v1/metrics/playback'
|
||||
let baseParams: PlaybackMetricCreate
|
||||
|
||||
before(function () {
|
||||
baseParams = {
|
||||
playerMode: 'p2p-media-loader',
|
||||
resolution: VideoResolution.H_1080P,
|
||||
fps: 30,
|
||||
resolutionChanges: 1,
|
||||
errors: 2,
|
||||
downloadedBytesP2P: 0,
|
||||
downloadedBytesHTTP: 0,
|
||||
uploadedBytesP2P: 0,
|
||||
videoId: videoUUID
|
||||
}
|
||||
})
|
||||
|
||||
it('Should fail with an invalid resolution', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, resolution: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an invalid fps', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, fps: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with a missing/invalid player mode', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: omit(baseParams, 'playerMode')
|
||||
})
|
||||
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, playerMode: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an missing/invalid resolution changes', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: omit(baseParams, 'resolutionChanges')
|
||||
})
|
||||
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, resolutionChanges: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with a missing errors', async function () {
|
||||
|
||||
})
|
||||
|
||||
it('Should fail with an missing/invalid errors', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: omit(baseParams, 'errors')
|
||||
})
|
||||
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, errors: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an missing/invalid downloadedBytesP2P', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: omit(baseParams, 'downloadedBytesP2P')
|
||||
})
|
||||
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, downloadedBytesP2P: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an missing/invalid downloadedBytesHTTP', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: omit(baseParams, 'downloadedBytesHTTP')
|
||||
})
|
||||
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, downloadedBytesHTTP: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an missing/invalid uploadedBytesP2P', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: omit(baseParams, 'uploadedBytesP2P')
|
||||
})
|
||||
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, uploadedBytesP2P: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with a bad video id', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, videoId: 'toto' }
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an unknown video', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: { ...baseParams, videoId: 42 },
|
||||
expectedStatus: HttpStatusCode.NOT_FOUND_404
|
||||
})
|
||||
})
|
||||
|
||||
it('Should succeed with the correct params', async function () {
|
||||
await makePostBodyRequest({
|
||||
url: server.url,
|
||||
path,
|
||||
fields: baseParams,
|
||||
expectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests([ server ])
|
||||
})
|
||||
})
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
import { expect } from 'chai'
|
||||
import { expectLogContain, expectLogDoesNotContain, MockHTTP } from '@server/tests/shared'
|
||||
import { HttpStatusCode, VideoPrivacy } from '@shared/models'
|
||||
import { HttpStatusCode, VideoPrivacy, VideoResolution } from '@shared/models'
|
||||
import { cleanupTests, createSingleServer, makeRawRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
|
||||
|
||||
describe('Open Telemetry', function () {
|
||||
let server: PeerTubeServer
|
||||
|
||||
describe('Metrics', function () {
|
||||
const metricsUrl = 'http://localhost:9091/metrics'
|
||||
const metricsUrl = 'http://localhost:9092/metrics'
|
||||
|
||||
it('Should not enable open telemetry metrics', async function () {
|
||||
server = await createSingleServer(1)
|
||||
|
@ -36,8 +36,33 @@ describe('Open Telemetry', function () {
|
|||
})
|
||||
|
||||
const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200)
|
||||
expect(res.text).to.contain('peertube_job_queue_total')
|
||||
expect(res.text).to.contain('peertube_job_queue_total{')
|
||||
})
|
||||
|
||||
it('Should have playback metrics', async function () {
|
||||
await setAccessTokensToServers([ server ])
|
||||
|
||||
const video = await server.videos.quickUpload({ name: 'video' })
|
||||
|
||||
await server.metrics.addPlaybackMetric({
|
||||
metrics: {
|
||||
playerMode: 'p2p-media-loader',
|
||||
resolution: VideoResolution.H_1080P,
|
||||
fps: 30,
|
||||
resolutionChanges: 1,
|
||||
errors: 2,
|
||||
downloadedBytesP2P: 0,
|
||||
downloadedBytesHTTP: 0,
|
||||
uploadedBytesP2P: 5,
|
||||
videoId: video.uuid
|
||||
}
|
||||
})
|
||||
|
||||
const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200)
|
||||
expect(res.text).to.contain('peertube_playback_http_uploaded_bytes_total{')
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await server.kill()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ export * from './custom-markup'
|
|||
export * from './feeds'
|
||||
export * from './http'
|
||||
export * from './joinpeertube'
|
||||
export * from './metrics'
|
||||
export * from './moderation'
|
||||
export * from './overviews'
|
||||
export * from './plugins'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './playback-metric-create.model'
|
|
@ -0,0 +1,19 @@
|
|||
import { VideoResolution } from '../videos'
|
||||
|
||||
export interface PlaybackMetricCreate {
|
||||
playerMode: 'p2p-media-loader' | 'webtorrent'
|
||||
|
||||
resolution?: VideoResolution
|
||||
fps?: number
|
||||
|
||||
resolutionChanges: number
|
||||
|
||||
errors: number
|
||||
|
||||
downloadedBytesP2P: number
|
||||
downloadedBytesHTTP: number
|
||||
|
||||
uploadedBytesP2P: number
|
||||
|
||||
videoId: number | string
|
||||
}
|
|
@ -5,6 +5,7 @@ export * from './follows-command'
|
|||
export * from './follows'
|
||||
export * from './jobs'
|
||||
export * from './jobs-command'
|
||||
export * from './metrics-command'
|
||||
export * from './object-storage-command'
|
||||
export * from './plugins-command'
|
||||
export * from './redundancy-command'
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models'
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared'
|
||||
|
||||
export class MetricsCommand extends AbstractCommand {
|
||||
|
||||
addPlaybackMetric (options: OverrideCommandOptions & { metrics: PlaybackMetricCreate }) {
|
||||
const path = '/api/v1/metrics/playback'
|
||||
|
||||
return this.postBodyRequest({
|
||||
...options,
|
||||
|
||||
path,
|
||||
fields: options.metrics,
|
||||
implicitToken: false,
|
||||
defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
|
||||
})
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ import { ContactFormCommand } from './contact-form-command'
|
|||
import { DebugCommand } from './debug-command'
|
||||
import { FollowsCommand } from './follows-command'
|
||||
import { JobsCommand } from './jobs-command'
|
||||
import { MetricsCommand } from './metrics-command'
|
||||
import { ObjectStorageCommand } from './object-storage-command'
|
||||
import { PluginsCommand } from './plugins-command'
|
||||
import { RedundancyCommand } from './redundancy-command'
|
||||
|
@ -104,6 +105,7 @@ export class PeerTubeServer {
|
|||
debug?: DebugCommand
|
||||
follows?: FollowsCommand
|
||||
jobs?: JobsCommand
|
||||
metrics?: MetricsCommand
|
||||
plugins?: PluginsCommand
|
||||
redundancy?: RedundancyCommand
|
||||
stats?: StatsCommand
|
||||
|
@ -377,6 +379,7 @@ export class PeerTubeServer {
|
|||
this.debug = new DebugCommand(this)
|
||||
this.follows = new FollowsCommand(this)
|
||||
this.jobs = new JobsCommand(this)
|
||||
this.metrics = new MetricsCommand(this)
|
||||
this.plugins = new PluginsCommand(this)
|
||||
this.redundancy = new RedundancyCommand(this)
|
||||
this.stats = new StatsCommand(this)
|
||||
|
|
|
@ -5009,6 +5009,21 @@ paths:
|
|||
'404':
|
||||
description: plugin not found
|
||||
|
||||
/metrics/playback:
|
||||
post:
|
||||
summary: Create playback metrics
|
||||
description: These metrics are exposed by OpenTelemetry metrics exporter if enabled.
|
||||
tags:
|
||||
- Stats
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PlaybackMetricCreate'
|
||||
responses:
|
||||
'204':
|
||||
description: successful operation
|
||||
|
||||
servers:
|
||||
- url: 'https://peertube2.cpy.re/api/v1'
|
||||
description: Live Test Server (live data - latest nightly version)
|
||||
|
@ -8195,44 +8210,86 @@ components:
|
|||
format: binary
|
||||
|
||||
LiveVideoSessionResponse:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
startDate:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Start date of the live session
|
||||
endDate:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: End date of the live session
|
||||
error:
|
||||
type: integer
|
||||
enum:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
nullable: true
|
||||
description: >
|
||||
Error type if an error occurred during the live session:
|
||||
- `1`: Bad socket health (transcoding is too slow)
|
||||
- `2`: Max duration exceeded
|
||||
- `3`: Quota exceeded
|
||||
- `4`: Quota FFmpeg error
|
||||
- `5`: Video has been blacklisted during the live
|
||||
replayVideo:
|
||||
type: object
|
||||
description: Video replay information
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
uuid:
|
||||
$ref: '#/components/schemas/UUIDv4'
|
||||
shortUUID:
|
||||
$ref: '#/components/schemas/shortUUID'
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
startDate:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Start date of the live session
|
||||
endDate:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: End date of the live session
|
||||
error:
|
||||
type: integer
|
||||
enum:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
nullable: true
|
||||
description: >
|
||||
Error type if an error occurred during the live session:
|
||||
- `1`: Bad socket health (transcoding is too slow)
|
||||
- `2`: Max duration exceeded
|
||||
- `3`: Quota exceeded
|
||||
- `4`: Quota FFmpeg error
|
||||
- `5`: Video has been blacklisted during the live
|
||||
replayVideo:
|
||||
type: object
|
||||
description: Video replay information
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
uuid:
|
||||
$ref: '#/components/schemas/UUIDv4'
|
||||
shortUUID:
|
||||
$ref: '#/components/schemas/shortUUID'
|
||||
|
||||
PlaybackMetricCreate:
|
||||
properties:
|
||||
playerMode:
|
||||
type: string
|
||||
enum:
|
||||
- 'p2p-media-loader'
|
||||
- 'webtorrent'
|
||||
resolution:
|
||||
type: number
|
||||
description: Current player video resolution
|
||||
fps:
|
||||
type: number
|
||||
description: Current player video fps
|
||||
resolutionChanges:
|
||||
type: number
|
||||
description: How many resolution changes occured since the last metric creation
|
||||
errors:
|
||||
type: number
|
||||
description: How many errors occured since the last metric creation
|
||||
downloadedBytesP2P:
|
||||
type: number
|
||||
description: How many bytes were downloaded with P2P since the last metric creation
|
||||
downloadedBytesHTTP:
|
||||
type: number
|
||||
description: How many bytes were downloaded with HTTP since the last metric creation
|
||||
uploadedBytesP2P:
|
||||
type: number
|
||||
description: How many bytes were uploaded with P2P since the last metric creation
|
||||
videoId:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/id'
|
||||
- $ref: '#/components/schemas/UUIDv4'
|
||||
- $ref: '#/components/schemas/shortUUID'
|
||||
required:
|
||||
- playerMode
|
||||
- resolutionChanges
|
||||
- errors
|
||||
- downloadedBytesP2P
|
||||
- downloadedBytesHTTP
|
||||
- uploadedBytesP2P
|
||||
- videoId
|
||||
|
||||
callbacks:
|
||||
searchIndex:
|
||||
|
|
80
yarn.lock
80
yarn.lock
|
@ -1616,10 +1616,10 @@
|
|||
dependencies:
|
||||
"@opentelemetry/api" "^1.0.0"
|
||||
|
||||
"@opentelemetry/api-metrics@0.30.0", "@opentelemetry/api-metrics@^0.30.0":
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.30.0.tgz#b5defd10756e81d1c7ce8669ff8a8d2465ba0be8"
|
||||
integrity sha512-jSb7iiYPY+DSUKIyzfGt0a5K1QGzWY5fSWtUB8Alfi27NhQGHBeuYYC5n9MaBP/HNWw5GpEIhXGEYCF9Pf8IEg==
|
||||
"@opentelemetry/api-metrics@0.31.0", "@opentelemetry/api-metrics@^0.31.0":
|
||||
version "0.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.31.0.tgz#0ed4cf4d7c731f968721c2b303eaf5e9fd42f736"
|
||||
integrity sha512-PcL1x0kZtMie7NsNy67OyMvzLEXqf3xd0TZJKHHPMGTe89oMpNVrD1zJB1kZcwXOxLlHHb6tz21G3vvXPdXyZg==
|
||||
dependencies:
|
||||
"@opentelemetry/api" "^1.0.0"
|
||||
|
||||
|
@ -1633,13 +1633,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.5.0.tgz#4955313e7f0ec0fe17c813328a2a7f39f262c0fa"
|
||||
integrity sha512-mhBPP0BU0RaH2HB8U4MDd5OjWA1y7SoLOovCT0iEpJAltaq2z04uxRJVzIs91vkpNnV0utUZowQQD3KElgU+VA==
|
||||
|
||||
"@opentelemetry/core@1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.4.0.tgz#26839ab9e36583a174273a1e1c5b33336c163725"
|
||||
integrity sha512-faq50VFEdyC7ICAOlhSi+yYZ+peznnGjTJToha9R63i9fVopzpKrkZt7AIdXUmz2+L2OqXrcJs7EIdN/oDyr5w==
|
||||
dependencies:
|
||||
"@opentelemetry/semantic-conventions" "1.4.0"
|
||||
|
||||
"@opentelemetry/core@1.5.0", "@opentelemetry/core@^1.0.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.5.0.tgz#717bceee15d4c69d4c7321c1fe0f5a562b60eb81"
|
||||
|
@ -1657,14 +1650,14 @@
|
|||
"@opentelemetry/semantic-conventions" "1.5.0"
|
||||
jaeger-client "^3.15.0"
|
||||
|
||||
"@opentelemetry/exporter-prometheus@~0.30.0":
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.30.0.tgz#f81322d3cb000170e716bc76820600d5649be538"
|
||||
integrity sha512-y0SXvpzoKR+Tk/UL6F1f7vAcCzqpCDP/cTEa+Z7sX57aEG0HDXLQiLmAgK/BHqcEN5MFQMZ+MDVDsUrvpa6/Jw==
|
||||
"@opentelemetry/exporter-prometheus@~0.31.0":
|
||||
version "0.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.31.0.tgz#b0696be42542a961ec1145f3754a845efbda942e"
|
||||
integrity sha512-EfWFzoCu/THw0kZiaA2RUrk6XIQbfaJHJ26LRrVIK7INwosW8Q+x4pGfiJ5nxhglYiG9OTqGrQ6nQ4T9q1UMpg==
|
||||
dependencies:
|
||||
"@opentelemetry/api-metrics" "0.30.0"
|
||||
"@opentelemetry/core" "1.4.0"
|
||||
"@opentelemetry/sdk-metrics-base" "0.30.0"
|
||||
"@opentelemetry/api-metrics" "0.31.0"
|
||||
"@opentelemetry/core" "1.5.0"
|
||||
"@opentelemetry/sdk-metrics-base" "0.31.0"
|
||||
|
||||
"@opentelemetry/instrumentation-dns@^0.29.0":
|
||||
version "0.29.0"
|
||||
|
@ -1694,14 +1687,14 @@
|
|||
"@opentelemetry/instrumentation" "^0.29.2"
|
||||
"@opentelemetry/semantic-conventions" "^1.0.0"
|
||||
|
||||
"@opentelemetry/instrumentation-http@^0.30.0":
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.30.0.tgz#312ef25defbff750dd9082356bb9a9137ed5fd82"
|
||||
integrity sha512-OhiuzR2mhlTcaXD1dYW/dqnC/zjIKHp2NWMUyDHEd4xS6NZAiTU5mNDv57Y9on+/VwYXWUZZ2tB7AOVPsFUIOg==
|
||||
"@opentelemetry/instrumentation-http@^0.31.0":
|
||||
version "0.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.31.0.tgz#5c6dea9cdb636543c6ed1f1a4e55d4422e50fa89"
|
||||
integrity sha512-DLw+H7UQZ+V3FX72iGXVMX4ylL4jV+GHraaUiVY0CIdxg1nrGmjLm4dPU5500IXlbgZUUoJ9jq02JDblujdKcQ==
|
||||
dependencies:
|
||||
"@opentelemetry/core" "1.4.0"
|
||||
"@opentelemetry/instrumentation" "0.30.0"
|
||||
"@opentelemetry/semantic-conventions" "1.4.0"
|
||||
"@opentelemetry/core" "1.5.0"
|
||||
"@opentelemetry/instrumentation" "0.31.0"
|
||||
"@opentelemetry/semantic-conventions" "1.5.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
"@opentelemetry/instrumentation-pg@^0.30.0":
|
||||
|
@ -1722,12 +1715,12 @@
|
|||
"@opentelemetry/instrumentation" "^0.29.2"
|
||||
"@opentelemetry/semantic-conventions" "^1.0.0"
|
||||
|
||||
"@opentelemetry/instrumentation@0.30.0", "@opentelemetry/instrumentation@^0.30.0":
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.30.0.tgz#97cca611bd276439cc4e01e0516e50cbbb1e3459"
|
||||
integrity sha512-9bjRx81B6wbJ7CGWc/WCUfcb0QIG5UIcjnPTzwYIURjYPd8d0ZzRlrnqEdQG62jn4lSPEvnNqTlyC7qXtn9nAA==
|
||||
"@opentelemetry/instrumentation@0.31.0", "@opentelemetry/instrumentation@^0.31.0":
|
||||
version "0.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.31.0.tgz#bee0052a86e22f57be3901c44234f1a210bcfda8"
|
||||
integrity sha512-b2hFebXPtBcut4d81b8Kg6GiCoAS8nxb8kYSronQYAXxwNSetqHwIJ2nKLo1slFH1UWUXn0zi3eDez2Sn/9uMQ==
|
||||
dependencies:
|
||||
"@opentelemetry/api-metrics" "0.30.0"
|
||||
"@opentelemetry/api-metrics" "0.31.0"
|
||||
require-in-the-middle "^5.0.3"
|
||||
semver "^7.3.2"
|
||||
shimmer "^1.2.1"
|
||||
|
@ -1756,14 +1749,6 @@
|
|||
dependencies:
|
||||
"@opentelemetry/core" "1.5.0"
|
||||
|
||||
"@opentelemetry/resources@1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.4.0.tgz#5e23b0d7976158861059dec17e0ee36a35a5ab85"
|
||||
integrity sha512-Q3pI5+pCM+Ur7YwK9GbG89UBipwJbfmuzSPAXTw964ZHFzSrz+JAgrETC9rqsUOYdUlj/V7LbRMG5bo72xE0Xw==
|
||||
dependencies:
|
||||
"@opentelemetry/core" "1.4.0"
|
||||
"@opentelemetry/semantic-conventions" "1.4.0"
|
||||
|
||||
"@opentelemetry/resources@1.5.0", "@opentelemetry/resources@^1.3.1":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.5.0.tgz#ce7fbdaec3494e41bc279ddbed3c478ee2570b03"
|
||||
|
@ -1772,14 +1757,14 @@
|
|||
"@opentelemetry/core" "1.5.0"
|
||||
"@opentelemetry/semantic-conventions" "1.5.0"
|
||||
|
||||
"@opentelemetry/sdk-metrics-base@0.30.0", "@opentelemetry/sdk-metrics-base@~0.30.0":
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.30.0.tgz#242d9260a89a1ac2bf1e167b3fda758f3883c769"
|
||||
integrity sha512-3BDg1MYDInDyGvy+bSH8OuCX5nsue7omH6Y2eidCGTTDYRPxDmq9tsRJxnTUepoMAvWX+1sTwZ4JqTFmc1z8Mw==
|
||||
"@opentelemetry/sdk-metrics-base@0.31.0", "@opentelemetry/sdk-metrics-base@~0.31.0":
|
||||
version "0.31.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.31.0.tgz#f797da702c8d9862a2fff55a1e7c70aa6845e535"
|
||||
integrity sha512-4R2Bjl3wlqIGcq4bCoI9/pD49ld+tEoM9n85UfFzr/aUe+2huY2jTPq/BP9SVB8d2Zfg7mGTIFeapcEvAdKK7g==
|
||||
dependencies:
|
||||
"@opentelemetry/api-metrics" "0.30.0"
|
||||
"@opentelemetry/core" "1.4.0"
|
||||
"@opentelemetry/resources" "1.4.0"
|
||||
"@opentelemetry/api-metrics" "0.31.0"
|
||||
"@opentelemetry/core" "1.5.0"
|
||||
"@opentelemetry/resources" "1.5.0"
|
||||
lodash.merge "4.6.2"
|
||||
|
||||
"@opentelemetry/sdk-trace-base@1.5.0", "@opentelemetry/sdk-trace-base@^1.3.1":
|
||||
|
@ -1803,11 +1788,6 @@
|
|||
"@opentelemetry/sdk-trace-base" "1.5.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
"@opentelemetry/semantic-conventions@1.4.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.4.0.tgz#facf2c67d6063b9918d5a5e3fdf25f3a30d547b6"
|
||||
integrity sha512-Hzl8soGpmyzja9w3kiFFcYJ7n5HNETpplY6cb67KR4QPlxp4FTTresO06qXHgHDhyIInmbLJXuwARjjpsKYGuQ==
|
||||
|
||||
"@opentelemetry/semantic-conventions@1.5.0", "@opentelemetry/semantic-conventions@^1.0.0", "@opentelemetry/semantic-conventions@^1.3.1":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.5.0.tgz#cea9792bfcf556c87ded17c6ac729348697bb632"
|
||||
|
|
Loading…
Reference in New Issue