@futdevpro/nts-dynamo 1.15.73 → 1.15.74
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/__documentations/plans/BEDROCK-HYPERPLAN.md +95 -95
- package/build/_collections/global-settings.const.d.ts.map +1 -1
- package/build/_collections/global-settings.const.js +1 -0
- package/build/_collections/global-settings.const.js.map +1 -1
- package/build/_models/interfaces/global-settings.interface.d.ts +7 -0
- package/build/_models/interfaces/global-settings.interface.d.ts.map +1 -1
- package/build/_services/core/memory-guard.service.d.ts +5 -0
- package/build/_services/core/memory-guard.service.d.ts.map +1 -1
- package/build/_services/core/memory-guard.service.js +12 -3
- package/build/_services/core/memory-guard.service.js.map +1 -1
- package/package.json +1 -1
- package/src/_collections/global-settings.const.ts +1 -0
- package/src/_models/interfaces/global-settings.interface.ts +216 -209
- package/src/_services/core/memory-guard.service.spec.ts +245 -227
- package/src/_services/core/memory-guard.service.ts +481 -470
|
@@ -1,227 +1,245 @@
|
|
|
1
|
-
import { DyNTS_global_settings } from '../../_collections/global-settings.const';
|
|
2
|
-
import { DyNTS_GlobalService } from './global.service';
|
|
3
|
-
import { DyNTS_MemoryGuard } from './memory-guard.service';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* FR-193 — a bedrock heap-watchdog spec-je. A `poll()`-t KÖZVETLENÜL hívjuk (nem
|
|
7
|
-
* az intervallumra várunk), mockolt heap-plafonnal + `process.memoryUsage`-dzsel,
|
|
8
|
-
* így determinisztikusan teszteljük a hiszterézis-állapotgépet + a tartós rögzítést.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const MB: number = 1024 * 1024;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A privát `getHeapLimitBytes` + `process.memoryUsage` mockolása adott heap-%-hoz.
|
|
15
|
-
* Visszaadja a memoryUsage-spy-t, hogy a hívó később átállíthassa (recovery-teszt).
|
|
16
|
-
* `process as any`: a `MemoryUsageFn` típus egy `.rss()` submetódust is hordoz, amit
|
|
17
|
-
* egy egyszerű return-value mock nem elégít ki — a cast kikerüli ezt a teszt-zajt.
|
|
18
|
-
*/
|
|
19
|
-
const mockHeap = (guard: DyNTS_MemoryGuard, limitMb: number, usedMb: number): jasmine.Spy => {
|
|
20
|
-
spyOn(guard as any, 'getHeapLimitBytes').and.returnValue(limitMb * MB);
|
|
21
|
-
return spyOn(process as any, 'memoryUsage').and.returnValue({
|
|
22
|
-
heapUsed: usedMb * MB,
|
|
23
|
-
heapTotal: usedMb * MB,
|
|
24
|
-
rss: (usedMb + 100) * MB,
|
|
25
|
-
external: 10 * MB,
|
|
26
|
-
arrayBuffers: 0,
|
|
27
|
-
} as NodeJS.MemoryUsage);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/** Egy poll-ciklus kézi kiváltása. */
|
|
31
|
-
const poll = (guard: DyNTS_MemoryGuard): void => { (guard as any).poll(); };
|
|
32
|
-
|
|
33
|
-
describe('| DyNTS_MemoryGuard (FR-193)', (): void => {
|
|
34
|
-
|
|
35
|
-
let guard: DyNTS_MemoryGuard;
|
|
36
|
-
let sinkSpy: jasmine.Spy;
|
|
37
|
-
let originalSink: typeof DyNTS_GlobalService.globalErrorHandler;
|
|
38
|
-
let originalSettings: typeof DyNTS_global_settings.memoryGuard;
|
|
39
|
-
|
|
40
|
-
beforeEach((): void => {
|
|
41
|
-
guard = DyNTS_MemoryGuard.getInstance();
|
|
42
|
-
guard._teardownForTesting();
|
|
43
|
-
|
|
44
|
-
originalSettings = DyNTS_global_settings.memoryGuard;
|
|
45
|
-
DyNTS_global_settings.memoryGuard = {
|
|
46
|
-
enabled: true,
|
|
47
|
-
pollIntervalMs: 999999, // ne tüzeljen az intervallum a teszt alatt
|
|
48
|
-
heapWarningThreshold: 85,
|
|
49
|
-
heapCriticalThreshold: 95,
|
|
50
|
-
recoveryMargin: 10,
|
|
51
|
-
maxHistoryCount: 100,
|
|
52
|
-
gcWarningFraction: 0.40,
|
|
53
|
-
gcCriticalFraction: 0.60,
|
|
54
|
-
// A tesztek NE regisztráljanak process-szintű crash-handlert / ne lépjenek ki (a jasmine-harness védelme).
|
|
55
|
-
installCrashHandlers: false,
|
|
56
|
-
exitOnSustainedCritical: false,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
originalSink = DyNTS_GlobalService.globalErrorHandler;
|
|
60
|
-
sinkSpy = jasmine.createSpy('globalErrorHandler');
|
|
61
|
-
(DyNTS_GlobalService as any).globalErrorHandler = sinkSpy;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
afterEach((): void => {
|
|
65
|
-
guard._teardownForTesting();
|
|
66
|
-
(DyNTS_GlobalService as any).globalErrorHandler = originalSink;
|
|
67
|
-
DyNTS_global_settings.memoryGuard = originalSettings;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('| install() idempotens — második hívás no-op', (): void => {
|
|
71
|
-
guard.install();
|
|
72
|
-
expect(guard.isInstalled()).toBe(true);
|
|
73
|
-
guard.install(); // nem dob, nem indít új timert
|
|
74
|
-
expect(guard.isInstalled()).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('| install() SOHA nem dob, akkor sem ha a settings hiányos', (): void => {
|
|
78
|
-
DyNTS_global_settings.memoryGuard = undefined as any;
|
|
79
|
-
expect((): void => guard.install()).not.toThrow();
|
|
80
|
-
expect(guard.isInstalled()).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('| warning küszöb alatt NINCS esemény', (): void => {
|
|
84
|
-
guard.install();
|
|
85
|
-
mockHeap(guard, 1000, 800); // 80% < 85
|
|
86
|
-
poll(guard);
|
|
87
|
-
expect(guard.getHistory().length).toBe(0);
|
|
88
|
-
expect(sinkSpy).not.toHaveBeenCalled();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('| warning küszöb átlépése → 1 warning esemény + tartós error-sink (level warning)', (): void => {
|
|
92
|
-
guard.install();
|
|
93
|
-
mockHeap(guard, 1000, 880); // 88% ∈ [85, 95)
|
|
94
|
-
poll(guard);
|
|
95
|
-
|
|
96
|
-
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
97
|
-
expect(hist.length).toBe(1);
|
|
98
|
-
expect(hist[0].level).toBe('warning');
|
|
99
|
-
expect(hist[0].heapPct).toBe(88);
|
|
100
|
-
expect(sinkSpy).toHaveBeenCalledTimes(1);
|
|
101
|
-
expect(guard.getStatus().state).toBe('warning');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('| warning-sávban maradva NINCS re-emit (hiszterézis)', (): void => {
|
|
105
|
-
guard.install();
|
|
106
|
-
mockHeap(guard, 1000, 880); // 88% → warning
|
|
107
|
-
poll(guard);
|
|
108
|
-
poll(guard); // még mindig 88% (ugyanaz a mock) → nincs új esemény
|
|
109
|
-
expect(guard.getHistory().length).toBe(1);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('| critical küszöb átlépése → critical esemény + onCritical hook', (): void => {
|
|
113
|
-
const onCritical: jasmine.Spy = jasmine.createSpy('onCritical');
|
|
114
|
-
guard.install({ onCritical: onCritical });
|
|
115
|
-
mockHeap(guard, 1000, 970); // 97% ≥ 95
|
|
116
|
-
poll(guard);
|
|
117
|
-
|
|
118
|
-
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
119
|
-
expect(hist[hist.length - 1].level).toBe('critical');
|
|
120
|
-
expect(onCritical).toHaveBeenCalledTimes(1);
|
|
121
|
-
expect(guard.getStatus().state).toBe('critical');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('| helyreállás: a warning-margó alá esve → recovered esemény (sink NÉLKÜL)', (): void => {
|
|
125
|
-
guard.install();
|
|
126
|
-
|
|
127
|
-
const muSpy: jasmine.Spy = mockHeap(guard, 1000, 880); // 88% → warning
|
|
128
|
-
poll(guard);
|
|
129
|
-
sinkSpy.calls.reset();
|
|
130
|
-
muSpy.and.returnValue({
|
|
131
|
-
heapUsed: 700 * MB, heapTotal: 700 * MB, rss: 800 * MB, external: 10 * MB, arrayBuffers: 0,
|
|
132
|
-
} as NodeJS.MemoryUsage); // 70% ≤ 85 - 10
|
|
133
|
-
poll(guard);
|
|
134
|
-
|
|
135
|
-
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
136
|
-
expect(hist[hist.length - 1].level).toBe('recovered');
|
|
137
|
-
expect(guard.getStatus().state).toBe('normal');
|
|
138
|
-
expect(sinkSpy).not.toHaveBeenCalled(); // recovered NEM ír az error-sinkbe
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('| history ring-buffer a maxHistoryCount-ra van kapva', (): void => {
|
|
142
|
-
DyNTS_global_settings.memoryGuard!.maxHistoryCount = 3;
|
|
143
|
-
guard.install();
|
|
144
|
-
spyOn(guard as any, 'getHeapLimitBytes').and.returnValue(1000 * MB);
|
|
145
|
-
const high: NodeJS.MemoryUsage =
|
|
146
|
-
{ heapUsed: 880 * MB, heapTotal: 880 * MB, rss: 980 * MB, external: 0, arrayBuffers: 0 } as NodeJS.MemoryUsage;
|
|
147
|
-
const low: NodeJS.MemoryUsage =
|
|
148
|
-
{ heapUsed: 700 * MB, heapTotal: 700 * MB, rss: 800 * MB, external: 0, arrayBuffers: 0 } as NodeJS.MemoryUsage;
|
|
149
|
-
let tick: number = 0;
|
|
150
|
-
// warning ↔ recovered oszcilláció: páros tick=high (warning), páratlan=low (recovered)
|
|
151
|
-
spyOn(process as any, 'memoryUsage').and.callFake((): NodeJS.MemoryUsage => (tick++ % 2 === 0 ? high : low));
|
|
152
|
-
for (let k: number = 0; k < 8; k++) { poll(guard); } // 8 állapot-váltás, csak 3 marad
|
|
153
|
-
expect(guard.getHistory().length).toBe(3);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('| poll() SOHA nem dob (heap limit 0 → no-op)', (): void => {
|
|
157
|
-
guard.install();
|
|
158
|
-
spyOn(guard as any, 'getHeapLimitBytes').and.returnValue(0);
|
|
159
|
-
expect((): void => poll(guard)).not.toThrow();
|
|
160
|
-
expect(guard.getHistory().length).toBe(0);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('| getStatus a csúcsokat is követi', (): void => {
|
|
164
|
-
guard.install();
|
|
165
|
-
mockHeap(guard, 1000, 920); // 92%
|
|
166
|
-
poll(guard);
|
|
167
|
-
expect(guard.getStatus().peakHeapPct).toBe(92);
|
|
168
|
-
expect(guard.getStatus().peakRssMb).toBeGreaterThan(0);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// --- GC-thrash precursor (a %-küszöb ALATT is OOM-jel) ---
|
|
172
|
-
|
|
173
|
-
it('| GC-thrash kritikus, bár a heap-% csak 70% (a %-küszöb ALATT) — trigger=gc', (): void => {
|
|
174
|
-
guard.install();
|
|
175
|
-
mockHeap(guard, 1000, 700); // 70% < warn 85
|
|
176
|
-
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.65); // GC 65% ≥ crit 60%
|
|
177
|
-
poll(guard);
|
|
178
|
-
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
179
|
-
expect(hist[hist.length - 1].level).toBe('critical');
|
|
180
|
-
expect(hist[hist.length - 1].trigger).toBe('gc');
|
|
181
|
-
expect(sinkSpy).toHaveBeenCalledTimes(1);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('| közepes GC (40–60%) alacsony heap-nél → warning (trigger=gc)', (): void => {
|
|
185
|
-
guard.install();
|
|
186
|
-
mockHeap(guard, 1000, 700); // 70%
|
|
187
|
-
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.45);
|
|
188
|
-
poll(guard);
|
|
189
|
-
expect(guard.getHistory()[0].level).toBe('warning');
|
|
190
|
-
expect(guard.getHistory()[0].trigger).toBe('gc');
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// --- pressure() / shouldShedLoad() — a fogyasztói load-shed gate ---
|
|
194
|
-
|
|
195
|
-
it('| pressure() a heap-% + GC közül a rosszabb szintet adja; shouldShedLoad critical-nál true', (): void => {
|
|
196
|
-
guard.install();
|
|
197
|
-
mockHeap(guard, 1000, 970); // 97% ≥ crit
|
|
198
|
-
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.0);
|
|
199
|
-
const p = guard.pressure();
|
|
200
|
-
expect(p.level).toBe('critical');
|
|
201
|
-
expect(p.heapPct).toBe(97);
|
|
202
|
-
expect(guard.shouldShedLoad()).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('| shouldShedLoad false, ha ok-szint (alacsony heap + alacsony GC)', (): void => {
|
|
206
|
-
guard.install();
|
|
207
|
-
mockHeap(guard, 1000, 500); // 50%
|
|
208
|
-
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.10);
|
|
209
|
-
expect(guard.pressure().level).toBe('ok');
|
|
210
|
-
expect(guard.shouldShedLoad()).toBe(false);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// --- graceful-exit: tartós critical → exit (a wrapper újraindít) ---
|
|
214
|
-
|
|
215
|
-
it('| exitOnSustainedCritical: N egymás utáni critical poll → gracefulExit (process.exit)', (): void => {
|
|
216
|
-
DyNTS_global_settings.memoryGuard!.exitOnSustainedCritical = true;
|
|
217
|
-
DyNTS_global_settings.memoryGuard!.sustainedCriticalPolls = 3;
|
|
218
|
-
|
|
219
|
-
guard.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
});
|
|
1
|
+
import { DyNTS_global_settings } from '../../_collections/global-settings.const';
|
|
2
|
+
import { DyNTS_GlobalService } from './global.service';
|
|
3
|
+
import { DyNTS_MemoryGuard } from './memory-guard.service';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FR-193 — a bedrock heap-watchdog spec-je. A `poll()`-t KÖZVETLENÜL hívjuk (nem
|
|
7
|
+
* az intervallumra várunk), mockolt heap-plafonnal + `process.memoryUsage`-dzsel,
|
|
8
|
+
* így determinisztikusan teszteljük a hiszterézis-állapotgépet + a tartós rögzítést.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const MB: number = 1024 * 1024;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A privát `getHeapLimitBytes` + `process.memoryUsage` mockolása adott heap-%-hoz.
|
|
15
|
+
* Visszaadja a memoryUsage-spy-t, hogy a hívó később átállíthassa (recovery-teszt).
|
|
16
|
+
* `process as any`: a `MemoryUsageFn` típus egy `.rss()` submetódust is hordoz, amit
|
|
17
|
+
* egy egyszerű return-value mock nem elégít ki — a cast kikerüli ezt a teszt-zajt.
|
|
18
|
+
*/
|
|
19
|
+
const mockHeap = (guard: DyNTS_MemoryGuard, limitMb: number, usedMb: number): jasmine.Spy => {
|
|
20
|
+
spyOn(guard as any, 'getHeapLimitBytes').and.returnValue(limitMb * MB);
|
|
21
|
+
return spyOn(process as any, 'memoryUsage').and.returnValue({
|
|
22
|
+
heapUsed: usedMb * MB,
|
|
23
|
+
heapTotal: usedMb * MB,
|
|
24
|
+
rss: (usedMb + 100) * MB,
|
|
25
|
+
external: 10 * MB,
|
|
26
|
+
arrayBuffers: 0,
|
|
27
|
+
} as NodeJS.MemoryUsage);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Egy poll-ciklus kézi kiváltása. */
|
|
31
|
+
const poll = (guard: DyNTS_MemoryGuard): void => { (guard as any).poll(); };
|
|
32
|
+
|
|
33
|
+
describe('| DyNTS_MemoryGuard (FR-193)', (): void => {
|
|
34
|
+
|
|
35
|
+
let guard: DyNTS_MemoryGuard;
|
|
36
|
+
let sinkSpy: jasmine.Spy;
|
|
37
|
+
let originalSink: typeof DyNTS_GlobalService.globalErrorHandler;
|
|
38
|
+
let originalSettings: typeof DyNTS_global_settings.memoryGuard;
|
|
39
|
+
|
|
40
|
+
beforeEach((): void => {
|
|
41
|
+
guard = DyNTS_MemoryGuard.getInstance();
|
|
42
|
+
guard._teardownForTesting();
|
|
43
|
+
|
|
44
|
+
originalSettings = DyNTS_global_settings.memoryGuard;
|
|
45
|
+
DyNTS_global_settings.memoryGuard = {
|
|
46
|
+
enabled: true,
|
|
47
|
+
pollIntervalMs: 999999, // ne tüzeljen az intervallum a teszt alatt
|
|
48
|
+
heapWarningThreshold: 85,
|
|
49
|
+
heapCriticalThreshold: 95,
|
|
50
|
+
recoveryMargin: 10,
|
|
51
|
+
maxHistoryCount: 100,
|
|
52
|
+
gcWarningFraction: 0.40,
|
|
53
|
+
gcCriticalFraction: 0.60,
|
|
54
|
+
// A tesztek NE regisztráljanak process-szintű crash-handlert / ne lépjenek ki (a jasmine-harness védelme).
|
|
55
|
+
installCrashHandlers: false,
|
|
56
|
+
exitOnSustainedCritical: false,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
originalSink = DyNTS_GlobalService.globalErrorHandler;
|
|
60
|
+
sinkSpy = jasmine.createSpy('globalErrorHandler');
|
|
61
|
+
(DyNTS_GlobalService as any).globalErrorHandler = sinkSpy;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach((): void => {
|
|
65
|
+
guard._teardownForTesting();
|
|
66
|
+
(DyNTS_GlobalService as any).globalErrorHandler = originalSink;
|
|
67
|
+
DyNTS_global_settings.memoryGuard = originalSettings;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('| install() idempotens — második hívás no-op', (): void => {
|
|
71
|
+
guard.install();
|
|
72
|
+
expect(guard.isInstalled()).toBe(true);
|
|
73
|
+
guard.install(); // nem dob, nem indít új timert
|
|
74
|
+
expect(guard.isInstalled()).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('| install() SOHA nem dob, akkor sem ha a settings hiányos', (): void => {
|
|
78
|
+
DyNTS_global_settings.memoryGuard = undefined as any;
|
|
79
|
+
expect((): void => guard.install()).not.toThrow();
|
|
80
|
+
expect(guard.isInstalled()).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('| warning küszöb alatt NINCS esemény', (): void => {
|
|
84
|
+
guard.install();
|
|
85
|
+
mockHeap(guard, 1000, 800); // 80% < 85
|
|
86
|
+
poll(guard);
|
|
87
|
+
expect(guard.getHistory().length).toBe(0);
|
|
88
|
+
expect(sinkSpy).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('| warning küszöb átlépése → 1 warning esemény + tartós error-sink (level warning)', (): void => {
|
|
92
|
+
guard.install();
|
|
93
|
+
mockHeap(guard, 1000, 880); // 88% ∈ [85, 95)
|
|
94
|
+
poll(guard);
|
|
95
|
+
|
|
96
|
+
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
97
|
+
expect(hist.length).toBe(1);
|
|
98
|
+
expect(hist[0].level).toBe('warning');
|
|
99
|
+
expect(hist[0].heapPct).toBe(88);
|
|
100
|
+
expect(sinkSpy).toHaveBeenCalledTimes(1);
|
|
101
|
+
expect(guard.getStatus().state).toBe('warning');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('| warning-sávban maradva NINCS re-emit (hiszterézis)', (): void => {
|
|
105
|
+
guard.install();
|
|
106
|
+
mockHeap(guard, 1000, 880); // 88% → warning
|
|
107
|
+
poll(guard);
|
|
108
|
+
poll(guard); // még mindig 88% (ugyanaz a mock) → nincs új esemény
|
|
109
|
+
expect(guard.getHistory().length).toBe(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('| critical küszöb átlépése → critical esemény + onCritical hook', (): void => {
|
|
113
|
+
const onCritical: jasmine.Spy = jasmine.createSpy('onCritical');
|
|
114
|
+
guard.install({ onCritical: onCritical });
|
|
115
|
+
mockHeap(guard, 1000, 970); // 97% ≥ 95
|
|
116
|
+
poll(guard);
|
|
117
|
+
|
|
118
|
+
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
119
|
+
expect(hist[hist.length - 1].level).toBe('critical');
|
|
120
|
+
expect(onCritical).toHaveBeenCalledTimes(1);
|
|
121
|
+
expect(guard.getStatus().state).toBe('critical');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('| helyreállás: a warning-margó alá esve → recovered esemény (sink NÉLKÜL)', (): void => {
|
|
125
|
+
guard.install();
|
|
126
|
+
|
|
127
|
+
const muSpy: jasmine.Spy = mockHeap(guard, 1000, 880); // 88% → warning
|
|
128
|
+
poll(guard);
|
|
129
|
+
sinkSpy.calls.reset();
|
|
130
|
+
muSpy.and.returnValue({
|
|
131
|
+
heapUsed: 700 * MB, heapTotal: 700 * MB, rss: 800 * MB, external: 10 * MB, arrayBuffers: 0,
|
|
132
|
+
} as NodeJS.MemoryUsage); // 70% ≤ 85 - 10
|
|
133
|
+
poll(guard);
|
|
134
|
+
|
|
135
|
+
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
136
|
+
expect(hist[hist.length - 1].level).toBe('recovered');
|
|
137
|
+
expect(guard.getStatus().state).toBe('normal');
|
|
138
|
+
expect(sinkSpy).not.toHaveBeenCalled(); // recovered NEM ír az error-sinkbe
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('| history ring-buffer a maxHistoryCount-ra van kapva', (): void => {
|
|
142
|
+
DyNTS_global_settings.memoryGuard!.maxHistoryCount = 3;
|
|
143
|
+
guard.install();
|
|
144
|
+
spyOn(guard as any, 'getHeapLimitBytes').and.returnValue(1000 * MB);
|
|
145
|
+
const high: NodeJS.MemoryUsage =
|
|
146
|
+
{ heapUsed: 880 * MB, heapTotal: 880 * MB, rss: 980 * MB, external: 0, arrayBuffers: 0 } as NodeJS.MemoryUsage;
|
|
147
|
+
const low: NodeJS.MemoryUsage =
|
|
148
|
+
{ heapUsed: 700 * MB, heapTotal: 700 * MB, rss: 800 * MB, external: 0, arrayBuffers: 0 } as NodeJS.MemoryUsage;
|
|
149
|
+
let tick: number = 0;
|
|
150
|
+
// warning ↔ recovered oszcilláció: páros tick=high (warning), páratlan=low (recovered)
|
|
151
|
+
spyOn(process as any, 'memoryUsage').and.callFake((): NodeJS.MemoryUsage => (tick++ % 2 === 0 ? high : low));
|
|
152
|
+
for (let k: number = 0; k < 8; k++) { poll(guard); } // 8 állapot-váltás, csak 3 marad
|
|
153
|
+
expect(guard.getHistory().length).toBe(3);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('| poll() SOHA nem dob (heap limit 0 → no-op)', (): void => {
|
|
157
|
+
guard.install();
|
|
158
|
+
spyOn(guard as any, 'getHeapLimitBytes').and.returnValue(0);
|
|
159
|
+
expect((): void => poll(guard)).not.toThrow();
|
|
160
|
+
expect(guard.getHistory().length).toBe(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('| getStatus a csúcsokat is követi', (): void => {
|
|
164
|
+
guard.install();
|
|
165
|
+
mockHeap(guard, 1000, 920); // 92%
|
|
166
|
+
poll(guard);
|
|
167
|
+
expect(guard.getStatus().peakHeapPct).toBe(92);
|
|
168
|
+
expect(guard.getStatus().peakRssMb).toBeGreaterThan(0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// --- GC-thrash precursor (a %-küszöb ALATT is OOM-jel) ---
|
|
172
|
+
|
|
173
|
+
it('| GC-thrash kritikus, bár a heap-% csak 70% (a %-küszöb ALATT) — trigger=gc', (): void => {
|
|
174
|
+
guard.install();
|
|
175
|
+
mockHeap(guard, 1000, 700); // 70% < warn 85
|
|
176
|
+
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.65); // GC 65% ≥ crit 60%
|
|
177
|
+
poll(guard);
|
|
178
|
+
const hist: ReturnType<typeof guard.getHistory> = guard.getHistory();
|
|
179
|
+
expect(hist[hist.length - 1].level).toBe('critical');
|
|
180
|
+
expect(hist[hist.length - 1].trigger).toBe('gc');
|
|
181
|
+
expect(sinkSpy).toHaveBeenCalledTimes(1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('| közepes GC (40–60%) alacsony heap-nél → warning (trigger=gc)', (): void => {
|
|
185
|
+
guard.install();
|
|
186
|
+
mockHeap(guard, 1000, 700); // 70%
|
|
187
|
+
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.45);
|
|
188
|
+
poll(guard);
|
|
189
|
+
expect(guard.getHistory()[0].level).toBe('warning');
|
|
190
|
+
expect(guard.getHistory()[0].trigger).toBe('gc');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// --- pressure() / shouldShedLoad() — a fogyasztói load-shed gate ---
|
|
194
|
+
|
|
195
|
+
it('| pressure() a heap-% + GC közül a rosszabb szintet adja; shouldShedLoad critical-nál true', (): void => {
|
|
196
|
+
guard.install();
|
|
197
|
+
mockHeap(guard, 1000, 970); // 97% ≥ crit
|
|
198
|
+
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.0);
|
|
199
|
+
const p = guard.pressure();
|
|
200
|
+
expect(p.level).toBe('critical');
|
|
201
|
+
expect(p.heapPct).toBe(97);
|
|
202
|
+
expect(guard.shouldShedLoad()).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('| shouldShedLoad false, ha ok-szint (alacsony heap + alacsony GC)', (): void => {
|
|
206
|
+
guard.install();
|
|
207
|
+
mockHeap(guard, 1000, 500); // 50%
|
|
208
|
+
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.10);
|
|
209
|
+
expect(guard.pressure().level).toBe('ok');
|
|
210
|
+
expect(guard.shouldShedLoad()).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// --- graceful-exit: tartós critical → exit (a wrapper újraindít) ---
|
|
214
|
+
|
|
215
|
+
it('| exitOnSustainedCritical: N egymás utáni critical poll → gracefulExit (process.exit)', (): void => {
|
|
216
|
+
DyNTS_global_settings.memoryGuard!.exitOnSustainedCritical = true;
|
|
217
|
+
DyNTS_global_settings.memoryGuard!.sustainedCriticalPolls = 3;
|
|
218
|
+
DyNTS_global_settings.memoryGuard!.bootGraceMs = 0; // nincs grace → azonnal élesedik
|
|
219
|
+
const exitSpy: jasmine.Spy = spyOn(guard as any, 'gracefulExit').and.stub();
|
|
220
|
+
guard.install();
|
|
221
|
+
mockHeap(guard, 1000, 970); // 97% critical
|
|
222
|
+
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.0);
|
|
223
|
+
poll(guard); poll(guard);
|
|
224
|
+
expect(exitSpy).not.toHaveBeenCalled(); // 2 critical < 3
|
|
225
|
+
poll(guard);
|
|
226
|
+
expect(exitSpy).toHaveBeenCalledTimes(1); // a 3. critical → exit
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('| BOOT-GRACE: a grace alatt a sustained-critical NEM lép ki; a grace után IGEN', (): void => {
|
|
230
|
+
DyNTS_global_settings.memoryGuard!.exitOnSustainedCritical = true;
|
|
231
|
+
DyNTS_global_settings.memoryGuard!.sustainedCriticalPolls = 3;
|
|
232
|
+
DyNTS_global_settings.memoryGuard!.bootGraceMs = 60000; // 60s grace
|
|
233
|
+
const exitSpy: jasmine.Spy = spyOn(guard as any, 'gracefulExit').and.stub();
|
|
234
|
+
guard.install();
|
|
235
|
+
mockHeap(guard, 1000, 970); // 97% critical
|
|
236
|
+
spyOn(guard as any, 'recentGcFraction').and.returnValue(0.0);
|
|
237
|
+
// A grace ALATT (installedAt épp most): hiába N+ critical poll, NINCS exit.
|
|
238
|
+
poll(guard); poll(guard); poll(guard); poll(guard);
|
|
239
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
240
|
+
// A grace UTÁN (installedAt 61s-cel ezelőttre állítva): a következő critical poll már kilép.
|
|
241
|
+
(guard as any).installedAt = Date.now() - 61000;
|
|
242
|
+
poll(guard);
|
|
243
|
+
expect(exitSpy).toHaveBeenCalledTimes(1);
|
|
244
|
+
});
|
|
245
|
+
});
|