@maioradv/nestjs-core 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/guards/index.d.ts
CHANGED
package/dist/guards/index.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LoggerService } from '@nestjs/common';
|
|
2
|
+
export type ResourceGuardOptions = {
|
|
3
|
+
/** CPU limit in percent (per core). Optional. */
|
|
4
|
+
cpuLimit?: number;
|
|
5
|
+
/** Memory limit in MB (RSS). Optional. */
|
|
6
|
+
memoryLimitMb?: number;
|
|
7
|
+
/** Interval in ms to check CPU/memory. Default: 2000 ms */
|
|
8
|
+
sampleIntervalMs?: number;
|
|
9
|
+
/** How long in ms the limit must be exceeded before shutdown. Default: 10000ms */
|
|
10
|
+
sustainForMs?: number;
|
|
11
|
+
/** Maximum time in ms to wait for shutdownFn before hard kill. Default: 5000ms */
|
|
12
|
+
hardKillAfterMs?: number;
|
|
13
|
+
/** Optional async shutdown function. If provided, will be called before hard exit. */
|
|
14
|
+
shutdownFn?: () => Promise<void>;
|
|
15
|
+
/** Optional NestJS LoggerService to use for logging. */
|
|
16
|
+
logger?: LoggerService;
|
|
17
|
+
/** Optional peak logging options: log when CPU/memory exceed thresholds for a sustained period. Default sustain: 3000ms */
|
|
18
|
+
logPeaks?: {
|
|
19
|
+
cpuThreshold?: number;
|
|
20
|
+
memoryThresholdMb?: number;
|
|
21
|
+
sustainMs?: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare class ResourceGuard {
|
|
25
|
+
private readonly opts;
|
|
26
|
+
private lastCpu;
|
|
27
|
+
private lastTime;
|
|
28
|
+
private overSince;
|
|
29
|
+
private peakCpuSince;
|
|
30
|
+
private peakMemSince;
|
|
31
|
+
private shuttingDown;
|
|
32
|
+
constructor(opts: ResourceGuardOptions);
|
|
33
|
+
/** Start monitoring CPU and memory */
|
|
34
|
+
start(): void;
|
|
35
|
+
private check;
|
|
36
|
+
private shutdown;
|
|
37
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResourceGuard = void 0;
|
|
4
|
+
class ResourceGuard {
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
this.opts = opts;
|
|
7
|
+
this.lastCpu = process.cpuUsage();
|
|
8
|
+
this.lastTime = Date.now();
|
|
9
|
+
this.overSince = null;
|
|
10
|
+
this.peakCpuSince = null;
|
|
11
|
+
this.peakMemSince = null;
|
|
12
|
+
this.shuttingDown = false;
|
|
13
|
+
this.opts.sampleIntervalMs ??= 2000;
|
|
14
|
+
this.opts.sustainForMs ??= 10000;
|
|
15
|
+
this.opts.hardKillAfterMs ??= 5000;
|
|
16
|
+
if (this.opts.logPeaks) {
|
|
17
|
+
this.opts.logPeaks.sustainMs ??= 3000;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Start monitoring CPU and memory */
|
|
21
|
+
start() {
|
|
22
|
+
if (!this.opts.cpuLimit && !this.opts.memoryLimitMb && !this.opts.logPeaks)
|
|
23
|
+
return;
|
|
24
|
+
setInterval(() => this.check(), this.opts.sampleIntervalMs).unref();
|
|
25
|
+
}
|
|
26
|
+
async check() {
|
|
27
|
+
if (this.shuttingDown)
|
|
28
|
+
return;
|
|
29
|
+
const nowTime = Date.now();
|
|
30
|
+
let over = false;
|
|
31
|
+
let currentCpuPercent = null;
|
|
32
|
+
let currentMemMb = null;
|
|
33
|
+
// compute CPU percent if needed for shutdown or peak logging
|
|
34
|
+
const needCpu = !!this.opts.cpuLimit || !!this.opts.logPeaks?.cpuThreshold;
|
|
35
|
+
if (needCpu) {
|
|
36
|
+
const now = process.cpuUsage(this.lastCpu);
|
|
37
|
+
const elapsedMs = nowTime - this.lastTime;
|
|
38
|
+
const cpuMs = (now.user + now.system) / 1000;
|
|
39
|
+
currentCpuPercent = (cpuMs / elapsedMs) * 100;
|
|
40
|
+
if (this.opts.cpuLimit && currentCpuPercent > this.opts.cpuLimit)
|
|
41
|
+
over = true;
|
|
42
|
+
this.lastCpu = process.cpuUsage();
|
|
43
|
+
}
|
|
44
|
+
const needMem = !!this.opts.memoryLimitMb || !!this.opts.logPeaks?.memoryThresholdMb;
|
|
45
|
+
if (needMem) {
|
|
46
|
+
currentMemMb = process.memoryUsage().rss / 1024 / 1024;
|
|
47
|
+
if (this.opts.memoryLimitMb && currentMemMb > this.opts.memoryLimitMb)
|
|
48
|
+
over = true;
|
|
49
|
+
}
|
|
50
|
+
this.lastTime = nowTime;
|
|
51
|
+
if (over) {
|
|
52
|
+
if (!this.overSince)
|
|
53
|
+
this.overSince = nowTime;
|
|
54
|
+
if (nowTime - this.overSince >= this.opts.sustainForMs) {
|
|
55
|
+
await this.shutdown(currentCpuPercent, currentMemMb);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.overSince = null;
|
|
60
|
+
}
|
|
61
|
+
const log = (msg) => {
|
|
62
|
+
if (this.opts.logger)
|
|
63
|
+
this.opts.logger.warn(`${msg}`);
|
|
64
|
+
else
|
|
65
|
+
console.warn(`${msg}`);
|
|
66
|
+
};
|
|
67
|
+
// Peak logging (non-shutdown): CPU
|
|
68
|
+
if (this.opts.logPeaks?.cpuThreshold && currentCpuPercent !== null) {
|
|
69
|
+
if (currentCpuPercent > this.opts.logPeaks.cpuThreshold) {
|
|
70
|
+
if (!this.peakCpuSince)
|
|
71
|
+
this.peakCpuSince = nowTime;
|
|
72
|
+
const sustainMs = this.opts.logPeaks.sustainMs;
|
|
73
|
+
if (nowTime - this.peakCpuSince >= sustainMs) {
|
|
74
|
+
const msg = `CPU: ${currentCpuPercent.toFixed(1)}% (${this.opts.logPeaks.cpuThreshold}%) ${nowTime - this.peakCpuSince}ms`;
|
|
75
|
+
log(msg);
|
|
76
|
+
this.peakCpuSince = null; // avoid repeated logging until it drops below
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.peakCpuSince = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Peak logging: Memory
|
|
84
|
+
if (this.opts.logPeaks?.memoryThresholdMb && currentMemMb !== null) {
|
|
85
|
+
if (currentMemMb > this.opts.logPeaks.memoryThresholdMb) {
|
|
86
|
+
if (!this.peakMemSince)
|
|
87
|
+
this.peakMemSince = nowTime;
|
|
88
|
+
const sustainMs = this.opts.logPeaks.sustainMs;
|
|
89
|
+
if (nowTime - this.peakMemSince >= sustainMs) {
|
|
90
|
+
const msg = `MEM: ${currentMemMb.toFixed(0)}MB (${this.opts.logPeaks.memoryThresholdMb}MB) ${nowTime - this.peakMemSince}ms`;
|
|
91
|
+
log(msg);
|
|
92
|
+
this.peakMemSince = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.peakMemSince = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async shutdown(currentCpu, currentMem) {
|
|
101
|
+
if (this.shuttingDown)
|
|
102
|
+
return;
|
|
103
|
+
this.shuttingDown = true;
|
|
104
|
+
const log = (msg) => {
|
|
105
|
+
if (this.opts.logger)
|
|
106
|
+
this.opts.logger.error(`${msg}`);
|
|
107
|
+
else
|
|
108
|
+
console.error(`${msg}`);
|
|
109
|
+
};
|
|
110
|
+
const overTimeMs = this.overSince ? Date.now() - this.overSince : 0;
|
|
111
|
+
const cpuMsg = this.opts.cpuLimit ? ` CPU: ${currentCpu?.toFixed(1)}% (${this.opts.cpuLimit}%)` : '';
|
|
112
|
+
const memMsg = this.opts.memoryLimitMb ? ` MEM: ${currentMem?.toFixed(0)}MB (${this.opts.memoryLimitMb}MB)` : '';
|
|
113
|
+
const append = [cpuMsg, memMsg, ` Exceeded for: ${overTimeMs}ms (${this.opts.sustainForMs}ms)`].filter(Boolean).join(';');
|
|
114
|
+
log(`LIMIT EXCEEDED → initiating shutdown;${append}`);
|
|
115
|
+
const hardKill = setTimeout(() => {
|
|
116
|
+
log('HARD EXIT');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}, this.opts.hardKillAfterMs);
|
|
119
|
+
try {
|
|
120
|
+
if (this.opts.shutdownFn) {
|
|
121
|
+
await this.opts.shutdownFn();
|
|
122
|
+
log('Soft shutdown completed successfully');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
log(`shutdownFn failed: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
clearTimeout(hardKill);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.ResourceGuard = ResourceGuard;
|