Add live and viewers otel metrics

pull/5163/head
Chocobozzz 2022-07-27 16:19:25 +02:00
parent 50cc1ee48a
commit adc94cf09c
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
8 changed files with 106 additions and 14 deletions

View File

@ -1,3 +1,5 @@
export * from './lives-observers-builder'
export * from './job-queue-observers-builder' export * from './job-queue-observers-builder'
export * from './nodejs-observers-builder' export * from './nodejs-observers-builder'
export * from './stats-observers-builder' export * from './stats-observers-builder'
export * from './viewers-observers-builder'

View File

@ -0,0 +1,21 @@
import { Meter } from '@opentelemetry/api-metrics'
import { VideoModel } from '@server/models/video/video'
export class LivesObserversBuilder {
constructor (private readonly meter: Meter) {
}
buildObservers () {
this.meter.createObservableGauge('peertube_running_lives_total', {
description: 'Total running lives on the instance'
}).addCallback(async observableResult => {
const local = await VideoModel.countLives({ remote: false, mode: 'published' })
const remote = await VideoModel.countLives({ remote: true, mode: 'published' })
observableResult.observe(local, { liveOrigin: 'local' })
observableResult.observe(remote, { liveOrigin: 'remote' })
})
}
}

View File

@ -0,0 +1,24 @@
import { Meter } from '@opentelemetry/api-metrics'
import { VideoScope, ViewerScope } from '@server/lib/views/shared'
import { VideoViewsManager } from '@server/lib/views/video-views-manager'
export class ViewersObserversBuilder {
constructor (private readonly meter: Meter) {
}
buildObservers () {
this.meter.createObservableGauge('peertube_viewers_total', {
description: 'Total viewers on the instance'
}).addCallback(observableResult => {
for (const viewerScope of [ 'local', 'remote' ] as ViewerScope[]) {
for (const videoScope of [ 'local', 'remote' ] as VideoScope[]) {
const result = VideoViewsManager.Instance.getTotalViewers({ viewerScope, videoScope })
observableResult.observe(result, { viewerOrigin: viewerScope, videoOrigin: videoScope })
}
}
})
}
}

View File

