dspx 0.1.1-alpha.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.
- package/.github/workflows/ci.yml +185 -0
- package/.vscode/c_cpp_properties.json +17 -0
- package/.vscode/settings.json +68 -0
- package/.vscode/tasks.json +28 -0
- package/DISCLAIMER.md +32 -0
- package/LICENSE +21 -0
- package/README.md +1803 -0
- package/ROADMAP.md +192 -0
- package/TECHNICAL_DEBT.md +165 -0
- package/binding.gyp +65 -0
- package/docs/ADVANCED_LOGGER_FEATURES.md +598 -0
- package/docs/AUTHENTICATION_SECURITY.md +396 -0
- package/docs/BACKEND_IMPROVEMENTS.md +399 -0
- package/docs/CHEBYSHEV_BIQUAD_EQ_IMPLEMENTATION.md +405 -0
- package/docs/FFT_IMPLEMENTATION.md +490 -0
- package/docs/FFT_IMPROVEMENTS_SUMMARY.md +387 -0
- package/docs/FFT_USER_GUIDE.md +494 -0
- package/docs/FILTERS_IMPLEMENTATION.md +260 -0
- package/docs/FILTER_API_GUIDE.md +418 -0
- package/docs/FIR_SIMD_OPTIMIZATION.md +175 -0
- package/docs/LOGGER_API_REFERENCE.md +350 -0
- package/docs/NOTCH_FILTER_QUICK_REF.md +121 -0
- package/docs/PHASE2_TESTS_AND_NOTCH_FILTER.md +341 -0
- package/docs/PHASES_5_7_SUMMARY.md +403 -0
- package/docs/PIPELINE_FILTER_INTEGRATION.md +446 -0
- package/docs/SIMD_OPTIMIZATIONS.md +211 -0
- package/docs/TEST_MIGRATION_SUMMARY.md +173 -0
- package/docs/TIMESERIES_IMPLEMENTATION_SUMMARY.md +322 -0
- package/docs/TIMESERIES_QUICK_REF.md +85 -0
- package/docs/advanced.md +559 -0
- package/docs/time-series-guide.md +617 -0
- package/docs/time-series-migration.md +376 -0
- package/jest.config.js +37 -0
- package/package.json +42 -0
- package/prebuilds/linux-x64/dsp-ts-redis.node +0 -0
- package/prebuilds/win32-x64/dsp-ts-redis.node +0 -0
- package/scripts/test.js +24 -0
- package/src/build/dsp-ts-redis.node +0 -0
- package/src/native/DspPipeline.cc +675 -0
- package/src/native/DspPipeline.h +44 -0
- package/src/native/FftBindings.cc +817 -0
- package/src/native/FilterBindings.cc +1001 -0
- package/src/native/IDspStage.h +53 -0
- package/src/native/adapters/InterpolatorStage.h +201 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +289 -0
- package/src/native/adapters/MovingAverageStage.h +306 -0
- package/src/native/adapters/RectifyStage.h +88 -0
- package/src/native/adapters/ResamplerStage.h +238 -0
- package/src/native/adapters/RmsStage.h +299 -0
- package/src/native/adapters/SscStage.h +121 -0
- package/src/native/adapters/VarianceStage.h +307 -0
- package/src/native/adapters/WampStage.h +114 -0
- package/src/native/adapters/WaveformLengthStage.h +115 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +326 -0
- package/src/native/core/FftEngine.cc +441 -0
- package/src/native/core/FftEngine.h +224 -0
- package/src/native/core/FirFilter.cc +324 -0
- package/src/native/core/FirFilter.h +149 -0
- package/src/native/core/IirFilter.cc +576 -0
- package/src/native/core/IirFilter.h +210 -0
- package/src/native/core/MovingAbsoluteValueFilter.cc +17 -0
- package/src/native/core/MovingAbsoluteValueFilter.h +135 -0
- package/src/native/core/MovingAverageFilter.cc +18 -0
- package/src/native/core/MovingAverageFilter.h +135 -0
- package/src/native/core/MovingFftFilter.cc +291 -0
- package/src/native/core/MovingFftFilter.h +203 -0
- package/src/native/core/MovingVarianceFilter.cc +194 -0
- package/src/native/core/MovingVarianceFilter.h +114 -0
- package/src/native/core/MovingZScoreFilter.cc +215 -0
- package/src/native/core/MovingZScoreFilter.h +113 -0
- package/src/native/core/Policies.h +352 -0
- package/src/native/core/RmsFilter.cc +18 -0
- package/src/native/core/RmsFilter.h +131 -0
- package/src/native/core/SscFilter.cc +16 -0
- package/src/native/core/SscFilter.h +137 -0
- package/src/native/core/WampFilter.cc +16 -0
- package/src/native/core/WampFilter.h +101 -0
- package/src/native/core/WaveformLengthFilter.cc +17 -0
- package/src/native/core/WaveformLengthFilter.h +98 -0
- package/src/native/utils/CircularBufferArray.cc +336 -0
- package/src/native/utils/CircularBufferArray.h +62 -0
- package/src/native/utils/CircularBufferVector.cc +145 -0
- package/src/native/utils/CircularBufferVector.h +45 -0
- package/src/native/utils/NapiUtils.cc +53 -0
- package/src/native/utils/NapiUtils.h +21 -0
- package/src/native/utils/SimdOps.h +870 -0
- package/src/native/utils/SlidingWindowFilter.cc +239 -0
- package/src/native/utils/SlidingWindowFilter.h +159 -0
- package/src/native/utils/TimeSeriesBuffer.cc +205 -0
- package/src/native/utils/TimeSeriesBuffer.h +140 -0
- package/src/ts/CircularLogBuffer.ts +87 -0
- package/src/ts/DriftDetector.ts +331 -0
- package/src/ts/TopicRouter.ts +428 -0
- package/src/ts/__tests__/AdvancedDsp.test.ts +585 -0
- package/src/ts/__tests__/AuthAndEdgeCases.test.ts +241 -0
- package/src/ts/__tests__/Chaining.test.ts +387 -0
- package/src/ts/__tests__/ChebyshevBiquad.test.ts +229 -0
- package/src/ts/__tests__/CircularLogBuffer.test.ts +158 -0
- package/src/ts/__tests__/DriftDetector.test.ts +389 -0
- package/src/ts/__tests__/Fft.test.ts +484 -0
- package/src/ts/__tests__/ListState.test.ts +153 -0
- package/src/ts/__tests__/Logger.test.ts +208 -0
- package/src/ts/__tests__/LoggerAdvanced.test.ts +319 -0
- package/src/ts/__tests__/LoggerMinor.test.ts +247 -0
- package/src/ts/__tests__/MeanAbsoluteValue.test.ts +398 -0
- package/src/ts/__tests__/MovingAverage.test.ts +322 -0
- package/src/ts/__tests__/RMS.test.ts +315 -0
- package/src/ts/__tests__/Rectify.test.ts +272 -0
- package/src/ts/__tests__/Redis.test.ts +456 -0
- package/src/ts/__tests__/SlopeSignChange.test.ts +166 -0
- package/src/ts/__tests__/Tap.test.ts +164 -0
- package/src/ts/__tests__/TimeBasedExpiration.test.ts +124 -0
- package/src/ts/__tests__/TimeBasedRmsAndMav.test.ts +231 -0
- package/src/ts/__tests__/TimeBasedVarianceAndZScore.test.ts +284 -0
- package/src/ts/__tests__/TimeSeries.test.ts +254 -0
- package/src/ts/__tests__/TopicRouter.test.ts +332 -0
- package/src/ts/__tests__/TopicRouterAdvanced.test.ts +483 -0
- package/src/ts/__tests__/TopicRouterPriority.test.ts +487 -0
- package/src/ts/__tests__/Variance.test.ts +509 -0
- package/src/ts/__tests__/WaveformLength.test.ts +147 -0
- package/src/ts/__tests__/WillisonAmplitude.test.ts +197 -0
- package/src/ts/__tests__/ZScoreNormalize.test.ts +459 -0
- package/src/ts/advanced-dsp.ts +566 -0
- package/src/ts/backends.ts +1137 -0
- package/src/ts/bindings.ts +1225 -0
- package/src/ts/easter-egg.ts +42 -0
- package/src/ts/examples/MeanAbsoluteValue/test-state.ts +99 -0
- package/src/ts/examples/MeanAbsoluteValue/test-streaming.ts +269 -0
- package/src/ts/examples/MovingAverage/test-state.ts +85 -0
- package/src/ts/examples/MovingAverage/test-streaming.ts +188 -0
- package/src/ts/examples/RMS/test-state.ts +97 -0
- package/src/ts/examples/RMS/test-streaming.ts +253 -0
- package/src/ts/examples/Rectify/test-state.ts +107 -0
- package/src/ts/examples/Rectify/test-streaming.ts +242 -0
- package/src/ts/examples/Variance/test-state.ts +195 -0
- package/src/ts/examples/Variance/test-streaming.ts +260 -0
- package/src/ts/examples/ZScoreNormalize/test-state.ts +277 -0
- package/src/ts/examples/ZScoreNormalize/test-streaming.ts +306 -0
- package/src/ts/examples/advanced-dsp-examples.ts +397 -0
- package/src/ts/examples/callbacks/advanced-router-features.ts +326 -0
- package/src/ts/examples/callbacks/benchmark-circular-buffer.ts +109 -0
- package/src/ts/examples/callbacks/monitoring-example.ts +265 -0
- package/src/ts/examples/callbacks/pipeline-callbacks-example.ts +137 -0
- package/src/ts/examples/callbacks/pooled-callbacks-example.ts +274 -0
- package/src/ts/examples/callbacks/priority-routing-example.ts +277 -0
- package/src/ts/examples/callbacks/production-topic-router.ts +214 -0
- package/src/ts/examples/callbacks/topic-based-logging.ts +161 -0
- package/src/ts/examples/chaining/test-chaining-redis.ts +113 -0
- package/src/ts/examples/chaining/test-chaining.ts +52 -0
- package/src/ts/examples/emg-features-example.ts +284 -0
- package/src/ts/examples/fft-example.ts +309 -0
- package/src/ts/examples/fft-examples.ts +349 -0
- package/src/ts/examples/filter-examples.ts +320 -0
- package/src/ts/examples/list-state-example.ts +131 -0
- package/src/ts/examples/logger-example.ts +91 -0
- package/src/ts/examples/notch-filter-examples.ts +243 -0
- package/src/ts/examples/phase5/drift-detection-example.ts +290 -0
- package/src/ts/examples/phase6-7/production-observability.ts +476 -0
- package/src/ts/examples/phase6-7/redis-timeseries-integration.ts +446 -0
- package/src/ts/examples/redis/redis-example.ts +202 -0
- package/src/ts/examples/redis-example.ts +202 -0
- package/src/ts/examples/simd-benchmark.ts +126 -0
- package/src/ts/examples/tap-debugging.ts +230 -0
- package/src/ts/examples/timeseries/comparison-example.ts +290 -0
- package/src/ts/examples/timeseries/iot-sensor-example.ts +143 -0
- package/src/ts/examples/timeseries/redis-streaming-example.ts +233 -0
- package/src/ts/examples/waveform-length-example.ts +139 -0
- package/src/ts/fft.ts +722 -0
- package/src/ts/filters.ts +1078 -0
- package/src/ts/index.ts +120 -0
- package/src/ts/types.ts +589 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createDspPipeline } from "../../bindings.js";
|
|
2
|
+
import type { PipelineCallbacks } from "../../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Example demonstrating the new pipeline callback API
|
|
6
|
+
* for monitoring, observability, and alerting
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Simulated metrics collector
|
|
10
|
+
const metrics = {
|
|
11
|
+
durations: [] as number[],
|
|
12
|
+
record(key: string, value: number) {
|
|
13
|
+
console.log(`Metric: ${key} = ${value.toFixed(2)}ms`);
|
|
14
|
+
this.durations.push(value);
|
|
15
|
+
},
|
|
16
|
+
getAverage() {
|
|
17
|
+
return this.durations.reduce((a, b) => a + b, 0) / this.durations.length;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Simulated logger
|
|
22
|
+
const logger = {
|
|
23
|
+
error(msg: string, err: Error) {
|
|
24
|
+
console.error(`❌ ${msg}:`, err.message);
|
|
25
|
+
},
|
|
26
|
+
info(msg: string, ctx?: any) {
|
|
27
|
+
console.log(`${msg}`, ctx || "");
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Alert threshold
|
|
32
|
+
const THRESHOLD = 0.8;
|
|
33
|
+
let alertCount = 0;
|
|
34
|
+
|
|
35
|
+
function triggerAlert(index: number, stage: string, value: number) {
|
|
36
|
+
alertCount++;
|
|
37
|
+
console.log(
|
|
38
|
+
`ALERT #${alertCount}: Sample ${index} in ${stage} exceeded threshold: ${value.toFixed(
|
|
39
|
+
4
|
|
40
|
+
)} > ${THRESHOLD}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Configure callbacks
|
|
45
|
+
const callbacks: PipelineCallbacks = {
|
|
46
|
+
onSample: (value, i, stage) => {
|
|
47
|
+
// Only trigger alerts for samples exceeding threshold
|
|
48
|
+
if (Math.abs(value) > THRESHOLD) {
|
|
49
|
+
triggerAlert(i, stage, value);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
onStageComplete: (stage, durationMs) => {
|
|
54
|
+
metrics.record(`dsp.${stage}.duration`, durationMs);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
onError: (stage, err) => {
|
|
58
|
+
logger.error(`Stage ${stage} failed`, err);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
onLog: (level, msg, ctx) => {
|
|
62
|
+
// Filter out debug logs for cleaner output
|
|
63
|
+
if (level === "debug") return;
|
|
64
|
+
|
|
65
|
+
const emoji = {
|
|
66
|
+
info: "ℹ️",
|
|
67
|
+
warn: "⚠️",
|
|
68
|
+
error: "❌",
|
|
69
|
+
debug: "🔍",
|
|
70
|
+
}[level];
|
|
71
|
+
|
|
72
|
+
console.log(`${emoji} [${level.toUpperCase()}] ${msg}`, ctx || "");
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
async function main() {
|
|
77
|
+
console.log("=".repeat(60));
|
|
78
|
+
console.log("Pipeline Callbacks Example");
|
|
79
|
+
console.log("=".repeat(60));
|
|
80
|
+
console.log();
|
|
81
|
+
|
|
82
|
+
// Create pipeline with callbacks configured
|
|
83
|
+
const pipeline = createDspPipeline()
|
|
84
|
+
.pipeline(callbacks) // Configure callbacks first
|
|
85
|
+
.MovingAverage({ mode: "moving", windowSize: 3 })
|
|
86
|
+
.Rectify({ mode: "full" })
|
|
87
|
+
.Rms({ mode: "moving", windowSize: 5 });
|
|
88
|
+
|
|
89
|
+
// Generate test signal with some peaks
|
|
90
|
+
const sampleRate = 44100;
|
|
91
|
+
const duration = 0.01; // 10ms = 441 samples
|
|
92
|
+
const numSamples = Math.floor(sampleRate * duration);
|
|
93
|
+
const testSignal = new Float32Array(numSamples);
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < numSamples; i++) {
|
|
96
|
+
// Mix of normal samples and some peaks
|
|
97
|
+
if (i % 100 === 0) {
|
|
98
|
+
// Inject peaks that will trigger alerts
|
|
99
|
+
testSignal[i] = 0.9 + Math.random() * 0.1;
|
|
100
|
+
} else if (i % 50 === 0) {
|
|
101
|
+
testSignal[i] = -0.85;
|
|
102
|
+
} else {
|
|
103
|
+
// Normal random signal
|
|
104
|
+
testSignal[i] = Math.random() * 0.4 - 0.2;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(`Input: ${numSamples} samples`);
|
|
109
|
+
console.log(`Pipeline: MovingAverage(3) → Rectify(full) → RMS(5)`);
|
|
110
|
+
console.log();
|
|
111
|
+
|
|
112
|
+
// Process with callbacks
|
|
113
|
+
const result = await pipeline.process(testSignal, {
|
|
114
|
+
sampleRate,
|
|
115
|
+
channels: 1,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
console.log();
|
|
119
|
+
console.log("=".repeat(60));
|
|
120
|
+
console.log("Results Summary");
|
|
121
|
+
console.log("=".repeat(60));
|
|
122
|
+
console.log(`Output: ${result.length} samples`);
|
|
123
|
+
console.log(`Total alerts triggered: ${alertCount}`);
|
|
124
|
+
console.log(`Average processing time: ${metrics.getAverage().toFixed(3)}ms`);
|
|
125
|
+
console.log(
|
|
126
|
+
`Output range: [${Math.min(...result).toFixed(4)}, ${Math.max(
|
|
127
|
+
...result
|
|
128
|
+
).toFixed(4)}]`
|
|
129
|
+
);
|
|
130
|
+
console.log();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Run the example
|
|
134
|
+
main().catch((err) => {
|
|
135
|
+
console.error("Example failed:", err);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pooled Callbacks Example
|
|
3
|
+
* Demonstrates the performance benefits of using batched callbacks
|
|
4
|
+
* over individual per-sample/per-log callbacks
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createDspPipeline,
|
|
9
|
+
type LogEntry,
|
|
10
|
+
type SampleBatch,
|
|
11
|
+
} from "../../index.js";
|
|
12
|
+
|
|
13
|
+
// Performance metrics collector using pooled callbacks
|
|
14
|
+
class PooledMonitor {
|
|
15
|
+
private batchCount = 0;
|
|
16
|
+
private totalSamples = 0;
|
|
17
|
+
private sampleSum = 0;
|
|
18
|
+
private sampleMin = Infinity;
|
|
19
|
+
private sampleMax = -Infinity;
|
|
20
|
+
|
|
21
|
+
onBatch(batch: SampleBatch): void {
|
|
22
|
+
this.batchCount++;
|
|
23
|
+
this.totalSamples += batch.count;
|
|
24
|
+
|
|
25
|
+
// Process entire batch efficiently (SIMD-friendly)
|
|
26
|
+
for (let i = 0; i < batch.samples.length; i++) {
|
|
27
|
+
const value = batch.samples[i];
|
|
28
|
+
this.sampleSum += value;
|
|
29
|
+
if (value < this.sampleMin) this.sampleMin = value;
|
|
30
|
+
if (value > this.sampleMax) this.sampleMax = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onLogBatch(logs: LogEntry[]): void {
|
|
35
|
+
console.log(`\n📦 Received ${logs.length} pooled log entries:`);
|
|
36
|
+
logs.forEach((log) => {
|
|
37
|
+
const emoji =
|
|
38
|
+
log.level === "debug"
|
|
39
|
+
? "🐛"
|
|
40
|
+
: log.level === "info"
|
|
41
|
+
? "ℹ️"
|
|
42
|
+
: log.level === "warn"
|
|
43
|
+
? "⚠️"
|
|
44
|
+
: "❌";
|
|
45
|
+
console.log(
|
|
46
|
+
` ${emoji} [${log.level.toUpperCase()}] ${log.message}`,
|
|
47
|
+
log.context || ""
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
report(): void {
|
|
53
|
+
console.log("\nPooled Monitoring Report:");
|
|
54
|
+
console.log(` Batches processed: ${this.batchCount}`);
|
|
55
|
+
console.log(` Total samples: ${this.totalSamples}`);
|
|
56
|
+
console.log(
|
|
57
|
+
` Sample range: [${this.sampleMin.toFixed(4)}, ${this.sampleMax.toFixed(
|
|
58
|
+
4
|
|
59
|
+
)}]`
|
|
60
|
+
);
|
|
61
|
+
console.log(
|
|
62
|
+
` Sample average: ${(this.sampleSum / this.totalSamples).toFixed(4)}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function compareCallbackPerformance() {
|
|
68
|
+
console.log("Comparing Callback Performance\n");
|
|
69
|
+
|
|
70
|
+
// Generate test signal (10 batches of 4096 samples each)
|
|
71
|
+
const batchSize = 4096;
|
|
72
|
+
const numBatches = 10;
|
|
73
|
+
|
|
74
|
+
// ============================================================
|
|
75
|
+
// Test 1: Using POOLED callbacks (onBatch + onLogBatch)
|
|
76
|
+
// ============================================================
|
|
77
|
+
console.log("Test 1: POOLED Callbacks (onBatch + onLogBatch)");
|
|
78
|
+
const monitor1 = new PooledMonitor();
|
|
79
|
+
const pipeline1 = createDspPipeline()
|
|
80
|
+
.pipeline({
|
|
81
|
+
onBatch: (batch) => monitor1.onBatch(batch),
|
|
82
|
+
onLogBatch: (logs) => monitor1.onLogBatch(logs),
|
|
83
|
+
onStageComplete: (stage, duration) => {
|
|
84
|
+
console.log(`Stage ${stage} completed in ${duration.toFixed(3)}ms`);
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
.MovingAverage({ mode: "moving", windowSize: 10 })
|
|
88
|
+
.Rectify({ mode: "full" })
|
|
89
|
+
.Rms({ mode: "moving", windowSize: 10 });
|
|
90
|
+
|
|
91
|
+
const startPooled = performance.now();
|
|
92
|
+
for (let batch = 0; batch < numBatches; batch++) {
|
|
93
|
+
const input = Float32Array.from({ length: batchSize }, (_, i) =>
|
|
94
|
+
Math.sin(2 * Math.PI * 440 * (i / 48000))
|
|
95
|
+
);
|
|
96
|
+
await pipeline1.process(input, { sampleRate: 48000 });
|
|
97
|
+
}
|
|
98
|
+
const durationPooled = performance.now() - startPooled;
|
|
99
|
+
|
|
100
|
+
monitor1.report();
|
|
101
|
+
console.log(
|
|
102
|
+
`\nTotal time (pooled): ${durationPooled.toFixed(3)}ms for ${
|
|
103
|
+
numBatches * batchSize
|
|
104
|
+
} samples`
|
|
105
|
+
);
|
|
106
|
+
console.log(
|
|
107
|
+
` Throughput: ${(
|
|
108
|
+
(numBatches * batchSize) /
|
|
109
|
+
(durationPooled / 1000)
|
|
110
|
+
).toLocaleString()} samples/sec`
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// ============================================================
|
|
114
|
+
// Test 2: Using INDIVIDUAL callbacks (onSample + onLog)
|
|
115
|
+
// ============================================================
|
|
116
|
+
console.log("\n\nTest 2: INDIVIDUAL Callbacks (onSample + onLog)");
|
|
117
|
+
console.log("⚠️ WARNING: This will make 40,960+ callback invocations!\n");
|
|
118
|
+
|
|
119
|
+
let sampleCallCount = 0;
|
|
120
|
+
let logCallCount = 0;
|
|
121
|
+
let sampleSum2 = 0;
|
|
122
|
+
let sampleMin2 = Infinity;
|
|
123
|
+
let sampleMax2 = -Infinity;
|
|
124
|
+
|
|
125
|
+
const pipeline2 = createDspPipeline()
|
|
126
|
+
.pipeline({
|
|
127
|
+
onSample: (value, _index, _stage) => {
|
|
128
|
+
sampleCallCount++;
|
|
129
|
+
// Realistic work: aggregate stats per sample (expensive!)
|
|
130
|
+
sampleSum2 += value;
|
|
131
|
+
if (value < sampleMin2) sampleMin2 = value;
|
|
132
|
+
if (value > sampleMax2) sampleMax2 = value;
|
|
133
|
+
},
|
|
134
|
+
onLog: (level, message, context) => {
|
|
135
|
+
logCallCount++;
|
|
136
|
+
// Realistic work: format and store log
|
|
137
|
+
const timestamp = Date.now();
|
|
138
|
+
const _logEntry = { level, message, context, timestamp };
|
|
139
|
+
// In real app: push to log buffer or write to file
|
|
140
|
+
},
|
|
141
|
+
onStageComplete: (stage, duration) => {
|
|
142
|
+
console.log(`Stage ${stage} completed in ${duration.toFixed(3)}ms`);
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
.MovingAverage({ mode: "moving", windowSize: 10 })
|
|
146
|
+
.Rectify({ mode: "full" })
|
|
147
|
+
.Rms({ mode: "moving", windowSize: 10 });
|
|
148
|
+
|
|
149
|
+
const startIndividual = performance.now();
|
|
150
|
+
for (let batch = 0; batch < numBatches; batch++) {
|
|
151
|
+
const input = Float32Array.from({ length: batchSize }, (_, i) =>
|
|
152
|
+
Math.sin(2 * Math.PI * 440 * (i / 48000))
|
|
153
|
+
);
|
|
154
|
+
await pipeline2.process(input, { sampleRate: 48000 });
|
|
155
|
+
}
|
|
156
|
+
const durationIndividual = performance.now() - startIndividual;
|
|
157
|
+
|
|
158
|
+
console.log(`\nIndividual Callbacks Report:`);
|
|
159
|
+
console.log(
|
|
160
|
+
` onSample() called: ${sampleCallCount.toLocaleString()} times`
|
|
161
|
+
);
|
|
162
|
+
console.log(` onLog() called: ${logCallCount} times`);
|
|
163
|
+
console.log(
|
|
164
|
+
` Sample range: [${sampleMin2.toFixed(4)}, ${sampleMax2.toFixed(4)}]`
|
|
165
|
+
);
|
|
166
|
+
console.log(
|
|
167
|
+
` Sample average: ${(sampleSum2 / sampleCallCount).toFixed(4)}`
|
|
168
|
+
);
|
|
169
|
+
console.log(
|
|
170
|
+
`\nTotal time (individual): ${durationIndividual.toFixed(3)}ms for ${
|
|
171
|
+
numBatches * batchSize
|
|
172
|
+
} samples`
|
|
173
|
+
);
|
|
174
|
+
console.log(
|
|
175
|
+
` Throughput: ${(
|
|
176
|
+
(numBatches * batchSize) /
|
|
177
|
+
(durationIndividual / 1000)
|
|
178
|
+
).toLocaleString()} samples/sec`
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// ============================================================
|
|
182
|
+
// Performance Comparison
|
|
183
|
+
// ============================================================
|
|
184
|
+
console.log("\n\nPerformance Comparison:");
|
|
185
|
+
console.log(
|
|
186
|
+
` Pooled callbacks: ${durationPooled.toFixed(3)}ms (${(
|
|
187
|
+
(numBatches * batchSize) /
|
|
188
|
+
(durationPooled / 1000)
|
|
189
|
+
).toFixed(1)} samples/sec)`
|
|
190
|
+
);
|
|
191
|
+
console.log(
|
|
192
|
+
` Individual callbacks: ${durationIndividual.toFixed(3)}ms (${(
|
|
193
|
+
(numBatches * batchSize) /
|
|
194
|
+
(durationIndividual / 1000)
|
|
195
|
+
).toFixed(1)} samples/sec)`
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (durationIndividual < durationPooled) {
|
|
199
|
+
console.log(
|
|
200
|
+
` \n Individual is ${(durationPooled / durationIndividual).toFixed(
|
|
201
|
+
2
|
|
202
|
+
)}x faster in raw speed`
|
|
203
|
+
);
|
|
204
|
+
} else {
|
|
205
|
+
console.log(
|
|
206
|
+
` \n Pooled is ${(durationIndividual / durationPooled).toFixed(
|
|
207
|
+
2
|
|
208
|
+
)}x faster`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log("\n\nProduction Architecture Analysis:");
|
|
213
|
+
console.log(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
214
|
+
console.log("\n ⚠️ Individual Callbacks (onSample, onLog):");
|
|
215
|
+
console.log(
|
|
216
|
+
` • Raw speed: ~${(
|
|
217
|
+
(numBatches * batchSize) /
|
|
218
|
+
(durationIndividual / 1000) /
|
|
219
|
+
1000000
|
|
220
|
+
).toFixed(1)}M samples/sec`
|
|
221
|
+
);
|
|
222
|
+
console.log(
|
|
223
|
+
` • Function calls: ${(
|
|
224
|
+
sampleCallCount + logCallCount
|
|
225
|
+
).toLocaleString()} per ${numBatches} batches`
|
|
226
|
+
);
|
|
227
|
+
console.log(" • Event loop: BLOCKS on each callback invocation");
|
|
228
|
+
console.log(" • GC pressure: HIGH (per-call allocations)");
|
|
229
|
+
console.log(" • I/O operations: SYNCHRONOUS (stalls pipeline)");
|
|
230
|
+
console.log(" • Production safety: NOT RECOMMENDED");
|
|
231
|
+
console.log(" • Use case: Microbenchmarks, toy examples only");
|
|
232
|
+
|
|
233
|
+
console.log("\n Pooled Callbacks (onBatch, onLogBatch):");
|
|
234
|
+
console.log(
|
|
235
|
+
` • Sustained throughput: ~${(
|
|
236
|
+
(numBatches * batchSize) /
|
|
237
|
+
(durationPooled / 1000) /
|
|
238
|
+
1000000
|
|
239
|
+
).toFixed(1)}M samples/sec`
|
|
240
|
+
);
|
|
241
|
+
console.log(
|
|
242
|
+
` • Function calls: ${numBatches} batches (1 call per process)`
|
|
243
|
+
);
|
|
244
|
+
console.log(" • Event loop: NON-BLOCKING (batched execution)");
|
|
245
|
+
console.log(" • GC pressure: LOW (circular buffer reuse)");
|
|
246
|
+
console.log(" • I/O operations: BATCHED (network-friendly)");
|
|
247
|
+
console.log(" • Production safety: RECOMMENDED");
|
|
248
|
+
console.log(
|
|
249
|
+
" • Industry alignment: Kafka producers, Loki agents, OTLP exporters"
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
console.log("\n Trade-off Summary:");
|
|
253
|
+
console.log(" Individual mode: 2x faster in synthetic tests, but...");
|
|
254
|
+
console.log(" - Millions of synchronous callbacks block event loop");
|
|
255
|
+
console.log(" - Synchronous I/O in callbacks stalls entire pipeline");
|
|
256
|
+
console.log(" - Unpredictable GC pauses under load");
|
|
257
|
+
console.log(" - Cannot handle backpressure from external systems");
|
|
258
|
+
console.log("");
|
|
259
|
+
console.log(" Pooled mode: Slight raw speed reduction, but...");
|
|
260
|
+
console.log(" - Guaranteed non-blocking behavior");
|
|
261
|
+
console.log(" - Predictable memory footprint");
|
|
262
|
+
console.log(" - Natural backpressure handling");
|
|
263
|
+
console.log(" - Matches production telemetry patterns");
|
|
264
|
+
|
|
265
|
+
console.log("\n Recommendation:");
|
|
266
|
+
console.log(" Use onBatch and onLogBatch for production servers");
|
|
267
|
+
console.log(" Individual callbacks are fast but dangerous at scale");
|
|
268
|
+
console.log(
|
|
269
|
+
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Run the comparison
|
|
274
|
+
compareCallbackPerformance().catch(console.error);
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Priority-Based Routing Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates 10-level priority system with smart routing:
|
|
5
|
+
* - Low priority (1-3): Debug logs → Console only
|
|
6
|
+
* - Normal priority (4-6): Info logs → Loki
|
|
7
|
+
* - High priority (7-8): Warnings → Prometheus + Loki
|
|
8
|
+
* - Critical priority (9-10): Errors → PagerDuty + All backends
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
createDspPipeline,
|
|
13
|
+
createTopicRouter,
|
|
14
|
+
createConsoleHandler,
|
|
15
|
+
createMockHandler,
|
|
16
|
+
} from "../../index.js";
|
|
17
|
+
import type { LogEntry } from "../../types.js";
|
|
18
|
+
|
|
19
|
+
console.log("Priority-Based Routing Example\n");
|
|
20
|
+
|
|
21
|
+
// Example 1: Basic Priority Filtering
|
|
22
|
+
console.log("1. Basic Priority Filtering\n");
|
|
23
|
+
{
|
|
24
|
+
const lowPriority = createMockHandler((log) => {
|
|
25
|
+
console.log(` [LOW] Priority ${log.priority}: ${log.message}`);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const normalPriority = createMockHandler((log) => {
|
|
29
|
+
console.log(` [NORMAL] Priority ${log.priority}: ${log.message}`);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const highPriority = createMockHandler((log) => {
|
|
33
|
+
console.log(` [HIGH] Priority ${log.priority}: ${log.message}`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const critical = createMockHandler((log) => {
|
|
37
|
+
console.log(` [CRITICAL] Priority ${log.priority}: ${log.message}`);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const router = createTopicRouter()
|
|
41
|
+
.custom(/^pipeline\./, lowPriority.handler, {
|
|
42
|
+
minPriority: 1,
|
|
43
|
+
maxPriority: 3,
|
|
44
|
+
})
|
|
45
|
+
.custom(/^pipeline\./, normalPriority.handler, {
|
|
46
|
+
minPriority: 4,
|
|
47
|
+
maxPriority: 6,
|
|
48
|
+
})
|
|
49
|
+
.custom(/^pipeline\./, highPriority.handler, {
|
|
50
|
+
minPriority: 7,
|
|
51
|
+
maxPriority: 8,
|
|
52
|
+
})
|
|
53
|
+
.custom(/^pipeline\./, critical.handler, {
|
|
54
|
+
minPriority: 9,
|
|
55
|
+
maxPriority: 10,
|
|
56
|
+
})
|
|
57
|
+
.build();
|
|
58
|
+
|
|
59
|
+
const pipeline = createDspPipeline();
|
|
60
|
+
pipeline
|
|
61
|
+
.pipeline({
|
|
62
|
+
onLogBatch: (logs) => router.routeBatch(logs),
|
|
63
|
+
})
|
|
64
|
+
.MovingAverage({ mode: "moving", windowSize: 3 });
|
|
65
|
+
|
|
66
|
+
const samples = new Float32Array([1, 2, 3]);
|
|
67
|
+
await pipeline.process(samples, { sampleRate: 1000 });
|
|
68
|
+
|
|
69
|
+
console.log(`\n Summary:`);
|
|
70
|
+
console.log(` • Low priority logs: ${lowPriority.getLogs().length}`);
|
|
71
|
+
console.log(` • Normal priority logs: ${normalPriority.getLogs().length}`);
|
|
72
|
+
console.log(` • High priority logs: ${highPriority.getLogs().length}`);
|
|
73
|
+
console.log(` • Critical logs: ${critical.getLogs().length}\n`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Example 2: Production Alert Routing
|
|
77
|
+
console.log("2. Production Alert Routing (Multi-Tier)\n");
|
|
78
|
+
{
|
|
79
|
+
console.log(" Routing Strategy:");
|
|
80
|
+
console.log(" • Priority 1-3 (Debug): Console only");
|
|
81
|
+
console.log(" • Priority 4-6 (Info): Loki (logs storage)");
|
|
82
|
+
console.log(" • Priority 7-8 (Warn): Prometheus + Loki");
|
|
83
|
+
console.log(" • Priority 9-10 (Critical): PagerDuty + Prometheus + Loki\n");
|
|
84
|
+
|
|
85
|
+
const consoleBackend = createMockHandler((log) => {
|
|
86
|
+
console.log(` [Console] ${log.message}`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const lokiBackend = createMockHandler((log) => {
|
|
90
|
+
console.log(` [Loki] Storing: ${log.message}`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const prometheusBackend = createMockHandler((log) => {
|
|
94
|
+
console.log(` [Prometheus] Recording metric for: ${log.message}`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const pagerDutyBackend = createMockHandler((log) => {
|
|
98
|
+
console.log(` [PagerDuty] ALERT triggered: ${log.message}`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const router = createTopicRouter()
|
|
102
|
+
// Low priority: Console only
|
|
103
|
+
.custom(/^pipeline\./, consoleBackend.handler, {
|
|
104
|
+
minPriority: 1,
|
|
105
|
+
maxPriority: 3,
|
|
106
|
+
})
|
|
107
|
+
// Normal priority: Loki
|
|
108
|
+
.custom(/^pipeline\./, lokiBackend.handler, {
|
|
109
|
+
minPriority: 4,
|
|
110
|
+
maxPriority: 10, // All normal and above
|
|
111
|
+
})
|
|
112
|
+
// High priority: Prometheus
|
|
113
|
+
.custom(/^pipeline\./, prometheusBackend.handler, {
|
|
114
|
+
minPriority: 7,
|
|
115
|
+
maxPriority: 10,
|
|
116
|
+
})
|
|
117
|
+
// Critical: PagerDuty
|
|
118
|
+
.custom(/^pipeline\./, pagerDutyBackend.handler, {
|
|
119
|
+
minPriority: 9,
|
|
120
|
+
maxPriority: 10,
|
|
121
|
+
concurrency: 3, // Rate-limit alerts
|
|
122
|
+
})
|
|
123
|
+
.build();
|
|
124
|
+
|
|
125
|
+
// Simulate logs with different priorities
|
|
126
|
+
await router.routeBatch([
|
|
127
|
+
{
|
|
128
|
+
level: "debug",
|
|
129
|
+
message: "Verbose debug info",
|
|
130
|
+
topic: "pipeline.debug",
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
priority: 2,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
level: "info",
|
|
136
|
+
message: "Normal operation",
|
|
137
|
+
topic: "pipeline.info",
|
|
138
|
+
timestamp: Date.now(),
|
|
139
|
+
priority: 5,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
level: "warn",
|
|
143
|
+
message: "High CPU usage detected",
|
|
144
|
+
topic: "pipeline.warn",
|
|
145
|
+
timestamp: Date.now(),
|
|
146
|
+
priority: 7,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
level: "error",
|
|
150
|
+
message: "Critical system failure!",
|
|
151
|
+
topic: "pipeline.error",
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
priority: 10,
|
|
154
|
+
},
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
console.log(`\n Backend Summary:`);
|
|
158
|
+
console.log(` • Console: ${consoleBackend.getLogs().length} logs`);
|
|
159
|
+
console.log(` • Loki: ${lokiBackend.getLogs().length} logs`);
|
|
160
|
+
console.log(` • Prometheus: ${prometheusBackend.getLogs().length} logs`);
|
|
161
|
+
console.log(` • PagerDuty: ${pagerDutyBackend.getLogs().length} alerts\n`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Example 3: Priority with Metrics Tracking
|
|
165
|
+
console.log("3. Priority Tiers with Metrics Tracking\n");
|
|
166
|
+
{
|
|
167
|
+
const router = createTopicRouter()
|
|
168
|
+
.custom(/^pipeline\./, createConsoleHandler(), {
|
|
169
|
+
minPriority: 1,
|
|
170
|
+
maxPriority: 5,
|
|
171
|
+
trackMetrics: true,
|
|
172
|
+
})
|
|
173
|
+
.custom(/^pipeline\./, createConsoleHandler(), {
|
|
174
|
+
minPriority: 6,
|
|
175
|
+
maxPriority: 10,
|
|
176
|
+
trackMetrics: true,
|
|
177
|
+
})
|
|
178
|
+
.build();
|
|
179
|
+
|
|
180
|
+
const pipeline = createDspPipeline();
|
|
181
|
+
pipeline
|
|
182
|
+
.pipeline({
|
|
183
|
+
onLogBatch: (logs) => router.routeBatch(logs),
|
|
184
|
+
})
|
|
185
|
+
.MovingAverage({ mode: "moving", windowSize: 10 })
|
|
186
|
+
.Rectify()
|
|
187
|
+
.Rms({ mode: "moving", windowSize: 5 });
|
|
188
|
+
|
|
189
|
+
const samples = new Float32Array(100).map(() => Math.random() * 10 - 5);
|
|
190
|
+
await pipeline.process(samples, { sampleRate: 48000 });
|
|
191
|
+
|
|
192
|
+
console.log(" Metrics by Priority Tier:\n");
|
|
193
|
+
const metrics = router.getMetrics();
|
|
194
|
+
|
|
195
|
+
console.log(" ┌──────────────┬──────────┬─────────────┐");
|
|
196
|
+
console.log(" │ Priority │ Exec │ Avg (ms) │");
|
|
197
|
+
console.log(" ├──────────────┼──────────┼─────────────┤");
|
|
198
|
+
|
|
199
|
+
metrics.forEach((m, idx) => {
|
|
200
|
+
const tier = idx === 0 ? "Low (1-5)" : "High (6-10)";
|
|
201
|
+
const exec = String(m.executionCount).padEnd(8);
|
|
202
|
+
const avg = m.averageDuration.toFixed(2).padEnd(11);
|
|
203
|
+
console.log(` │ ${tier.padEnd(12)} │ ${exec} │ ${avg} │`);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
console.log(" └──────────────┴──────────┴─────────────┘\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Example 4: Dynamic Priority Assignment
|
|
210
|
+
console.log("4. Dynamic Priority Based on Context\n");
|
|
211
|
+
{
|
|
212
|
+
const mockHandler = createMockHandler((log) => {
|
|
213
|
+
const priority = log.priority ?? 1;
|
|
214
|
+
const emoji =
|
|
215
|
+
priority >= 9 ? "🚨" : priority >= 7 ? "⚠️" : priority >= 4 ? "ℹ️" : "🔍";
|
|
216
|
+
console.log(` ${emoji} [P${priority}] ${log.message}`);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const router = createTopicRouter()
|
|
220
|
+
.custom(/^pipeline\./, mockHandler.handler)
|
|
221
|
+
.build();
|
|
222
|
+
|
|
223
|
+
// Custom logs with varying priorities
|
|
224
|
+
const customLogs: LogEntry[] = [
|
|
225
|
+
{
|
|
226
|
+
level: "debug",
|
|
227
|
+
message: "Trace-level debugging",
|
|
228
|
+
topic: "pipeline.debug",
|
|
229
|
+
timestamp: Date.now(),
|
|
230
|
+
priority: 1,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
level: "info",
|
|
234
|
+
message: "User action completed",
|
|
235
|
+
topic: "pipeline.info",
|
|
236
|
+
timestamp: Date.now(),
|
|
237
|
+
priority: 5,
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
level: "warn",
|
|
241
|
+
message: "Memory usage at 80%",
|
|
242
|
+
topic: "pipeline.warn",
|
|
243
|
+
timestamp: Date.now(),
|
|
244
|
+
priority: 7,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
level: "error",
|
|
248
|
+
message: "Database connection lost",
|
|
249
|
+
topic: "pipeline.error",
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
priority: 9,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
level: "error",
|
|
255
|
+
message: "SYSTEM CRITICAL: Out of memory",
|
|
256
|
+
topic: "pipeline.error",
|
|
257
|
+
timestamp: Date.now(),
|
|
258
|
+
priority: 10,
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
await router.routeBatch(customLogs);
|
|
263
|
+
console.log();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log("Priority System Summary:");
|
|
267
|
+
console.log(" - 10-level priority (1=lowest, 10=highest)");
|
|
268
|
+
console.log(" - Default priorities: debug=2, info=5, warn=7, error=9");
|
|
269
|
+
console.log(" - Route filtering with minPriority/maxPriority");
|
|
270
|
+
console.log(" - Multi-tier routing (low→console, high→alerts)");
|
|
271
|
+
console.log(" - Priority-aware metrics tracking");
|
|
272
|
+
|
|
273
|
+
console.log("\nUse Cases:");
|
|
274
|
+
console.log(" • Route critical errors (9-10) to PagerDuty");
|
|
275
|
+
console.log(" • Send warnings (7-8) to Prometheus + Loki");
|
|
276
|
+
console.log(" • Store info logs (4-6) in Loki only");
|
|
277
|
+
console.log(" • Keep debug logs (1-3) local/console");
|