aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/lib/opentelemetry/metric-helpers/index.ts2
-rw-r--r--server/lib/opentelemetry/metric-helpers/lives-observers-builder.ts21
-rw-r--r--server/lib/opentelemetry/metric-helpers/viewers-observers-builder.ts24
-rw-r--r--server/lib/opentelemetry/metrics.ts14
-rw-r--r--server/lib/views/shared/video-viewer-counters.ts31
-rw-r--r--server/lib/views/video-views-manager.ts9
-rw-r--r--server/middlewares/validators/videos/video-live.ts2
-rw-r--r--server/models/video/video.ts17
8 files changed, 106 insertions, 14 deletions
diff --git a/server/lib/opentelemetry/metric-helpers/index.ts b/server/lib/opentelemetry/metric-helpers/index.ts
index ff0aff9fd..1b3813743 100644
--- a/server/lib/opentelemetry/metric-helpers/index.ts
+++ b/server/lib/opentelemetry/metric-helpers/index.ts
@@ -1,3 +1,5 @@
1export * from './lives-observers-builder'
1export * from './job-queue-observers-builder' 2export * from './job-queue-observers-builder'
2export * from './nodejs-observers-builder' 3export * from './nodejs-observers-builder'
3export * from './stats-observers-builder' 4export * from './stats-observers-builder'
5export * from './viewers-observers-builder'
diff --git a/server/lib/opentelemetry/metric-helpers/lives-observers-builder.ts b/server/lib/opentelemetry/metric-helpers/lives-observers-builder.ts
new file mode 100644
index 000000000..e27bd8548
--- /dev/null
+++ b/server/lib/opentelemetry/metric-helpers/lives-observers-builder.ts
@@ -0,0 +1,21 @@
1import { Meter } from '@opentelemetry/api-metrics'
2import { VideoModel } from '@server/models/video/video'
3
4export class LivesObserversBuilder {
5
6 constructor (private readonly meter: Meter) {
7
8 }
9
10 buildObservers () {
11 this.meter.createObservableGauge('peertube_running_lives_total', {
12 description: 'Total running lives on the instance'
13 }).addCallback(async observableResult => {
14 const local = await VideoModel.countLives({ remote: false, mode: 'published' })
15 const remote = await VideoModel.countLives({ remote: true, mode: 'published' })
16
17 observableResult.observe(local, { liveOrigin: 'local' })
18 observableResult.observe(remote, { liveOrigin: 'remote' })
19 })
20 }
21}
diff --git a/server/lib/opentelemetry/metric-helpers/viewers-observers-builder.ts b/server/lib/opentelemetry/metric-helpers/viewers-observers-builder.ts
new file mode 100644
index 000000000..634e5bbc9
--- /dev/null
+++ b/server/lib/opentelemetry/metric-helpers/viewers-observers-builder.ts
@@ -0,0 +1,24 @@
1import { Meter } from '@opentelemetry/api-metrics'
2import { VideoScope, ViewerScope } from '@server/lib/views/shared'
3import { VideoViewsManager } from '@server/lib/views/video-views-manager'
4
5export class ViewersObserversBuilder {
6
7 constructor (private readonly meter: Meter) {
8
9 }
10
11 buildObservers () {
12 this.meter.createObservableGauge('peertube_viewers_total', {
13 description: 'Total viewers on the instance'
14 }).addCallback(observableResult => {
15 for (const viewerScope of [ 'local', 'remote' ] as ViewerScope[]) {
16 for (const videoScope of [ 'local', 'remote' ] as VideoScope[]) {
17 const result = VideoViewsManager.Instance.getTotalViewers({ viewerScope, videoScope })
18
19 observableResult.observe(result, { viewerOrigin: viewerScope, videoOrigin: videoScope })
20 }
21 }
22 })
23 }
24}
diff --git a/server/lib/opentelemetry/metrics.ts b/server/lib/opentelemetry/metrics.ts
index 149f421be..ffe493670 100644
--- a/server/lib/opentelemetry/metrics.ts
+++ b/server/lib/opentelemetry/metrics.ts
@@ -4,7 +4,13 @@ import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
4import { MeterProvider } from '@opentelemetry/sdk-metrics-base' 4import { MeterProvider } from '@opentelemetry/sdk-metrics-base'
5import { logger } from '@server/helpers/logger' 5import { logger } from '@server/helpers/logger'
6import { CONFIG } from '@server/initializers/config' 6import { CONFIG } from '@server/initializers/config'
7import { JobQueueObserversBuilder, NodeJSObserversBuilder, StatsObserversBuilder } from './metric-helpers' 7import {
8 JobQueueObserversBuilder,
9 LivesObserversBuilder,
10 NodeJSObserversBuilder,
11 StatsObserversBuilder,
12 ViewersObserversBuilder
13} from './metric-helpers'
8 14
9class OpenTelemetryMetrics { 15class OpenTelemetryMetrics {
10 16
@@ -53,6 +59,12 @@ class OpenTelemetryMetrics {
53 59
54 const statsObserversBuilder = new StatsObserversBuilder(this.meter) 60 const statsObserversBuilder = new StatsObserversBuilder(this.meter)
55 statsObserversBuilder.buildObservers() 61 statsObserversBuilder.buildObservers()
62
63 const livesObserversBuilder = new LivesObserversBuilder(this.meter)
64 livesObserversBuilder.buildObservers()
65
66 const viewersObserversBuilder = new ViewersObserversBuilder(this.meter)
67 viewersObserversBuilder.buildObservers()
56 } 68 }
57 69
58 private buildRequestObserver () { 70 private buildRequestObserver () {
diff --git a/server/lib/views/shared/video-viewer-counters.ts b/server/lib/views/shared/video-viewer-counters.ts
index f851ce050..f5b83130e 100644
--- a/server/lib/views/shared/video-viewer-counters.ts
+++ b/server/lib/views/shared/video-viewer-counters.ts
@@ -10,9 +10,14 @@ import { buildUUID, sha256 } from '@shared/extra-utils'
10 10
11const lTags = loggerTagsFactory('views') 11const lTags = loggerTagsFactory('views')
12 12
13export type ViewerScope = 'local' | 'remote'
14export type VideoScope = 'local' | 'remote'
15
13type Viewer = { 16type Viewer = {
14 expires: number 17 expires: number
15 id: string 18 id: string
19 viewerScope: ViewerScope
20 videoScope: VideoScope
16 lastFederation?: number 21 lastFederation?: number
17} 22}
18 23
@@ -50,7 +55,7 @@ export class VideoViewerCounters {
50 return false 55 return false
51 } 56 }
52 57
53 const newViewer = await this.addViewerToVideo({ viewerId, video }) 58 const newViewer = await this.addViewerToVideo({ viewerId, video, viewerScope: 'local' })
54 await this.federateViewerIfNeeded(video, newViewer) 59 await this.federateViewerIfNeeded(video, newViewer)
55 60
56 return true 61 return true
@@ -65,13 +70,26 @@ export class VideoViewerCounters {
65 70
66 logger.debug('Adding remote viewer to video %s.', video.uuid, { ...lTags(video.uuid) }) 71 logger.debug('Adding remote viewer to video %s.', video.uuid, { ...lTags(video.uuid) })
67 72
68 await this.addViewerToVideo({ video, viewerExpires, viewerId }) 73 await this.addViewerToVideo({ video, viewerExpires, viewerId, viewerScope: 'remote' })
69 74
70 return true 75 return true
71 } 76 }
72 77
73 // --------------------------------------------------------------------------- 78 // ---------------------------------------------------------------------------
74 79
80 getTotalViewers (options: {
81 viewerScope: ViewerScope
82 videoScope: VideoScope
83 }) {
84 let total = 0
85
86 for (const viewers of this.viewersPerVideo.values()) {
87 total += viewers.filter(v => v.viewerScope === options.viewerScope && v.videoScope === options.videoScope).length
88 }
89
90 return total
91 }
92
75 getViewers (video: MVideo) { 93 getViewers (video: MVideo) {
76 const viewers = this.viewersPerVideo.get(video.id) 94 const viewers = this.viewersPerVideo.get(video.id)
77 if (!viewers) return 0 95 if (!viewers) return 0
@@ -88,9 +106,10 @@ export class VideoViewerCounters {
88 private async addViewerToVideo (options: { 106 private async addViewerToVideo (options: {
89 video: MVideoImmutable 107 video: MVideoImmutable
90 viewerId: string 108 viewerId: string
109 viewerScope: ViewerScope
91 viewerExpires?: Date 110 viewerExpires?: Date
92 }) { 111 }) {
93 const { video, viewerExpires, viewerId } = options 112 const { video, viewerExpires, viewerId, viewerScope } = options
94 113
95 let watchers = this.viewersPerVideo.get(video.id) 114 let watchers = this.viewersPerVideo.get(video.id)
96 115
@@ -103,7 +122,11 @@ export class VideoViewerCounters {
103 ? viewerExpires.getTime() 122 ? viewerExpires.getTime()
104 : this.buildViewerExpireTime() 123 : this.buildViewerExpireTime()
105 124
106 const viewer = { id: viewerId, expires } 125 const videoScope: VideoScope = video.remote
126 ? 'remote'
127 : 'local'
128
129 const viewer = { id: viewerId, expires, videoScope, viewerScope }
107 watchers.push(viewer) 130 watchers.push(viewer)
108 131
109 this.idToViewer.set(viewerId, viewer) 132 this.idToViewer.set(viewerId, viewer)
diff --git a/server/lib/views/video-views-manager.ts b/server/lib/views/video-views-manager.ts
index 86758e8d8..c088dad5e 100644
--- a/server/lib/views/video-views-manager.ts
+++ b/server/lib/views/video-views-manager.ts
@@ -1,7 +1,7 @@
1import { logger, loggerTagsFactory } from '@server/helpers/logger' 1import { logger, loggerTagsFactory } from '@server/helpers/logger'
2import { MVideo, MVideoImmutable } from '@server/types/models' 2import { MVideo, MVideoImmutable } from '@server/types/models'
3import { VideoViewEvent } from '@shared/models' 3import { VideoViewEvent } from '@shared/models'
4import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared' 4import { VideoScope, VideoViewerCounters, VideoViewerStats, VideoViews, ViewerScope } from './shared'
5 5
6/** 6/**
7 * If processing a local view: 7 * If processing a local view:
@@ -79,6 +79,13 @@ export class VideoViewsManager {
79 return this.videoViewerCounters.getViewers(video) 79 return this.videoViewerCounters.getViewers(video)
80 } 80 }
81 81
82 getTotalViewers (options: {
83 viewerScope: ViewerScope
84 videoScope: VideoScope
85 }) {
86 return this.videoViewerCounters.getTotalViewers(options)
87 }
88
82 buildViewerExpireTime () { 89 buildViewerExpireTime () {
83 return this.videoViewerCounters.buildViewerExpireTime() 90 return this.videoViewerCounters.buildViewerExpireTime()
84 } 91 }
diff --git a/server/middlewares/validators/videos/video-live.ts b/server/middlewares/validators/videos/video-live.ts
index 59638d5e0..777b57e9a 100644
--- a/server/middlewares/validators/videos/video-live.ts
+++ b/server/middlewares/validators/videos/video-live.ts
@@ -119,7 +119,7 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
119 if (!await doesVideoChannelOfAccountExist(body.channelId, user, res)) return cleanUpReqFiles(req) 119 if (!await doesVideoChannelOfAccountExist(body.channelId, user, res)) return cleanUpReqFiles(req)
120 120
121 if (CONFIG.LIVE.MAX_INSTANCE_LIVES !== -1) { 121 if (CONFIG.LIVE.MAX_INSTANCE_LIVES !== -1) {
122 const totalInstanceLives = await VideoModel.countLocalLives() 122 const totalInstanceLives = await VideoModel.countLives({ remote: false, mode: 'not-ended' })
123 123
124 if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) { 124 if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) {
125 cleanUpReqFiles(req) 125 cleanUpReqFiles(req)
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 27e605be6..924f12a5e 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1209,18 +1209,21 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1209 return VideoModel.getAvailableForApi(queryOptions) 1209 return VideoModel.getAvailableForApi(queryOptions)
1210 } 1210 }
1211 1211
1212 static countLocalLives () { 1212 static countLives (options: {
1213 const options = { 1213 remote: boolean
1214 mode: 'published' | 'not-ended'
1215 }) {
1216 const query = {
1214 where: { 1217 where: {
1215 remote: false, 1218 remote: options.remote,
1216 isLive: true, 1219 isLive: true,
1217 state: { 1220 state: options.mode === 'not-ended'
1218 [Op.ne]: VideoState.LIVE_ENDED 1221 ? { [Op.ne]: VideoState.LIVE_ENDED }
1219 } 1222 : { [Op.eq]: VideoState.PUBLISHED }
1220 } 1223 }
1221 } 1224 }
1222 1225
1223 return VideoModel.count(options) 1226 return VideoModel.count(query)
1224 } 1227 }
1225 1228
1226 static countVideosUploadedByUserSince (userId: number, since: Date) { 1229 static countVideosUploadedByUserSince (userId: number, since: Date) {