diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index 8d9c08ab3..9ae6f9f12 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts @@ -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, diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index b9077dcae..0d4acc3d9 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -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' diff --git a/client/src/assets/player/shared/control-bar/p2p-info-button.ts b/client/src/assets/player/shared/control-bar/p2p-info-button.ts index 36517e125..1979654ad 100644 --- a/client/src/assets/player/shared/control-bar/p2p-info-button.ts +++ b/client/src/assets/player/shared/control-bar/p2p-info-button.ts @@ -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' diff --git a/client/src/assets/player/shared/manager-options/manager-options-builder.ts b/client/src/assets/player/shared/manager-options/manager-options-builder.ts index bc70bb12f..07678493d 100644 --- a/client/src/assets/player/shared/manager-options/manager-options-builder.ts +++ b/client/src/assets/player/shared/manager-options/manager-options-builder.ts @@ -44,6 +44,14 @@ export class ManagerOptionsBuilder { 'isLive', 'videoUUID' ]) + }, + metrics: { + mode: this.mode, + + ...pick(commonOptions, [ + 'metricsUrl', + 'videoUUID' + ]) } } diff --git a/client/src/assets/player/shared/metrics/index.ts b/client/src/assets/player/shared/metrics/index.ts new file mode 100644 index 000000000..85d75cdc7 --- /dev/null +++ b/client/src/assets/player/shared/metrics/index.ts @@ -0,0 +1 @@ +export * from './metrics-plugin' diff --git a/client/src/assets/player/shared/metrics/metrics-plugin.ts b/client/src/assets/player/shared/metrics/metrics-plugin.ts new file mode 100644 index 000000000..1b2349eba --- /dev/null +++ b/client/src/assets/player/shared/metrics/metrics-plugin.ts @@ -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 } 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 e5f099dea..54d87aea5 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 @@ -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, diff --git a/client/src/assets/player/shared/peertube/peertube-plugin.ts b/client/src/assets/player/shared/peertube/peertube-plugin.ts index 69a7b2d65..83c32415e 100644 --- a/client/src/assets/player/shared/peertube/peertube-plugin.ts +++ b/client/src/assets/player/shared/peertube/peertube-plugin.ts @@ -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() diff --git a/client/src/assets/player/shared/stats/stats-card.ts b/client/src/assets/player/shared/stats/stats-card.ts index b65adcfca..1199d3285 100644 --- a/client/src/assets/player/shared/stats/stats-card.ts +++ b/client/src/assets/player/shared/stats/stats-card.ts @@ -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' diff --git a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts index 9fd5f593e..fa3f48a9a 100644 --- a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts +++ b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts @@ -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, diff --git a/client/src/assets/player/types/manager-options.ts b/client/src/assets/player/types/manager-options.ts index cadce739d..b4d9374c3 100644 --- a/client/src/assets/player/types/manager-options.ts +++ b/client/src/assets/player/types/manager-options.ts @@ -59,6 +59,8 @@ export interface CommonOptions extends CustomizationOptions { videoViewUrl: string authorizationHeader?: string + metricsUrl: string + embedUrl: string embedTitle: string diff --git a/client/src/assets/player/types/peertube-videojs-typings.ts b/client/src/assets/player/types/peertube-videojs-typings.ts index 115afb614..6df94992c 100644 --- a/client/src/assets/player/types/peertube-videojs-typings.ts +++ b/client/src/assets/player/types/peertube-videojs-typings.ts @@ -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, diff --git a/client/src/standalone/videos/shared/player-manager-options.ts b/client/src/standalone/videos/shared/player-manager-options.ts index 9cebdcd10..eed821994 100644 --- a/client/src/standalone/videos/shared/player-manager-options.ts +++ b/client/src/standalone/videos/shared/player-manager-options.ts @@ -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, diff --git a/config/test.yaml b/config/test.yaml index 9b24d44c0..a87642bd8 100644 --- a/config/test.yaml +++ b/config/test.yaml @@ -148,3 +148,8 @@ geo_ip: video_studio: enabled: true + +open_telemetry: + metrics: + prometheus_exporter: + port: 9092 diff --git a/package.json b/package.json index 64faf8355..be66e0744 100644 --- a/package.json +++ b/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", diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 8c8ebd061..e1d197c8a 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts @@ -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) diff --git a/server/controllers/api/metrics.ts b/server/controllers/api/metrics.ts new file mode 100644 index 000000000..578b023a1 --- /dev/null +++ b/server/controllers/api/metrics.ts @@ -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) +} diff --git a/server/helpers/custom-validators/metrics.ts b/server/helpers/custom-validators/metrics.ts new file mode 100644 index 000000000..533f8988d --- /dev/null +++ b/server/helpers/custom-validators/metrics.ts @@ -0,0 +1,9 @@ +function isValidPlayerMode (value: any) { + return value === 'webtorrent' || value === 'p2p-media-loader' +} + +// --------------------------------------------------------------------------- + +export { + isValidPlayerMode +} diff --git a/server/lib/opentelemetry/metric-helpers/index.ts b/server/lib/opentelemetry/metric-helpers/index.ts index 1b3813743..775d954ba 100644 --- a/server/lib/opentelemetry/metric-helpers/index.ts +++ b/server/lib/opentelemetry/metric-helpers/index.ts @@ -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' diff --git a/server/lib/opentelemetry/metric-helpers/nodejs-observers-builder.ts b/server/lib/opentelemetry/metric-helpers/nodejs-observers-builder.ts index 766cbe03b..473015e91 100644 --- a/server/lib/opentelemetry/metric-helpers/nodejs-observers-builder.ts +++ b/server/lib/opentelemetry/metric-helpers/nodejs-observers-builder.ts @@ -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' }) diff --git a/server/lib/opentelemetry/metric-helpers/playback-metrics.ts b/server/lib/opentelemetry/metric-helpers/playback-metrics.ts new file mode 100644 index 000000000..d2abdee62 --- /dev/null +++ b/server/lib/opentelemetry/metric-helpers/playback-metrics.ts @@ -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) + } +} diff --git a/server/lib/opentelemetry/metrics.ts b/server/lib/opentelemetry/metrics.ts index ffe493670..ba33c9505 100644 --- a/server/lib/opentelemetry/metrics.ts +++ b/server/lib/opentelemetry/metrics.ts @@ -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', diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index b0ad04819..ffadb3b49 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts @@ -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' diff --git a/server/middlewares/validators/metrics.ts b/server/middlewares/validators/metrics.ts new file mode 100644 index 000000000..b1dbec603 --- /dev/null +++ b/server/middlewares/validators/metrics.ts @@ -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 +} diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 149305f49..cd7a38459 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts @@ -10,6 +10,7 @@ import './follows' import './jobs' import './live' import './logs' +import './metrics' import './my-user' import './plugins' import './redundancy' diff --git a/server/tests/api/check-params/metrics.ts b/server/tests/api/check-params/metrics.ts new file mode 100644 index 000000000..2d4509406 --- /dev/null +++ b/server/tests/api/check-params/metrics.ts @@ -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 ]) + }) +}) diff --git a/server/tests/api/server/open-telemetry.ts b/server/tests/api/server/open-telemetry.ts index 20909429f..3137a9eb6 100644 --- a/server/tests/api/server/open-telemetry.ts +++ b/server/tests/api/server/open-telemetry.ts @@ -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() }) }) diff --git a/shared/models/index.ts b/shared/models/index.ts index 78723d830..439e9c8e1 100644 --- a/shared/models/index.ts +++ b/shared/models/index.ts @@ -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' diff --git a/shared/models/metrics/index.ts b/shared/models/metrics/index.ts new file mode 100644 index 000000000..24194cce3 --- /dev/null +++ b/shared/models/metrics/index.ts @@ -0,0 +1 @@ +export * from './playback-metric-create.model' diff --git a/shared/models/metrics/playback-metric-create.model.ts b/shared/models/metrics/playback-metric-create.model.ts new file mode 100644 index 000000000..d669ab690 --- /dev/null +++ b/shared/models/metrics/playback-metric-create.model.ts @@ -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 +} diff --git a/shared/server-commands/server/index.ts b/shared/server-commands/server/index.ts index 0a4b21fc4..9a2fbf8d3 100644 --- a/shared/server-commands/server/index.ts +++ b/shared/server-commands/server/index.ts @@ -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' diff --git a/shared/server-commands/server/metrics-command.ts b/shared/server-commands/server/metrics-command.ts new file mode 100644 index 000000000..d22b4833d --- /dev/null +++ b/shared/server-commands/server/metrics-command.ts @@ -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 + }) + } +} diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts index c05d16ad2..2b4c9c9f8 100644 --- a/shared/server-commands/server/server.ts +++ b/shared/server-commands/server/server.ts @@ -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) diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 4402de954..5077f8d90 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -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: diff --git a/yarn.lock b/yarn.lock index d16fd026c..0a479ac57 100644 --- a/yarn.lock +++ b/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"