2022-07-05 15:43:21 +02:00
|
|
|
import { Application, Request, Response } from 'express'
|
2022-11-15 13:56:04 +01:00
|
|
|
import { Meter, metrics } from '@opentelemetry/api'
|
2022-07-05 15:43:21 +02:00
|
|
|
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
|
2022-09-09 11:11:47 +02:00
|
|
|
import { MeterProvider } from '@opentelemetry/sdk-metrics'
|
2023-07-31 14:34:36 +02:00
|
|
|
import { logger } from '@server/helpers/logger.js'
|
|
|
|
import { CONFIG } from '@server/initializers/config.js'
|
|
|
|
import { MVideoImmutable } from '@server/types/models/index.js'
|
|
|
|
import { PlaybackMetricCreate } from '@peertube/peertube-models'
|
2022-07-27 16:19:25 +02:00
|
|
|
import {
|
2023-01-05 10:19:51 +01:00
|
|
|
BittorrentTrackerObserversBuilder,
|
2022-07-27 16:19:25 +02:00
|
|
|
JobQueueObserversBuilder,
|
|
|
|
LivesObserversBuilder,
|
|
|
|
NodeJSObserversBuilder,
|
2022-08-12 16:41:29 +02:00
|
|
|
PlaybackMetrics,
|
2022-07-27 16:19:25 +02:00
|
|
|
StatsObserversBuilder,
|
|
|
|
ViewersObserversBuilder
|
2023-07-31 14:34:36 +02:00
|
|
|
} from './metric-helpers/index.js'
|
2023-10-26 16:34:54 +02:00
|
|
|
import { WorkerThreadsObserversBuilder } from './metric-helpers/worker-threads-observers.js'
|
2022-07-05 15:43:21 +02:00
|
|
|
|
|
|
|
class OpenTelemetryMetrics {
|
|
|
|
|
|
|
|
private static instance: OpenTelemetryMetrics
|
|
|
|
|
|
|
|
private meter: Meter
|
|
|
|
|
|
|
|
private onRequestDuration: (req: Request, res: Response) => void
|
|
|
|
|
2022-08-12 16:41:29 +02:00
|
|
|
private playbackMetrics: PlaybackMetrics
|
|
|
|
|
2022-07-05 15:43:21 +02:00
|
|
|
private constructor () {}
|
|
|
|
|
|
|
|
init (app: Application) {
|
|
|
|
if (CONFIG.OPEN_TELEMETRY.METRICS.ENABLED !== true) return
|
|
|
|
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
res.once('finish', () => {
|
|
|
|
if (!this.onRequestDuration) return
|
|
|
|
|
|
|
|
this.onRequestDuration(req as Request, res as Response)
|
|
|
|
})
|
|
|
|
|
|
|
|
next()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-05 10:19:51 +01:00
|
|
|
registerMetrics (options: { trackerServer: any }) {
|
2022-07-05 15:43:21 +02:00
|
|
|
if (CONFIG.OPEN_TELEMETRY.METRICS.ENABLED !== true) return
|
|
|
|
|
|
|
|
logger.info('Registering Open Telemetry metrics')
|
|
|
|
|
2022-08-12 16:41:29 +02:00
|
|
|
const provider = new MeterProvider({
|
|
|
|
views: [
|
|
|
|
...NodeJSObserversBuilder.getViews()
|
|
|
|
]
|
|
|
|
})
|
2022-07-05 15:43:21 +02:00
|
|
|
|
2022-11-15 13:56:04 +01:00
|
|
|
provider.addMetricReader(new PrometheusExporter({
|
|
|
|
host: CONFIG.OPEN_TELEMETRY.METRICS.PROMETHEUS_EXPORTER.HOSTNAME,
|
|
|
|
port: CONFIG.OPEN_TELEMETRY.METRICS.PROMETHEUS_EXPORTER.PORT
|
|
|
|
}))
|
2022-07-05 15:43:21 +02:00
|
|
|
|
|
|
|
metrics.setGlobalMeterProvider(provider)
|
|
|
|
|
|
|
|
this.meter = metrics.getMeter('default')
|
|
|
|
|
2023-02-27 13:53:54 +01:00
|
|
|
if (CONFIG.OPEN_TELEMETRY.METRICS.HTTP_REQUEST_DURATION.ENABLED === true) {
|
|
|
|
this.buildRequestObserver()
|
|
|
|
}
|
2022-07-05 15:43:21 +02:00
|
|
|
|
2022-08-12 16:41:29 +02:00
|
|
|
this.playbackMetrics = new PlaybackMetrics(this.meter)
|
|
|
|
this.playbackMetrics.buildCounters()
|
|
|
|
|
2023-10-26 16:34:54 +02:00
|
|
|
new NodeJSObserversBuilder(this.meter).buildObservers()
|
|
|
|
new JobQueueObserversBuilder(this.meter).buildObservers()
|
|
|
|
new StatsObserversBuilder(this.meter).buildObservers()
|
|
|
|
new LivesObserversBuilder(this.meter).buildObservers()
|
|
|
|
new ViewersObserversBuilder(this.meter).buildObservers()
|
|
|
|
new WorkerThreadsObserversBuilder(this.meter).buildObservers()
|
|
|
|
new BittorrentTrackerObserversBuilder(this.meter, options.trackerServer).buildObservers()
|
2022-07-05 15:43:21 +02:00
|
|
|
}
|
|
|
|
|
2022-08-12 16:41:29 +02:00
|
|
|
observePlaybackMetric (video: MVideoImmutable, metrics: PlaybackMetricCreate) {
|
|
|
|
this.playbackMetrics.observe(video, metrics)
|
|
|
|
}
|
|
|
|
|
2022-07-05 15:43:21 +02:00
|
|
|
private buildRequestObserver () {
|
|
|
|
const requestDuration = this.meter.createHistogram('http_request_duration_ms', {
|
|
|
|
unit: 'milliseconds',
|
|
|
|
description: 'Duration of HTTP requests in ms'
|
|
|
|
})
|
|
|
|
|
|
|
|
this.onRequestDuration = (req: Request, res: Response) => {
|
|
|
|
const duration = Date.now() - res.locals.requestStart
|
|
|
|
|
|
|
|
requestDuration.record(duration, {
|
|
|
|
path: this.buildRequestPath(req.originalUrl),
|
|
|
|
method: req.method,
|
|
|
|
statusCode: res.statusCode + ''
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private buildRequestPath (path: string) {
|
|
|
|
return path.split('?')[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
static get Instance () {
|
|
|
|
return this.instance || (this.instance = new this())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export {
|
|
|
|
OpenTelemetryMetrics
|
|
|
|
}
|