express-under-pressure
Version:
Express Under Pressure
137 lines (136 loc) • 5.91 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TYPE_EVENT_LOOP_UTILIZATION = exports.TYPE_RSS_BYTES = exports.TYPE_HEAP_USED_BYTES = exports.TYPE_EVENT_LOOP_DELAY = exports.pressureType = exports.pressureReason = void 0;
exports.underPressure = underPressure;
const node_perf_hooks_1 = require("node:perf_hooks");
const node_assert_1 = __importDefault(require("node:assert"));
const { eventLoopUtilization } = node_perf_hooks_1.performance;
const SERVICE_UNAVAILABLE = 503;
exports.pressureReason = Symbol('pressureReason');
exports.pressureType = Symbol('pressureType');
exports.TYPE_EVENT_LOOP_DELAY = 'eventLoopDelay';
exports.TYPE_HEAP_USED_BYTES = 'heapUsedBytes';
exports.TYPE_RSS_BYTES = 'rssBytes';
// const TYPE_HEALTH_CHECK = 'healthCheck';
exports.TYPE_EVENT_LOOP_UTILIZATION = 'eventLoopUtilization';
function underPressure(app, opts = {}) {
const resolution = 10;
const disableCheck = opts.disableCheck || false;
const sampleInterval = opts.sampleInterval || 1000;
const maxEventLoopDelay = opts.maxEventLoopDelay || 0;
const maxHeapUsedBytes = opts.maxHeapUsedBytes || 0;
const maxRssBytes = opts.maxRssBytes || 0;
const maxEventLoopUtilization = opts.maxEventLoopUtilization || 0;
const message = opts.message || 'Service Unavailable';
const checkMaxEventLoopDelay = maxEventLoopDelay > 0;
const checkMaxHeapUsedBytes = maxHeapUsedBytes > 0;
const checkMaxRssBytes = maxRssBytes > 0;
const checkMaxEventLoopUtilization = maxEventLoopUtilization > 0;
const pressureHandler = opts.pressureHandler || null;
if (disableCheck) {
return;
}
if (checkMaxEventLoopUtilization === false &&
checkMaxEventLoopDelay === false &&
checkMaxHeapUsedBytes === false &&
checkMaxRssBytes === false) {
return;
}
if (pressureHandler) {
(0, node_assert_1.default)(typeof pressureHandler === 'function', "Invalid option! 'pressureHandler' must be of type function");
}
let heapUsed = 0;
let rssBytes = 0;
let eventLoopDelay = 0;
let eventLoopUtilized = 0;
const histogram = (0, node_perf_hooks_1.monitorEventLoopDelay)({ resolution });
histogram.enable();
const elu = eventLoopUtilization();
const timer = setTimeout(beginMemoryUsageUpdate, sampleInterval);
timer.unref();
beginMemoryUsageUpdate();
const retryAfter = opts.retryAfter || 10;
function isUnderPressure() {
if (checkMaxEventLoopDelay && eventLoopDelay > maxEventLoopDelay) {
return true;
}
if (checkMaxHeapUsedBytes && heapUsed > maxHeapUsedBytes) {
return true;
}
if (checkMaxRssBytes && rssBytes > maxRssBytes) {
return true;
}
return (checkMaxEventLoopUtilization &&
eventLoopUtilized > maxEventLoopUtilization);
}
function updateEventLoopDelay() {
eventLoopDelay = Math.max(0, histogram.mean / 1e6 - resolution);
if (Number.isNaN(eventLoopDelay))
eventLoopDelay = Infinity;
histogram.reset();
}
function updateEventLoopUtilization() {
eventLoopUtilized = eventLoopUtilization(elu).utilization;
}
function beginMemoryUsageUpdate() {
updateMemoryUsage();
timer.refresh();
}
function updateMemoryUsage() {
const mem = process.memoryUsage();
heapUsed = mem.heapUsed;
rssBytes = mem.rss;
updateEventLoopDelay();
updateEventLoopUtilization();
}
function handlePressure(request, response, next) {
if (pressureHandler) {
pressureHandler(request, response, next);
}
else {
response.setHeader('Retry-After', retryAfter);
response.status(SERVICE_UNAVAILABLE).send(message);
}
}
function underPressureHandler(request, response, next) {
if (checkMaxEventLoopDelay && eventLoopDelay > maxEventLoopDelay) {
const reason = `[Event Loop Delay]: Max Allowed: ${maxEventLoopDelay}, current: ${eventLoopDelay} `;
response.locals[exports.pressureReason] =
reason;
response.locals[exports.pressureType] =
exports.TYPE_EVENT_LOOP_DELAY;
return handlePressure(request, response, next);
}
if (checkMaxHeapUsedBytes && heapUsed > maxHeapUsedBytes) {
const reason = `[Max Heap Used Bytes]: Max Allowed: ${maxHeapUsedBytes}, current: ${heapUsed} `;
response.locals[exports.pressureReason] =
reason;
response.locals[exports.pressureType] =
exports.TYPE_HEAP_USED_BYTES;
return handlePressure(request, response, next);
}
if (checkMaxRssBytes && rssBytes > maxRssBytes) {
const reason = `[Max RSS Bytes]: Max Allowed: ${maxRssBytes}, current: ${rssBytes} `;
response.locals[exports.pressureReason] =
reason;
response.locals[exports.pressureType] =
exports.TYPE_RSS_BYTES;
return handlePressure(request, response, next);
}
if (checkMaxEventLoopUtilization &&
eventLoopUtilized > maxEventLoopUtilization) {
const reason = `[Max Event Loop Utilization]: Max Allowed: ${maxEventLoopUtilization}, current: ${eventLoopUtilized} `;
response.locals[exports.pressureReason] =
reason;
response.locals[exports.pressureType] =
exports.TYPE_EVENT_LOOP_UTILIZATION;
return handlePressure(request, response, next);
}
response.locals.isUnderPressure = isUnderPressure;
next();
}
app.use(underPressureHandler);
}