@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
87 lines • 4.18 kB
JavaScript
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