CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/hub/servers/app/metrics.ts
Views: 687
1
/*
2
Express middleware for recording metrics about response time to requests.
3
*/
4
5
import { dirname } from "path";
6
import { Router } from "express";
7
const { get, new_histogram } = require("@cocalc/hub/metrics-recorder");
8
import { join } from "path";
9
import basePath from "@cocalc/backend/base-path";
10
import getPool from "@cocalc/database/pool";
11
import { getLogger } from "@cocalc/hub/logger";
12
13
const log = getLogger("metrics");
14
15
// initialize metrics
16
const responseTimeHistogram = new_histogram("http_histogram", "http server", {
17
buckets: [0.01, 0.1, 1, 2, 5, 10, 20],
18
labels: ["path", "method", "code"],
19
});
20
21
// response time metrics
22
function metrics(req, res, next) {
23
const resFinished = responseTimeHistogram.startTimer();
24
const originalEnd = res.end;
25
res.end = (...args) => {
26
originalEnd.apply(res, args);
27
if (!req.path) {
28
return;
29
}
30
const pathSplit = req.path.split("/");
31
// for API paths, we want to have data for each endpoint
32
const path_tail = pathSplit.slice(pathSplit.length - 3);
33
const is_api = path_tail[0] === "api" && path_tail[1] === "v1";
34
let path;
35
if (is_api) {
36
path = path_tail.join("/");
37
} else {
38
// for regular paths, we ignore the file
39
path = dirname(req.path).split("/").slice(0, 2).join("/");
40
}
41
resFinished({
42
path,
43
method: req.method,
44
code: res.statusCode,
45
});
46
};
47
next();
48
}
49
50
export function setupInstrumentation(router: Router) {
51
router.use(metrics);
52
}
53
54
async function isEnabled(pool): Promise<boolean> {
55
const {rows} = await pool.query(
56
"SELECT value FROM server_settings WHERE name='prometheus_metrics'"
57
);
58
const enabled = rows.length > 0 && rows[0].value == "yes";
59
log.info("isEnabled", enabled);
60
return enabled;
61
}
62
63
export function initMetricsEndpoint(router: Router) {
64
const endpoint = join(basePath, "metrics");
65
log.info("initMetricsEndpoint at ", endpoint);
66
// long cache so we can easily check before each response and it is still fast.
67
const pool = getPool("long");
68
69
router.get(endpoint, async (_req, res) => {
70
res.header("Content-Type", "text/plain");
71
res.header("Cache-Control", "no-cache, no-store");
72
if (!(await isEnabled(pool))) {
73
res.json({
74
error:
75
"Sharing of metrics at /metrics is disabled. Metrics can be enabled in the site administration page.",
76
});
77
return;
78
}
79
const metricsRecorder = get();
80
if (metricsRecorder != null) {
81
res.send(await metricsRecorder.metrics());
82
} else {
83
res.json({ error: "Metrics recorder not initialized." });
84
}
85
});
86
}
87
88