Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
152 lines
4.6 KiB
TypeScript
152 lines
4.6 KiB
TypeScript
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
import { monitorEventLoopDelay } from 'node:perf_hooks';
|
|
import { getHeapStatistics } from 'node:v8';
|
|
|
|
import { MongoInternals } from 'meteor/mongo';
|
|
import { WebApp } from 'meteor/webapp';
|
|
|
|
import { SystemLogger } from '../lib/logger/system';
|
|
|
|
const { mongo } = MongoInternals.defaultRemoteCollectionDriver();
|
|
|
|
export function pingMongo() {
|
|
return mongo.db.command({ ping: 1 });
|
|
}
|
|
|
|
function setDefaultHeaders(res: ServerResponse) {
|
|
// Set headers to prevent any caching of the health check response.
|
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
res.setHeader('Pragma', 'no-cache');
|
|
res.setHeader('Expires', '0');
|
|
res.setHeader('Content-Type', 'application/json');
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED Liveness Probe (`/health`)
|
|
* Maintained for backward compatibility. Behaves like a simple liveness probe.
|
|
* @deprecated Update infrastructure to use /livez and /readyz.
|
|
*/
|
|
WebApp.rawHandlers.use('/health', (_req: IncomingMessage, res: ServerResponse) => {
|
|
setDefaultHeaders(res);
|
|
|
|
res.writeHead(200);
|
|
res.end(JSON.stringify({ status: 'ok' }));
|
|
});
|
|
|
|
/**
|
|
* Readiness Probe Configuration
|
|
* Defines tunable thresholds for readiness checks.
|
|
*/
|
|
|
|
const eventLoopHistogramLiveness = monitorEventLoopDelay();
|
|
eventLoopHistogramLiveness.enable();
|
|
|
|
const eventLoopHistogramReadiness = monitorEventLoopDelay();
|
|
eventLoopHistogramReadiness.enable();
|
|
|
|
const READINESS_THRESHOLDS = {
|
|
EVENT_LOOP_LAG_MS: Number.parseFloat(process.env.EVENT_LOOP_LAG_MS ?? '') || 70,
|
|
HEAP_USAGE_PERCENT: Number.parseFloat(process.env.HEAP_USAGE_PERCENT ?? '') || 0.85, // 85%
|
|
} as const;
|
|
|
|
/**
|
|
* Checks event loop lag using the provided histogram. Can optionally reset the histogram after reading.
|
|
* @param histogram - The event loop histogram to use.
|
|
* @returns The status and p99 lag.
|
|
*/
|
|
function checkEventLoopLag(histogram: ReturnType<typeof monitorEventLoopDelay>) {
|
|
const lagInNs = histogram.percentile(99);
|
|
const lagInMs = lagInNs / 1_000_000;
|
|
|
|
histogram.reset();
|
|
|
|
const lagValue = Number.parseFloat(lagInMs.toFixed(2));
|
|
|
|
if (lagInMs > READINESS_THRESHOLDS.EVENT_LOOP_LAG_MS) {
|
|
return { status: 'degraded', lagMs: lagValue };
|
|
}
|
|
return { status: 'ok', lagMs: lagValue };
|
|
}
|
|
|
|
/**
|
|
* Checks heap memory usage against a configured threshold.
|
|
* @returns The status and memory usage percentage.
|
|
*/
|
|
function checkMemoryUsage() {
|
|
const { heap_size_limit, used_heap_size } = getHeapStatistics();
|
|
const usageRatio = used_heap_size / heap_size_limit;
|
|
const usageValue = Number.parseFloat((usageRatio * 100).toFixed(2));
|
|
|
|
if (usageRatio > READINESS_THRESHOLDS.HEAP_USAGE_PERCENT) {
|
|
return { status: 'degraded', percentile: usageValue };
|
|
}
|
|
return { status: 'ok', percentile: usageValue };
|
|
}
|
|
|
|
async function checkMongo() {
|
|
try {
|
|
await pingMongo();
|
|
return { status: 'ok' };
|
|
} catch (err) {
|
|
return { status: 'degraded', error: err instanceof Error ? err.message : String(err) };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs the core health checks (memory, event loop) and returns the result.
|
|
* @param shouldResetHistogram - Controls whether the event loop histogram is reset.
|
|
*/
|
|
async function performHealthChecks(histogram: ReturnType<typeof monitorEventLoopDelay>) {
|
|
const checks = {
|
|
memory: checkMemoryUsage(),
|
|
eventLoop: checkEventLoopLag(histogram),
|
|
mongo: await checkMongo(),
|
|
};
|
|
|
|
const isHealthy = checks.memory.status === 'ok' && checks.eventLoop.status === 'ok' && checks.mongo.status === 'ok';
|
|
const statusCode = isHealthy ? 200 : 503;
|
|
|
|
const body = {
|
|
status: isHealthy ? 'ok' : 'unavailable',
|
|
checks,
|
|
};
|
|
|
|
return { statusCode, body, isHealthy };
|
|
}
|
|
|
|
/**
|
|
* Liveness Probe (`/livez`)
|
|
* Performs a non-destructive health check. A failure here indicates a pod is
|
|
* unrecoverable and should be restarted.
|
|
*/
|
|
WebApp.rawHandlers.use('/livez', async (_req: IncomingMessage, res: ServerResponse) => {
|
|
const { statusCode, body, isHealthy } = await performHealthChecks(eventLoopHistogramLiveness);
|
|
|
|
if (!isHealthy) {
|
|
SystemLogger.error({ msg: 'Liveness check failed', details: body });
|
|
}
|
|
|
|
setDefaultHeaders(res);
|
|
|
|
res.writeHead(statusCode);
|
|
res.end(JSON.stringify(body));
|
|
});
|
|
|
|
/**
|
|
* Readiness Probe Endpoint (`/readyz`)
|
|
* Performs a destructive health check, resetting the histogram for the next interval.
|
|
* A failure tells the orchestrator to stop sending traffic to this instance.
|
|
*/
|
|
WebApp.rawHandlers.use('/readyz', async (_req: IncomingMessage, res: ServerResponse) => {
|
|
const { statusCode, body, isHealthy } = await performHealthChecks(eventLoopHistogramReadiness);
|
|
|
|
if (!isHealthy) {
|
|
SystemLogger.warn({ msg: 'Readiness check failed', details: body });
|
|
}
|
|
|
|
setDefaultHeaders(res);
|
|
|
|
res.writeHead(statusCode);
|
|
res.end(JSON.stringify(body));
|
|
});
|