@usefy/use-memory-monitor 0.0.1
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/README.md +612 -0
- package/dist/index.d.mts +537 -0
- package/dist/index.d.ts +537 -0
- package/dist/index.js +1114 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1077 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
// src/useMemoryMonitor.ts
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useSyncExternalStore
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
// src/constants.ts
|
|
11
|
+
var DEFAULT_INTERVAL = 5e3;
|
|
12
|
+
var DEFAULT_HISTORY_SIZE = 50;
|
|
13
|
+
var DEFAULT_WARNING_THRESHOLD = 70;
|
|
14
|
+
var DEFAULT_CRITICAL_THRESHOLD = 90;
|
|
15
|
+
var DEFAULT_LEAK_WINDOW_SIZE = 10;
|
|
16
|
+
var MIN_LEAK_DETECTION_SAMPLES = 5;
|
|
17
|
+
var LEAK_SENSITIVITY_CONFIG = {
|
|
18
|
+
low: {
|
|
19
|
+
minSlope: 1e5,
|
|
20
|
+
// 100KB/sample
|
|
21
|
+
minR2: 0.7,
|
|
22
|
+
probabilityMultiplier: 0.8
|
|
23
|
+
},
|
|
24
|
+
medium: {
|
|
25
|
+
minSlope: 5e4,
|
|
26
|
+
// 50KB/sample
|
|
27
|
+
minR2: 0.6,
|
|
28
|
+
probabilityMultiplier: 1
|
|
29
|
+
},
|
|
30
|
+
high: {
|
|
31
|
+
minSlope: 1e4,
|
|
32
|
+
// 10KB/sample
|
|
33
|
+
minR2: 0.5,
|
|
34
|
+
probabilityMultiplier: 1.2
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var TREND_THRESHOLDS = {
|
|
38
|
+
increasing: 0.01,
|
|
39
|
+
// Slope > 0.01 = increasing
|
|
40
|
+
decreasing: -0.01
|
|
41
|
+
// Slope < -0.01 = decreasing
|
|
42
|
+
};
|
|
43
|
+
var DEFAULT_OPTIONS = {
|
|
44
|
+
interval: DEFAULT_INTERVAL,
|
|
45
|
+
autoStart: true,
|
|
46
|
+
enabled: true,
|
|
47
|
+
enableHistory: false,
|
|
48
|
+
historySize: DEFAULT_HISTORY_SIZE,
|
|
49
|
+
thresholds: {
|
|
50
|
+
warning: DEFAULT_WARNING_THRESHOLD,
|
|
51
|
+
critical: DEFAULT_CRITICAL_THRESHOLD
|
|
52
|
+
},
|
|
53
|
+
leakDetection: {
|
|
54
|
+
enabled: false,
|
|
55
|
+
sensitivity: "medium",
|
|
56
|
+
windowSize: DEFAULT_LEAK_WINDOW_SIZE,
|
|
57
|
+
threshold: void 0
|
|
58
|
+
},
|
|
59
|
+
devMode: false,
|
|
60
|
+
trackDOMNodes: false,
|
|
61
|
+
trackEventListeners: false,
|
|
62
|
+
logToConsole: false,
|
|
63
|
+
disableInProduction: false,
|
|
64
|
+
fallbackStrategy: "dom-only"
|
|
65
|
+
};
|
|
66
|
+
var DEFAULT_SEVERITY = "normal";
|
|
67
|
+
var DEFAULT_TREND = "stable";
|
|
68
|
+
var SSR_INITIAL_STATE = {
|
|
69
|
+
memory: null,
|
|
70
|
+
domNodes: null,
|
|
71
|
+
eventListeners: null,
|
|
72
|
+
isMonitoring: false,
|
|
73
|
+
severity: DEFAULT_SEVERITY,
|
|
74
|
+
lastUpdated: 0
|
|
75
|
+
};
|
|
76
|
+
var SSR_FORMATTED_MEMORY = {
|
|
77
|
+
heapUsed: "N/A",
|
|
78
|
+
heapTotal: "N/A",
|
|
79
|
+
heapLimit: "N/A",
|
|
80
|
+
domNodes: void 0,
|
|
81
|
+
eventListeners: void 0
|
|
82
|
+
};
|
|
83
|
+
var BYTE_UNITS = ["B", "KB", "MB", "GB", "TB"];
|
|
84
|
+
var BYTES_PER_UNIT = 1024;
|
|
85
|
+
|
|
86
|
+
// src/utils/detection.ts
|
|
87
|
+
function isServer() {
|
|
88
|
+
return typeof window === "undefined";
|
|
89
|
+
}
|
|
90
|
+
function isClient() {
|
|
91
|
+
return typeof window !== "undefined";
|
|
92
|
+
}
|
|
93
|
+
function hasLegacyMemoryAPI() {
|
|
94
|
+
if (isServer()) return false;
|
|
95
|
+
return "memory" in performance;
|
|
96
|
+
}
|
|
97
|
+
function hasPreciseMemoryAPI() {
|
|
98
|
+
if (isServer()) return false;
|
|
99
|
+
return "measureUserAgentSpecificMemory" in performance;
|
|
100
|
+
}
|
|
101
|
+
function isSecureContext() {
|
|
102
|
+
if (isServer()) return false;
|
|
103
|
+
return window.isSecureContext ?? false;
|
|
104
|
+
}
|
|
105
|
+
function isCrossOriginIsolated() {
|
|
106
|
+
if (isServer()) return false;
|
|
107
|
+
return window.crossOriginIsolated ?? false;
|
|
108
|
+
}
|
|
109
|
+
function detectBrowser() {
|
|
110
|
+
if (isServer()) return void 0;
|
|
111
|
+
const userAgent = navigator.userAgent;
|
|
112
|
+
if (userAgent.includes("Chrome") && !userAgent.includes("Edg")) {
|
|
113
|
+
return "Chrome";
|
|
114
|
+
}
|
|
115
|
+
if (userAgent.includes("Edg")) {
|
|
116
|
+
return "Edge";
|
|
117
|
+
}
|
|
118
|
+
if (userAgent.includes("Firefox")) {
|
|
119
|
+
return "Firefox";
|
|
120
|
+
}
|
|
121
|
+
if (userAgent.includes("Safari") && !userAgent.includes("Chrome")) {
|
|
122
|
+
return "Safari";
|
|
123
|
+
}
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
function determineSupportLevel() {
|
|
127
|
+
if (isServer()) return "none";
|
|
128
|
+
if (hasLegacyMemoryAPI()) {
|
|
129
|
+
return "full";
|
|
130
|
+
}
|
|
131
|
+
return "partial";
|
|
132
|
+
}
|
|
133
|
+
function getAvailableMetrics() {
|
|
134
|
+
if (isServer()) return [];
|
|
135
|
+
const metrics = [];
|
|
136
|
+
if (hasLegacyMemoryAPI()) {
|
|
137
|
+
metrics.push("heapUsed", "heapTotal", "heapLimit");
|
|
138
|
+
}
|
|
139
|
+
metrics.push("domNodes");
|
|
140
|
+
metrics.push("eventListeners");
|
|
141
|
+
return metrics;
|
|
142
|
+
}
|
|
143
|
+
function getLimitations() {
|
|
144
|
+
const limitations = [];
|
|
145
|
+
if (isServer()) {
|
|
146
|
+
limitations.push("Running in server-side environment");
|
|
147
|
+
return limitations;
|
|
148
|
+
}
|
|
149
|
+
if (!hasLegacyMemoryAPI()) {
|
|
150
|
+
limitations.push("performance.memory API not available (not Chrome/Edge)");
|
|
151
|
+
}
|
|
152
|
+
if (!isSecureContext()) {
|
|
153
|
+
limitations.push("Not running in secure context (HTTPS required for precise API)");
|
|
154
|
+
}
|
|
155
|
+
if (!isCrossOriginIsolated()) {
|
|
156
|
+
limitations.push(
|
|
157
|
+
"Not cross-origin isolated (COOP/COEP headers required for precise API)"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return limitations;
|
|
161
|
+
}
|
|
162
|
+
function detectSupport() {
|
|
163
|
+
return {
|
|
164
|
+
level: determineSupportLevel(),
|
|
165
|
+
availableMetrics: getAvailableMetrics(),
|
|
166
|
+
limitations: getLimitations(),
|
|
167
|
+
isSecureContext: isSecureContext(),
|
|
168
|
+
isCrossOriginIsolated: isCrossOriginIsolated(),
|
|
169
|
+
hasPreciseMemoryAPI: hasPreciseMemoryAPI()
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function getUnsupportedReason() {
|
|
173
|
+
if (isServer()) {
|
|
174
|
+
return "server-side";
|
|
175
|
+
}
|
|
176
|
+
if (!isSecureContext() && hasPreciseMemoryAPI()) {
|
|
177
|
+
return "insecure-context";
|
|
178
|
+
}
|
|
179
|
+
if (!hasLegacyMemoryAPI() && !hasPreciseMemoryAPI()) {
|
|
180
|
+
return "no-api";
|
|
181
|
+
}
|
|
182
|
+
return "browser-restriction";
|
|
183
|
+
}
|
|
184
|
+
function getAvailableFallbacks() {
|
|
185
|
+
if (isServer()) {
|
|
186
|
+
return ["none"];
|
|
187
|
+
}
|
|
188
|
+
const fallbacks = ["none"];
|
|
189
|
+
fallbacks.push("dom-only");
|
|
190
|
+
fallbacks.push("estimation");
|
|
191
|
+
return fallbacks;
|
|
192
|
+
}
|
|
193
|
+
function createUnsupportedInfo() {
|
|
194
|
+
return {
|
|
195
|
+
reason: getUnsupportedReason(),
|
|
196
|
+
browser: detectBrowser(),
|
|
197
|
+
availableFallbacks: getAvailableFallbacks()
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function canMonitorMemory() {
|
|
201
|
+
return isClient() && (hasLegacyMemoryAPI() || hasPreciseMemoryAPI());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/store.ts
|
|
205
|
+
function createMemoryStore() {
|
|
206
|
+
let state = { ...SSR_INITIAL_STATE };
|
|
207
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
208
|
+
function subscribe(listener) {
|
|
209
|
+
listeners.add(listener);
|
|
210
|
+
return () => {
|
|
211
|
+
listeners.delete(listener);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function getSnapshot() {
|
|
215
|
+
return state;
|
|
216
|
+
}
|
|
217
|
+
function getServerSnapshot() {
|
|
218
|
+
return SSR_INITIAL_STATE;
|
|
219
|
+
}
|
|
220
|
+
function notify() {
|
|
221
|
+
listeners.forEach((listener) => listener());
|
|
222
|
+
}
|
|
223
|
+
function updateMemory(memory) {
|
|
224
|
+
if (state.memory === memory) return;
|
|
225
|
+
state = {
|
|
226
|
+
...state,
|
|
227
|
+
memory,
|
|
228
|
+
lastUpdated: Date.now()
|
|
229
|
+
};
|
|
230
|
+
notify();
|
|
231
|
+
}
|
|
232
|
+
function updateDOMNodes(count) {
|
|
233
|
+
if (state.domNodes === count) return;
|
|
234
|
+
state = {
|
|
235
|
+
...state,
|
|
236
|
+
domNodes: count,
|
|
237
|
+
lastUpdated: Date.now()
|
|
238
|
+
};
|
|
239
|
+
notify();
|
|
240
|
+
}
|
|
241
|
+
function updateEventListeners(count) {
|
|
242
|
+
if (state.eventListeners === count) return;
|
|
243
|
+
state = {
|
|
244
|
+
...state,
|
|
245
|
+
eventListeners: count,
|
|
246
|
+
lastUpdated: Date.now()
|
|
247
|
+
};
|
|
248
|
+
notify();
|
|
249
|
+
}
|
|
250
|
+
function updateMonitoringStatus(isMonitoring) {
|
|
251
|
+
if (state.isMonitoring === isMonitoring) return;
|
|
252
|
+
state = {
|
|
253
|
+
...state,
|
|
254
|
+
isMonitoring,
|
|
255
|
+
lastUpdated: Date.now()
|
|
256
|
+
};
|
|
257
|
+
notify();
|
|
258
|
+
}
|
|
259
|
+
function updateSeverity(severity) {
|
|
260
|
+
if (state.severity === severity) return;
|
|
261
|
+
state = {
|
|
262
|
+
...state,
|
|
263
|
+
severity,
|
|
264
|
+
lastUpdated: Date.now()
|
|
265
|
+
};
|
|
266
|
+
notify();
|
|
267
|
+
}
|
|
268
|
+
function batchUpdate(updates) {
|
|
269
|
+
const newState = { ...state, ...updates, lastUpdated: Date.now() };
|
|
270
|
+
const hasChanges = Object.keys(updates).some(
|
|
271
|
+
(key) => state[key] !== updates[key]
|
|
272
|
+
);
|
|
273
|
+
if (!hasChanges) return;
|
|
274
|
+
state = newState;
|
|
275
|
+
notify();
|
|
276
|
+
}
|
|
277
|
+
function reset() {
|
|
278
|
+
state = { ...SSR_INITIAL_STATE };
|
|
279
|
+
notify();
|
|
280
|
+
}
|
|
281
|
+
function getSubscriberCount() {
|
|
282
|
+
return listeners.size;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
subscribe,
|
|
286
|
+
getSnapshot,
|
|
287
|
+
getServerSnapshot,
|
|
288
|
+
updateMemory,
|
|
289
|
+
updateDOMNodes,
|
|
290
|
+
updateEventListeners,
|
|
291
|
+
updateMonitoringStatus,
|
|
292
|
+
updateSeverity,
|
|
293
|
+
batchUpdate,
|
|
294
|
+
reset,
|
|
295
|
+
getSubscriberCount
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function createStore() {
|
|
299
|
+
return createMemoryStore();
|
|
300
|
+
}
|
|
301
|
+
function readMemoryFromAPI() {
|
|
302
|
+
if (isServer()) return null;
|
|
303
|
+
const memory = performance.memory;
|
|
304
|
+
if (!memory) return null;
|
|
305
|
+
return {
|
|
306
|
+
heapUsed: Math.max(0, memory.usedJSHeapSize),
|
|
307
|
+
heapTotal: Math.max(0, memory.totalJSHeapSize),
|
|
308
|
+
heapLimit: Math.max(0, memory.jsHeapSizeLimit),
|
|
309
|
+
timestamp: Date.now()
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function countDOMNodes() {
|
|
313
|
+
if (isServer()) return null;
|
|
314
|
+
try {
|
|
315
|
+
return document.querySelectorAll("*").length;
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function estimateEventListeners() {
|
|
321
|
+
if (isServer()) return null;
|
|
322
|
+
try {
|
|
323
|
+
const interactiveElements = document.querySelectorAll(
|
|
324
|
+
"button, a, input, select, textarea, [onclick], [onchange], [onkeydown], [onkeyup], [onmouseover], [onmouseout], [onfocus], [onblur], [tabindex]"
|
|
325
|
+
);
|
|
326
|
+
return Math.round(interactiveElements.length * 1.5);
|
|
327
|
+
} catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/utils/formatting.ts
|
|
333
|
+
function formatBytes(bytes, decimals = 2) {
|
|
334
|
+
if (bytes === null || bytes === void 0) {
|
|
335
|
+
return "N/A";
|
|
336
|
+
}
|
|
337
|
+
if (bytes === 0) {
|
|
338
|
+
return "0 B";
|
|
339
|
+
}
|
|
340
|
+
if (bytes < 0) {
|
|
341
|
+
return `-${formatBytes(Math.abs(bytes), decimals)}`;
|
|
342
|
+
}
|
|
343
|
+
const unitIndex = Math.max(
|
|
344
|
+
0,
|
|
345
|
+
Math.min(
|
|
346
|
+
Math.floor(Math.log(bytes) / Math.log(BYTES_PER_UNIT)),
|
|
347
|
+
BYTE_UNITS.length - 1
|
|
348
|
+
)
|
|
349
|
+
);
|
|
350
|
+
const value = bytes / Math.pow(BYTES_PER_UNIT, unitIndex);
|
|
351
|
+
const unit = BYTE_UNITS[unitIndex];
|
|
352
|
+
const formatted = value.toFixed(decimals);
|
|
353
|
+
const trimmed = parseFloat(formatted).toString();
|
|
354
|
+
return `${trimmed} ${unit}`;
|
|
355
|
+
}
|
|
356
|
+
function formatPercentage(percentage, decimals = 1) {
|
|
357
|
+
if (percentage === null || percentage === void 0) {
|
|
358
|
+
return "N/A";
|
|
359
|
+
}
|
|
360
|
+
return `${percentage.toFixed(decimals)}%`;
|
|
361
|
+
}
|
|
362
|
+
function formatNumber(value) {
|
|
363
|
+
if (value === null || value === void 0) {
|
|
364
|
+
return "N/A";
|
|
365
|
+
}
|
|
366
|
+
return value.toLocaleString();
|
|
367
|
+
}
|
|
368
|
+
function createFormattedMemory(memory, domNodes, eventListeners) {
|
|
369
|
+
return {
|
|
370
|
+
heapUsed: formatBytes(memory?.heapUsed),
|
|
371
|
+
heapTotal: formatBytes(memory?.heapTotal),
|
|
372
|
+
heapLimit: formatBytes(memory?.heapLimit),
|
|
373
|
+
domNodes: domNodes != null ? formatNumber(domNodes) : void 0,
|
|
374
|
+
eventListeners: eventListeners != null ? formatNumber(eventListeners) : void 0
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function calculateUsagePercentage(heapUsed, heapLimit) {
|
|
378
|
+
if (heapUsed == null || heapLimit == null || heapLimit <= 0) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const percentage = heapUsed / heapLimit * 100;
|
|
382
|
+
return Math.min(100, Math.max(0, percentage));
|
|
383
|
+
}
|
|
384
|
+
function formatDuration(ms) {
|
|
385
|
+
if (ms < 1e3) {
|
|
386
|
+
return `${ms}ms`;
|
|
387
|
+
}
|
|
388
|
+
if (ms < 6e4) {
|
|
389
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
390
|
+
}
|
|
391
|
+
if (ms < 36e5) {
|
|
392
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
393
|
+
}
|
|
394
|
+
return `${(ms / 36e5).toFixed(1)}h`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/utils/circularBuffer.ts
|
|
398
|
+
var CircularBuffer = class {
|
|
399
|
+
/**
|
|
400
|
+
* Create a new circular buffer with the specified capacity
|
|
401
|
+
*
|
|
402
|
+
* @param capacity - Maximum number of items the buffer can hold
|
|
403
|
+
* @throws Error if capacity is less than 1
|
|
404
|
+
*/
|
|
405
|
+
constructor(capacity) {
|
|
406
|
+
this.head = 0;
|
|
407
|
+
this.tail = 0;
|
|
408
|
+
this._size = 0;
|
|
409
|
+
if (capacity < 1) {
|
|
410
|
+
throw new Error("CircularBuffer capacity must be at least 1");
|
|
411
|
+
}
|
|
412
|
+
this._capacity = capacity;
|
|
413
|
+
this.buffer = new Array(capacity);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Add an item to the buffer.
|
|
417
|
+
* If the buffer is full, the oldest item will be overwritten.
|
|
418
|
+
*
|
|
419
|
+
* @param item - Item to add
|
|
420
|
+
*/
|
|
421
|
+
push(item) {
|
|
422
|
+
this.buffer[this.tail] = item;
|
|
423
|
+
this.tail = (this.tail + 1) % this._capacity;
|
|
424
|
+
if (this._size < this._capacity) {
|
|
425
|
+
this._size++;
|
|
426
|
+
} else {
|
|
427
|
+
this.head = (this.head + 1) % this._capacity;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get all items in the buffer as an array, from oldest to newest.
|
|
432
|
+
*
|
|
433
|
+
* @returns Array of items in insertion order (oldest first)
|
|
434
|
+
*/
|
|
435
|
+
toArray() {
|
|
436
|
+
const result = [];
|
|
437
|
+
for (let i = 0; i < this._size; i++) {
|
|
438
|
+
const index = (this.head + i) % this._capacity;
|
|
439
|
+
result.push(this.buffer[index]);
|
|
440
|
+
}
|
|
441
|
+
return result;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get the most recent N items from the buffer.
|
|
445
|
+
*
|
|
446
|
+
* @param count - Number of items to retrieve
|
|
447
|
+
* @returns Array of most recent items (oldest first within the slice)
|
|
448
|
+
*/
|
|
449
|
+
getRecent(count) {
|
|
450
|
+
const actualCount = Math.min(count, this._size);
|
|
451
|
+
const result = [];
|
|
452
|
+
const startOffset = this._size - actualCount;
|
|
453
|
+
for (let i = 0; i < actualCount; i++) {
|
|
454
|
+
const index = (this.head + startOffset + i) % this._capacity;
|
|
455
|
+
result.push(this.buffer[index]);
|
|
456
|
+
}
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get the most recently added item.
|
|
461
|
+
*
|
|
462
|
+
* @returns The most recent item, or undefined if buffer is empty
|
|
463
|
+
*/
|
|
464
|
+
get last() {
|
|
465
|
+
if (this._size === 0) {
|
|
466
|
+
return void 0;
|
|
467
|
+
}
|
|
468
|
+
const lastIndex = (this.tail - 1 + this._capacity) % this._capacity;
|
|
469
|
+
return this.buffer[lastIndex];
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Get the oldest item in the buffer.
|
|
473
|
+
*
|
|
474
|
+
* @returns The oldest item, or undefined if buffer is empty
|
|
475
|
+
*/
|
|
476
|
+
get first() {
|
|
477
|
+
if (this._size === 0) {
|
|
478
|
+
return void 0;
|
|
479
|
+
}
|
|
480
|
+
return this.buffer[this.head];
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Get an item at a specific index (0 = oldest).
|
|
484
|
+
*
|
|
485
|
+
* @param index - Index of the item to retrieve
|
|
486
|
+
* @returns The item at the index, or undefined if out of bounds
|
|
487
|
+
*/
|
|
488
|
+
at(index) {
|
|
489
|
+
if (index < 0 || index >= this._size) {
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
const bufferIndex = (this.head + index) % this._capacity;
|
|
493
|
+
return this.buffer[bufferIndex];
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Current number of items in the buffer.
|
|
497
|
+
*/
|
|
498
|
+
get size() {
|
|
499
|
+
return this._size;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Maximum capacity of the buffer.
|
|
503
|
+
*/
|
|
504
|
+
get capacity() {
|
|
505
|
+
return this._capacity;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Check if the buffer is empty.
|
|
509
|
+
*/
|
|
510
|
+
get isEmpty() {
|
|
511
|
+
return this._size === 0;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Check if the buffer is full.
|
|
515
|
+
*/
|
|
516
|
+
get isFull() {
|
|
517
|
+
return this._size === this._capacity;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Clear all items from the buffer.
|
|
521
|
+
*/
|
|
522
|
+
clear() {
|
|
523
|
+
this.buffer = new Array(this._capacity);
|
|
524
|
+
this.head = 0;
|
|
525
|
+
this.tail = 0;
|
|
526
|
+
this._size = 0;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Iterate over all items in the buffer (oldest to newest).
|
|
530
|
+
*/
|
|
531
|
+
*[Symbol.iterator]() {
|
|
532
|
+
for (let i = 0; i < this._size; i++) {
|
|
533
|
+
const index = (this.head + i) % this._capacity;
|
|
534
|
+
yield this.buffer[index];
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Apply a function to each item in the buffer.
|
|
539
|
+
*
|
|
540
|
+
* @param callback - Function to call for each item
|
|
541
|
+
*/
|
|
542
|
+
forEach(callback) {
|
|
543
|
+
for (let i = 0; i < this._size; i++) {
|
|
544
|
+
const bufferIndex = (this.head + i) % this._capacity;
|
|
545
|
+
callback(this.buffer[bufferIndex], i);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Map items to a new array.
|
|
550
|
+
*
|
|
551
|
+
* @param callback - Function to transform each item
|
|
552
|
+
* @returns Array of transformed items
|
|
553
|
+
*/
|
|
554
|
+
map(callback) {
|
|
555
|
+
const result = [];
|
|
556
|
+
for (let i = 0; i < this._size; i++) {
|
|
557
|
+
const bufferIndex = (this.head + i) % this._capacity;
|
|
558
|
+
result.push(callback(this.buffer[bufferIndex], i));
|
|
559
|
+
}
|
|
560
|
+
return result;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Filter items based on a predicate.
|
|
564
|
+
*
|
|
565
|
+
* @param predicate - Function to test each item
|
|
566
|
+
* @returns Array of items that pass the test
|
|
567
|
+
*/
|
|
568
|
+
filter(predicate) {
|
|
569
|
+
const result = [];
|
|
570
|
+
for (let i = 0; i < this._size; i++) {
|
|
571
|
+
const bufferIndex = (this.head + i) % this._capacity;
|
|
572
|
+
const item = this.buffer[bufferIndex];
|
|
573
|
+
if (predicate(item, i)) {
|
|
574
|
+
result.push(item);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
// src/utils/leakDetection.ts
|
|
582
|
+
function linearRegression(points) {
|
|
583
|
+
const n = points.length;
|
|
584
|
+
if (n < 2) {
|
|
585
|
+
return { slope: 0, intercept: 0, rSquared: 0 };
|
|
586
|
+
}
|
|
587
|
+
let sumX = 0;
|
|
588
|
+
let sumY = 0;
|
|
589
|
+
let sumXY = 0;
|
|
590
|
+
let sumX2 = 0;
|
|
591
|
+
let sumY2 = 0;
|
|
592
|
+
for (const [x, y] of points) {
|
|
593
|
+
sumX += x;
|
|
594
|
+
sumY += y;
|
|
595
|
+
sumXY += x * y;
|
|
596
|
+
sumX2 += x * x;
|
|
597
|
+
sumY2 += y * y;
|
|
598
|
+
}
|
|
599
|
+
const denominator = n * sumX2 - sumX * sumX;
|
|
600
|
+
if (denominator === 0) {
|
|
601
|
+
return { slope: 0, intercept: sumY / n, rSquared: 0 };
|
|
602
|
+
}
|
|
603
|
+
const slope = (n * sumXY - sumX * sumY) / denominator;
|
|
604
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
605
|
+
const meanY = sumY / n;
|
|
606
|
+
let ssTotal = 0;
|
|
607
|
+
let ssResidual = 0;
|
|
608
|
+
for (const [x, y] of points) {
|
|
609
|
+
const predicted = slope * x + intercept;
|
|
610
|
+
ssTotal += Math.pow(y - meanY, 2);
|
|
611
|
+
ssResidual += Math.pow(y - predicted, 2);
|
|
612
|
+
}
|
|
613
|
+
const rSquared = ssTotal === 0 ? 0 : 1 - ssResidual / ssTotal;
|
|
614
|
+
return {
|
|
615
|
+
slope,
|
|
616
|
+
intercept,
|
|
617
|
+
rSquared: Math.max(0, Math.min(1, rSquared))
|
|
618
|
+
// Clamp to [0, 1]
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function calculateTrend(samples) {
|
|
622
|
+
if (samples.length < 2) {
|
|
623
|
+
return "stable";
|
|
624
|
+
}
|
|
625
|
+
const points = samples.map((s, i) => [i, s.heapUsed]);
|
|
626
|
+
const { slope } = linearRegression(points);
|
|
627
|
+
const avgHeap = samples.reduce((sum, s) => sum + s.heapUsed, 0) / samples.length;
|
|
628
|
+
const normalizedSlope = avgHeap > 0 ? slope / avgHeap : 0;
|
|
629
|
+
if (normalizedSlope > TREND_THRESHOLDS.increasing) {
|
|
630
|
+
return "increasing";
|
|
631
|
+
}
|
|
632
|
+
if (normalizedSlope < TREND_THRESHOLDS.decreasing) {
|
|
633
|
+
return "decreasing";
|
|
634
|
+
}
|
|
635
|
+
return "stable";
|
|
636
|
+
}
|
|
637
|
+
function calculateAverageGrowth(samples) {
|
|
638
|
+
if (samples.length < 2) {
|
|
639
|
+
return 0;
|
|
640
|
+
}
|
|
641
|
+
const points = samples.map((s, i) => [i, s.heapUsed]);
|
|
642
|
+
const { slope } = linearRegression(points);
|
|
643
|
+
return slope;
|
|
644
|
+
}
|
|
645
|
+
function generateRecommendation(probability, trend, averageGrowth) {
|
|
646
|
+
if (probability < 30) {
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
649
|
+
if (probability >= 80) {
|
|
650
|
+
return `Critical: High probability of memory leak detected. Memory is growing at ${formatGrowthRate(averageGrowth)} per sample. Consider profiling with browser DevTools.`;
|
|
651
|
+
}
|
|
652
|
+
if (probability >= 60) {
|
|
653
|
+
return `Warning: Possible memory leak detected. Memory trend is ${trend}. Monitor closely and check for retained references.`;
|
|
654
|
+
}
|
|
655
|
+
if (probability >= 30 && trend === "increasing") {
|
|
656
|
+
return `Note: Memory usage is trending upward. This may be normal for your application, but consider monitoring.`;
|
|
657
|
+
}
|
|
658
|
+
return void 0;
|
|
659
|
+
}
|
|
660
|
+
function formatGrowthRate(bytesPerSample) {
|
|
661
|
+
const absBytes = Math.abs(bytesPerSample);
|
|
662
|
+
if (absBytes >= 1024 * 1024) {
|
|
663
|
+
return `${(bytesPerSample / (1024 * 1024)).toFixed(2)} MB`;
|
|
664
|
+
}
|
|
665
|
+
if (absBytes >= 1024) {
|
|
666
|
+
return `${(bytesPerSample / 1024).toFixed(2)} KB`;
|
|
667
|
+
}
|
|
668
|
+
return `${bytesPerSample.toFixed(0)} bytes`;
|
|
669
|
+
}
|
|
670
|
+
function analyzeLeakProbability(samples, sensitivity = "medium", customThreshold) {
|
|
671
|
+
if (samples.length < MIN_LEAK_DETECTION_SAMPLES) {
|
|
672
|
+
return {
|
|
673
|
+
isLeaking: false,
|
|
674
|
+
probability: 0,
|
|
675
|
+
trend: calculateTrend(samples),
|
|
676
|
+
averageGrowth: calculateAverageGrowth(samples),
|
|
677
|
+
rSquared: 0,
|
|
678
|
+
samples,
|
|
679
|
+
recommendation: void 0
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const config = LEAK_SENSITIVITY_CONFIG[sensitivity];
|
|
683
|
+
const threshold = customThreshold ?? config.minSlope;
|
|
684
|
+
const points = samples.map((s, i) => [i, s.heapUsed]);
|
|
685
|
+
const { slope, rSquared } = linearRegression(points);
|
|
686
|
+
let probability = 0;
|
|
687
|
+
if (slope > 0 && rSquared >= config.minR2) {
|
|
688
|
+
const slopeRatio = slope / threshold;
|
|
689
|
+
probability = Math.min(100, slopeRatio * 50 * rSquared * config.probabilityMultiplier);
|
|
690
|
+
}
|
|
691
|
+
const trend = calculateTrend(samples);
|
|
692
|
+
const averageGrowth = slope;
|
|
693
|
+
if (trend === "increasing" && rSquared > 0.7 && probability < 60) {
|
|
694
|
+
probability = Math.max(probability, 40);
|
|
695
|
+
}
|
|
696
|
+
probability = Math.min(100, Math.max(0, probability));
|
|
697
|
+
const isLeaking = probability > 50;
|
|
698
|
+
return {
|
|
699
|
+
isLeaking,
|
|
700
|
+
probability: Math.round(probability),
|
|
701
|
+
trend,
|
|
702
|
+
averageGrowth,
|
|
703
|
+
rSquared,
|
|
704
|
+
samples,
|
|
705
|
+
recommendation: generateRecommendation(probability, trend, averageGrowth)
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/useMemoryMonitor.ts
|
|
710
|
+
function useMemoryMonitor(options = {}) {
|
|
711
|
+
const mergedOptions = useMemo(
|
|
712
|
+
() => ({
|
|
713
|
+
...DEFAULT_OPTIONS,
|
|
714
|
+
...options,
|
|
715
|
+
thresholds: {
|
|
716
|
+
...DEFAULT_OPTIONS.thresholds,
|
|
717
|
+
...options.thresholds
|
|
718
|
+
},
|
|
719
|
+
leakDetection: {
|
|
720
|
+
...DEFAULT_OPTIONS.leakDetection,
|
|
721
|
+
...options.leakDetection
|
|
722
|
+
}
|
|
723
|
+
}),
|
|
724
|
+
[options]
|
|
725
|
+
);
|
|
726
|
+
const {
|
|
727
|
+
interval,
|
|
728
|
+
autoStart,
|
|
729
|
+
enabled,
|
|
730
|
+
enableHistory,
|
|
731
|
+
historySize,
|
|
732
|
+
thresholds,
|
|
733
|
+
leakDetection,
|
|
734
|
+
devMode,
|
|
735
|
+
trackDOMNodes,
|
|
736
|
+
trackEventListeners,
|
|
737
|
+
logToConsole,
|
|
738
|
+
disableInProduction,
|
|
739
|
+
fallbackStrategy
|
|
740
|
+
} = mergedOptions;
|
|
741
|
+
const shouldDisable = disableInProduction && typeof globalThis.process !== "undefined" && globalThis.process?.env?.NODE_ENV === "production";
|
|
742
|
+
const onUpdateRef = useRef(options.onUpdate);
|
|
743
|
+
const onWarningRef = useRef(options.onWarning);
|
|
744
|
+
const onCriticalRef = useRef(options.onCritical);
|
|
745
|
+
const onLeakDetectedRef = useRef(options.onLeakDetected);
|
|
746
|
+
const onUnsupportedRef = useRef(options.onUnsupported);
|
|
747
|
+
useEffect(() => {
|
|
748
|
+
onUpdateRef.current = options.onUpdate;
|
|
749
|
+
onWarningRef.current = options.onWarning;
|
|
750
|
+
onCriticalRef.current = options.onCritical;
|
|
751
|
+
onLeakDetectedRef.current = options.onLeakDetected;
|
|
752
|
+
onUnsupportedRef.current = options.onUnsupported;
|
|
753
|
+
}, [options.onUpdate, options.onWarning, options.onCritical, options.onLeakDetected, options.onUnsupported]);
|
|
754
|
+
const browserSupport = useMemo(() => detectSupport(), []);
|
|
755
|
+
const storeRef = useRef(null);
|
|
756
|
+
if (!storeRef.current) {
|
|
757
|
+
storeRef.current = createStore();
|
|
758
|
+
}
|
|
759
|
+
const store = storeRef.current;
|
|
760
|
+
const storeState = useSyncExternalStore(
|
|
761
|
+
store.subscribe,
|
|
762
|
+
store.getSnapshot,
|
|
763
|
+
store.getServerSnapshot
|
|
764
|
+
);
|
|
765
|
+
const historyBufferRef = useRef(null);
|
|
766
|
+
if (enableHistory && !historyBufferRef.current) {
|
|
767
|
+
historyBufferRef.current = new CircularBuffer(historySize);
|
|
768
|
+
}
|
|
769
|
+
const snapshotsRef = useRef(/* @__PURE__ */ new Map());
|
|
770
|
+
const intervalIdRef = useRef(null);
|
|
771
|
+
const prevSeverityRef = useRef(DEFAULT_SEVERITY);
|
|
772
|
+
const prevLeakDetectedRef = useRef(false);
|
|
773
|
+
const isMonitoringRef = useRef(false);
|
|
774
|
+
const isSupported = useMemo(() => {
|
|
775
|
+
if (isServer()) return false;
|
|
776
|
+
if (shouldDisable) return false;
|
|
777
|
+
if (fallbackStrategy === "none") {
|
|
778
|
+
return hasLegacyMemoryAPI();
|
|
779
|
+
}
|
|
780
|
+
return true;
|
|
781
|
+
}, [shouldDisable, fallbackStrategy]);
|
|
782
|
+
const supportLevel = useMemo(() => {
|
|
783
|
+
if (!isSupported) return "none";
|
|
784
|
+
return browserSupport.level;
|
|
785
|
+
}, [isSupported, browserSupport.level]);
|
|
786
|
+
const availableMetrics = useMemo(() => {
|
|
787
|
+
if (!isSupported) return [];
|
|
788
|
+
return browserSupport.availableMetrics;
|
|
789
|
+
}, [isSupported, browserSupport.availableMetrics]);
|
|
790
|
+
const memory = storeState.memory;
|
|
791
|
+
const heapUsed = memory?.heapUsed ?? null;
|
|
792
|
+
const heapTotal = memory?.heapTotal ?? null;
|
|
793
|
+
const heapLimit = memory?.heapLimit ?? null;
|
|
794
|
+
const usagePercentage = calculateUsagePercentage(heapUsed, heapLimit);
|
|
795
|
+
const domNodes = storeState.domNodes;
|
|
796
|
+
const eventListeners = storeState.eventListeners;
|
|
797
|
+
const severity = useMemo(() => {
|
|
798
|
+
if (usagePercentage === null) return DEFAULT_SEVERITY;
|
|
799
|
+
if (usagePercentage >= (thresholds.critical ?? 90)) {
|
|
800
|
+
return "critical";
|
|
801
|
+
}
|
|
802
|
+
if (usagePercentage >= (thresholds.warning ?? 70)) {
|
|
803
|
+
return "warning";
|
|
804
|
+
}
|
|
805
|
+
return "normal";
|
|
806
|
+
}, [usagePercentage, thresholds.critical, thresholds.warning]);
|
|
807
|
+
const history = useMemo(() => {
|
|
808
|
+
if (!enableHistory || !historyBufferRef.current) return [];
|
|
809
|
+
return historyBufferRef.current.toArray();
|
|
810
|
+
}, [enableHistory, storeState.lastUpdated]);
|
|
811
|
+
const trend = useMemo(() => {
|
|
812
|
+
if (!enableHistory || history.length < 3) return DEFAULT_TREND;
|
|
813
|
+
return calculateTrend(history);
|
|
814
|
+
}, [enableHistory, history]);
|
|
815
|
+
const leakAnalysis = useMemo(() => {
|
|
816
|
+
if (!leakDetection.enabled || !enableHistory) return null;
|
|
817
|
+
const windowSize = leakDetection.windowSize ?? 10;
|
|
818
|
+
const samples = historyBufferRef.current?.getRecent(windowSize) ?? [];
|
|
819
|
+
if (samples.length < 5) return null;
|
|
820
|
+
return analyzeLeakProbability(
|
|
821
|
+
samples,
|
|
822
|
+
leakDetection.sensitivity ?? "medium",
|
|
823
|
+
leakDetection.threshold
|
|
824
|
+
);
|
|
825
|
+
}, [
|
|
826
|
+
leakDetection.enabled,
|
|
827
|
+
leakDetection.sensitivity,
|
|
828
|
+
leakDetection.windowSize,
|
|
829
|
+
leakDetection.threshold,
|
|
830
|
+
enableHistory,
|
|
831
|
+
history
|
|
832
|
+
]);
|
|
833
|
+
const isLeakDetected = leakAnalysis?.isLeaking ?? false;
|
|
834
|
+
const leakProbability = leakAnalysis?.probability ?? 0;
|
|
835
|
+
const formatted = useMemo(() => {
|
|
836
|
+
if (!isSupported) return SSR_FORMATTED_MEMORY;
|
|
837
|
+
return createFormattedMemory(memory, domNodes, eventListeners);
|
|
838
|
+
}, [isSupported, memory, domNodes, eventListeners]);
|
|
839
|
+
const poll = useCallback(() => {
|
|
840
|
+
if (!isSupported || !enabled) return;
|
|
841
|
+
const newMemory = readMemoryFromAPI();
|
|
842
|
+
if (newMemory) {
|
|
843
|
+
store.updateMemory(newMemory);
|
|
844
|
+
if (enableHistory && historyBufferRef.current) {
|
|
845
|
+
historyBufferRef.current.push(newMemory);
|
|
846
|
+
}
|
|
847
|
+
onUpdateRef.current?.(newMemory);
|
|
848
|
+
if (logToConsole && devMode) {
|
|
849
|
+
console.log("[useMemoryMonitor]", {
|
|
850
|
+
heapUsed: newMemory.heapUsed,
|
|
851
|
+
heapTotal: newMemory.heapTotal,
|
|
852
|
+
heapLimit: newMemory.heapLimit
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (trackDOMNodes) {
|
|
857
|
+
const nodeCount = countDOMNodes();
|
|
858
|
+
store.updateDOMNodes(nodeCount);
|
|
859
|
+
}
|
|
860
|
+
if (trackEventListeners) {
|
|
861
|
+
const listenerCount = estimateEventListeners();
|
|
862
|
+
store.updateEventListeners(listenerCount);
|
|
863
|
+
}
|
|
864
|
+
}, [
|
|
865
|
+
isSupported,
|
|
866
|
+
enabled,
|
|
867
|
+
store,
|
|
868
|
+
enableHistory,
|
|
869
|
+
trackDOMNodes,
|
|
870
|
+
trackEventListeners,
|
|
871
|
+
logToConsole,
|
|
872
|
+
devMode
|
|
873
|
+
]);
|
|
874
|
+
const start = useCallback(() => {
|
|
875
|
+
if (!isSupported || !enabled || isMonitoringRef.current) return;
|
|
876
|
+
isMonitoringRef.current = true;
|
|
877
|
+
store.updateMonitoringStatus(true);
|
|
878
|
+
poll();
|
|
879
|
+
intervalIdRef.current = setInterval(poll, interval);
|
|
880
|
+
if (devMode && logToConsole) {
|
|
881
|
+
console.log("[useMemoryMonitor] Monitoring started");
|
|
882
|
+
}
|
|
883
|
+
}, [isSupported, enabled, store, poll, interval, devMode, logToConsole]);
|
|
884
|
+
const stop = useCallback(() => {
|
|
885
|
+
if (!isMonitoringRef.current) return;
|
|
886
|
+
isMonitoringRef.current = false;
|
|
887
|
+
store.updateMonitoringStatus(false);
|
|
888
|
+
if (intervalIdRef.current) {
|
|
889
|
+
clearInterval(intervalIdRef.current);
|
|
890
|
+
intervalIdRef.current = null;
|
|
891
|
+
}
|
|
892
|
+
if (devMode && logToConsole) {
|
|
893
|
+
console.log("[useMemoryMonitor] Monitoring stopped");
|
|
894
|
+
}
|
|
895
|
+
}, [store, devMode, logToConsole]);
|
|
896
|
+
const takeSnapshot = useCallback(
|
|
897
|
+
(id) => {
|
|
898
|
+
if (!isSupported || !memory) return null;
|
|
899
|
+
const snapshot = {
|
|
900
|
+
id,
|
|
901
|
+
memory: { ...memory },
|
|
902
|
+
domNodes: domNodes ?? void 0,
|
|
903
|
+
eventListeners: eventListeners ?? void 0,
|
|
904
|
+
timestamp: Date.now()
|
|
905
|
+
};
|
|
906
|
+
snapshotsRef.current.set(id, snapshot);
|
|
907
|
+
return snapshot;
|
|
908
|
+
},
|
|
909
|
+
[isSupported, memory, domNodes, eventListeners]
|
|
910
|
+
);
|
|
911
|
+
const compareSnapshots = useCallback(
|
|
912
|
+
(id1, id2) => {
|
|
913
|
+
const snapshot1 = snapshotsRef.current.get(id1);
|
|
914
|
+
const snapshot2 = snapshotsRef.current.get(id2);
|
|
915
|
+
if (!snapshot1 || !snapshot2) return null;
|
|
916
|
+
return {
|
|
917
|
+
heapDelta: snapshot2.memory.heapUsed - snapshot1.memory.heapUsed,
|
|
918
|
+
heapPercentChange: snapshot1.memory.heapUsed > 0 ? (snapshot2.memory.heapUsed - snapshot1.memory.heapUsed) / snapshot1.memory.heapUsed * 100 : 0,
|
|
919
|
+
domNodesDelta: snapshot1.domNodes != null && snapshot2.domNodes != null ? snapshot2.domNodes - snapshot1.domNodes : void 0,
|
|
920
|
+
eventListenersDelta: snapshot1.eventListeners != null && snapshot2.eventListeners != null ? snapshot2.eventListeners - snapshot1.eventListeners : void 0,
|
|
921
|
+
timeDelta: snapshot2.timestamp - snapshot1.timestamp
|
|
922
|
+
};
|
|
923
|
+
},
|
|
924
|
+
[]
|
|
925
|
+
);
|
|
926
|
+
const clearHistory = useCallback(() => {
|
|
927
|
+
if (historyBufferRef.current) {
|
|
928
|
+
historyBufferRef.current.clear();
|
|
929
|
+
}
|
|
930
|
+
}, []);
|
|
931
|
+
const requestGC = useCallback(() => {
|
|
932
|
+
if (isServer()) return;
|
|
933
|
+
try {
|
|
934
|
+
const pressure = new Array(1e6).fill(0);
|
|
935
|
+
pressure.length = 0;
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
if (devMode && logToConsole) {
|
|
939
|
+
console.log("[useMemoryMonitor] GC hint requested");
|
|
940
|
+
}
|
|
941
|
+
}, [devMode, logToConsole]);
|
|
942
|
+
useEffect(() => {
|
|
943
|
+
if (autoStart && isSupported && enabled && !shouldDisable) {
|
|
944
|
+
start();
|
|
945
|
+
}
|
|
946
|
+
return () => {
|
|
947
|
+
stop();
|
|
948
|
+
};
|
|
949
|
+
}, [autoStart, isSupported, enabled, shouldDisable, start, stop]);
|
|
950
|
+
useEffect(() => {
|
|
951
|
+
if (isServer()) return;
|
|
952
|
+
const handleVisibilityChange = () => {
|
|
953
|
+
if (document.hidden) {
|
|
954
|
+
stop();
|
|
955
|
+
} else if (autoStart && isSupported && enabled) {
|
|
956
|
+
start();
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
960
|
+
return () => {
|
|
961
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
962
|
+
};
|
|
963
|
+
}, [autoStart, isSupported, enabled, start, stop]);
|
|
964
|
+
useEffect(() => {
|
|
965
|
+
if (severity === prevSeverityRef.current) return;
|
|
966
|
+
const prevSeverity = prevSeverityRef.current;
|
|
967
|
+
prevSeverityRef.current = severity;
|
|
968
|
+
if (!memory || !usagePercentage) return;
|
|
969
|
+
const callbackData = {
|
|
970
|
+
memory,
|
|
971
|
+
usagePercentage,
|
|
972
|
+
threshold: severity === "critical" ? thresholds.critical ?? 90 : thresholds.warning ?? 70,
|
|
973
|
+
timestamp: Date.now()
|
|
974
|
+
};
|
|
975
|
+
if (severity === "warning" && prevSeverity === "normal") {
|
|
976
|
+
onWarningRef.current?.(callbackData);
|
|
977
|
+
} else if (severity === "critical") {
|
|
978
|
+
onCriticalRef.current?.(callbackData);
|
|
979
|
+
}
|
|
980
|
+
}, [severity, memory, usagePercentage, thresholds.warning, thresholds.critical]);
|
|
981
|
+
useEffect(() => {
|
|
982
|
+
if (!leakAnalysis) return;
|
|
983
|
+
if (isLeakDetected && !prevLeakDetectedRef.current) {
|
|
984
|
+
onLeakDetectedRef.current?.(leakAnalysis);
|
|
985
|
+
}
|
|
986
|
+
prevLeakDetectedRef.current = isLeakDetected;
|
|
987
|
+
}, [isLeakDetected, leakAnalysis]);
|
|
988
|
+
useEffect(() => {
|
|
989
|
+
if (!isSupported && onUnsupportedRef.current) {
|
|
990
|
+
const unsupportedInfo = createUnsupportedInfo();
|
|
991
|
+
onUnsupportedRef.current(unsupportedInfo);
|
|
992
|
+
}
|
|
993
|
+
}, [isSupported]);
|
|
994
|
+
if (isServer()) {
|
|
995
|
+
return {
|
|
996
|
+
memory: null,
|
|
997
|
+
heapUsed: null,
|
|
998
|
+
heapTotal: null,
|
|
999
|
+
heapLimit: null,
|
|
1000
|
+
usagePercentage: null,
|
|
1001
|
+
domNodes: null,
|
|
1002
|
+
eventListeners: null,
|
|
1003
|
+
isSupported: false,
|
|
1004
|
+
isMonitoring: false,
|
|
1005
|
+
isLeakDetected: false,
|
|
1006
|
+
severity: DEFAULT_SEVERITY,
|
|
1007
|
+
supportLevel: "none",
|
|
1008
|
+
availableMetrics: [],
|
|
1009
|
+
history: [],
|
|
1010
|
+
trend: DEFAULT_TREND,
|
|
1011
|
+
leakProbability: 0,
|
|
1012
|
+
start: () => {
|
|
1013
|
+
},
|
|
1014
|
+
stop: () => {
|
|
1015
|
+
},
|
|
1016
|
+
takeSnapshot: () => null,
|
|
1017
|
+
compareSnapshots: () => null,
|
|
1018
|
+
clearHistory: () => {
|
|
1019
|
+
},
|
|
1020
|
+
requestGC: () => {
|
|
1021
|
+
},
|
|
1022
|
+
formatted: SSR_FORMATTED_MEMORY
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
// Current State
|
|
1027
|
+
memory,
|
|
1028
|
+
heapUsed,
|
|
1029
|
+
heapTotal,
|
|
1030
|
+
heapLimit,
|
|
1031
|
+
usagePercentage,
|
|
1032
|
+
// DOM Related
|
|
1033
|
+
domNodes,
|
|
1034
|
+
eventListeners,
|
|
1035
|
+
// Status Flags
|
|
1036
|
+
isSupported,
|
|
1037
|
+
isMonitoring: storeState.isMonitoring,
|
|
1038
|
+
isLeakDetected,
|
|
1039
|
+
severity,
|
|
1040
|
+
// Support Details
|
|
1041
|
+
supportLevel,
|
|
1042
|
+
availableMetrics,
|
|
1043
|
+
// Analysis Data
|
|
1044
|
+
history,
|
|
1045
|
+
trend,
|
|
1046
|
+
leakProbability,
|
|
1047
|
+
// Actions
|
|
1048
|
+
start,
|
|
1049
|
+
stop,
|
|
1050
|
+
takeSnapshot,
|
|
1051
|
+
compareSnapshots,
|
|
1052
|
+
clearHistory,
|
|
1053
|
+
requestGC,
|
|
1054
|
+
// Formatting
|
|
1055
|
+
formatted
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
export {
|
|
1059
|
+
CircularBuffer,
|
|
1060
|
+
analyzeLeakProbability,
|
|
1061
|
+
calculateTrend,
|
|
1062
|
+
calculateUsagePercentage,
|
|
1063
|
+
canMonitorMemory,
|
|
1064
|
+
detectBrowser,
|
|
1065
|
+
detectSupport,
|
|
1066
|
+
formatBytes,
|
|
1067
|
+
formatDuration,
|
|
1068
|
+
formatNumber,
|
|
1069
|
+
formatPercentage,
|
|
1070
|
+
hasLegacyMemoryAPI,
|
|
1071
|
+
hasPreciseMemoryAPI,
|
|
1072
|
+
isClient,
|
|
1073
|
+
isServer,
|
|
1074
|
+
linearRegression,
|
|
1075
|
+
useMemoryMonitor
|
|
1076
|
+
};
|
|
1077
|
+
//# sourceMappingURL=index.mjs.map
|