UNPKG

express-under-pressure

Version:
137 lines (136 loc) 5.91 kB
"use strict"; 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); }