@flowrdesk/silo 1.0.0

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,112 @@
1
+ /**
2
+ * Copyright 2026 John Spriggs (Flowrdesk LLC)
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+
18
+ import Logs from '../index.js';
19
+ import { execSync } from 'child_process';
20
+
21
+ const args = process.argv.slice(2)
22
+ const totalLogInstances = parseInt(args[0]) || 30
23
+ const logsPerInstance = parseInt(args[1]) || 10_000
24
+
25
+ async function runMegaStressTest(targetInstances, loggingEachInstance) {
26
+ let maxLoopLag = 0;
27
+ const startLagMonitor = () => {
28
+ let lastTime = Date.now();
29
+ const interval = setInterval(() => {
30
+ const now = Date.now();
31
+ const lag = now - lastTime - 100; // 100ms is the interval
32
+ if (lag > maxLoopLag) maxLoopLag = Math.max(0, lag);
33
+ lastTime = now;
34
+ }, 100);
35
+ return () => clearInterval(interval);
36
+ };
37
+ const instances = [];
38
+ const logsPerInstance = loggingEachInstance;
39
+
40
+ console.log(`\n========================================================================`);
41
+ console.log(` SILO ENGINE MULTI-INSTANCE STRESS TEST: ${targetInstances} INSTANCES`);
42
+ console.log(`========================================================================`);
43
+
44
+ // 1. OS PRE-CHECK
45
+ try {
46
+ const limit = execSync('ulimit -n').toString().trim();
47
+ console.log(`ℹ️ Current OS File Limit: ${limit}`);
48
+ if (parseInt(limit) < targetInstances + 100) {
49
+ console.log(`⚠️ WARNING: Your limit is lower than your target instances.`);
50
+ }
51
+ } catch (e) {
52
+ console.log(`ℹ️ Could not check ulimit (likely Windows). Ensure stability.`);
53
+ }
54
+
55
+ const initialMem = process.memoryUsage().heapUsed;
56
+ console.time('🚀 Boot Time');
57
+
58
+ for (let i = 0; i < targetInstances; i++) {
59
+ try {
60
+ const logger = new Logs({
61
+ filename: `silo_log_instance_test_${i + 1}`,
62
+ benchmark: true,
63
+ toTerminal: false, // Keep terminal quiet so we can see logic speed
64
+ toFile: true
65
+ });
66
+ instances.push(logger);
67
+ } catch (err) {
68
+ console.error(`❌ FAILED at instance ${i}: ${err.message}`);
69
+ break;
70
+ }
71
+ }
72
+ console.timeEnd('🚀 Boot Time');
73
+ const stopLag = startLagMonitor(); // Start monitoring right before hammering
74
+
75
+ // 3. THE HAMMER PHASE (Simultaneous Batching)
76
+ console.log(`🔨 Hammering ${instances.length} instances with ${logsPerInstance} logs each...`);
77
+ const startHammer = Date.now();
78
+
79
+ // We send all logs as fast as the CPU allows
80
+ await Promise.all(instances.map(async (logger) => {
81
+ for (let j = 0; j < logsPerInstance; j++) {
82
+ await logger.file({ msg: `Stress test log ${j}`, val: j * 32 });
83
+ }
84
+ }));
85
+
86
+ // 4. THE FLUSH (Wait for all Gatekeepers to finish)
87
+ console.log(`⏳ Waiting for all file queues to flush to disk...`);
88
+ await Promise.all(instances.map(inst => inst.flush()));
89
+ stopLag(); // Stop monitoring once queues are flushed
90
+
91
+ if (global.gc) { global.gc(); global.gc(); }
92
+
93
+
94
+ const memGrowthMB = (process.memoryUsage().heapUsed - initialMem) / 1024 / 1024;
95
+ const totalLogsCount = instances.length * logsPerInstance; // Use .length here
96
+ const finalDurationSec = (Date.now() - startHammer) / 1000; // Time from start of logs to flush
97
+
98
+ console.log(`\n========================================================================`);
99
+ console.log(` FINAL STRESS REPORT: THE "WINDOWS WALL" EDITION`);
100
+ console.log(`========================================================================`);
101
+ console.log(`✅ Successful Instances: ${instances.length}`);
102
+ console.log(`📈 Total Logs Written: ${totalLogsCount.toLocaleString()}`);
103
+ console.log(`⏱️ Execution Time: ${finalDurationSec.toFixed(3)}s`);
104
+ console.log(`🚀 Throughput: ${Math.floor(totalLogsCount / finalDurationSec).toLocaleString()} LPS`);
105
+ console.log(`🧠 Mem Growth: +${memGrowthMB.toFixed(2)} MB`);
106
+ console.log(`💎 Avg Mem Per Instance: ${(memGrowthMB / instances.length).toFixed(3)} MB`);
107
+ console.log(`⚡ Max Event Loop Lag: ${maxLoopLag}ms`);
108
+ console.log(`📊 Density: ${Math.floor(totalLogsCount / memGrowthMB).toLocaleString()} Logs per 1MB RAM`);
109
+ console.log(`========================================================================`);
110
+ }
111
+
112
+ runMegaStressTest(totalLogInstances, logsPerInstance);
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Copyright 2026 John Spriggs (Flowrdesk LLC)
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ // ============================================================
18
+ // SILO — Sustained Load Memory Stability Test (Backpressure Enabled)
19
+ // ============================================================
20
+
21
+ import Logs from '../index.js';
22
+ import { performance } from 'perf_hooks';
23
+
24
+ const DURATION_MS = 60_000;
25
+ const SAMPLE_INTERVAL_MS = 2_000;
26
+ const LOG_MSG = { msg: "Sustained load test entry", val: 0.123456789 };
27
+
28
+ function mb(bytes) { return (bytes / 1024 / 1024).toFixed(2); }
29
+
30
+ async function runSustainedTest() {
31
+ console.log(`\n🔬 SILO — Sustained Load Memory Stability Test (Backpressure Enabled)`);
32
+ console.log(` Duration: ${DURATION_MS / 1000}s`);
33
+ console.log(` Sample interval: ${SAMPLE_INTERVAL_MS / 1000}s`);
34
+ console.log(`────────────────────────────────────────────────────`);
35
+ console.log(` Time(s) | Heap Used | Delta from Start | LPS (interval)`);
36
+ console.log(`────────────────────────────────────────────────────`);
37
+
38
+ if (global.gc) { global.gc(); global.gc(); }
39
+
40
+ const logger = new Logs({ filename: 'silo_memory_test', toFile: true, toTerminal: false });
41
+
42
+ const startMem = process.memoryUsage().heapUsed;
43
+ const startTime = performance.now();
44
+
45
+ let totalLogs = 0;
46
+ let intervalLogs = 0;
47
+ let lastSampleTime = performance.now();
48
+ const samples = [];
49
+
50
+ // Sampling interval — snapshots heap independently of the write loop
51
+ const sampler = setInterval(() => {
52
+ const now = performance.now();
53
+ const elapsed = ((now - startTime) / 1000).toFixed(1);
54
+ const heap = process.memoryUsage().heapUsed;
55
+ const deltaFromStart = mb(heap - startMem);
56
+ const intervalSecs = (now - lastSampleTime) / 1000;
57
+ const intervalLPS = Math.floor(intervalLogs / intervalSecs).toLocaleString();
58
+
59
+ console.log(` ${String(elapsed).padEnd(7)}s | ${mb(heap).padStart(7)} MB | ${String(deltaFromStart).padStart(14)} MB | ${intervalLPS}`);
60
+
61
+ samples.push({ elapsed: parseFloat(elapsed), heap });
62
+ lastSampleTime = now;
63
+ intervalLogs = 0;
64
+ }, SAMPLE_INTERVAL_MS);
65
+
66
+ // Write loop — yields when queue is under pressure, free-runs when it has room
67
+ const endTime = startTime + DURATION_MS;
68
+ while (performance.now() < endTime) {
69
+ // Gate: parks here if fileQueue >= maxQueueDepth
70
+ // Resolves immediately (zero cost) when queue has room
71
+ // await logger.waitForQueueSpace();
72
+ await logger.file(LOG_MSG);
73
+ totalLogs++;
74
+ intervalLogs++;
75
+ }
76
+
77
+ clearInterval(sampler);
78
+ await logger.flush();
79
+
80
+ const totalElapsed = (performance.now() - startTime) / 1000;
81
+ const finalHeap = process.memoryUsage().heapUsed;
82
+
83
+ // Linear regression for trend
84
+ const n = samples.length;
85
+ let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
86
+ for (const s of samples) {
87
+ sumX += s.elapsed;
88
+ sumY += s.heap;
89
+ sumXY += s.elapsed * s.heap;
90
+ sumX2 += s.elapsed * s.elapsed;
91
+ }
92
+ const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
93
+ const slopeMBperMin = ((slope * 60) / 1024 / 1024).toFixed(2);
94
+ const trending = Math.abs(parseFloat(slopeMBperMin)) < 150
95
+ ? '✅ STABLE — memory is not growing meaningfully'
96
+ : parseFloat(slopeMBperMin) > 0
97
+ ? '⚠️ GROWING — possible memory pressure, consider tuning maxQueueDepth'
98
+ : '✅ SHRINKING — GC is reclaiming memory over time';
99
+
100
+ console.log(`────────────────────────────────────────────────────`);
101
+ console.log(`\n📊 FINAL SUMMARY`);
102
+ console.log(` Total logs written : ${totalLogs.toLocaleString()}`);
103
+ console.log(` Total time : ${totalElapsed.toFixed(2)}s`);
104
+ console.log(` Avg LPS : ${Math.floor(totalLogs / totalElapsed).toLocaleString()}`);
105
+ console.log(` Heap at start : ${mb(startMem)} MB`);
106
+ console.log(` Heap at end : ${mb(finalHeap)} MB`);
107
+ console.log(` Net heap delta : ${mb(finalHeap - startMem)} MB`);
108
+ console.log(`\n📈 MEMORY TREND ANALYSIS`);
109
+ console.log(` Slope : ${slopeMBperMin} MB/min`);
110
+ console.log(` Verdict : ${trending}`);
111
+ console.log(`\n────────────────────────────────────────────────────\n`);
112
+ }
113
+
114
+ runSustainedTest().catch(console.error);