diff options
author | Chocobozzz <me@florianbigard.com> | 2022-08-12 16:41:29 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-08-16 10:33:27 +0200 |
commit | fd3c2e87051f5029cdec39d877b576a62f48e219 (patch) | |
tree | a3c657f178702a3363af680ed8ffb7cd038243b8 | |
parent | 0e6cd1c00f71554fe7375a96db693a6983951ba6 (diff) | |
download | PeerTube-fd3c2e87051f5029cdec39d877b576a62f48e219.tar.gz PeerTube-fd3c2e87051f5029cdec39d877b576a62f48e219.tar.zst PeerTube-fd3c2e87051f5029cdec39d877b576a62f48e219.zip |
Add playback metric endpoint sent to OTEL
35 files changed, 748 insertions, 127 deletions
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 { | |||
628 | : null, | 628 | : null, |
629 | authorizationHeader: this.authService.getRequestHeaderValue(), | 629 | authorizationHeader: this.authService.getRequestHeaderValue(), |
630 | 630 | ||
631 | metricsUrl: environment.apiUrl + '/api/v1/metrics/playback', | ||
632 | |||
631 | embedUrl: video.embedUrl, | 633 | embedUrl: video.embedUrl, |
632 | embedTitle: video.name, | 634 | embedTitle: video.name, |
633 | instanceName: this.serverConfig.instance.name, | 635 | 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' | |||
22 | import './shared/mobile/peertube-mobile-plugin' | 22 | import './shared/mobile/peertube-mobile-plugin' |
23 | import './shared/mobile/peertube-mobile-buttons' | 23 | import './shared/mobile/peertube-mobile-buttons' |
24 | import './shared/hotkeys/peertube-hotkeys-plugin' | 24 | import './shared/hotkeys/peertube-hotkeys-plugin' |
25 | import './shared/metrics/metrics-plugin' | ||
25 | import videojs from 'video.js' | 26 | import videojs from 'video.js' |
26 | import { logger } from '@root-helpers/logger' | 27 | import { logger } from '@root-helpers/logger' |
27 | import { PluginsManager } from '@root-helpers/plugins-manager' | 28 | 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 { | |||
87 | const httpStats = data.http | 87 | const httpStats = data.http |
88 | 88 | ||
89 | const downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed) | 89 | const downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed) |
90 | const uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed) | 90 | const uploadSpeed = bytes(p2pStats.uploadSpeed) |
91 | const totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded) | 91 | const totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded) |
92 | const totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded) | 92 | const totalUploaded = bytes(p2pStats.uploaded) |
93 | const numPeers = p2pStats.numPeers | 93 | const numPeers = p2pStats.numPeers |
94 | 94 | ||
95 | subDivWebtorrent.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' | 95 | 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 { | |||
44 | 'isLive', | 44 | 'isLive', |
45 | 'videoUUID' | 45 | 'videoUUID' |
46 | ]) | 46 | ]) |
47 | }, | ||
48 | metrics: { | ||
49 | mode: this.mode, | ||
50 | |||
51 | ...pick(commonOptions, [ | ||
52 | 'metricsUrl', | ||
53 | 'videoUUID' | ||
54 | ]) | ||
47 | } | 55 | } |
48 | } | 56 | } |
49 | 57 | ||
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 @@ | |||
1 | import videojs from 'video.js' | ||
2 | import { PlaybackMetricCreate } from '../../../../../../shared/models' | ||
3 | import { MetricsPluginOptions, PlayerMode, PlayerNetworkInfo } from '../../types' | ||
4 | |||
5 | const Plugin = videojs.getPlugin('plugin') | ||
6 | |||
7 | class MetricsPlugin extends Plugin { | ||
8 | private readonly metricsUrl: string | ||
9 | private readonly videoUUID: string | ||
10 | private readonly mode: PlayerMode | ||
11 | |||
12 | private downloadedBytesP2P = 0 | ||
13 | private downloadedBytesHTTP = 0 | ||
14 | private uploadedBytesP2P = 0 | ||
15 | |||
16 | private resolutionChanges = 0 | ||
17 | private errors = 0 | ||
18 | |||
19 | private lastPlayerNetworkInfo: PlayerNetworkInfo | ||
20 | |||
21 | private metricsInterval: any | ||
22 | |||
23 | private readonly CONSTANTS = { | ||
24 | METRICS_INTERVAL: 15000 | ||
25 | } | ||
26 | |||
27 | constructor (player: videojs.Player, options: MetricsPluginOptions) { | ||
28 | super(player) | ||
29 | |||
30 | this.metricsUrl = options.metricsUrl | ||
31 | this.videoUUID = options.videoUUID | ||
32 | this.mode = options.mode | ||
33 | |||
34 | this.player.one('play', () => { | ||
35 | this.runMetricsInterval() | ||
36 | |||
37 | this.trackBytes() | ||
38 | this.trackResolutionChange() | ||
39 | this.trackErrors() | ||
40 | }) | ||
41 | } | ||
42 | |||
43 | dispose () { | ||
44 | if (this.metricsInterval) clearInterval(this.metricsInterval) | ||
45 | } | ||
46 | |||
47 | private runMetricsInterval () { | ||
48 | this.metricsInterval = setInterval(() => { | ||
49 | let resolution: number | ||
50 | let fps: number | ||
51 | |||
52 | if (this.mode === 'p2p-media-loader') { | ||
53 | const level = this.player.p2pMediaLoader().getCurrentLevel() | ||
54 | if (!level) return | ||
55 | |||
56 | resolution = Math.min(level.height || 0, level.width || 0) | ||
57 | |||
58 | const framerate = level?.attrs['FRAME-RATE'] | ||
59 | fps = framerate | ||
60 | ? parseInt(framerate, 10) | ||
61 | : undefined | ||
62 | } else { // webtorrent | ||
63 | const videoFile = this.player.webtorrent().getCurrentVideoFile() | ||
64 | if (!videoFile) return | ||
65 | |||
66 | resolution = videoFile.resolution.id | ||
67 | fps = videoFile.fps | ||
68 | } | ||
69 | |||
70 | const body: PlaybackMetricCreate = { | ||
71 | resolution, | ||
72 | fps, | ||
73 | |||
74 | playerMode: this.mode, | ||
75 | |||
76 | resolutionChanges: this.resolutionChanges, | ||
77 | |||
78 | errors: this.errors, | ||
79 | |||
80 | downloadedBytesP2P: this.downloadedBytesP2P, | ||
81 | downloadedBytesHTTP: this.downloadedBytesHTTP, | ||
82 | |||
83 | uploadedBytesP2P: this.uploadedBytesP2P, | ||
84 | |||
85 | videoId: this.videoUUID | ||
86 | } | ||
87 | |||
88 | this.resolutionChanges = 0 | ||
89 | |||
90 | this.downloadedBytesP2P = 0 | ||
91 | this.downloadedBytesHTTP = 0 | ||
92 | |||
93 | this.uploadedBytesP2P = 0 | ||
94 | |||
95 | this.errors = 0 | ||
96 | |||
97 | const headers = new Headers({ 'Content-type': 'application/json; charset=UTF-8' }) | ||
98 | |||
99 | return fetch(this.metricsUrl, { method: 'POST', body: JSON.stringify(body), headers }) | ||
100 | }, this.CONSTANTS.METRICS_INTERVAL) | ||
101 | } | ||
102 | |||
103 | private trackBytes () { | ||
104 | this.player.on('p2pInfo', (_event, data: PlayerNetworkInfo) => { | ||
105 | this.downloadedBytesHTTP += data.http.downloaded - (this.lastPlayerNetworkInfo?.http.downloaded || 0) | ||
106 | this.downloadedBytesP2P += data.p2p.downloaded - (this.lastPlayerNetworkInfo?.p2p.downloaded || 0) | ||
107 | |||
108 | this.uploadedBytesP2P += data.p2p.uploaded - (this.lastPlayerNetworkInfo?.p2p.uploaded || 0) | ||
109 | |||
110 | this.lastPlayerNetworkInfo = data | ||
111 | }) | ||
112 | } | ||
113 | |||
114 | private trackResolutionChange () { | ||
115 | this.player.on('engineResolutionChange', () => { | ||
116 | this.resolutionChanges++ | ||
117 | }) | ||
118 | } | ||
119 | |||
120 | private trackErrors () { | ||
121 | this.player.on('error', () => { | ||
122 | this.errors++ | ||
123 | }) | ||
124 | } | ||
125 | } | ||
126 | |||
127 | videojs.registerPlugin('metrics', MetricsPlugin) | ||
128 | 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' | |||
2 | import videojs from 'video.js' | 2 | import videojs from 'video.js' |
3 | import { Events, Segment } from '@peertube/p2p-media-loader-core' | 3 | import { Events, Segment } from '@peertube/p2p-media-loader-core' |
4 | import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs' | 4 | import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs' |
5 | import { logger } from '@root-helpers/logger' | ||
5 | import { timeToInt } from '@shared/core-utils' | 6 | import { timeToInt } from '@shared/core-utils' |
6 | import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types' | 7 | import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types' |
7 | import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' | 8 | import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' |
8 | import { logger } from '@root-helpers/logger' | ||
9 | 9 | ||
10 | registerConfigPlugin(videojs) | 10 | registerConfigPlugin(videojs) |
11 | registerSourceHandler(videojs) | 11 | registerSourceHandler(videojs) |
@@ -29,9 +29,7 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
29 | } | 29 | } |
30 | private statsHTTPBytes = { | 30 | private statsHTTPBytes = { |
31 | pendingDownload: [] as number[], | 31 | pendingDownload: [] as number[], |
32 | pendingUpload: [] as number[], | 32 | totalDownload: 0 |
33 | totalDownload: 0, | ||
34 | totalUpload: 0 | ||
35 | } | 33 | } |
36 | private startTime: number | 34 | private startTime: number |
37 | 35 | ||
@@ -123,6 +121,8 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
123 | this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() | 121 | this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() |
124 | 122 | ||
125 | this.runStats() | 123 | this.runStats() |
124 | |||
125 | this.hlsjs.on(Hlsjs.Events.LEVEL_SWITCHED, () => this.player.trigger('engineResolutionChange')) | ||
126 | } | 126 | } |
127 | 127 | ||
128 | private runStats () { | 128 | private runStats () { |
@@ -134,10 +134,13 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
134 | }) | 134 | }) |
135 | 135 | ||
136 | this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, _segment, bytes: number) => { | 136 | this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, _segment, bytes: number) => { |
137 | const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes | 137 | if (method !== 'p2p') { |
138 | logger.error(`Received upload from unknown method ${method}`) | ||
139 | return | ||
140 | } | ||
138 | 141 | ||
139 | elem.pendingUpload.push(bytes) | 142 | this.statsP2PBytes.pendingUpload.push(bytes) |
140 | elem.totalUpload += bytes | 143 | this.statsP2PBytes.totalUpload += bytes |
141 | }) | 144 | }) |
142 | 145 | ||
143 | this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) | 146 | this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) |
@@ -148,20 +151,16 @@ class P2pMediaLoaderPlugin extends Plugin { | |||
148 | const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload) | 151 | const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload) |
149 | 152 | ||
150 | const httpDownloadSpeed = this.arraySum(this.statsHTTPBytes.pendingDownload) | 153 | const httpDownloadSpeed = this.arraySum(this.statsHTTPBytes.pendingDownload) |
151 | const httpUploadSpeed = this.arraySum(this.statsHTTPBytes.pendingUpload) | ||
152 | 154 | ||
153 | this.statsP2PBytes.pendingDownload = [] | 155 | this.statsP2PBytes.pendingDownload = [] |
154 | this.statsP2PBytes.pendingUpload = [] | 156 | this.statsP2PBytes.pendingUpload = [] |
155 | this.statsHTTPBytes.pendingDownload = [] | 157 | this.statsHTTPBytes.pendingDownload = [] |
156 | this.statsHTTPBytes.pendingUpload = [] | ||
157 | 158 | ||
158 | return this.player.trigger('p2pInfo', { | 159 | return this.player.trigger('p2pInfo', { |
159 | source: 'p2p-media-loader', | 160 | source: 'p2p-media-loader', |
160 | http: { | 161 | http: { |
161 | downloadSpeed: httpDownloadSpeed, | 162 | downloadSpeed: httpDownloadSpeed, |
162 | uploadSpeed: httpUploadSpeed, | 163 | downloaded: this.statsHTTPBytes.totalDownload |
163 | downloaded: this.statsHTTPBytes.totalDownload, | ||
164 | uploaded: this.statsHTTPBytes.totalUpload | ||
165 | }, | 164 | }, |
166 | p2p: { | 165 | p2p: { |
167 | downloadSpeed: p2pDownloadSpeed, | 166 | 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 { | |||
144 | this.listenFullScreenChange() | 144 | this.listenFullScreenChange() |
145 | } | 145 | } |
146 | 146 | ||
147 | // --------------------------------------------------------------------------- | ||
148 | |||
147 | private runUserViewing () { | 149 | private runUserViewing () { |
148 | let lastCurrentTime = this.startTime | 150 | let lastCurrentTime = this.startTime |
149 | let lastViewEvent: VideoViewEvent | 151 | let lastViewEvent: VideoViewEvent |
@@ -205,6 +207,8 @@ class PeerTubePlugin extends Plugin { | |||
205 | return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers }) | 207 | return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers }) |
206 | } | 208 | } |
207 | 209 | ||
210 | // --------------------------------------------------------------------------- | ||
211 | |||
208 | private listenFullScreenChange () { | 212 | private listenFullScreenChange () { |
209 | this.player.on('fullscreenchange', () => { | 213 | this.player.on('fullscreenchange', () => { |
210 | if (this.player.isFullscreen()) this.player.focus() | 214 | 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 { | |||
95 | const httpStats = data.http | 95 | const httpStats = data.http |
96 | 96 | ||
97 | this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ') | 97 | this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ') |
98 | this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed).join(' ') | 98 | this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed).join(' ') |
99 | this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ') | 99 | this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ') |
100 | this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ') | 100 | this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded).join(' ') |
101 | this.playerNetworkInfo.numPeers = p2pStats.numPeers | 101 | this.playerNetworkInfo.numPeers = p2pStats.numPeers |
102 | this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s' | 102 | this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s' |
103 | 103 | ||
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 { | |||
204 | } | 204 | } |
205 | 205 | ||
206 | this.updateVideoFile(newVideoFile, options) | 206 | this.updateVideoFile(newVideoFile, options) |
207 | |||
208 | this.player.trigger('engineResolutionChange') | ||
207 | } | 209 | } |
208 | 210 | ||
209 | flushVideoFile (videoFile: VideoFile, destroyRenderer = true) { | 211 | flushVideoFile (videoFile: VideoFile, destroyRenderer = true) { |
@@ -506,9 +508,7 @@ class WebTorrentPlugin extends Plugin { | |||
506 | source: 'webtorrent', | 508 | source: 'webtorrent', |
507 | http: { | 509 | http: { |
508 | downloadSpeed: 0, | 510 | downloadSpeed: 0, |
509 | uploadSpeed: 0, | 511 | downloaded: 0 |
510 | downloaded: 0, | ||
511 | uploaded: 0 | ||
512 | }, | 512 | }, |
513 | p2p: { | 513 | p2p: { |
514 | downloadSpeed: this.torrent.downloadSpeed, | 514 | 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 { | |||
59 | videoViewUrl: string | 59 | videoViewUrl: string |
60 | authorizationHeader?: string | 60 | authorizationHeader?: string |
61 | 61 | ||
62 | metricsUrl: string | ||
63 | |||
62 | embedUrl: string | 64 | embedUrl: string |
63 | embedTitle: string | 65 | embedTitle: string |
64 | 66 | ||
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 = { | |||
109 | videoUUID: string | 109 | videoUUID: string |
110 | } | 110 | } |
111 | 111 | ||
112 | type MetricsPluginOptions = { | ||
113 | mode: PlayerMode | ||
114 | metricsUrl: string | ||
115 | videoUUID: string | ||
116 | } | ||
117 | |||
112 | type PlaylistPluginOptions = { | 118 | type PlaylistPluginOptions = { |
113 | elements: VideoPlaylistElement[] | 119 | elements: VideoPlaylistElement[] |
114 | 120 | ||
@@ -165,6 +171,7 @@ type VideoJSPluginOptions = { | |||
165 | playlist?: PlaylistPluginOptions | 171 | playlist?: PlaylistPluginOptions |
166 | 172 | ||
167 | peertube: PeerTubePluginOptions | 173 | peertube: PeerTubePluginOptions |
174 | metrics: MetricsPluginOptions | ||
168 | 175 | ||
169 | webtorrent?: WebtorrentPluginOptions | 176 | webtorrent?: WebtorrentPluginOptions |
170 | 177 | ||
@@ -197,9 +204,7 @@ type PlayerNetworkInfo = { | |||
197 | 204 | ||
198 | http: { | 205 | http: { |
199 | downloadSpeed: number | 206 | downloadSpeed: number |
200 | uploadSpeed: number | ||
201 | downloaded: number | 207 | downloaded: number |
202 | uploaded: number | ||
203 | } | 208 | } |
204 | 209 | ||
205 | p2p: { | 210 | p2p: { |
@@ -227,6 +232,7 @@ export { | |||
227 | ResolutionUpdateData, | 232 | ResolutionUpdateData, |
228 | AutoResolutionUpdateData, | 233 | AutoResolutionUpdateData, |
229 | PlaylistPluginOptions, | 234 | PlaylistPluginOptions, |
235 | MetricsPluginOptions, | ||
230 | VideoJSCaption, | 236 | VideoJSCaption, |
231 | PeerTubePluginOptions, | 237 | PeerTubePluginOptions, |
232 | WebtorrentPluginOptions, | 238 | 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 { | |||
203 | videoCaptions, | 203 | videoCaptions, |
204 | inactivityTimeout: 2500, | 204 | inactivityTimeout: 2500, |
205 | videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), | 205 | videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), |
206 | metricsUrl: window.location.origin + '/api/v1/metrics/playback', | ||
206 | 207 | ||
207 | videoShortUUID: video.shortUUID, | 208 | videoShortUUID: video.shortUUID, |
208 | videoUUID: video.uuid, | 209 | 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: | |||
148 | 148 | ||
149 | video_studio: | 149 | video_studio: |
150 | enabled: true | 150 | enabled: true |
151 | |||
152 | open_telemetry: | ||
153 | metrics: | ||
154 | prometheus_exporter: | ||
155 | port: 9092 | ||
diff --git a/package.json b/package.json index 64faf8355..be66e0744 100644 --- a/package.json +++ b/package.json | |||
@@ -86,18 +86,18 @@ | |||
86 | "@babel/parser": "^7.17.8", | 86 | "@babel/parser": "^7.17.8", |
87 | "@node-oauth/oauth2-server": "^4.2.0", | 87 | "@node-oauth/oauth2-server": "^4.2.0", |
88 | "@opentelemetry/api": "^1.1.0", | 88 | "@opentelemetry/api": "^1.1.0", |
89 | "@opentelemetry/api-metrics": "^0.30.0", | 89 | "@opentelemetry/api-metrics": "^0.31.0", |
90 | "@opentelemetry/exporter-jaeger": "^1.3.1", | 90 | "@opentelemetry/exporter-jaeger": "^1.3.1", |
91 | "@opentelemetry/exporter-prometheus": "~0.30.0", | 91 | "@opentelemetry/exporter-prometheus": "~0.31.0", |
92 | "@opentelemetry/instrumentation": "^0.30.0", | 92 | "@opentelemetry/instrumentation": "^0.31.0", |
93 | "@opentelemetry/instrumentation-dns": "^0.29.0", | 93 | "@opentelemetry/instrumentation-dns": "^0.29.0", |
94 | "@opentelemetry/instrumentation-express": "^0.30.0", | 94 | "@opentelemetry/instrumentation-express": "^0.30.0", |
95 | "@opentelemetry/instrumentation-fs": "^0.4.0", | 95 | "@opentelemetry/instrumentation-fs": "^0.4.0", |
96 | "@opentelemetry/instrumentation-http": "^0.30.0", | 96 | "@opentelemetry/instrumentation-http": "^0.31.0", |
97 | "@opentelemetry/instrumentation-pg": "^0.30.0", | 97 | "@opentelemetry/instrumentation-pg": "^0.30.0", |
98 | "@opentelemetry/instrumentation-redis-4": "^0.31.0", | 98 | "@opentelemetry/instrumentation-redis-4": "^0.31.0", |
99 | "@opentelemetry/resources": "^1.3.1", | 99 | "@opentelemetry/resources": "^1.3.1", |
100 | "@opentelemetry/sdk-metrics-base": "~0.30.0", | 100 | "@opentelemetry/sdk-metrics-base": "~0.31.0", |
101 | "@opentelemetry/sdk-trace-base": "^1.3.1", | 101 | "@opentelemetry/sdk-trace-base": "^1.3.1", |
102 | "@opentelemetry/sdk-trace-node": "^1.3.1", | 102 | "@opentelemetry/sdk-trace-node": "^1.3.1", |
103 | "@opentelemetry/semantic-conventions": "^1.3.1", | 103 | "@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' | |||
11 | import { configRouter } from './config' | 11 | import { configRouter } from './config' |
12 | import { customPageRouter } from './custom-page' | 12 | import { customPageRouter } from './custom-page' |
13 | import { jobsRouter } from './jobs' | 13 | import { jobsRouter } from './jobs' |
14 | import { metricsRouter } from './metrics' | ||
14 | import { oauthClientsRouter } from './oauth-clients' | 15 | import { oauthClientsRouter } from './oauth-clients' |
15 | import { overviewsRouter } from './overviews' | 16 | import { overviewsRouter } from './overviews' |
16 | import { pluginRouter } from './plugins' | 17 | import { pluginRouter } from './plugins' |
@@ -18,9 +19,9 @@ import { searchRouter } from './search' | |||
18 | import { serverRouter } from './server' | 19 | import { serverRouter } from './server' |
19 | import { usersRouter } from './users' | 20 | import { usersRouter } from './users' |
20 | import { videoChannelRouter } from './video-channel' | 21 | import { videoChannelRouter } from './video-channel' |
22 | import { videoChannelSyncRouter } from './video-channel-sync' | ||
21 | import { videoPlaylistRouter } from './video-playlist' | 23 | import { videoPlaylistRouter } from './video-playlist' |
22 | import { videosRouter } from './videos' | 24 | import { videosRouter } from './videos' |
23 | import { videoChannelSyncRouter } from './video-channel-sync' | ||
24 | 25 | ||
25 | const apiRouter = express.Router() | 26 | const apiRouter = express.Router() |
26 | 27 | ||
@@ -48,6 +49,7 @@ apiRouter.use('/video-channel-syncs', videoChannelSyncRouter) | |||
48 | apiRouter.use('/video-playlists', videoPlaylistRouter) | 49 | apiRouter.use('/video-playlists', videoPlaylistRouter) |
49 | apiRouter.use('/videos', videosRouter) | 50 | apiRouter.use('/videos', videosRouter) |
50 | apiRouter.use('/jobs', jobsRouter) | 51 | apiRouter.use('/jobs', jobsRouter) |
52 | apiRouter.use('/metrics', metricsRouter) | ||
51 | apiRouter.use('/search', searchRouter) | 53 | apiRouter.use('/search', searchRouter) |
52 | apiRouter.use('/overviews', overviewsRouter) | 54 | apiRouter.use('/overviews', overviewsRouter) |
53 | apiRouter.use('/plugins', pluginRouter) | 55 | 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 @@ | |||
1 | import express from 'express' | ||
2 | import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics' | ||
3 | import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' | ||
4 | import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares' | ||
5 | |||
6 | const metricsRouter = express.Router() | ||
7 | |||
8 | metricsRouter.post('/playback', | ||
9 | asyncMiddleware(addPlaybackMetricValidator), | ||
10 | addPlaybackMetric | ||
11 | ) | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | export { | ||
16 | metricsRouter | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | function addPlaybackMetric (req: express.Request, res: express.Response) { | ||
22 | const body: PlaybackMetricCreate = req.body | ||
23 | |||
24 | OpenTelemetryMetrics.Instance.observePlaybackMetric(res.locals.onlyImmutableVideo, body) | ||
25 | |||
26 | return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
27 | } | ||
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 @@ | |||
1 | function isValidPlayerMode (value: any) { | ||
2 | return value === 'webtorrent' || value === 'p2p-media-loader' | ||
3 | } | ||
4 | |||
5 | // --------------------------------------------------------------------------- | ||
6 | |||
7 | export { | ||
8 | isValidPlayerMode | ||
9 | } | ||
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 @@ | |||
1 | export * from './lives-observers-builder' | 1 | export * from './lives-observers-builder' |
2 | export * from './job-queue-observers-builder' | 2 | export * from './job-queue-observers-builder' |
3 | export * from './nodejs-observers-builder' | 3 | export * from './nodejs-observers-builder' |
4 | export * from './playback-metrics' | ||
4 | export * from './stats-observers-builder' | 5 | export * from './stats-observers-builder' |
5 | export * from './viewers-observers-builder' | 6 | 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' | |||
2 | import { constants, PerformanceObserver } from 'perf_hooks' | 2 | import { constants, PerformanceObserver } from 'perf_hooks' |
3 | import * as process from 'process' | 3 | import * as process from 'process' |
4 | import { Meter, ObservableResult } from '@opentelemetry/api-metrics' | 4 | import { Meter, ObservableResult } from '@opentelemetry/api-metrics' |
5 | import { ExplicitBucketHistogramAggregation, MeterProvider } from '@opentelemetry/sdk-metrics-base' | 5 | import { ExplicitBucketHistogramAggregation } from '@opentelemetry/sdk-metrics-base' |
6 | import { View } from '@opentelemetry/sdk-metrics-base/build/src/view/View' | 6 | import { View } from '@opentelemetry/sdk-metrics-base/build/src/view/View' |
7 | import { logger } from '@server/helpers/logger' | 7 | import { logger } from '@server/helpers/logger' |
8 | 8 | ||
@@ -12,7 +12,16 @@ import { logger } from '@server/helpers/logger' | |||
12 | 12 | ||
13 | export class NodeJSObserversBuilder { | 13 | export class NodeJSObserversBuilder { |
14 | 14 | ||
15 | constructor (private readonly meter: Meter, private readonly meterProvider: MeterProvider) { | 15 | constructor (private readonly meter: Meter) { |
16 | } | ||
17 | |||
18 | static getViews () { | ||
19 | return [ | ||
20 | new View({ | ||
21 | aggregation: new ExplicitBucketHistogramAggregation([ 0.001, 0.01, 0.1, 1, 2, 5 ]), | ||
22 | instrumentName: 'nodejs_gc_duration_seconds' | ||
23 | }) | ||
24 | ] | ||
16 | } | 25 | } |
17 | 26 | ||
18 | buildObservers () { | 27 | buildObservers () { |
@@ -91,11 +100,6 @@ export class NodeJSObserversBuilder { | |||
91 | [constants.NODE_PERFORMANCE_GC_WEAKCB]: 'weakcb' | 100 | [constants.NODE_PERFORMANCE_GC_WEAKCB]: 'weakcb' |
92 | } | 101 | } |
93 | 102 | ||
94 | this.meterProvider.addView( | ||
95 | new View({ aggregation: new ExplicitBucketHistogramAggregation([ 0.001, 0.01, 0.1, 1, 2, 5 ]) }), | ||
96 | { instrument: { name: 'nodejs_gc_duration_seconds' } } | ||
97 | ) | ||
98 | |||
99 | const histogram = this.meter.createHistogram('nodejs_gc_duration_seconds', { | 103 | const histogram = this.meter.createHistogram('nodejs_gc_duration_seconds', { |
100 | description: 'Garbage collection duration by kind, one of major, minor, incremental or weakcb' | 104 | description: 'Garbage collection duration by kind, one of major, minor, incremental or weakcb' |
101 | }) | 105 | }) |
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 @@ | |||
1 | import { Counter, Meter } from '@opentelemetry/api-metrics' | ||
2 | import { MVideoImmutable } from '@server/types/models' | ||
3 | import { PlaybackMetricCreate } from '@shared/models' | ||
4 | |||
5 | export class PlaybackMetrics { | ||
6 | private errorsCounter: Counter | ||
7 | private resolutionChangesCounter: Counter | ||
8 | |||
9 | private downloadedBytesP2PCounter: Counter | ||
10 | private uploadedBytesP2PCounter: Counter | ||
11 | |||
12 | private downloadedBytesHTTPCounter: Counter | ||
13 | |||
14 | constructor (private readonly meter: Meter) { | ||
15 | |||
16 | } | ||
17 | |||
18 | buildCounters () { | ||
19 | this.errorsCounter = this.meter.createCounter('peertube_playback_errors_count', { | ||
20 | description: 'Errors collected from PeerTube player.' | ||
21 | }) | ||
22 | |||
23 | this.resolutionChangesCounter = this.meter.createCounter('peertube_playback_resolution_changes_count', { | ||
24 | description: 'Resolution changes collected from PeerTube player.' | ||
25 | }) | ||
26 | |||
27 | this.downloadedBytesHTTPCounter = this.meter.createCounter('peertube_playback_http_downloaded_bytes', { | ||
28 | description: 'Downloaded bytes with HTTP by PeerTube player.' | ||
29 | }) | ||
30 | this.downloadedBytesP2PCounter = this.meter.createCounter('peertube_playback_p2p_downloaded_bytes', { | ||
31 | description: 'Downloaded bytes with P2P by PeerTube player.' | ||
32 | }) | ||
33 | |||
34 | this.uploadedBytesP2PCounter = this.meter.createCounter('peertube_playback_p2p_uploaded_bytes', { | ||
35 | description: 'Uploaded bytes with P2P by PeerTube player.' | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | observe (video: MVideoImmutable, metrics: PlaybackMetricCreate) { | ||
40 | const attributes = { | ||
41 | videoOrigin: video.remote | ||
42 | ? 'remote' | ||
43 | : 'local', | ||
44 | |||
45 | playerMode: metrics.playerMode, | ||
46 | |||
47 | resolution: metrics.resolution + '', | ||
48 | fps: metrics.fps + '' | ||
49 | } | ||
50 | |||
51 | this.errorsCounter.add(metrics.errors, attributes) | ||
52 | this.resolutionChangesCounter.add(metrics.resolutionChanges, attributes) | ||
53 | |||
54 | this.downloadedBytesHTTPCounter.add(metrics.downloadedBytesHTTP, attributes) | ||
55 | this.downloadedBytesP2PCounter.add(metrics.downloadedBytesP2P, attributes) | ||
56 | |||
57 | this.uploadedBytesP2PCounter.add(metrics.uploadedBytesP2P, attributes) | ||
58 | } | ||
59 | } | ||
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' | |||
4 | import { MeterProvider } from '@opentelemetry/sdk-metrics-base' | 4 | import { MeterProvider } from '@opentelemetry/sdk-metrics-base' |
5 | import { logger } from '@server/helpers/logger' | 5 | import { logger } from '@server/helpers/logger' |
6 | import { CONFIG } from '@server/initializers/config' | 6 | import { CONFIG } from '@server/initializers/config' |
7 | import { MVideoImmutable } from '@server/types/models' | ||
8 | import { PlaybackMetricCreate } from '@shared/models' | ||
7 | import { | 9 | import { |
8 | JobQueueObserversBuilder, | 10 | JobQueueObserversBuilder, |
9 | LivesObserversBuilder, | 11 | LivesObserversBuilder, |
10 | NodeJSObserversBuilder, | 12 | NodeJSObserversBuilder, |
13 | PlaybackMetrics, | ||
11 | StatsObserversBuilder, | 14 | StatsObserversBuilder, |
12 | ViewersObserversBuilder | 15 | ViewersObserversBuilder |
13 | } from './metric-helpers' | 16 | } from './metric-helpers' |
@@ -20,6 +23,8 @@ class OpenTelemetryMetrics { | |||
20 | 23 | ||
21 | private onRequestDuration: (req: Request, res: Response) => void | 24 | private onRequestDuration: (req: Request, res: Response) => void |
22 | 25 | ||
26 | private playbackMetrics: PlaybackMetrics | ||
27 | |||
23 | private constructor () {} | 28 | private constructor () {} |
24 | 29 | ||
25 | init (app: Application) { | 30 | init (app: Application) { |
@@ -41,7 +46,11 @@ class OpenTelemetryMetrics { | |||
41 | 46 | ||
42 | logger.info('Registering Open Telemetry metrics') | 47 | logger.info('Registering Open Telemetry metrics') |
43 | 48 | ||
44 | const provider = new MeterProvider() | 49 | const provider = new MeterProvider({ |
50 | views: [ | ||
51 | ...NodeJSObserversBuilder.getViews() | ||
52 | ] | ||
53 | }) | ||
45 | 54 | ||
46 | provider.addMetricReader(new PrometheusExporter({ port: CONFIG.OPEN_TELEMETRY.METRICS.PROMETHEUS_EXPORTER.PORT })) | 55 | provider.addMetricReader(new PrometheusExporter({ port: CONFIG.OPEN_TELEMETRY.METRICS.PROMETHEUS_EXPORTER.PORT })) |
47 | 56 | ||
@@ -51,7 +60,10 @@ class OpenTelemetryMetrics { | |||
51 | 60 | ||
52 | this.buildRequestObserver() | 61 | this.buildRequestObserver() |
53 | 62 | ||
54 | const nodeJSObserversBuilder = new NodeJSObserversBuilder(this.meter, provider) | 63 | this.playbackMetrics = new PlaybackMetrics(this.meter) |
64 | this.playbackMetrics.buildCounters() | ||
65 | |||
66 | const nodeJSObserversBuilder = new NodeJSObserversBuilder(this.meter) | ||
55 | nodeJSObserversBuilder.buildObservers() | 67 | nodeJSObserversBuilder.buildObservers() |
56 | 68 | ||
57 | const jobQueueObserversBuilder = new JobQueueObserversBuilder(this.meter) | 69 | const jobQueueObserversBuilder = new JobQueueObserversBuilder(this.meter) |
@@ -67,6 +79,10 @@ class OpenTelemetryMetrics { | |||
67 | viewersObserversBuilder.buildObservers() | 79 | viewersObserversBuilder.buildObservers() |
68 | } | 80 | } |
69 | 81 | ||
82 | observePlaybackMetric (video: MVideoImmutable, metrics: PlaybackMetricCreate) { | ||
83 | this.playbackMetrics.observe(video, metrics) | ||
84 | } | ||
85 | |||
70 | private buildRequestObserver () { | 86 | private buildRequestObserver () { |
71 | const requestDuration = this.meter.createHistogram('http_request_duration_ms', { | 87 | const requestDuration = this.meter.createHistogram('http_request_duration_ms', { |
72 | unit: 'milliseconds', | 88 | 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' | |||
10 | export * from './feeds' | 10 | export * from './feeds' |
11 | export * from './follows' | 11 | export * from './follows' |
12 | export * from './jobs' | 12 | export * from './jobs' |
13 | export * from './metrics' | ||
13 | export * from './logs' | 14 | export * from './logs' |
14 | export * from './oembed' | 15 | export * from './oembed' |
15 | export * from './pagination' | 16 | 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 @@ | |||
1 | import express from 'express' | ||
2 | import { body } from 'express-validator' | ||
3 | import { isValidPlayerMode } from '@server/helpers/custom-validators/metrics' | ||
4 | import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc' | ||
5 | import { CONFIG } from '@server/initializers/config' | ||
6 | import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' | ||
7 | import { logger } from '../../helpers/logger' | ||
8 | import { areValidationErrors, doesVideoExist } from './shared' | ||
9 | |||
10 | const addPlaybackMetricValidator = [ | ||
11 | body('resolution') | ||
12 | .isInt({ min: 0 }).withMessage('Invalid resolution'), | ||
13 | body('fps') | ||
14 | .optional() | ||
15 | .isInt({ min: 0 }).withMessage('Invalid fps'), | ||
16 | body('playerMode') | ||
17 | .custom(isValidPlayerMode).withMessage('Invalid playerMode'), | ||
18 | |||
19 | body('resolutionChanges') | ||
20 | .isInt({ min: 0 }).withMessage('Invalid resolutionChanges'), | ||
21 | |||
22 | body('errors') | ||
23 | .isInt({ min: 0 }).withMessage('Invalid errors'), | ||
24 | |||
25 | body('downloadedBytesP2P') | ||
26 | .isInt({ min: 0 }).withMessage('Invalid downloadedBytesP2P'), | ||
27 | body('downloadedBytesHTTP') | ||
28 | .isInt({ min: 0 }).withMessage('Invalid downloadedBytesHTTP'), | ||
29 | |||
30 | body('uploadedBytesP2P') | ||
31 | .isInt({ min: 0 }).withMessage('Invalid uploadedBytesP2P'), | ||
32 | |||
33 | body('videoId') | ||
34 | .customSanitizer(toCompleteUUID) | ||
35 | .optional() | ||
36 | .custom(isIdOrUUIDValid), | ||
37 | |||
38 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
39 | logger.debug('Checking addPlaybackMetricValidator parameters.', { parameters: req.query }) | ||
40 | |||
41 | if (!CONFIG.OPEN_TELEMETRY.METRICS.ENABLED) return res.sendStatus(HttpStatusCode.NO_CONTENT_204) | ||
42 | |||
43 | const body: PlaybackMetricCreate = req.body | ||
44 | |||
45 | if (areValidationErrors(req, res)) return | ||
46 | if (!await doesVideoExist(body.videoId, res, 'only-immutable-attributes')) return | ||
47 | |||
48 | return next() | ||
49 | } | ||
50 | ] | ||
51 | |||
52 | // --------------------------------------------------------------------------- | ||
53 | |||
54 | export { | ||
55 | addPlaybackMetricValidator | ||
56 | } | ||
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' | |||
10 | import './jobs' | 10 | import './jobs' |
11 | import './live' | 11 | import './live' |
12 | import './logs' | 12 | import './logs' |
13 | import './metrics' | ||
13 | import './my-user' | 14 | import './my-user' |
14 | import './plugins' | 15 | import './plugins' |
15 | import './redundancy' | 16 | 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 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { omit } from 'lodash' | ||
5 | import { HttpStatusCode, PlaybackMetricCreate, VideoResolution } from '@shared/models' | ||
6 | import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' | ||
7 | |||
8 | describe('Test metrics API validators', function () { | ||
9 | let server: PeerTubeServer | ||
10 | let videoUUID: string | ||
11 | |||
12 | // --------------------------------------------------------------- | ||
13 | |||
14 | before(async function () { | ||
15 | this.timeout(120000) | ||
16 | |||
17 | server = await createSingleServer(1, { | ||
18 | open_telemetry: { | ||
19 | metrics: { | ||
20 | enabled: true | ||
21 | } | ||
22 | } | ||
23 | }) | ||
24 | |||
25 | await setAccessTokensToServers([ server ]) | ||
26 | |||
27 | const { uuid } = await server.videos.quickUpload({ name: 'video' }) | ||
28 | videoUUID = uuid | ||
29 | }) | ||
30 | |||
31 | describe('When adding playback metrics', function () { | ||
32 | const path = '/api/v1/metrics/playback' | ||
33 | let baseParams: PlaybackMetricCreate | ||
34 | |||
35 | before(function () { | ||
36 | baseParams = { | ||
37 | playerMode: 'p2p-media-loader', | ||
38 | resolution: VideoResolution.H_1080P, | ||
39 | fps: 30, | ||
40 | resolutionChanges: 1, | ||
41 | errors: 2, | ||
42 | downloadedBytesP2P: 0, | ||
43 | downloadedBytesHTTP: 0, | ||
44 | uploadedBytesP2P: 0, | ||
45 | videoId: videoUUID | ||
46 | } | ||
47 | }) | ||
48 | |||
49 | it('Should fail with an invalid resolution', async function () { | ||
50 | await makePostBodyRequest({ | ||
51 | url: server.url, | ||
52 | path, | ||
53 | fields: { ...baseParams, resolution: 'toto' } | ||
54 | }) | ||
55 | }) | ||
56 | |||
57 | it('Should fail with an invalid fps', async function () { | ||
58 | await makePostBodyRequest({ | ||
59 | url: server.url, | ||
60 | path, | ||
61 | fields: { ...baseParams, fps: 'toto' } | ||
62 | }) | ||
63 | }) | ||
64 | |||
65 | it('Should fail with a missing/invalid player mode', async function () { | ||
66 | await makePostBodyRequest({ | ||
67 | url: server.url, | ||
68 | path, | ||
69 | fields: omit(baseParams, 'playerMode') | ||
70 | }) | ||
71 | |||
72 | await makePostBodyRequest({ | ||
73 | url: server.url, | ||
74 | path, | ||
75 | fields: { ...baseParams, playerMode: 'toto' } | ||
76 | }) | ||
77 | }) | ||
78 | |||
79 | it('Should fail with an missing/invalid resolution changes', async function () { | ||
80 | await makePostBodyRequest({ | ||
81 | url: server.url, | ||
82 | path, | ||
83 | fields: omit(baseParams, 'resolutionChanges') | ||
84 | }) | ||
85 | |||
86 | await makePostBodyRequest({ | ||
87 | url: server.url, | ||
88 | path, | ||
89 | fields: { ...baseParams, resolutionChanges: 'toto' } | ||
90 | }) | ||
91 | }) | ||
92 | |||
93 | it('Should fail with a missing errors', async function () { | ||
94 | |||
95 | }) | ||
96 | |||
97 | it('Should fail with an missing/invalid errors', async function () { | ||
98 | await makePostBodyRequest({ | ||
99 | url: server.url, | ||
100 | path, | ||
101 | fields: omit(baseParams, 'errors') | ||
102 | }) | ||
103 | |||
104 | await makePostBodyRequest({ | ||
105 | url: server.url, | ||
106 | path, | ||
107 | fields: { ...baseParams, errors: 'toto' } | ||
108 | }) | ||
109 | }) | ||
110 | |||
111 | it('Should fail with an missing/invalid downloadedBytesP2P', async function () { | ||
112 | await makePostBodyRequest({ | ||
113 | url: server.url, | ||
114 | path, | ||
115 | fields: omit(baseParams, 'downloadedBytesP2P') | ||
116 | }) | ||
117 | |||
118 | await makePostBodyRequest({ | ||
119 | url: server.url, | ||
120 | path, | ||
121 | fields: { ...baseParams, downloadedBytesP2P: 'toto' } | ||
122 | }) | ||
123 | }) | ||
124 | |||
125 | it('Should fail with an missing/invalid downloadedBytesHTTP', async function () { | ||
126 | await makePostBodyRequest({ | ||
127 | url: server.url, | ||
128 | path, | ||
129 | fields: omit(baseParams, 'downloadedBytesHTTP') | ||
130 | }) | ||
131 | |||
132 | await makePostBodyRequest({ | ||
133 | url: server.url, | ||
134 | path, | ||
135 | fields: { ...baseParams, downloadedBytesHTTP: 'toto' } | ||
136 | }) | ||
137 | }) | ||
138 | |||
139 | it('Should fail with an missing/invalid uploadedBytesP2P', async function () { | ||
140 | await makePostBodyRequest({ | ||
141 | url: server.url, | ||
142 | path, | ||
143 | fields: omit(baseParams, 'uploadedBytesP2P') | ||
144 | }) | ||
145 | |||
146 | await makePostBodyRequest({ | ||
147 | url: server.url, | ||
148 | path, | ||
149 | fields: { ...baseParams, uploadedBytesP2P: 'toto' } | ||
150 | }) | ||
151 | }) | ||
152 | |||
153 | it('Should fail with a bad video id', async function () { | ||
154 | await makePostBodyRequest({ | ||
155 | url: server.url, | ||
156 | path, | ||
157 | fields: { ...baseParams, videoId: 'toto' } | ||
158 | }) | ||
159 | }) | ||
160 | |||
161 | it('Should fail with an unknown video', async function () { | ||
162 | await makePostBodyRequest({ | ||
163 | url: server.url, | ||
164 | path, | ||
165 | fields: { ...baseParams, videoId: 42 }, | ||
166 | expectedStatus: HttpStatusCode.NOT_FOUND_404 | ||
167 | }) | ||
168 | }) | ||
169 | |||
170 | it('Should succeed with the correct params', async function () { | ||
171 | await makePostBodyRequest({ | ||
172 | url: server.url, | ||
173 | path, | ||
174 | fields: baseParams, | ||
175 | expectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
176 | }) | ||
177 | }) | ||
178 | }) | ||
179 | |||
180 | after(async function () { | ||
181 | await cleanupTests([ server ]) | ||
182 | }) | ||
183 | }) | ||
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 @@ | |||
2 | 2 | ||
3 | import { expect } from 'chai' | 3 | import { expect } from 'chai' |
4 | import { expectLogContain, expectLogDoesNotContain, MockHTTP } from '@server/tests/shared' | 4 | import { expectLogContain, expectLogDoesNotContain, MockHTTP } from '@server/tests/shared' |
5 | import { HttpStatusCode, VideoPrivacy } from '@shared/models' | 5 | import { HttpStatusCode, VideoPrivacy, VideoResolution } from '@shared/models' |
6 | import { cleanupTests, createSingleServer, makeRawRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' | 6 | import { cleanupTests, createSingleServer, makeRawRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' |
7 | 7 | ||
8 | describe('Open Telemetry', function () { | 8 | describe('Open Telemetry', function () { |
9 | let server: PeerTubeServer | 9 | let server: PeerTubeServer |
10 | 10 | ||
11 | describe('Metrics', function () { | 11 | describe('Metrics', function () { |
12 | const metricsUrl = 'http://localhost:9091/metrics' | 12 | const metricsUrl = 'http://localhost:9092/metrics' |
13 | 13 | ||
14 | it('Should not enable open telemetry metrics', async function () { | 14 | it('Should not enable open telemetry metrics', async function () { |
15 | server = await createSingleServer(1) | 15 | server = await createSingleServer(1) |
@@ -36,8 +36,33 @@ describe('Open Telemetry', function () { | |||
36 | }) | 36 | }) |
37 | 37 | ||
38 | const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200) | 38 | const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200) |
39 | expect(res.text).to.contain('peertube_job_queue_total') | 39 | expect(res.text).to.contain('peertube_job_queue_total{') |
40 | }) | ||
41 | |||
42 | it('Should have playback metrics', async function () { | ||
43 | await setAccessTokensToServers([ server ]) | ||
44 | |||
45 | const video = await server.videos.quickUpload({ name: 'video' }) | ||
46 | |||
47 | await server.metrics.addPlaybackMetric({ | ||
48 | metrics: { | ||
49 | playerMode: 'p2p-media-loader', | ||
50 | resolution: VideoResolution.H_1080P, | ||
51 | fps: 30, | ||
52 | resolutionChanges: 1, | ||
53 | errors: 2, | ||
54 | downloadedBytesP2P: 0, | ||
55 | downloadedBytesHTTP: 0, | ||
56 | uploadedBytesP2P: 5, | ||
57 | videoId: video.uuid | ||
58 | } | ||
59 | }) | ||
40 | 60 | ||
61 | const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200) | ||
62 | expect(res.text).to.contain('peertube_playback_http_uploaded_bytes_total{') | ||
63 | }) | ||
64 | |||
65 | after(async function () { | ||
41 | await server.kill() | 66 | await server.kill() |
42 | }) | 67 | }) |
43 | }) | 68 | }) |
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' | |||
6 | export * from './feeds' | 6 | export * from './feeds' |
7 | export * from './http' | 7 | export * from './http' |
8 | export * from './joinpeertube' | 8 | export * from './joinpeertube' |
9 | export * from './metrics' | ||
9 | export * from './moderation' | 10 | export * from './moderation' |
10 | export * from './overviews' | 11 | export * from './overviews' |
11 | export * from './plugins' | 12 | 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 @@ | |||
1 | import { VideoResolution } from '../videos' | ||
2 | |||
3 | export interface PlaybackMetricCreate { | ||
4 | playerMode: 'p2p-media-loader' | 'webtorrent' | ||
5 | |||
6 | resolution?: VideoResolution | ||
7 | fps?: number | ||
8 | |||
9 | resolutionChanges: number | ||
10 | |||
11 | errors: number | ||
12 | |||
13 | downloadedBytesP2P: number | ||
14 | downloadedBytesHTTP: number | ||
15 | |||
16 | uploadedBytesP2P: number | ||
17 | |||
18 | videoId: number | string | ||
19 | } | ||
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' | |||
5 | export * from './follows' | 5 | export * from './follows' |
6 | export * from './jobs' | 6 | export * from './jobs' |
7 | export * from './jobs-command' | 7 | export * from './jobs-command' |
8 | export * from './metrics-command' | ||
8 | export * from './object-storage-command' | 9 | export * from './object-storage-command' |
9 | export * from './plugins-command' | 10 | export * from './plugins-command' |
10 | export * from './redundancy-command' | 11 | 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 @@ | |||
1 | import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' | ||
2 | import { AbstractCommand, OverrideCommandOptions } from '../shared' | ||
3 | |||
4 | export class MetricsCommand extends AbstractCommand { | ||
5 | |||
6 | addPlaybackMetric (options: OverrideCommandOptions & { metrics: PlaybackMetricCreate }) { | ||
7 | const path = '/api/v1/metrics/playback' | ||
8 | |||
9 | return this.postBodyRequest({ | ||
10 | ...options, | ||
11 | |||
12 | path, | ||
13 | fields: options.metrics, | ||
14 | implicitToken: false, | ||
15 | defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 | ||
16 | }) | ||
17 | } | ||
18 | } | ||
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' | |||
37 | import { DebugCommand } from './debug-command' | 37 | import { DebugCommand } from './debug-command' |
38 | import { FollowsCommand } from './follows-command' | 38 | import { FollowsCommand } from './follows-command' |
39 | import { JobsCommand } from './jobs-command' | 39 | import { JobsCommand } from './jobs-command' |
40 | import { MetricsCommand } from './metrics-command' | ||
40 | import { ObjectStorageCommand } from './object-storage-command' | 41 | import { ObjectStorageCommand } from './object-storage-command' |
41 | import { PluginsCommand } from './plugins-command' | 42 | import { PluginsCommand } from './plugins-command' |
42 | import { RedundancyCommand } from './redundancy-command' | 43 | import { RedundancyCommand } from './redundancy-command' |
@@ -104,6 +105,7 @@ export class PeerTubeServer { | |||
104 | debug?: DebugCommand | 105 | debug?: DebugCommand |
105 | follows?: FollowsCommand | 106 | follows?: FollowsCommand |
106 | jobs?: JobsCommand | 107 | jobs?: JobsCommand |
108 | metrics?: MetricsCommand | ||
107 | plugins?: PluginsCommand | 109 | plugins?: PluginsCommand |
108 | redundancy?: RedundancyCommand | 110 | redundancy?: RedundancyCommand |
109 | stats?: StatsCommand | 111 | stats?: StatsCommand |
@@ -377,6 +379,7 @@ export class PeerTubeServer { | |||
377 | this.debug = new DebugCommand(this) | 379 | this.debug = new DebugCommand(this) |
378 | this.follows = new FollowsCommand(this) | 380 | this.follows = new FollowsCommand(this) |
379 | this.jobs = new JobsCommand(this) | 381 | this.jobs = new JobsCommand(this) |
382 | this.metrics = new MetricsCommand(this) | ||
380 | this.plugins = new PluginsCommand(this) | 383 | this.plugins = new PluginsCommand(this) |
381 | this.redundancy = new RedundancyCommand(this) | 384 | this.redundancy = new RedundancyCommand(this) |
382 | this.stats = new StatsCommand(this) | 385 | 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: | |||
5009 | '404': | 5009 | '404': |
5010 | description: plugin not found | 5010 | description: plugin not found |
5011 | 5011 | ||
5012 | /metrics/playback: | ||
5013 | post: | ||
5014 | summary: Create playback metrics | ||
5015 | description: These metrics are exposed by OpenTelemetry metrics exporter if enabled. | ||
5016 | tags: | ||
5017 | - Stats | ||
5018 | requestBody: | ||
5019 | content: | ||
5020 | application/json: | ||
5021 | schema: | ||
5022 | $ref: '#/components/schemas/PlaybackMetricCreate' | ||
5023 | responses: | ||
5024 | '204': | ||
5025 | description: successful operation | ||
5026 | |||
5012 | servers: | 5027 | servers: |
5013 | - url: 'https://peertube2.cpy.re/api/v1' | 5028 | - url: 'https://peertube2.cpy.re/api/v1' |
5014 | description: Live Test Server (live data - latest nightly version) | 5029 | description: Live Test Server (live data - latest nightly version) |
@@ -8195,44 +8210,86 @@ components: | |||
8195 | format: binary | 8210 | format: binary |
8196 | 8211 | ||
8197 | LiveVideoSessionResponse: | 8212 | LiveVideoSessionResponse: |
8198 | properties: | 8213 | properties: |
8199 | id: | 8214 | id: |
8200 | type: integer | 8215 | type: integer |
8201 | startDate: | 8216 | startDate: |
8202 | type: string | 8217 | type: string |
8203 | format: date-time | 8218 | format: date-time |
8204 | description: Start date of the live session | 8219 | description: Start date of the live session |
8205 | endDate: | 8220 | endDate: |
8206 | type: string | 8221 | type: string |
8207 | format: date-time | 8222 | format: date-time |
8208 | nullable: true | 8223 | nullable: true |
8209 | description: End date of the live session | 8224 | description: End date of the live session |
8210 | error: | 8225 | error: |
8211 | type: integer | 8226 | type: integer |
8212 | enum: | 8227 | enum: |
8213 | - 1 | 8228 | - 1 |
8214 | - 2 | 8229 | - 2 |
8215 | - 3 | 8230 | - 3 |
8216 | - 4 | 8231 | - 4 |
8217 | - 5 | 8232 | - 5 |
8218 | nullable: true | 8233 | nullable: true |
8219 | description: > | 8234 | description: > |
8220 | Error type if an error occurred during the live session: | 8235 | Error type if an error occurred during the live session: |
8221 | - `1`: Bad socket health (transcoding is too slow) | 8236 | - `1`: Bad socket health (transcoding is too slow) |
8222 | - `2`: Max duration exceeded | 8237 | - `2`: Max duration exceeded |
8223 | - `3`: Quota exceeded | 8238 | - `3`: Quota exceeded |
8224 | - `4`: Quota FFmpeg error | 8239 | - `4`: Quota FFmpeg error |
8225 | - `5`: Video has been blacklisted during the live | 8240 | - `5`: Video has been blacklisted during the live |
8226 | replayVideo: | 8241 | replayVideo: |
8227 | type: object | 8242 | type: object |
8228 | description: Video replay information | 8243 | description: Video replay information |
8229 | properties: | 8244 | properties: |
8230 | id: | 8245 | id: |
8231 | type: number | 8246 | type: number |
8232 | uuid: | 8247 | uuid: |
8233 | $ref: '#/components/schemas/UUIDv4' | 8248 | $ref: '#/components/schemas/UUIDv4' |
8234 | shortUUID: | 8249 | shortUUID: |
8235 | $ref: '#/components/schemas/shortUUID' | 8250 | $ref: '#/components/schemas/shortUUID' |
8251 | |||
8252 | PlaybackMetricCreate: | ||
8253 | properties: | ||
8254 | playerMode: | ||
8255 | type: string | ||
8256 | enum: | ||
8257 | - 'p2p-media-loader' | ||
8258 | - 'webtorrent' | ||
8259 | resolution: | ||
8260 | type: number | ||
8261 | description: Current player video resolution | ||
8262 | fps: | ||
8263 | type: number | ||
8264 | description: Current player video fps | ||
8265 | resolutionChanges: | ||
8266 | type: number | ||
8267 | description: How many resolution changes occured since the last metric creation | ||
8268 | errors: | ||
8269 | type: number | ||
8270 | description: How many errors occured since the last metric creation | ||
8271 | downloadedBytesP2P: | ||
8272 | type: number | ||
8273 | description: How many bytes were downloaded with P2P since the last metric creation | ||
8274 | downloadedBytesHTTP: | ||
8275 | type: number | ||
8276 | description: How many bytes were downloaded with HTTP since the last metric creation | ||
8277 | uploadedBytesP2P: | ||
8278 | type: number | ||
8279 | description: How many bytes were uploaded with P2P since the last metric creation | ||
8280 | videoId: | ||
8281 | oneOf: | ||
8282 | - $ref: '#/components/schemas/id' | ||
8283 | - $ref: '#/components/schemas/UUIDv4' | ||
8284 | - $ref: '#/components/schemas/shortUUID' | ||
8285 | required: | ||
8286 | - playerMode | ||
8287 | - resolutionChanges | ||
8288 | - errors | ||
8289 | - downloadedBytesP2P | ||
8290 | - downloadedBytesHTTP | ||
8291 | - uploadedBytesP2P | ||
8292 | - videoId | ||
8236 | 8293 | ||
8237 | callbacks: | 8294 | callbacks: |
8238 | searchIndex: | 8295 | searchIndex: |
@@ -1616,10 +1616,10 @@ | |||
1616 | dependencies: | 1616 | dependencies: |
1617 | "@opentelemetry/api" "^1.0.0" | 1617 | "@opentelemetry/api" "^1.0.0" |
1618 | 1618 | ||
1619 | "@opentelemetry/api-metrics@0.30.0", "@opentelemetry/api-metrics@^0.30.0": | 1619 | "@opentelemetry/api-metrics@0.31.0", "@opentelemetry/api-metrics@^0.31.0": |
1620 | version "0.30.0" | 1620 | version "0.31.0" |
1621 | resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.30.0.tgz#b5defd10756e81d1c7ce8669ff8a8d2465ba0be8" | 1621 | resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.31.0.tgz#0ed4cf4d7c731f968721c2b303eaf5e9fd42f736" |
1622 | integrity sha512-jSb7iiYPY+DSUKIyzfGt0a5K1QGzWY5fSWtUB8Alfi27NhQGHBeuYYC5n9MaBP/HNWw5GpEIhXGEYCF9Pf8IEg== | 1622 | integrity sha512-PcL1x0kZtMie7NsNy67OyMvzLEXqf3xd0TZJKHHPMGTe89oMpNVrD1zJB1kZcwXOxLlHHb6tz21G3vvXPdXyZg== |
1623 | dependencies: | 1623 | dependencies: |
1624 | "@opentelemetry/api" "^1.0.0" | 1624 | "@opentelemetry/api" "^1.0.0" |
1625 | 1625 | ||
@@ -1633,13 +1633,6 @@ | |||
1633 | resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.5.0.tgz#4955313e7f0ec0fe17c813328a2a7f39f262c0fa" | 1633 | resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.5.0.tgz#4955313e7f0ec0fe17c813328a2a7f39f262c0fa" |
1634 | integrity sha512-mhBPP0BU0RaH2HB8U4MDd5OjWA1y7SoLOovCT0iEpJAltaq2z04uxRJVzIs91vkpNnV0utUZowQQD3KElgU+VA== | 1634 | integrity sha512-mhBPP0BU0RaH2HB8U4MDd5OjWA1y7SoLOovCT0iEpJAltaq2z04uxRJVzIs91vkpNnV0utUZowQQD3KElgU+VA== |
1635 | 1635 | ||
1636 | "@opentelemetry/core@1.4.0": | ||
1637 | version "1.4.0" | ||
1638 | resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.4.0.tgz#26839ab9e36583a174273a1e1c5b33336c163725" | ||
1639 | integrity sha512-faq50VFEdyC7ICAOlhSi+yYZ+peznnGjTJToha9R63i9fVopzpKrkZt7AIdXUmz2+L2OqXrcJs7EIdN/oDyr5w== | ||
1640 | dependencies: | ||
1641 | "@opentelemetry/semantic-conventions" "1.4.0" | ||
1642 | |||
1643 | "@opentelemetry/core@1.5.0", "@opentelemetry/core@^1.0.0": | 1636 | "@opentelemetry/core@1.5.0", "@opentelemetry/core@^1.0.0": |
1644 | version "1.5.0" | 1637 | version "1.5.0" |
1645 | resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.5.0.tgz#717bceee15d4c69d4c7321c1fe0f5a562b60eb81" | 1638 | resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.5.0.tgz#717bceee15d4c69d4c7321c1fe0f5a562b60eb81" |
@@ -1657,14 +1650,14 @@ | |||
1657 | "@opentelemetry/semantic-conventions" "1.5.0" | 1650 | "@opentelemetry/semantic-conventions" "1.5.0" |
1658 | jaeger-client "^3.15.0" | 1651 | jaeger-client "^3.15.0" |
1659 | 1652 | ||
1660 | "@opentelemetry/exporter-prometheus@~0.30.0": | 1653 | "@opentelemetry/exporter-prometheus@~0.31.0": |
1661 | version "0.30.0" | 1654 | version "0.31.0" |
1662 | resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.30.0.tgz#f81322d3cb000170e716bc76820600d5649be538" | 1655 | resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.31.0.tgz#b0696be42542a961ec1145f3754a845efbda942e" |
1663 | integrity sha512-y0SXvpzoKR+Tk/UL6F1f7vAcCzqpCDP/cTEa+Z7sX57aEG0HDXLQiLmAgK/BHqcEN5MFQMZ+MDVDsUrvpa6/Jw== | 1656 | integrity sha512-EfWFzoCu/THw0kZiaA2RUrk6XIQbfaJHJ26LRrVIK7INwosW8Q+x4pGfiJ5nxhglYiG9OTqGrQ6nQ4T9q1UMpg== |
1664 | dependencies: | 1657 | dependencies: |
1665 | "@opentelemetry/api-metrics" "0.30.0" | 1658 | "@opentelemetry/api-metrics" "0.31.0" |
1666 | "@opentelemetry/core" "1.4.0" | 1659 | "@opentelemetry/core" "1.5.0" |
1667 | "@opentelemetry/sdk-metrics-base" "0.30.0" | 1660 | "@opentelemetry/sdk-metrics-base" "0.31.0" |
1668 | 1661 | ||
1669 | "@opentelemetry/instrumentation-dns@^0.29.0": | 1662 | "@opentelemetry/instrumentation-dns@^0.29.0": |
1670 | version "0.29.0" | 1663 | version "0.29.0" |
@@ -1694,14 +1687,14 @@ | |||
1694 | "@opentelemetry/instrumentation" "^0.29.2" | 1687 | "@opentelemetry/instrumentation" "^0.29.2" |
1695 | "@opentelemetry/semantic-conventions" "^1.0.0" | 1688 | "@opentelemetry/semantic-conventions" "^1.0.0" |
1696 | 1689 | ||
1697 | "@opentelemetry/instrumentation-http@^0.30.0": | 1690 | "@opentelemetry/instrumentation-http@^0.31.0": |
1698 | version "0.30.0" | 1691 | version "0.31.0" |
1699 | resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.30.0.tgz#312ef25defbff750dd9082356bb9a9137ed5fd82" | 1692 | resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.31.0.tgz#5c6dea9cdb636543c6ed1f1a4e55d4422e50fa89" |
1700 | integrity sha512-OhiuzR2mhlTcaXD1dYW/dqnC/zjIKHp2NWMUyDHEd4xS6NZAiTU5mNDv57Y9on+/VwYXWUZZ2tB7AOVPsFUIOg== | 1693 | integrity sha512-DLw+H7UQZ+V3FX72iGXVMX4ylL4jV+GHraaUiVY0CIdxg1nrGmjLm4dPU5500IXlbgZUUoJ9jq02JDblujdKcQ== |
1701 | dependencies: | 1694 | dependencies: |
1702 | "@opentelemetry/core" "1.4.0" | 1695 | "@opentelemetry/core" "1.5.0" |
1703 | "@opentelemetry/instrumentation" "0.30.0" | 1696 | "@opentelemetry/instrumentation" "0.31.0" |
1704 | "@opentelemetry/semantic-conventions" "1.4.0" | 1697 | "@opentelemetry/semantic-conventions" "1.5.0" |
1705 | semver "^7.3.5" | 1698 | semver "^7.3.5" |
1706 | 1699 | ||
1707 | "@opentelemetry/instrumentation-pg@^0.30.0": | 1700 | "@opentelemetry/instrumentation-pg@^0.30.0": |
@@ -1722,12 +1715,12 @@ | |||
1722 | "@opentelemetry/instrumentation" "^0.29.2" | 1715 | "@opentelemetry/instrumentation" "^0.29.2" |
1723 | "@opentelemetry/semantic-conventions" "^1.0.0" | 1716 | "@opentelemetry/semantic-conventions" "^1.0.0" |
1724 | 1717 | ||
1725 | "@opentelemetry/instrumentation@0.30.0", "@opentelemetry/instrumentation@^0.30.0": | 1718 | "@opentelemetry/instrumentation@0.31.0", "@opentelemetry/instrumentation@^0.31.0": |
1726 | version "0.30.0" | 1719 | version "0.31.0" |
1727 | resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.30.0.tgz#97cca611bd276439cc4e01e0516e50cbbb1e3459" | 1720 | resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.31.0.tgz#bee0052a86e22f57be3901c44234f1a210bcfda8" |
1728 | integrity sha512-9bjRx81B6wbJ7CGWc/WCUfcb0QIG5UIcjnPTzwYIURjYPd8d0ZzRlrnqEdQG62jn4lSPEvnNqTlyC7qXtn9nAA== | 1721 | integrity sha512-b2hFebXPtBcut4d81b8Kg6GiCoAS8nxb8kYSronQYAXxwNSetqHwIJ2nKLo1slFH1UWUXn0zi3eDez2Sn/9uMQ== |
1729 | dependencies: | 1722 | dependencies: |
1730 | "@opentelemetry/api-metrics" "0.30.0" | 1723 | "@opentelemetry/api-metrics" "0.31.0" |
1731 | require-in-the-middle "^5.0.3" | 1724 | require-in-the-middle "^5.0.3" |
1732 | semver "^7.3.2" | 1725 | semver "^7.3.2" |
1733 | shimmer "^1.2.1" | 1726 | shimmer "^1.2.1" |
@@ -1756,14 +1749,6 @@ | |||
1756 | dependencies: | 1749 | dependencies: |
1757 | "@opentelemetry/core" "1.5.0" | 1750 | "@opentelemetry/core" "1.5.0" |
1758 | 1751 | ||
1759 | "@opentelemetry/resources@1.4.0": | ||
1760 | version "1.4.0" | ||
1761 | resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.4.0.tgz#5e23b0d7976158861059dec17e0ee36a35a5ab85" | ||
1762 | integrity sha512-Q3pI5+pCM+Ur7YwK9GbG89UBipwJbfmuzSPAXTw964ZHFzSrz+JAgrETC9rqsUOYdUlj/V7LbRMG5bo72xE0Xw== | ||
1763 | dependencies: | ||
1764 | "@opentelemetry/core" "1.4.0" | ||
1765 | "@opentelemetry/semantic-conventions" "1.4.0" | ||
1766 | |||
1767 | "@opentelemetry/resources@1.5.0", "@opentelemetry/resources@^1.3.1": | 1752 | "@opentelemetry/resources@1.5.0", "@opentelemetry/resources@^1.3.1": |
1768 | version "1.5.0" | 1753 | version "1.5.0" |
1769 | resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.5.0.tgz#ce7fbdaec3494e41bc279ddbed3c478ee2570b03" | 1754 | resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.5.0.tgz#ce7fbdaec3494e41bc279ddbed3c478ee2570b03" |
@@ -1772,14 +1757,14 @@ | |||
1772 | "@opentelemetry/core" "1.5.0" | 1757 | "@opentelemetry/core" "1.5.0" |
1773 | "@opentelemetry/semantic-conventions" "1.5.0" | 1758 | "@opentelemetry/semantic-conventions" "1.5.0" |
1774 | 1759 | ||
1775 | "@opentelemetry/sdk-metrics-base@0.30.0", "@opentelemetry/sdk-metrics-base@~0.30.0": | 1760 | "@opentelemetry/sdk-metrics-base@0.31.0", "@opentelemetry/sdk-metrics-base@~0.31.0": |
1776 | version "0.30.0" | 1761 | version "0.31.0" |
1777 | resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.30.0.tgz#242d9260a89a1ac2bf1e167b3fda758f3883c769" | 1762 | resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.31.0.tgz#f797da702c8d9862a2fff55a1e7c70aa6845e535" |
1778 | integrity sha512-3BDg1MYDInDyGvy+bSH8OuCX5nsue7omH6Y2eidCGTTDYRPxDmq9tsRJxnTUepoMAvWX+1sTwZ4JqTFmc1z8Mw== | 1763 | integrity sha512-4R2Bjl3wlqIGcq4bCoI9/pD49ld+tEoM9n85UfFzr/aUe+2huY2jTPq/BP9SVB8d2Zfg7mGTIFeapcEvAdKK7g== |
1779 | dependencies: | 1764 | dependencies: |
1780 | "@opentelemetry/api-metrics" "0.30.0" | 1765 | "@opentelemetry/api-metrics" "0.31.0" |
1781 | "@opentelemetry/core" "1.4.0" | 1766 | "@opentelemetry/core" "1.5.0" |
1782 | "@opentelemetry/resources" "1.4.0" | 1767 | "@opentelemetry/resources" "1.5.0" |
1783 | lodash.merge "4.6.2" | 1768 | lodash.merge "4.6.2" |
1784 | 1769 | ||
1785 | "@opentelemetry/sdk-trace-base@1.5.0", "@opentelemetry/sdk-trace-base@^1.3.1": | 1770 | "@opentelemetry/sdk-trace-base@1.5.0", "@opentelemetry/sdk-trace-base@^1.3.1": |
@@ -1803,11 +1788,6 @@ | |||
1803 | "@opentelemetry/sdk-trace-base" "1.5.0" | 1788 | "@opentelemetry/sdk-trace-base" "1.5.0" |
1804 | semver "^7.3.5" | 1789 | semver "^7.3.5" |
1805 | 1790 | ||
1806 | "@opentelemetry/semantic-conventions@1.4.0": | ||
1807 | version "1.4.0" | ||
1808 | resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.4.0.tgz#facf2c67d6063b9918d5a5e3fdf25f3a30d547b6" | ||
1809 | integrity sha512-Hzl8soGpmyzja9w3kiFFcYJ7n5HNETpplY6cb67KR4QPlxp4FTTresO06qXHgHDhyIInmbLJXuwARjjpsKYGuQ== | ||
1810 | |||
1811 | "@opentelemetry/semantic-conventions@1.5.0", "@opentelemetry/semantic-conventions@^1.0.0", "@opentelemetry/semantic-conventions@^1.3.1": | 1791 | "@opentelemetry/semantic-conventions@1.5.0", "@opentelemetry/semantic-conventions@^1.0.0", "@opentelemetry/semantic-conventions@^1.3.1": |
1812 | version "1.5.0" | 1792 | version "1.5.0" |
1813 | resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.5.0.tgz#cea9792bfcf556c87ded17c6ac729348697bb632" | 1793 | resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.5.0.tgz#cea9792bfcf556c87ded17c6ac729348697bb632" |