@ -4,7 +4,13 @@ import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
import { MeterProvider } from '@opentelemetry/sdk-metrics-base' import { MeterProvider } from '@opentelemetry/sdk-metrics-base'
import { logger } from '@server/helpers/logger' import { logger } from '@server/helpers/logger'
import { CONFIG } from '@server/initializers/config' import { CONFIG } from '@server/initializers/config'
import { JobQueueObserversBuilder, NodeJSObserversBuilder, StatsObserversBuilder } from './metric-helpers' import {
JobQueueObserversBuilder,
LivesObserversBuilder,
NodeJSObserversBuilder,
StatsObserversBuilder,
ViewersObserversBuilder
} from './metric-helpers'
class OpenTelemetryMetrics { class OpenTelemetryMetrics {
@ -53,6 +59,12 @@ class OpenTelemetryMetrics {
const statsObserversBuilder = new StatsObserversBuilder(this.meter) const statsObserversBuilder = new StatsObserversBuilder(this.meter)
statsObserversBuilder.buildObservers() statsObserversBuilder.buildObservers()
const livesObserversBuilder = new LivesObserversBuilder(this.meter)
livesObserversBuilder.buildObservers()
const viewersObserversBuilder = new ViewersObserversBuilder(this.meter)
viewersObserversBuilder.buildObservers()
} }
private buildRequestObserver () { private buildRequestObserver () {

View File

@ -10,9 +10,14 @@ import { buildUUID, sha256 } from '@shared/extra-utils'
const lTags = loggerTagsFactory('views') const lTags = loggerTagsFactory('views')
export type ViewerScope = 'local' | 'remote'
export type VideoScope = 'local' | 'remote'
type Viewer = { type Viewer = {
expires: number expires: number
id: string id: string
viewerScope: ViewerScope
videoScope: VideoScope
lastFederation?: number lastFederation?: number
} }
@ -50,7 +55,7 @@ export class VideoViewerCounters {
return false return false
} }
const newViewer = await this.addViewerToVideo({ viewerId, video }) const newViewer = await this.addViewerToVideo({ viewerId, video, viewerScope: 'local' })
await this.federateViewerIfNeeded(video, newViewer) await this.federateViewerIfNeeded(video, newViewer)
return true return true
@ -65,13 +70,26 @@ export class VideoViewerCounters {
logger.debug('Adding remote viewer to video %s.', video.uuid, { ...lTags(video.uuid) }) logger.debug('Adding remote viewer to video %s.', video.uuid, { ...lTags(video.uuid) })
await this.addViewerToVideo({ video, viewerExpires, viewerId }) await this.addViewerToVideo({ video, viewerExpires, viewerId, viewerScope: 'remote' })
return true return true
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
getTotalViewers (options: {
viewerScope: ViewerScope
videoScope: VideoScope
}) {
let total = 0
for (const viewers of this.viewersPerVideo.values()) {
total += viewers.filter(v => v.viewerScope === options.viewerScope && v.videoScope === options.videoScope).length
}
return total
}
getViewers (video: MVideo) { getViewers (video: MVideo) {
const viewers = this.viewersPerVideo.get(video.id) const viewers = this.viewersPerVideo.get(video.id)
if (!viewers) return 0 if (!viewers) return 0
@ -88,9 +106,10 @@ export class VideoViewerCounters {
private async addViewerToVideo (options: { private async addViewerToVideo (options: {
video: MVideoImmutable video: MVideoImmutable
viewerId: string viewerId: string
viewerScope: ViewerScope
viewerExpires?: Date viewerExpires?: Date
}) { }) {
const { video, viewerExpires, viewerId } = options const { video, viewerExpires, viewerId, viewerScope } = options
let watchers = this.viewersPerVideo.get(video.id) let watchers = this.viewersPerVideo.get(video.id)
@ -103,7 +122,11 @@ export class VideoViewerCounters {
? viewerExpires.getTime() ? viewerExpires.getTime()
: this.buildViewerExpireTime() : this.buildViewerExpireTime()
const viewer = { id: viewerId, expires } const videoScope: VideoScope = video.remote
? 'remote'
: 'local'
const viewer = { id: viewerId, expires, videoScope, viewerScope }
watchers.push(viewer) watchers.push(viewer)
this.idToViewer.set(viewerId, viewer) this.idToViewer.set(viewerId, viewer)

View File

@ -1,7 +1,7 @@
import { logger, loggerTagsFactory } from '@server/helpers/logger' import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { MVideo, MVideoImmutable } from '@server/types/models' import { MVideo, MVideoImmutable } from '@server/types/models'
import { VideoViewEvent } from '@shared/models' import { VideoViewEvent } from '@shared/models'
import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared' import { VideoScope, VideoViewerCounters, VideoViewerStats, VideoViews, ViewerScope } from './shared'
/** /**
* If processing a local view: * If processing a local view:
@ -79,6 +79,13 @@ export class VideoViewsManager {
return this.videoViewerCounters.getViewers(video) return this.videoViewerCounters.getViewers(video)
} }
getTotalViewers (options: {
viewerScope: ViewerScope
videoScope: VideoScope
}) {
return this.videoViewerCounters.getTotalViewers(options)
}
buildViewerExpireTime () { buildViewerExpireTime () {
return this.videoViewerCounters.buildViewerExpireTime() return this.videoViewerCounters.buildViewerExpireTime()
} }

View File

@ -119,7 +119,7 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
if (!await doesVideoChannelOfAccountExist(body.channelId, user, res)) return cleanUpReqFiles(req) if (!await doesVideoChannelOfAccountExist(body.channelId, user, res)) return cleanUpReqFiles(req)
if (CONFIG.LIVE.MAX_INSTANCE_LIVES !== -1) { if (CONFIG.LIVE.MAX_INSTANCE_LIVES !== -1) {
const totalInstanceLives = await VideoModel.countLocalLives() const totalInstanceLives = await VideoModel.countLives({ remote: false, mode: 'not-ended' })
if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) { if (totalInstanceLives >= CONFIG.LIVE.MAX_INSTANCE_LIVES) {
cleanUpReqFiles(req) cleanUpReqFiles(req)

View File

@ -1209,18 +1209,21 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
return VideoModel.getAvailableForApi(queryOptions) return VideoModel.getAvailableForApi(queryOptions)
} }
static countLocalLives () { static countLives (options: {
const options = { remote: boolean
mode: 'published' | 'not-ended'
}) {
const query = {
where: { where: {
remote: false, remote: options.remote,
isLive: true, isLive: true,
state: { state: options.mode === 'not-ended'
[Op.ne]: VideoState.LIVE_ENDED ? { [Op.ne]: VideoState.LIVE_ENDED }
} : { [Op.eq]: VideoState.PUBLISHED }
} }
} }
return VideoModel.count(options) return VideoModel.count(query)
} }
static countVideosUploadedByUserSince (userId: number, since: Date) { static countVideosUploadedByUserSince (userId: number, since: Date) {