From fe03a91a4917fd39fa8a248bbd8ba20259883ca0 Mon Sep 17 00:00:00 2001 From: Florent FAYOLLE Date: Thu, 12 Oct 2023 18:02:43 +0200 Subject: [PATCH 1/3] Add option to serve Prometheus metrics #671 --- README.md | 1 + package.json | 1 + sandbox/run.sh | 6 +++++- sandbox/watch.sh | 8 +++++++- stubs/app/server/prometheus-exporter.ts | 24 ++++++++++++++++++++++++ yarn.lock | 12 ++++++++++++ 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 stubs/app/server/prometheus-exporter.ts diff --git a/README.md b/README.md index 8dead7d679..4c7d062a94 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,7 @@ PORT | port number to listen on for Grist server REDIS_URL | optional redis server for browser sessions and db query caching GRIST_SNAPSHOT_TIME_CAP | optional. Define the caps for tracking buckets. Usage: {"hour": 25, "day": 32, "isoWeek": 12, "month": 96, "year": 1000} GRIST_SNAPSHOT_KEEP | optional. Number of recent snapshots to retain unconditionally for a document, regardless of when they were made +GRIST_PROMCLIENT_PORT | optional. If set, serve the Prometheus metrics on the specified port number. ⚠️ Be sure to use a port which is not publicly exposed ⚠️. #### AI Formula Assistant related variables (all optional): diff --git a/package.json b/package.json index e8ca28d3ac..815b5c29b0 100644 --- a/package.json +++ b/package.json @@ -173,6 +173,7 @@ "plotly.js-basic-dist": "2.13.2", "popper-max-size-modifier": "0.2.0", "popweasel": "0.1.20", + "prom-client": "14.2.0", "qrcode": "1.5.0", "randomcolor": "0.5.3", "redis": "3.1.1", diff --git a/sandbox/run.sh b/sandbox/run.sh index c213d48583..c315ffb91b 100755 --- a/sandbox/run.sh +++ b/sandbox/run.sh @@ -7,4 +7,8 @@ if [[ "$GRIST_SANDBOX_FLAVOR" = "gvisor" ]]; then source ./sandbox/gvisor/get_checkpoint_path.sh fi -NODE_PATH=_build:_build/stubs:_build/ext node _build/stubs/app/server/server.js +if [[ -n "$GRIST_PROMCLIENT_PORT" ]]; then + require_promclient="--require ./_build/stubs/app/server/prometheus-exporter.js" +fi + +NODE_PATH=_build:_build/stubs:_build/ext node ${require_promclient} _build/stubs/app/server/server.js diff --git a/sandbox/watch.sh b/sandbox/watch.sh index cac00b81c5..4f6929f9bf 100755 --- a/sandbox/watch.sh +++ b/sandbox/watch.sh @@ -15,8 +15,14 @@ if [ ! -e _build ]; then buildtools/build.sh fi +if [ -n "$GRIST_PROMCLIENT_PORT" ]; then + require_promclient="--require ./_build/stubs/app/server/prometheus-exporter.js" +fi + +ls _build/stubs/app/server + tsc --build -w --preserveWatchOutput $PROJECT & catw app/client/*.css app/client/*/*.css -o static/bundle.css -v & webpack --config $WEBPACK_CONFIG --mode development --watch & -NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT:+--inspect} --delay 1 -w _build/app/server -w _build/app/common _build/stubs/app/server/server.js & +NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT:+--inspect} --delay 1 -w _build/app/server -w _build/app/common ${require_promclient} _build/stubs/app/server/server.js & wait diff --git a/stubs/app/server/prometheus-exporter.ts b/stubs/app/server/prometheus-exporter.ts new file mode 100644 index 0000000000..1de29fcf13 --- /dev/null +++ b/stubs/app/server/prometheus-exporter.ts @@ -0,0 +1,24 @@ +import { collectDefaultMetrics, register } from 'prom-client'; +import http from 'http'; + +collectDefaultMetrics(); + +const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { + register.metrics().then((metrics) => { + res.writeHead(200, { 'Content-Type': register.contentType }); + res.end(metrics); + }).catch((e) => { + res.writeHead(500); + res.end(e.message); + }); +}); + +const port = parseInt(process.env.GRIST_PROMCLIENT_PORT!, 10); +if (isNaN(port)) { + throw new Error(`Invalid port: ${process.env.GRIST_PROMCLIENT_PORT}`); +} +server.listen(port, '0.0.0.0'); + +console.log("---------------------------------------------"); +console.log(`Prometheus exporter listening on port ${port}`); +console.log("---------------------------------------------"); diff --git a/yarn.lock b/yarn.lock index 7ff7005839..05d037cdcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1739,6 +1739,11 @@ binary@~0.3.0: buffers "~0.1.1" chainsaw "~0.1.0" +bintrees@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" + integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== + bl@^4.0.3: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -6520,6 +6525,13 @@ process@~0.11.0: resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +prom-client@14.2.0: + version "14.2.0" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-14.2.0.tgz#ca94504e64156f6506574c25fb1c34df7812cf11" + integrity sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA== + dependencies: + tdigest "^0.1.1" + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" From 1213700f4c02ff955385c22636eb3731aaed737d Mon Sep 17 00:00:00 2001 From: Florent FAYOLLE Date: Tue, 24 Oct 2023 12:40:22 +0200 Subject: [PATCH 2/3] Run prometheus-exporter as a module #671 --- sandbox/run.sh | 6 +----- sandbox/watch.sh | 8 +------- stubs/app/server/prometheus-exporter.ts | 25 +++++++++++++------------ stubs/app/server/server.ts | 5 +++++ 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/sandbox/run.sh b/sandbox/run.sh index c315ffb91b..c213d48583 100755 --- a/sandbox/run.sh +++ b/sandbox/run.sh @@ -7,8 +7,4 @@ if [[ "$GRIST_SANDBOX_FLAVOR" = "gvisor" ]]; then source ./sandbox/gvisor/get_checkpoint_path.sh fi -if [[ -n "$GRIST_PROMCLIENT_PORT" ]]; then - require_promclient="--require ./_build/stubs/app/server/prometheus-exporter.js" -fi - -NODE_PATH=_build:_build/stubs:_build/ext node ${require_promclient} _build/stubs/app/server/server.js +NODE_PATH=_build:_build/stubs:_build/ext node _build/stubs/app/server/server.js diff --git a/sandbox/watch.sh b/sandbox/watch.sh index 4f6929f9bf..cac00b81c5 100755 --- a/sandbox/watch.sh +++ b/sandbox/watch.sh @@ -15,14 +15,8 @@ if [ ! -e _build ]; then buildtools/build.sh fi -if [ -n "$GRIST_PROMCLIENT_PORT" ]; then - require_promclient="--require ./_build/stubs/app/server/prometheus-exporter.js" -fi - -ls _build/stubs/app/server - tsc --build -w --preserveWatchOutput $PROJECT & catw app/client/*.css app/client/*/*.css -o static/bundle.css -v & webpack --config $WEBPACK_CONFIG --mode development --watch & -NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT:+--inspect} --delay 1 -w _build/app/server -w _build/app/common ${require_promclient} _build/stubs/app/server/server.js & +NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT:+--inspect} --delay 1 -w _build/app/server -w _build/app/common _build/stubs/app/server/server.js & wait diff --git a/stubs/app/server/prometheus-exporter.ts b/stubs/app/server/prometheus-exporter.ts index 1de29fcf13..7bf97d33d0 100644 --- a/stubs/app/server/prometheus-exporter.ts +++ b/stubs/app/server/prometheus-exporter.ts @@ -1,9 +1,7 @@ import { collectDefaultMetrics, register } from 'prom-client'; import http from 'http'; -collectDefaultMetrics(); - -const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { +const reqListener = (req: http.IncomingMessage, res: http.ServerResponse) => { register.metrics().then((metrics) => { res.writeHead(200, { 'Content-Type': register.contentType }); res.end(metrics); @@ -11,14 +9,17 @@ const server = http.createServer((req: http.IncomingMessage, res: http.ServerRes res.writeHead(500); res.end(e.message); }); -}); +}; -const port = parseInt(process.env.GRIST_PROMCLIENT_PORT!, 10); -if (isNaN(port)) { - throw new Error(`Invalid port: ${process.env.GRIST_PROMCLIENT_PORT}`); -} -server.listen(port, '0.0.0.0'); +export function runPrometheusExporter(port: number) { + collectDefaultMetrics(); -console.log("---------------------------------------------"); -console.log(`Prometheus exporter listening on port ${port}`); -console.log("---------------------------------------------"); + if (isNaN(port)) { + throw new Error(`Invalid port: ${process.env.GRIST_PROMCLIENT_PORT}`); + } + const server = http.createServer(reqListener); + server.listen(port, '0.0.0.0'); + + console.log(`Prometheus exporter listening on port ${port}.`); + return server; +} diff --git a/stubs/app/server/server.ts b/stubs/app/server/server.ts index 3ddaa4222e..1b408457c9 100644 --- a/stubs/app/server/server.ts +++ b/stubs/app/server/server.ts @@ -36,6 +36,7 @@ setDefaultEnv('GRIST_WIDGET_LIST_URL', commonUrls.gristLabsWidgetRepository); import {updateDb} from 'app/server/lib/dbUtils'; import {main as mergedServerMain, parseServerTypes} from 'app/server/mergedServerMain'; import * as fse from 'fs-extra'; +import {runPrometheusExporter} from './prometheus-exporter'; const G = { port: parseInt(process.env.PORT!, 10) || 8484, @@ -102,6 +103,10 @@ export async function main() { console.log('For full logs, re-run with DEBUG=1'); } + if (process.env.GRIST_PROMCLIENT_PORT) { + runPrometheusExporter(parseInt(process.env.GRIST_PROMCLIENT_PORT, 10)); + } + // If SAML is not configured, there's no login system, so provide a default email address. setDefaultEnv('GRIST_DEFAULT_EMAIL', 'you@example.com'); // Set directory for uploaded documents. From f46ce54162f9f44b76965ea80c7698c934d8cbff Mon Sep 17 00:00:00 2001 From: Florent FAYOLLE Date: Tue, 24 Oct 2023 12:55:03 +0200 Subject: [PATCH 3/3] Fix conflict in yarn.lock #671 --- yarn.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/yarn.lock b/yarn.lock index 05d037cdcc..9349d7f099 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7700,6 +7700,13 @@ tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" +tdigest@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" + integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== + dependencies: + bintrees "1.0.2" + term-size@^2.1.0: version "2.2.0" resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz"