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,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 6: Production Observability Example
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive monitoring patterns for production DSP pipelines:
|
|
5
|
+
* - Drift detection with alerting
|
|
6
|
+
* - Performance metrics tracking
|
|
7
|
+
* - Health checks
|
|
8
|
+
* - Graceful error handling
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
createDspPipeline,
|
|
13
|
+
DriftDetector,
|
|
14
|
+
detectGaps,
|
|
15
|
+
validateMonotonicity,
|
|
16
|
+
estimateSampleRate,
|
|
17
|
+
} from "../../index.js";
|
|
18
|
+
|
|
19
|
+
console.log("=== Phase 6: Production Observability ===\n");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Example 1: Production Monitoring Setup
|
|
23
|
+
*/
|
|
24
|
+
async function example1_ProductionMonitoring() {
|
|
25
|
+
console.log("\n--- Example 1: Production Monitoring Setup ---\n");
|
|
26
|
+
|
|
27
|
+
// Metrics collector
|
|
28
|
+
const metrics = {
|
|
29
|
+
samplesProcessed: 0,
|
|
30
|
+
driftEvents: 0,
|
|
31
|
+
gaps: 0,
|
|
32
|
+
nonMonotonic: 0,
|
|
33
|
+
processingTimeMs: [] as number[],
|
|
34
|
+
lastHealthCheck: Date.now(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Create pipeline with drift detection
|
|
38
|
+
const pipeline = createDspPipeline();
|
|
39
|
+
pipeline
|
|
40
|
+
.MovingAverage({ mode: "moving", windowDuration: 100 })
|
|
41
|
+
.Rms({ mode: "moving", windowDuration: 50 });
|
|
42
|
+
|
|
43
|
+
// Simulate sensor data with issues
|
|
44
|
+
const generateBatch = (startIdx: number, count: number) => {
|
|
45
|
+
const samples = new Float32Array(count);
|
|
46
|
+
const timestamps = new Float32Array(count);
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < count; i++) {
|
|
49
|
+
samples[i] = Math.sin((startIdx + i) * 0.1) * 100 + Math.random() * 10;
|
|
50
|
+
|
|
51
|
+
// Introduce timing issues
|
|
52
|
+
if (i === 10) {
|
|
53
|
+
// Gap: skip 50ms
|
|
54
|
+
timestamps[i] = Date.now() + (startIdx + i) * 10 + 50;
|
|
55
|
+
} else if (i === 20) {
|
|
56
|
+
// Backwards timestamp
|
|
57
|
+
timestamps[i] = Date.now() + (startIdx + i) * 10 - 20;
|
|
58
|
+
} else {
|
|
59
|
+
timestamps[i] = Date.now() + (startIdx + i) * 10;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { samples, timestamps };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Process 5 batches
|
|
67
|
+
console.log("Processing batches with monitoring:\n");
|
|
68
|
+
|
|
69
|
+
for (let batchIdx = 0; batchIdx < 5; batchIdx++) {
|
|
70
|
+
const { samples, timestamps } = generateBatch(batchIdx * 50, 50);
|
|
71
|
+
|
|
72
|
+
const startTime = performance.now();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Process with drift detection
|
|
76
|
+
const processed = await pipeline.process(samples, timestamps, {
|
|
77
|
+
channels: 1,
|
|
78
|
+
sampleRate: 100,
|
|
79
|
+
enableDriftDetection: true,
|
|
80
|
+
driftThreshold: 5.0, // 5% threshold
|
|
81
|
+
onDriftDetected: (stats) => {
|
|
82
|
+
metrics.driftEvents++;
|
|
83
|
+
console.log(
|
|
84
|
+
` ā ļø Drift detected: ${stats.relativeDrift.toFixed(
|
|
85
|
+
2
|
|
86
|
+
)}% (expected: ${
|
|
87
|
+
stats.expectedMs
|
|
88
|
+
}ms, actual: ${stats.deltaMs.toFixed(2)}ms)`
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const endTime = performance.now();
|
|
94
|
+
const processingTime = endTime - startTime;
|
|
95
|
+
metrics.processingTimeMs.push(processingTime);
|
|
96
|
+
|
|
97
|
+
// Validate data quality
|
|
98
|
+
const gaps = detectGaps(timestamps, 100); // 100 Hz expected
|
|
99
|
+
const violations = validateMonotonicity(timestamps);
|
|
100
|
+
|
|
101
|
+
if (gaps.length > 0) {
|
|
102
|
+
metrics.gaps += gaps.length;
|
|
103
|
+
console.log(` ā ļø Batch ${batchIdx}: ${gaps.length} gaps detected`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (violations.length > 0) {
|
|
107
|
+
metrics.nonMonotonic++;
|
|
108
|
+
const first = violations[0];
|
|
109
|
+
console.log(
|
|
110
|
+
` ā ļø Batch ${batchIdx}: Non-monotonic timestamps at index ${first.index} (${first.violation})`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
metrics.samplesProcessed += samples.length;
|
|
115
|
+
|
|
116
|
+
console.log(
|
|
117
|
+
` ā
Batch ${batchIdx}: ${
|
|
118
|
+
samples.length
|
|
119
|
+
} samples processed in ${processingTime.toFixed(2)}ms`
|
|
120
|
+
);
|
|
121
|
+
} catch (error: any) {
|
|
122
|
+
console.error(` ā Batch ${batchIdx} failed: ${error.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Health check summary
|
|
127
|
+
console.log("\nš Health Check Summary:");
|
|
128
|
+
console.log(` Total samples processed: ${metrics.samplesProcessed}`);
|
|
129
|
+
console.log(` Drift events: ${metrics.driftEvents}`);
|
|
130
|
+
console.log(` Gaps detected: ${metrics.gaps}`);
|
|
131
|
+
console.log(` Non-monotonic batches: ${metrics.nonMonotonic}`);
|
|
132
|
+
|
|
133
|
+
const avgProcessingTime =
|
|
134
|
+
metrics.processingTimeMs.reduce((a, b) => a + b, 0) /
|
|
135
|
+
metrics.processingTimeMs.length;
|
|
136
|
+
const maxProcessingTime = Math.max(...metrics.processingTimeMs);
|
|
137
|
+
|
|
138
|
+
console.log(` Avg processing time: ${avgProcessingTime.toFixed(2)}ms`);
|
|
139
|
+
console.log(` Max processing time: ${maxProcessingTime.toFixed(2)}ms`);
|
|
140
|
+
|
|
141
|
+
console.log("\nā
Example 1 complete\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Example 2: Alerting Thresholds
|
|
146
|
+
*/
|
|
147
|
+
async function example2_AlertingThresholds() {
|
|
148
|
+
console.log("\n--- Example 2: Alerting Thresholds ---\n");
|
|
149
|
+
|
|
150
|
+
// Alert configuration
|
|
151
|
+
const alertConfig = {
|
|
152
|
+
maxDriftPercent: 5.0,
|
|
153
|
+
maxGapsPerBatch: 2,
|
|
154
|
+
maxProcessingTimeMs: 50,
|
|
155
|
+
alertCallback: (level: "warning" | "critical", message: string) => {
|
|
156
|
+
const prefix = level === "critical" ? "š“" : "š”";
|
|
157
|
+
console.log(`${prefix} [${level.toUpperCase()}] ${message}`);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const pipeline = createDspPipeline();
|
|
162
|
+
pipeline.MovingAverage({ mode: "moving", windowDuration: 100 });
|
|
163
|
+
|
|
164
|
+
// Simulate problematic data
|
|
165
|
+
const samples = new Float32Array(100);
|
|
166
|
+
const timestamps = new Float32Array(100);
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < 100; i++) {
|
|
169
|
+
samples[i] = Math.sin(i * 0.1) * 100;
|
|
170
|
+
|
|
171
|
+
// Introduce multiple issues
|
|
172
|
+
if (i < 50) {
|
|
173
|
+
timestamps[i] = Date.now() + i * 10; // Normal
|
|
174
|
+
} else {
|
|
175
|
+
// Large drift in second half
|
|
176
|
+
timestamps[i] = Date.now() + i * 15; // 150 Hz instead of 100 Hz
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log("Processing data with alerting thresholds:\n");
|
|
181
|
+
|
|
182
|
+
const startTime = performance.now();
|
|
183
|
+
|
|
184
|
+
await pipeline.process(samples, timestamps, {
|
|
185
|
+
channels: 1,
|
|
186
|
+
sampleRate: 100,
|
|
187
|
+
enableDriftDetection: true,
|
|
188
|
+
driftThreshold: alertConfig.maxDriftPercent,
|
|
189
|
+
onDriftDetected: (stats) => {
|
|
190
|
+
if (Math.abs(stats.relativeDrift) > alertConfig.maxDriftPercent * 2) {
|
|
191
|
+
alertConfig.alertCallback(
|
|
192
|
+
"critical",
|
|
193
|
+
`Severe drift: ${stats.relativeDrift.toFixed(2)}%`
|
|
194
|
+
);
|
|
195
|
+
} else {
|
|
196
|
+
alertConfig.alertCallback(
|
|
197
|
+
"warning",
|
|
198
|
+
`Drift: ${stats.relativeDrift.toFixed(2)}%`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const endTime = performance.now();
|
|
205
|
+
const processingTime = endTime - startTime;
|
|
206
|
+
|
|
207
|
+
// Check processing time
|
|
208
|
+
if (processingTime > alertConfig.maxProcessingTimeMs) {
|
|
209
|
+
alertConfig.alertCallback(
|
|
210
|
+
"warning",
|
|
211
|
+
`Processing time (${processingTime.toFixed(2)}ms) exceeded threshold (${
|
|
212
|
+
alertConfig.maxProcessingTimeMs
|
|
213
|
+
}ms)`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check for gaps
|
|
218
|
+
const gaps = detectGaps(timestamps, 100);
|
|
219
|
+
if (gaps.length > alertConfig.maxGapsPerBatch) {
|
|
220
|
+
alertConfig.alertCallback("critical", `Too many gaps: ${gaps.length}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log("\nā
Example 2 complete\n");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Example 3: Sample Rate Validation
|
|
228
|
+
*/
|
|
229
|
+
async function example3_SampleRateValidation() {
|
|
230
|
+
console.log("\n--- Example 3: Sample Rate Validation ---\n");
|
|
231
|
+
|
|
232
|
+
const expectedRate = 100; // Hz
|
|
233
|
+
const tolerance = 0.05; // 5%
|
|
234
|
+
|
|
235
|
+
// Generate data with varying sample rates
|
|
236
|
+
const testCases = [
|
|
237
|
+
{ name: "Correct rate (100 Hz)", rate: 100 },
|
|
238
|
+
{ name: "Slightly fast (103 Hz)", rate: 103 },
|
|
239
|
+
{ name: "Slightly slow (97 Hz)", rate: 97 },
|
|
240
|
+
{ name: "Too fast (120 Hz)", rate: 120 },
|
|
241
|
+
{ name: "Too slow (80 Hz)", rate: 80 },
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
for (const testCase of testCases) {
|
|
245
|
+
const count = 100;
|
|
246
|
+
const timestamps = new Float32Array(count);
|
|
247
|
+
const intervalMs = 1000 / testCase.rate;
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < count; i++) {
|
|
250
|
+
timestamps[i] = Date.now() + i * intervalMs;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const rateInfo = estimateSampleRate(timestamps);
|
|
254
|
+
const deviation =
|
|
255
|
+
Math.abs(rateInfo.estimatedRate - expectedRate) / expectedRate;
|
|
256
|
+
|
|
257
|
+
if (deviation <= tolerance) {
|
|
258
|
+
console.log(
|
|
259
|
+
`ā
${testCase.name}: ${rateInfo.estimatedRate.toFixed(
|
|
260
|
+
2
|
|
261
|
+
)} Hz (within tolerance)`
|
|
262
|
+
);
|
|
263
|
+
} else {
|
|
264
|
+
console.log(
|
|
265
|
+
`ā ļø ${testCase.name}: ${rateInfo.estimatedRate.toFixed(
|
|
266
|
+
2
|
|
267
|
+
)} Hz (exceeds ${(tolerance * 100).toFixed(0)}% tolerance)`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(` Regularity: ${rateInfo.regularity}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log("\nā
Example 3 complete\n");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Example 4: Production Health Check Endpoint
|
|
279
|
+
*/
|
|
280
|
+
class DspHealthMonitor {
|
|
281
|
+
private metrics = {
|
|
282
|
+
totalSamples: 0,
|
|
283
|
+
totalBatches: 0,
|
|
284
|
+
failedBatches: 0,
|
|
285
|
+
driftEvents: 0,
|
|
286
|
+
gaps: 0,
|
|
287
|
+
processingTimes: [] as number[],
|
|
288
|
+
lastProcessedAt: 0,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
private readonly maxMetricHistory = 100;
|
|
292
|
+
|
|
293
|
+
recordBatchSuccess(sampleCount: number, processingTimeMs: number): void {
|
|
294
|
+
this.metrics.totalSamples += sampleCount;
|
|
295
|
+
this.metrics.totalBatches++;
|
|
296
|
+
this.metrics.processingTimes.push(processingTimeMs);
|
|
297
|
+
this.metrics.lastProcessedAt = Date.now();
|
|
298
|
+
|
|
299
|
+
// Keep history bounded
|
|
300
|
+
if (this.metrics.processingTimes.length > this.maxMetricHistory) {
|
|
301
|
+
this.metrics.processingTimes.shift();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
recordBatchFailure(): void {
|
|
306
|
+
this.metrics.failedBatches++;
|
|
307
|
+
this.metrics.totalBatches++;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
recordDriftEvent(): void {
|
|
311
|
+
this.metrics.driftEvents++;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
recordGap(): void {
|
|
315
|
+
this.metrics.gaps++;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
getHealthStatus(): {
|
|
319
|
+
status: "healthy" | "degraded" | "unhealthy";
|
|
320
|
+
metrics: any;
|
|
321
|
+
issues: string[];
|
|
322
|
+
} {
|
|
323
|
+
const issues: string[] = [];
|
|
324
|
+
let status: "healthy" | "degraded" | "unhealthy" = "healthy";
|
|
325
|
+
|
|
326
|
+
// Check failure rate
|
|
327
|
+
const failureRate =
|
|
328
|
+
this.metrics.failedBatches / Math.max(1, this.metrics.totalBatches);
|
|
329
|
+
if (failureRate > 0.1) {
|
|
330
|
+
issues.push(`High failure rate: ${(failureRate * 100).toFixed(1)}%`);
|
|
331
|
+
status = "unhealthy";
|
|
332
|
+
} else if (failureRate > 0.05) {
|
|
333
|
+
issues.push(`Elevated failure rate: ${(failureRate * 100).toFixed(1)}%`);
|
|
334
|
+
status = status === "healthy" ? "degraded" : status;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Check drift events
|
|
338
|
+
const driftRate =
|
|
339
|
+
this.metrics.driftEvents / Math.max(1, this.metrics.totalBatches);
|
|
340
|
+
if (driftRate > 0.2) {
|
|
341
|
+
issues.push(`High drift rate: ${(driftRate * 100).toFixed(1)}%`);
|
|
342
|
+
status = status === "healthy" ? "degraded" : status;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Check processing time
|
|
346
|
+
if (this.metrics.processingTimes.length > 0) {
|
|
347
|
+
const avgTime =
|
|
348
|
+
this.metrics.processingTimes.reduce((a, b) => a + b, 0) /
|
|
349
|
+
this.metrics.processingTimes.length;
|
|
350
|
+
|
|
351
|
+
if (avgTime > 100) {
|
|
352
|
+
issues.push(`Slow processing: ${avgTime.toFixed(2)}ms avg`);
|
|
353
|
+
status = status === "healthy" ? "degraded" : status;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Check last processed timestamp
|
|
358
|
+
const timeSinceLastProcess = Date.now() - this.metrics.lastProcessedAt;
|
|
359
|
+
if (this.metrics.lastProcessedAt > 0 && timeSinceLastProcess > 60000) {
|
|
360
|
+
issues.push("No data processed in last 60 seconds");
|
|
361
|
+
status = "unhealthy";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
status,
|
|
366
|
+
metrics: {
|
|
367
|
+
...this.metrics,
|
|
368
|
+
avgProcessingTimeMs:
|
|
369
|
+
this.metrics.processingTimes.length > 0
|
|
370
|
+
? this.metrics.processingTimes.reduce((a, b) => a + b, 0) /
|
|
371
|
+
this.metrics.processingTimes.length
|
|
372
|
+
: 0,
|
|
373
|
+
},
|
|
374
|
+
issues,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function example4_HealthCheckEndpoint() {
|
|
380
|
+
console.log("\n--- Example 4: Production Health Check ---\n");
|
|
381
|
+
|
|
382
|
+
const monitor = new DspHealthMonitor();
|
|
383
|
+
const pipeline = createDspPipeline();
|
|
384
|
+
pipeline.MovingAverage({ mode: "moving", windowDuration: 100 });
|
|
385
|
+
|
|
386
|
+
// Simulate processing batches
|
|
387
|
+
console.log("Simulating production workload:\n");
|
|
388
|
+
|
|
389
|
+
for (let i = 0; i < 10; i++) {
|
|
390
|
+
const samples = new Float32Array(50);
|
|
391
|
+
const timestamps = new Float32Array(50);
|
|
392
|
+
|
|
393
|
+
for (let j = 0; j < 50; j++) {
|
|
394
|
+
samples[j] = Math.sin((i * 50 + j) * 0.1) * 100;
|
|
395
|
+
timestamps[j] = Date.now() + (i * 50 + j) * 10;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const startTime = performance.now();
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
await pipeline.process(samples, timestamps, {
|
|
402
|
+
channels: 1,
|
|
403
|
+
sampleRate: 100,
|
|
404
|
+
enableDriftDetection: true,
|
|
405
|
+
driftThreshold: 5.0,
|
|
406
|
+
onDriftDetected: () => {
|
|
407
|
+
monitor.recordDriftEvent();
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const endTime = performance.now();
|
|
412
|
+
monitor.recordBatchSuccess(samples.length, endTime - startTime);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
monitor.recordBatchFailure();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Simulate occasional issues
|
|
418
|
+
if (i === 5) {
|
|
419
|
+
monitor.recordGap();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Get health status
|
|
424
|
+
const health = monitor.getHealthStatus();
|
|
425
|
+
|
|
426
|
+
console.log("š Health Check Result:");
|
|
427
|
+
console.log(` Status: ${health.status.toUpperCase()}`);
|
|
428
|
+
console.log(` Total samples: ${health.metrics.totalSamples}`);
|
|
429
|
+
console.log(` Total batches: ${health.metrics.totalBatches}`);
|
|
430
|
+
console.log(` Failed batches: ${health.metrics.failedBatches}`);
|
|
431
|
+
console.log(` Drift events: ${health.metrics.driftEvents}`);
|
|
432
|
+
console.log(` Gaps: ${health.metrics.gaps}`);
|
|
433
|
+
console.log(
|
|
434
|
+
` Avg processing time: ${health.metrics.avgProcessingTimeMs.toFixed(2)}ms`
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
if (health.issues.length > 0) {
|
|
438
|
+
console.log("\n Issues:");
|
|
439
|
+
health.issues.forEach((issue) => {
|
|
440
|
+
console.log(` ā ļø ${issue}`);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
console.log("\nš” Expose this as HTTP endpoint:");
|
|
445
|
+
console.log(" GET /health ā { status, metrics, issues }");
|
|
446
|
+
console.log("\nā
Example 4 complete\n");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Run all examples
|
|
450
|
+
async function main() {
|
|
451
|
+
console.log("š Production observability patterns for DSP pipelines\n");
|
|
452
|
+
|
|
453
|
+
await example1_ProductionMonitoring();
|
|
454
|
+
await example2_AlertingThresholds();
|
|
455
|
+
await example3_SampleRateValidation();
|
|
456
|
+
await example4_HealthCheckEndpoint();
|
|
457
|
+
|
|
458
|
+
console.log("=== Phase 6 Complete ===\n");
|
|
459
|
+
console.log("ā
Production observability includes:");
|
|
460
|
+
console.log(" ⢠Comprehensive metrics tracking");
|
|
461
|
+
console.log(" ⢠Alerting with configurable thresholds");
|
|
462
|
+
console.log(" ⢠Sample rate validation");
|
|
463
|
+
console.log(" ⢠Health check endpoints");
|
|
464
|
+
console.log(" ⢠Graceful error handling\n");
|
|
465
|
+
|
|
466
|
+
console.log("š” Integration ideas:");
|
|
467
|
+
console.log(" ⢠Export metrics to Prometheus");
|
|
468
|
+
console.log(" ⢠Send alerts to PagerDuty/Slack");
|
|
469
|
+
console.log(" ⢠Create Grafana dashboards");
|
|
470
|
+
console.log(" ⢠Log to structured logging system\n");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
main().catch((error) => {
|
|
474
|
+
console.error("Error:", error.message);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
});
|