From bfe4017663b80ae370db6b6261901f3745597eb2 Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Wed, 13 Mar 2024 16:29:10 +0000 Subject: [PATCH] katex_server: Add Prometheus metrics. --- package.json | 1 + pnpm-lock.yaml | 26 +++++++++++++++++++++++ web/server/katex_server.ts | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/package.json b/package.json index 41787189eb..bbfd4e6f55 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "postcss-prefixwrap": "^1.24.0", "postcss-preset-env": "^9.0.0", "postcss-simple-vars": "^7.0.0", + "prom-client": "^15.1.0", "regenerator-runtime": "^0.14.0", "shebang-loader": "^0.0.1", "simplebar": "^6.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61ce854304..3fb3245f4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -214,6 +214,9 @@ dependencies: postcss-simple-vars: specifier: ^7.0.0 version: 7.0.1(postcss@8.4.35) + prom-client: + specifier: ^15.1.0 + version: 15.1.0 regenerator-runtime: specifier: ^0.14.0 version: 0.14.1 @@ -2727,6 +2730,11 @@ packages: rimraf: 3.0.2 dev: false + /@opentelemetry/api@1.8.0: + resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} + engines: {node: '>=8.0.0'} + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -4360,6 +4368,10 @@ packages: file-uri-to-path: 1.0.0 dev: false + /bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + dev: false + /bit-twiddle@1.0.2: resolution: {integrity: sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==} dev: false @@ -10573,6 +10585,14 @@ packages: engines: {node: '>=0.4.0'} dev: true + /prom-client@15.1.0: + resolution: {integrity: sha512-cCD7jLTqyPdjEPBo/Xk4Iu8jxjuZgZJ3e/oET3L+ZwOuap/7Cw3dH/TJSsZKs1TQLZ2IHpIlRAKw82ef06kmMw==} + engines: {node: ^16 || ^18 || >=20} + dependencies: + '@opentelemetry/api': 1.8.0 + tdigest: 0.1.2 + dev: false + /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -12002,6 +12022,12 @@ packages: yallist: 4.0.0 dev: false + /tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + dependencies: + bintrees: 1.0.2 + dev: false + /terser-webpack-plugin@5.3.10(webpack@5.90.3): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} diff --git a/web/server/katex_server.ts b/web/server/katex_server.ts index 950465e4da..609c073a20 100644 --- a/web/server/katex_server.ts +++ b/web/server/katex_server.ts @@ -3,6 +3,7 @@ import crypto from "node:crypto"; import bodyParser from "@koa/bodyparser"; import katex from "katex"; import Koa from "koa"; +import Prometheus from "prom-client"; const host = "localhost"; const port = Number(process.argv[2] ?? "9700"); @@ -30,6 +31,43 @@ const compare_secret = (given_secret: string): boolean => { const app = new Koa(); app.use(bodyParser()); +Prometheus.collectDefaultMetrics(); +const httpRequestDurationSeconds = new Prometheus.Histogram({ + name: "katex_http_request_duration_seconds", + help: "Duration of HTTP requests in seconds", + labelNames: ["method", "path", "status"] as const, + buckets: [ + 0.00001, 0.00002, 0.00005, 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, + 0.1, + ], +}); + +const httpRequestSizeBytes = new Prometheus.Histogram({ + name: "katex_request_size_bytes", + help: "Size of successful KaTeX input in bytes", + labelNames: ["display_mode"] as const, + buckets: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000], +}); + +const httpResponseSizeBytes = new Prometheus.Histogram({ + name: "katex_response_size_bytes", + help: "Size of successful KaTeX output in bytes", + labelNames: ["display_mode"] as const, + buckets: [100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000], +}); + +app.use(async (ctx, next) => { + if (ctx.request.method === "GET" && ctx.request.path === "/metrics") { + ctx.body = await Prometheus.register.metrics(); + return; + } + const endTimer = httpRequestDurationSeconds.startTimer(); + await next(); + const {method, path} = ctx.request; + const {status} = ctx.response; + httpRequestDurationSeconds.labels({method, path, status: String(status)}).observe(endTimer()); +}); + app.use((ctx, _next) => { if (ctx.request.method !== "POST" || ctx.request.path !== "/") { ctx.status = 404; @@ -59,8 +97,12 @@ app.use((ctx, _next) => { return; } + httpRequestSizeBytes.labels(String(is_display)).observe(Buffer.byteLength(content, "utf8")); try { ctx.body = katex.renderToString(content, {displayMode: is_display}); + httpResponseSizeBytes + .labels(String(is_display)) + .observe(Buffer.byteLength(ctx.body, "utf8")); } catch (error) { if (error instanceof katex.ParseError) { ctx.status = 400;