UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

87 lines 4.18 kB
import http from "node:http"; import { HttpActiveSocketsTracker } from "../../api/rest/activeSockets.js"; import { wrapError } from "../../util/wrapError.js"; import { RegistryMetricCreator } from "../utils/registryMetricCreator.js"; var RequestStatus; (function (RequestStatus) { RequestStatus["success"] = "success"; RequestStatus["error"] = "error"; })(RequestStatus || (RequestStatus = {})); export async function getHttpMetricsServer(opts, { register, getOtherMetrics = async () => [], logger, }) { // New registry to metric the metrics. Using the same registry would deadlock the .metrics promise const httpServerRegister = new RegistryMetricCreator(); const scrapeTimeMetric = httpServerRegister.histogram({ name: "lodestar_metrics_scrape_seconds", help: "Lodestar metrics server async time to scrape metrics", labelNames: ["status"], buckets: [0.1, 1, 10], }); const server = http.createServer(async function onRequest(req, res) { if (req.method === "GET" && req.url && req.url.includes("/metrics")) { const timer = scrapeTimeMetric.startTimer(); const metricsRes = await Promise.all([wrapError(register.metrics()), getOtherMetrics()]); timer({ status: metricsRes[0].err ? RequestStatus.error : RequestStatus.success }); // Ensure we only writeHead once if (metricsRes[0].err) { res.writeHead(500, { "content-type": "text/plain" }).end(metricsRes[0].err.stack); } else { // Get scrape time metrics const httpServerMetrics = await httpServerRegister.metrics(); const metrics = [metricsRes[0].result, httpServerMetrics, ...metricsRes[1]]; const metricsStr = metrics.join("\n\n"); res.writeHead(200, { "content-type": register.contentType }).end(metricsStr); } } else { res.writeHead(404).end(); } }); const socketsMetrics = { activeSockets: httpServerRegister.gauge({ name: "lodestar_metrics_server_active_sockets_count", help: "Metrics server current count of active sockets", }), socketsBytesRead: httpServerRegister.gauge({ name: "lodestar_metrics_server_sockets_bytes_read_total", help: "Metrics server total count of bytes read on all sockets", }), socketsBytesWritten: httpServerRegister.gauge({ name: "lodestar_metrics_server_sockets_bytes_written_total", help: "Metrics server total count of bytes written on all sockets", }), }; const activeSockets = new HttpActiveSocketsTracker(server, socketsMetrics); await new Promise((resolve, reject) => { server.once("error", (err) => { logger.error("Error starting metrics HTTP server", opts, err); reject(err); }); server.listen(opts.port, opts.address, () => { const { port, address: host, family } = server.address(); const address = `http://${family === "IPv6" ? `[${host}]` : host}:${port}`; logger.info("Started metrics HTTP server", { address }); resolve(); }); }); return { async close() { // In NodeJS land calling close() only causes new connections to be rejected. // Existing connections can prevent .close() from resolving for potentially forever. // In Lodestar case when the BeaconNode wants to close we will attempt to gracefully // close all existing connections but forcefully terminate after timeout for a fast shutdown. // Inspired by https://github.com/gajus/http-terminator/ await activeSockets.terminate(); await new Promise((resolve, reject) => { server.close((err) => { if (err) reject(err); else resolve(); }); }); logger.debug("Metrics HTTP server closed"); }, }; } //# sourceMappingURL=http.js.map