diff --git a/src/metrics.ts b/src/metrics.ts new file mode 100644 index 0000000000000000000000000000000000000000..82329d824e84f4633554f2f9f731a6dca289bb39 --- /dev/null +++ b/src/metrics.ts @@ -0,0 +1,132 @@ +import { PrometheusMetrics } from "matrix-appservice-bridge"; +import { Gauge, Counter, Histogram } from "prom-client"; +import { Log } from "./log"; + +const AgeCounters = PrometheusMetrics.AgeCounters; +const log = new Log("BridgeMetrics"); + +interface IAgeCounter { + setGauge(gauge: Gauge, morelabels: any); + bump(age: number); +} + +interface IBridgeGauges { + matrixRoomConfigs: number; + remoteRoomConfigs: number; + matrixGhosts: number; + remoteGhosts: number; + matrixRoomsByAge: IAgeCounter; + remoteRoomsByAge: IAgeCounter; + matrixUsersByAge: IAgeCounter; + remoteUsersByAge: IAgeCounter; +} + +export interface IBridgeMetrics { + registerRequest(id: string); + requestOutcome(id: string, isRemote: boolean, outcome: string); + remoteCall(method: string); + setPresenceCount(count: number); + storeCall(method: string, cached: boolean); +} + +export class DummyBridgeMetrics implements IBridgeMetrics { + registerRequest() {} + requestOutcome() {} + remoteCall() {} + setPresenceCount() {} + storeCall() {} +} + +export class MetricPeg { + private static _metrics: IBridgeMetrics = new DummyBridgeMetrics(); + + public static get get() : IBridgeMetrics { + return this._metrics; + } + + public static setMetrics(metrics: IBridgeMetrics) { + this._metrics = metrics; + } +} + + +export class PrometheusBridgeMetrics implements IBridgeMetrics { + private metrics; + private remoteCallCounter: Counter; + private storeCallCounter: Counter; + private presenceGauge: Gauge; + private remoteRequest: Histogram; + private matrixRequest: Histogram; + private requestsInFlight: Map<string, number>; + private bridgeGauges: IBridgeGauges = { + matrixRoomConfigs: 0, + remoteRoomConfigs: 0, + matrixGhosts: 0, + remoteGhosts: 0, + matrixRoomsByAge: new AgeCounters(), + remoteRoomsByAge: new AgeCounters(), + matrixUsersByAge: new AgeCounters(), + remoteUsersByAge: new AgeCounters(), + }; + + public init(bridge: any) { + this.metrics = new PrometheusMetrics(); + this.metrics.registerMatrixSdkMetrics(); + this.metrics.registerBridgeGauges(() => this.bridgeGauges); + this.metrics.addAppServicePath(bridge); + this.remoteCallCounter = this.metrics.addCounter({ + name: "remote_api_calls", + help: "Count of remote API calls made", + labels: ["method"], + }); + this.storeCallCounter = this.metrics.addCounter({ + name: "store_calls", + help: "Count of store function calls made", + labels: ["method", "cached"], + }); + this.presenceGauge = this.metrics.addGauge({ + name: "active_presence_users", + help: "Count of users in the presence queue", + labels: [], + }); + this.matrixRequest = this.metrics.addTimer({ + name: "matrix_request_seconds", + help: "Histogram of processing durations of received Matrix messages", + labels: ["outcome"], + }); + this.remoteRequest = this.metrics.addTimer({ + name: "remote_request_seconds", + help: "Histogram of processing durations of received remote messages", + labels: ["outcome"], + }); + this.requestsInFlight = new Map(); + return this; + } + + public registerRequest(id: string) { + this.requestsInFlight.set(id, Date.now()); + } + + public requestOutcome(id: string, isRemote: boolean, outcome: string) { + const startTime = this.requestsInFlight.get(id); + if (!startTime) { + log.verbose(`Got "requestOutcome" for ${id}, but this request was never started`); + return; + } + const duration = Date.now() - startTime; + (isRemote ? this.remoteRequest : this.matrixRequest).observe({outcome}, duration / 1000); + } + + public setPresenceCount(count: number) { + this.presenceGauge.set(count); + } + + public remoteCall(method: string) { + this.remoteCallCounter.inc({method}); + } + + public storeCall(method: string, cached: boolean) { + this.storeCallCounter.inc({method, cached: cached ? "yes" : "no"}); + } +}; +