musubi-sdd 5.6.3 → 5.7.2

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.
@@ -0,0 +1,557 @@
1
+ /**
2
+ * Memory Optimizer
3
+ *
4
+ * Phase 6 P1: Memory Optimization for enterprise-grade performance
5
+ *
6
+ * Features:
7
+ * - Object pooling for frequently created objects
8
+ * - Weak reference caching for large objects
9
+ * - Memory pressure monitoring
10
+ * - Automatic garbage collection hints
11
+ *
12
+ * @module src/performance/memory-optimizer
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ /**
18
+ * Memory pressure levels
19
+ */
20
+ const MemoryPressure = {
21
+ LOW: 'low', // < 50% heap used
22
+ MODERATE: 'moderate', // 50-75% heap used
23
+ HIGH: 'high', // 75-90% heap used
24
+ CRITICAL: 'critical', // > 90% heap used
25
+ };
26
+
27
+ /**
28
+ * Object Pool for reusable objects
29
+ * Reduces GC pressure by recycling objects
30
+ */
31
+ class ObjectPool {
32
+ /**
33
+ * @param {Function} factory - Factory function to create new objects
34
+ * @param {Object} options
35
+ * @param {number} options.initialSize - Initial pool size
36
+ * @param {number} options.maxSize - Maximum pool size
37
+ * @param {Function} options.reset - Reset function for recycled objects
38
+ */
39
+ constructor(factory, options = {}) {
40
+ this.factory = factory;
41
+ this.maxSize = options.maxSize || 100;
42
+ this.reset = options.reset || (obj => obj);
43
+ this.pool = [];
44
+ this.created = 0;
45
+ this.recycled = 0;
46
+ this.borrowed = 0;
47
+
48
+ // Pre-populate pool
49
+ const initialSize = options.initialSize || 10;
50
+ for (let i = 0; i < Math.min(initialSize, this.maxSize); i++) {
51
+ this.pool.push(this.factory());
52
+ this.created++;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Acquire an object from the pool
58
+ * @returns {*} Object from pool or newly created
59
+ */
60
+ acquire() {
61
+ this.borrowed++;
62
+ if (this.pool.length > 0) {
63
+ this.recycled++;
64
+ return this.pool.pop();
65
+ }
66
+ this.created++;
67
+ return this.factory();
68
+ }
69
+
70
+ /**
71
+ * Release an object back to the pool
72
+ * @param {*} obj - Object to release
73
+ */
74
+ release(obj) {
75
+ if (this.pool.length < this.maxSize) {
76
+ this.reset(obj);
77
+ this.pool.push(obj);
78
+ }
79
+ // If pool is full, let GC handle it
80
+ }
81
+
82
+ /**
83
+ * Get pool statistics
84
+ * @returns {Object}
85
+ */
86
+ getStats() {
87
+ return {
88
+ poolSize: this.pool.length,
89
+ maxSize: this.maxSize,
90
+ created: this.created,
91
+ recycled: this.recycled,
92
+ borrowed: this.borrowed,
93
+ recycleRate: this.borrowed > 0 ? this.recycled / this.borrowed : 0,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Clear the pool
99
+ */
100
+ clear() {
101
+ this.pool = [];
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Weak Cache for large objects
107
+ * Allows GC to collect objects when memory is needed
108
+ */
109
+ class WeakCache {
110
+ constructor() {
111
+ this.cache = new Map();
112
+ this.hits = 0;
113
+ this.misses = 0;
114
+ }
115
+
116
+ /**
117
+ * Set a value with weak reference
118
+ * @param {string} key
119
+ * @param {Object} value - Must be an object (for WeakRef)
120
+ */
121
+ set(key, value) {
122
+ if (typeof value !== 'object' || value === null) {
123
+ throw new Error('WeakCache only supports object values');
124
+ }
125
+ this.cache.set(key, new WeakRef(value));
126
+ }
127
+
128
+ /**
129
+ * Get a value (may return undefined if GC collected it)
130
+ * @param {string} key
131
+ * @returns {Object|undefined}
132
+ */
133
+ get(key) {
134
+ const ref = this.cache.get(key);
135
+ if (!ref) {
136
+ this.misses++;
137
+ return undefined;
138
+ }
139
+ const value = ref.deref();
140
+ if (value === undefined) {
141
+ this.cache.delete(key);
142
+ this.misses++;
143
+ return undefined;
144
+ }
145
+ this.hits++;
146
+ return value;
147
+ }
148
+
149
+ /**
150
+ * Check if key exists and is still valid
151
+ * @param {string} key
152
+ * @returns {boolean}
153
+ */
154
+ has(key) {
155
+ const ref = this.cache.get(key);
156
+ if (!ref) return false;
157
+ const value = ref.deref();
158
+ if (value === undefined) {
159
+ this.cache.delete(key);
160
+ return false;
161
+ }
162
+ return true;
163
+ }
164
+
165
+ /**
166
+ * Delete a key
167
+ * @param {string} key
168
+ */
169
+ delete(key) {
170
+ this.cache.delete(key);
171
+ }
172
+
173
+ /**
174
+ * Clean up dead references
175
+ * @returns {number} Number of cleaned entries
176
+ */
177
+ cleanup() {
178
+ let cleaned = 0;
179
+ for (const [key, ref] of this.cache) {
180
+ if (ref.deref() === undefined) {
181
+ this.cache.delete(key);
182
+ cleaned++;
183
+ }
184
+ }
185
+ return cleaned;
186
+ }
187
+
188
+ /**
189
+ * Get cache statistics
190
+ * @returns {Object}
191
+ */
192
+ getStats() {
193
+ return {
194
+ size: this.cache.size,
195
+ hits: this.hits,
196
+ misses: this.misses,
197
+ hitRate: this.hits + this.misses > 0 ? this.hits / (this.hits + this.misses) : 0,
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Clear the cache
203
+ */
204
+ clear() {
205
+ this.cache.clear();
206
+ this.hits = 0;
207
+ this.misses = 0;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Memory Monitor for tracking heap usage
213
+ */
214
+ class MemoryMonitor {
215
+ constructor(options = {}) {
216
+ this.checkInterval = options.checkInterval || 30000; // 30 seconds
217
+ this.thresholds = {
218
+ moderate: options.moderateThreshold || 0.5,
219
+ high: options.highThreshold || 0.75,
220
+ critical: options.criticalThreshold || 0.9,
221
+ };
222
+ this.history = [];
223
+ this.maxHistory = options.maxHistory || 100;
224
+ this.listeners = new Map();
225
+ this.intervalId = null;
226
+ }
227
+
228
+ /**
229
+ * Get current memory usage
230
+ * @returns {Object}
231
+ */
232
+ getMemoryUsage() {
233
+ const usage = process.memoryUsage();
234
+ const heapUsed = usage.heapUsed;
235
+ const heapTotal = usage.heapTotal;
236
+ const heapRatio = heapTotal > 0 ? heapUsed / heapTotal : 0;
237
+
238
+ return {
239
+ heapUsed,
240
+ heapTotal,
241
+ heapRatio,
242
+ external: usage.external,
243
+ rss: usage.rss,
244
+ arrayBuffers: usage.arrayBuffers || 0,
245
+ timestamp: Date.now(),
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Get current pressure level
251
+ * @returns {string}
252
+ */
253
+ getPressureLevel() {
254
+ const { heapRatio } = this.getMemoryUsage();
255
+
256
+ if (heapRatio >= this.thresholds.critical) return MemoryPressure.CRITICAL;
257
+ if (heapRatio >= this.thresholds.high) return MemoryPressure.HIGH;
258
+ if (heapRatio >= this.thresholds.moderate) return MemoryPressure.MODERATE;
259
+ return MemoryPressure.LOW;
260
+ }
261
+
262
+ /**
263
+ * Add a pressure level listener
264
+ * @param {string} level - Pressure level to listen for
265
+ * @param {Function} callback - Callback function
266
+ */
267
+ onPressure(level, callback) {
268
+ if (!this.listeners.has(level)) {
269
+ this.listeners.set(level, []);
270
+ }
271
+ this.listeners.get(level).push(callback);
272
+ }
273
+
274
+ /**
275
+ * Start monitoring
276
+ */
277
+ start() {
278
+ if (this.intervalId) return;
279
+
280
+ this.intervalId = setInterval(() => {
281
+ const usage = this.getMemoryUsage();
282
+ this.history.push(usage);
283
+
284
+ if (this.history.length > this.maxHistory) {
285
+ this.history.shift();
286
+ }
287
+
288
+ const pressure = this.getPressureLevel();
289
+ const callbacks = this.listeners.get(pressure) || [];
290
+ for (const callback of callbacks) {
291
+ try {
292
+ callback(usage, pressure);
293
+ } catch (_e) {
294
+ // Ignore callback errors
295
+ }
296
+ }
297
+ }, this.checkInterval);
298
+ }
299
+
300
+ /**
301
+ * Stop monitoring
302
+ */
303
+ stop() {
304
+ if (this.intervalId) {
305
+ clearInterval(this.intervalId);
306
+ this.intervalId = null;
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Get memory statistics
312
+ * @returns {Object}
313
+ */
314
+ getStats() {
315
+ if (this.history.length === 0) {
316
+ return { current: this.getMemoryUsage(), history: [], trend: 'stable' };
317
+ }
318
+
319
+ const current = this.getMemoryUsage();
320
+ const avgHeapRatio =
321
+ this.history.reduce((sum, h) => sum + h.heapRatio, 0) / this.history.length;
322
+ const recentAvg =
323
+ this.history.slice(-10).reduce((sum, h) => sum + h.heapRatio, 0) /
324
+ Math.min(10, this.history.length);
325
+
326
+ let trend = 'stable';
327
+ if (recentAvg > avgHeapRatio * 1.1) trend = 'increasing';
328
+ if (recentAvg < avgHeapRatio * 0.9) trend = 'decreasing';
329
+
330
+ return {
331
+ current,
332
+ pressure: this.getPressureLevel(),
333
+ avgHeapRatio,
334
+ recentAvgHeapRatio: recentAvg,
335
+ trend,
336
+ historyLength: this.history.length,
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Suggest garbage collection
342
+ * @returns {boolean} Whether GC was suggested
343
+ */
344
+ suggestGC() {
345
+ const pressure = this.getPressureLevel();
346
+ if (pressure === MemoryPressure.HIGH || pressure === MemoryPressure.CRITICAL) {
347
+ if (global.gc) {
348
+ global.gc();
349
+ return true;
350
+ }
351
+ }
352
+ return false;
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Streaming Buffer for memory-efficient large data processing
358
+ */
359
+ class StreamingBuffer {
360
+ /**
361
+ * @param {Object} options
362
+ * @param {number} options.chunkSize - Size of each chunk
363
+ * @param {number} options.maxBuffered - Maximum buffered chunks
364
+ */
365
+ constructor(options = {}) {
366
+ this.chunkSize = options.chunkSize || 1024 * 64; // 64KB
367
+ this.maxBuffered = options.maxBuffered || 10;
368
+ this.chunks = [];
369
+ this.totalProcessed = 0;
370
+ }
371
+
372
+ /**
373
+ * Add data to buffer
374
+ * @param {*} data
375
+ * @returns {boolean} Whether buffer is full
376
+ */
377
+ push(data) {
378
+ this.chunks.push(data);
379
+ return this.chunks.length >= this.maxBuffered;
380
+ }
381
+
382
+ /**
383
+ * Get and clear buffered chunks
384
+ * @returns {Array}
385
+ */
386
+ flush() {
387
+ const data = this.chunks;
388
+ this.totalProcessed += data.length;
389
+ this.chunks = [];
390
+ return data;
391
+ }
392
+
393
+ /**
394
+ * Check if buffer has data
395
+ * @returns {boolean}
396
+ */
397
+ hasData() {
398
+ return this.chunks.length > 0;
399
+ }
400
+
401
+ /**
402
+ * Get buffer statistics
403
+ * @returns {Object}
404
+ */
405
+ getStats() {
406
+ return {
407
+ buffered: this.chunks.length,
408
+ maxBuffered: this.maxBuffered,
409
+ totalProcessed: this.totalProcessed,
410
+ };
411
+ }
412
+
413
+ /**
414
+ * Clear the buffer
415
+ */
416
+ clear() {
417
+ this.chunks = [];
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Memory Optimizer - Main coordinator
423
+ */
424
+ class MemoryOptimizer {
425
+ constructor(options = {}) {
426
+ this.monitor = new MemoryMonitor(options.monitor || {});
427
+ this.pools = new Map();
428
+ this.weakCaches = new Map();
429
+ this.autoGC = options.autoGC !== false;
430
+
431
+ if (this.autoGC) {
432
+ this.monitor.onPressure(MemoryPressure.HIGH, () => this.onHighPressure());
433
+ this.monitor.onPressure(MemoryPressure.CRITICAL, () => this.onCriticalPressure());
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Create or get an object pool
439
+ * @param {string} name - Pool name
440
+ * @param {Function} factory - Object factory
441
+ * @param {Object} options - Pool options
442
+ * @returns {ObjectPool}
443
+ */
444
+ createPool(name, factory, options = {}) {
445
+ if (!this.pools.has(name)) {
446
+ this.pools.set(name, new ObjectPool(factory, options));
447
+ }
448
+ return this.pools.get(name);
449
+ }
450
+
451
+ /**
452
+ * Get an existing pool
453
+ * @param {string} name
454
+ * @returns {ObjectPool|undefined}
455
+ */
456
+ getPool(name) {
457
+ return this.pools.get(name);
458
+ }
459
+
460
+ /**
461
+ * Create or get a weak cache
462
+ * @param {string} name - Cache name
463
+ * @returns {WeakCache}
464
+ */
465
+ createWeakCache(name) {
466
+ if (!this.weakCaches.has(name)) {
467
+ this.weakCaches.set(name, new WeakCache());
468
+ }
469
+ return this.weakCaches.get(name);
470
+ }
471
+
472
+ /**
473
+ * Get an existing weak cache
474
+ * @param {string} name
475
+ * @returns {WeakCache|undefined}
476
+ */
477
+ getWeakCache(name) {
478
+ return this.weakCaches.get(name);
479
+ }
480
+
481
+ /**
482
+ * Handle high memory pressure
483
+ */
484
+ onHighPressure() {
485
+ // Clean up weak caches
486
+ for (const cache of this.weakCaches.values()) {
487
+ cache.cleanup();
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Handle critical memory pressure
493
+ */
494
+ onCriticalPressure() {
495
+ // Clear all pools
496
+ for (const pool of this.pools.values()) {
497
+ pool.clear();
498
+ }
499
+
500
+ // Clear all weak caches
501
+ for (const cache of this.weakCaches.values()) {
502
+ cache.clear();
503
+ }
504
+
505
+ // Suggest GC
506
+ this.monitor.suggestGC();
507
+ }
508
+
509
+ /**
510
+ * Start memory monitoring
511
+ */
512
+ start() {
513
+ this.monitor.start();
514
+ }
515
+
516
+ /**
517
+ * Stop memory monitoring
518
+ */
519
+ stop() {
520
+ this.monitor.stop();
521
+ }
522
+
523
+ /**
524
+ * Get comprehensive statistics
525
+ * @returns {Object}
526
+ */
527
+ getStats() {
528
+ const poolStats = {};
529
+ for (const [name, pool] of this.pools) {
530
+ poolStats[name] = pool.getStats();
531
+ }
532
+
533
+ const cacheStats = {};
534
+ for (const [name, cache] of this.weakCaches) {
535
+ cacheStats[name] = cache.getStats();
536
+ }
537
+
538
+ return {
539
+ memory: this.monitor.getStats(),
540
+ pools: poolStats,
541
+ weakCaches: cacheStats,
542
+ };
543
+ }
544
+ }
545
+
546
+ // Default instance
547
+ const defaultOptimizer = new MemoryOptimizer();
548
+
549
+ module.exports = {
550
+ MemoryPressure,
551
+ ObjectPool,
552
+ WeakCache,
553
+ MemoryMonitor,
554
+ StreamingBuffer,
555
+ MemoryOptimizer,
556
+ defaultOptimizer,
557
+ };