@unrdf/kgc-probe 26.4.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,715 @@
1
+ /**
2
+ * @file WASM Surface Probe - WebAssembly Capabilities Detection & Benchmarking
3
+ * @module @unrdf/kgc-probe/probes/wasm
4
+ *
5
+ * @description
6
+ * Probes WebAssembly runtime capabilities and performance characteristics:
7
+ * - WASM instantiation support (WebAssembly.instantiate)
8
+ * - Memory growth limits (WebAssembly.Memory)
9
+ * - Initial/maximum memory pages
10
+ * - Compile time for minimal module
11
+ * - Instantiate time for minimal module
12
+ * - Call overhead (JS -> WASM -> JS)
13
+ * - Table/global support
14
+ * - SIMD support detection
15
+ * - Threads support detection
16
+ *
17
+ * All operations are guarded with timeouts and resource limits to prevent hangs.
18
+ *
19
+ * @example
20
+ * import { probeWasm } from '@unrdf/kgc-probe/probes/wasm';
21
+ *
22
+ * const observations = await probeWasm({
23
+ * samples: 100,
24
+ * timeout: 5000,
25
+ * maxMemoryMB: 1024
26
+ * });
27
+ *
28
+ * console.log(observations.find(o => o.metric === 'wasm.compile.time'));
29
+ */
30
+
31
+ import { z } from 'zod';
32
+
33
+ // =============================================================================
34
+ // Observation Schema - Probe Output Type
35
+ // =============================================================================
36
+
37
+ /**
38
+ * Observation schema for probe results
39
+ *
40
+ * Each observation represents a single metric measurement:
41
+ * - metric: Dot-notation metric name (e.g., 'wasm.compile.time')
42
+ * - value: Numeric measurement value
43
+ * - unit: Measurement unit (ms, bytes, boolean, count, etc.)
44
+ * - timestamp: When measurement was taken (Unix epoch ms)
45
+ * - status: Measurement status (success, error, unsupported, timeout)
46
+ * - error: Error message if status is 'error'
47
+ * - metadata: Additional context (samples, iterations, etc.)
48
+ *
49
+ * @constant
50
+ * @type {z.ZodObject}
51
+ */
52
+ export const ObservationSchema = z.object({
53
+ /** Metric name in dot notation */
54
+ metric: z.string().min(1).max(200),
55
+ /** Measured value */
56
+ value: z.union([z.number(), z.boolean(), z.string()]),
57
+ /** Measurement unit */
58
+ unit: z.enum(['ms', 'ns', 'bytes', 'pages', 'boolean', 'count', 'percent', 'ratio']),
59
+ /** Measurement timestamp (Unix epoch ms) */
60
+ timestamp: z.number().int().positive(),
61
+ /** Measurement status */
62
+ status: z.enum(['success', 'error', 'unsupported', 'timeout']),
63
+ /** Error message if failed */
64
+ error: z.string().optional(),
65
+ /** Additional metadata */
66
+ metadata: z.record(z.string(), z.any()).optional(),
67
+ });
68
+
69
+ /**
70
+ * WASM probe configuration schema
71
+ *
72
+ * @constant
73
+ * @type {z.ZodObject}
74
+ */
75
+ export const WasmProbeConfigSchema = z.object({
76
+ /** Number of samples for benchmarking (default: 100) */
77
+ samples: z.number().int().positive().max(10000).default(100),
78
+ /** Timeout per operation in milliseconds (default: 5000) */
79
+ timeout: z.number().int().positive().max(30000).default(5000),
80
+ /** Maximum memory allocation in MB (default: 1024) */
81
+ maxMemoryMB: z.number().int().positive().max(4096).default(1024),
82
+ /** Enable SIMD detection (default: true) */
83
+ detectSIMD: z.boolean().default(true),
84
+ /** Enable threads detection (default: true) */
85
+ detectThreads: z.boolean().default(true),
86
+ });
87
+
88
+ // =============================================================================
89
+ // Minimal WASM Test Module
90
+ // =============================================================================
91
+
92
+ /**
93
+ * Minimal WASM module (WAT format):
94
+ * ```wat
95
+ * (module
96
+ * (func (export "add") (param i32 i32) (result i32)
97
+ * local.get 0
98
+ * local.get 1
99
+ * i32.add
100
+ * )
101
+ * )
102
+ * ```
103
+ *
104
+ * This is the smallest possible WASM module for testing basic functionality.
105
+ * Binary format (WebAssembly binary):
106
+ */
107
+ const MINIMAL_WASM_BYTES = new Uint8Array([
108
+ 0x00, 0x61, 0x73, 0x6d, // Magic number: \0asm
109
+ 0x01, 0x00, 0x00, 0x00, // Version: 1
110
+ 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, // Type section: (i32, i32) -> i32
111
+ 0x03, 0x02, 0x01, 0x00, // Function section: func 0 has type 0
112
+ 0x07, 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, // Export section: "add" = func 0
113
+ 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b, // Code section: add implementation
114
+ ]);
115
+
116
+ /**
117
+ * WASM module with memory export for memory testing:
118
+ * ```wat
119
+ * (module
120
+ * (memory (export "memory") 1 10)
121
+ * )
122
+ * ```
123
+ */
124
+ const WASM_MEMORY_MODULE = new Uint8Array([
125
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // Magic + version
126
+ 0x05, 0x04, 0x01, 0x01, 0x01, 0x0a, // Memory section: initial=1, max=10
127
+ 0x07, 0x0a, 0x01, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, // Export "memory"
128
+ ]);
129
+
130
+ // =============================================================================
131
+ // Guards - Poka Yoke Error Prevention
132
+ // =============================================================================
133
+
134
+ /**
135
+ * Guard W1: Validate WASM support exists
136
+ * Prevents: Runtime error from missing WebAssembly global
137
+ * Action: Check typeof WebAssembly !== 'undefined'
138
+ */
139
+ function guardWasmSupport() {
140
+ if (typeof WebAssembly === 'undefined') {
141
+ throw new Error('Guard W1 failed: WebAssembly not supported in this environment');
142
+ }
143
+ return true;
144
+ }
145
+
146
+ /**
147
+ * Guard W2: Validate timeout value
148
+ * Prevents: Infinite hang from missing timeout
149
+ * Action: Ensure timeout is positive integer
150
+ */
151
+ function guardTimeout(timeout) {
152
+ if (typeof timeout !== 'number' || timeout <= 0 || !Number.isFinite(timeout)) {
153
+ throw new TypeError(`Guard W2 failed: Timeout must be positive number, got ${timeout}`);
154
+ }
155
+ return true;
156
+ }
157
+
158
+ /**
159
+ * Guard W3: Validate memory limit
160
+ * Prevents: OOM from unbounded memory growth test
161
+ * Action: Cap at 1GB max
162
+ */
163
+ function guardMemoryLimit(maxMemoryMB) {
164
+ if (typeof maxMemoryMB !== 'number' || maxMemoryMB <= 0 || maxMemoryMB > 4096) {
165
+ throw new RangeError(`Guard W3 failed: maxMemoryMB must be 1-4096, got ${maxMemoryMB}`);
166
+ }
167
+ return true;
168
+ }
169
+
170
+ /**
171
+ * Guard W4: Validate WASM module bytes
172
+ * Prevents: Invalid module bytes causing instantiation failure
173
+ * Action: Check magic number \0asm
174
+ */
175
+ function guardWasmBytes(bytes) {
176
+ if (!(bytes instanceof Uint8Array)) {
177
+ throw new TypeError(`Guard W4 failed: WASM bytes must be Uint8Array, got ${typeof bytes}`);
178
+ }
179
+ if (bytes.length < 8) {
180
+ throw new Error(`Guard W4 failed: WASM module too small (${bytes.length} bytes)`);
181
+ }
182
+ // Check magic number: \0asm
183
+ if (bytes[0] !== 0x00 || bytes[1] !== 0x61 || bytes[2] !== 0x73 || bytes[3] !== 0x6d) {
184
+ throw new Error(`Guard W4 failed: Invalid WASM magic number`);
185
+ }
186
+ return true;
187
+ }
188
+
189
+ // =============================================================================
190
+ // Helper Functions
191
+ // =============================================================================
192
+
193
+ /**
194
+ * Create an observation object
195
+ * @param {string} metric - Metric name
196
+ * @param {number|boolean|string} value - Metric value
197
+ * @param {string} unit - Measurement unit
198
+ * @param {string} status - Measurement status
199
+ * @param {string} [error] - Error message if failed
200
+ * @param {Object} [metadata] - Additional metadata
201
+ * @returns {Object} Validated observation
202
+ */
203
+ function createObservation(metric, value, unit, status, error = undefined, metadata = {}) {
204
+ const observation = {
205
+ metric,
206
+ value,
207
+ unit,
208
+ timestamp: Date.now(),
209
+ status,
210
+ error,
211
+ metadata,
212
+ };
213
+ return ObservationSchema.parse(observation);
214
+ }
215
+
216
+ /**
217
+ * Execute with timeout
218
+ * @param {Function} fn - Async function to execute
219
+ * @param {number} timeoutMs - Timeout in milliseconds
220
+ * @returns {Promise<any>} Result or throws timeout error
221
+ */
222
+ async function withTimeout(fn, timeoutMs) {
223
+ guardTimeout(timeoutMs);
224
+
225
+ return Promise.race([
226
+ fn(),
227
+ new Promise((_, reject) =>
228
+ setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs)
229
+ ),
230
+ ]);
231
+ }
232
+
233
+ /**
234
+ * Measure execution time of function
235
+ * @param {Function} fn - Function to measure
236
+ * @returns {Promise<number>} Duration in milliseconds
237
+ */
238
+ async function measureTime(fn) {
239
+ const start = performance.now();
240
+ await fn();
241
+ const end = performance.now();
242
+ return end - start;
243
+ }
244
+
245
+ /**
246
+ * Calculate statistics from samples
247
+ * @param {number[]} samples - Array of measurements
248
+ * @returns {Object} { mean, median, min, max, stddev }
249
+ */
250
+ function calculateStats(samples) {
251
+ if (samples.length === 0) {
252
+ throw new Error('Cannot calculate stats from empty array');
253
+ }
254
+
255
+ const sorted = [...samples].sort((a, b) => a - b);
256
+ const mean = samples.reduce((sum, x) => sum + x, 0) / samples.length;
257
+ const median = sorted[Math.floor(sorted.length / 2)];
258
+ const min = sorted[0];
259
+ const max = sorted[sorted.length - 1];
260
+
261
+ // Standard deviation
262
+ const variance = samples.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / samples.length;
263
+ const stddev = Math.sqrt(variance);
264
+
265
+ return { mean, median, min, max, stddev };
266
+ }
267
+
268
+ // =============================================================================
269
+ // WASM Capability Probes
270
+ // =============================================================================
271
+
272
+ /**
273
+ * Probe WASM instantiation support
274
+ * @param {number} timeout - Timeout in milliseconds
275
+ * @returns {Promise<Object>} Observation
276
+ */
277
+ async function probeInstantiationSupport(timeout) {
278
+ try {
279
+ guardWasmSupport();
280
+ guardWasmBytes(MINIMAL_WASM_BYTES);
281
+
282
+ await withTimeout(async () => {
283
+ await WebAssembly.instantiate(MINIMAL_WASM_BYTES);
284
+ }, timeout);
285
+
286
+ return createObservation('wasm.support.instantiate', true, 'boolean', 'success');
287
+ } catch (error) {
288
+ return createObservation('wasm.support.instantiate', false, 'boolean', 'error', error.message);
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Probe WASM compile support
294
+ * @param {number} timeout - Timeout in milliseconds
295
+ * @returns {Promise<Object>} Observation
296
+ */
297
+ async function probeCompileSupport(timeout) {
298
+ try {
299
+ guardWasmSupport();
300
+ guardWasmBytes(MINIMAL_WASM_BYTES);
301
+
302
+ await withTimeout(async () => {
303
+ await WebAssembly.compile(MINIMAL_WASM_BYTES);
304
+ }, timeout);
305
+
306
+ return createObservation('wasm.support.compile', true, 'boolean', 'success');
307
+ } catch (error) {
308
+ return createObservation('wasm.support.compile', false, 'boolean', 'error', error.message);
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Probe WASM memory support
314
+ * @param {number} timeout - Timeout in milliseconds
315
+ * @returns {Promise<Object[]>} Observations
316
+ */
317
+ async function probeMemorySupport(timeout) {
318
+ const observations = [];
319
+
320
+ try {
321
+ guardWasmSupport();
322
+
323
+ // Test basic memory creation
324
+ await withTimeout(async () => {
325
+ new WebAssembly.Memory({ initial: 1 });
326
+ }, timeout);
327
+
328
+ observations.push(createObservation('wasm.support.memory', true, 'boolean', 'success'));
329
+ } catch (error) {
330
+ observations.push(createObservation('wasm.support.memory', false, 'boolean', 'error', error.message));
331
+ return observations;
332
+ }
333
+
334
+ // Test memory growth
335
+ try {
336
+ await withTimeout(async () => {
337
+ const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });
338
+ memory.grow(1);
339
+ }, timeout);
340
+
341
+ observations.push(createObservation('wasm.support.memory.grow', true, 'boolean', 'success'));
342
+ } catch (error) {
343
+ observations.push(createObservation('wasm.support.memory.grow', false, 'boolean', 'error', error.message));
344
+ }
345
+
346
+ return observations;
347
+ }
348
+
349
+ /**
350
+ * Probe WASM table support
351
+ * @param {number} timeout - Timeout in milliseconds
352
+ * @returns {Promise<Object>} Observation
353
+ */
354
+ async function probeTableSupport(timeout) {
355
+ try {
356
+ guardWasmSupport();
357
+
358
+ await withTimeout(async () => {
359
+ new WebAssembly.Table({ initial: 1, element: 'anyfunc' });
360
+ }, timeout);
361
+
362
+ return createObservation('wasm.support.table', true, 'boolean', 'success');
363
+ } catch (error) {
364
+ return createObservation('wasm.support.table', false, 'boolean', 'error', error.message);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Probe WASM global support
370
+ * @param {number} timeout - Timeout in milliseconds
371
+ * @returns {Promise<Object>} Observation
372
+ */
373
+ async function probeGlobalSupport(timeout) {
374
+ try {
375
+ guardWasmSupport();
376
+
377
+ await withTimeout(async () => {
378
+ new WebAssembly.Global({ value: 'i32', mutable: true }, 42);
379
+ }, timeout);
380
+
381
+ return createObservation('wasm.support.global', true, 'boolean', 'success');
382
+ } catch (error) {
383
+ return createObservation('wasm.support.global', false, 'boolean', 'error', error.message);
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Probe SIMD support
389
+ * @param {number} timeout - Timeout in milliseconds
390
+ * @returns {Promise<Object>} Observation
391
+ */
392
+ async function probeSIMDSupport(timeout) {
393
+ try {
394
+ guardWasmSupport();
395
+
396
+ // WASM module with SIMD v128 instruction (minimal test)
397
+ // This will fail if SIMD is not supported
398
+ const simdTestBytes = new Uint8Array([
399
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
400
+ ]);
401
+
402
+ // Check if SIMD is available via feature detection
403
+ const hasSIMD = typeof WebAssembly.validate === 'function' && WebAssembly.validate(simdTestBytes);
404
+
405
+ return createObservation('wasm.support.simd', hasSIMD, 'boolean', hasSIMD ? 'success' : 'unsupported');
406
+ } catch (error) {
407
+ return createObservation('wasm.support.simd', false, 'boolean', 'error', error.message);
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Probe threads support
413
+ * @param {number} timeout - Timeout in milliseconds
414
+ * @returns {Promise<Object>} Observation
415
+ */
416
+ async function probeThreadsSupport(timeout) {
417
+ try {
418
+ guardWasmSupport();
419
+
420
+ // Check for SharedArrayBuffer (required for threads)
421
+ const hasSharedArrayBuffer = typeof SharedArrayBuffer !== 'undefined';
422
+
423
+ // Check for Atomics (required for threads)
424
+ const hasAtomics = typeof Atomics !== 'undefined';
425
+
426
+ const hasThreads = hasSharedArrayBuffer && hasAtomics;
427
+
428
+ return createObservation(
429
+ 'wasm.support.threads',
430
+ hasThreads,
431
+ 'boolean',
432
+ hasThreads ? 'success' : 'unsupported',
433
+ undefined,
434
+ { hasSharedArrayBuffer, hasAtomics }
435
+ );
436
+ } catch (error) {
437
+ return createObservation('wasm.support.threads', false, 'boolean', 'error', error.message);
438
+ }
439
+ }
440
+
441
+ // =============================================================================
442
+ // WASM Performance Benchmarks
443
+ // =============================================================================
444
+
445
+ /**
446
+ * Benchmark WASM compile time
447
+ * @param {number} samples - Number of samples to collect
448
+ * @param {number} timeout - Timeout per operation
449
+ * @returns {Promise<Object[]>} Observations
450
+ */
451
+ async function benchmarkCompileTime(samples, timeout) {
452
+ const observations = [];
453
+ const times = [];
454
+
455
+ try {
456
+ guardWasmSupport();
457
+ guardWasmBytes(MINIMAL_WASM_BYTES);
458
+
459
+ for (let i = 0; i < samples; i++) {
460
+ const duration = await withTimeout(async () => {
461
+ return measureTime(async () => {
462
+ await WebAssembly.compile(MINIMAL_WASM_BYTES);
463
+ });
464
+ }, timeout);
465
+
466
+ times.push(duration);
467
+ }
468
+
469
+ const stats = calculateStats(times);
470
+
471
+ observations.push(createObservation('wasm.compile.time.mean', stats.mean, 'ms', 'success', undefined, { samples }));
472
+ observations.push(createObservation('wasm.compile.time.median', stats.median, 'ms', 'success', undefined, { samples }));
473
+ observations.push(createObservation('wasm.compile.time.min', stats.min, 'ms', 'success', undefined, { samples }));
474
+ observations.push(createObservation('wasm.compile.time.max', stats.max, 'ms', 'success', undefined, { samples }));
475
+ observations.push(createObservation('wasm.compile.time.stddev', stats.stddev, 'ms', 'success', undefined, { samples }));
476
+ } catch (error) {
477
+ observations.push(createObservation('wasm.compile.time.mean', 0, 'ms', 'error', error.message));
478
+ }
479
+
480
+ return observations;
481
+ }
482
+
483
+ /**
484
+ * Benchmark WASM instantiate time
485
+ * @param {number} samples - Number of samples to collect
486
+ * @param {number} timeout - Timeout per operation
487
+ * @returns {Promise<Object[]>} Observations
488
+ */
489
+ async function benchmarkInstantiateTime(samples, timeout) {
490
+ const observations = [];
491
+ const times = [];
492
+
493
+ try {
494
+ guardWasmSupport();
495
+ guardWasmBytes(MINIMAL_WASM_BYTES);
496
+
497
+ for (let i = 0; i < samples; i++) {
498
+ const duration = await withTimeout(async () => {
499
+ return measureTime(async () => {
500
+ await WebAssembly.instantiate(MINIMAL_WASM_BYTES);
501
+ });
502
+ }, timeout);
503
+
504
+ times.push(duration);
505
+ }
506
+
507
+ const stats = calculateStats(times);
508
+
509
+ observations.push(createObservation('wasm.instantiate.time.mean', stats.mean, 'ms', 'success', undefined, { samples }));
510
+ observations.push(createObservation('wasm.instantiate.time.median', stats.median, 'ms', 'success', undefined, { samples }));
511
+ observations.push(createObservation('wasm.instantiate.time.min', stats.min, 'ms', 'success', undefined, { samples }));
512
+ observations.push(createObservation('wasm.instantiate.time.max', stats.max, 'ms', 'success', undefined, { samples }));
513
+ observations.push(createObservation('wasm.instantiate.time.stddev', stats.stddev, 'ms', 'success', undefined, { samples }));
514
+ } catch (error) {
515
+ observations.push(createObservation('wasm.instantiate.time.mean', 0, 'ms', 'error', error.message));
516
+ }
517
+
518
+ return observations;
519
+ }
520
+
521
+ /**
522
+ * Benchmark WASM call overhead (JS -> WASM -> JS)
523
+ * @param {number} samples - Number of samples to collect
524
+ * @param {number} timeout - Timeout per operation
525
+ * @returns {Promise<Object[]>} Observations
526
+ */
527
+ async function benchmarkCallOverhead(samples, timeout) {
528
+ const observations = [];
529
+ const times = [];
530
+
531
+ try {
532
+ guardWasmSupport();
533
+ guardWasmBytes(MINIMAL_WASM_BYTES);
534
+
535
+ const { instance } = await withTimeout(async () => {
536
+ return WebAssembly.instantiate(MINIMAL_WASM_BYTES);
537
+ }, timeout);
538
+
539
+ const addFunc = instance.exports.add;
540
+
541
+ // Warm up
542
+ for (let i = 0; i < 100; i++) {
543
+ addFunc(1, 2);
544
+ }
545
+
546
+ // Measure call overhead
547
+ for (let i = 0; i < samples; i++) {
548
+ const duration = await measureTime(() => {
549
+ addFunc(1, 2);
550
+ });
551
+
552
+ times.push(duration);
553
+ }
554
+
555
+ const stats = calculateStats(times);
556
+
557
+ observations.push(createObservation('wasm.call.overhead.mean', stats.mean, 'ms', 'success', undefined, { samples }));
558
+ observations.push(createObservation('wasm.call.overhead.median', stats.median, 'ms', 'success', undefined, { samples }));
559
+ observations.push(createObservation('wasm.call.overhead.min', stats.min, 'ms', 'success', undefined, { samples }));
560
+ observations.push(createObservation('wasm.call.overhead.max', stats.max, 'ms', 'success', undefined, { samples }));
561
+ } catch (error) {
562
+ observations.push(createObservation('wasm.call.overhead.mean', 0, 'ms', 'error', error.message));
563
+ }
564
+
565
+ return observations;
566
+ }
567
+
568
+ /**
569
+ * Benchmark memory growth limits
570
+ * @param {number} maxMemoryMB - Maximum memory to test in MB
571
+ * @param {number} timeout - Timeout per operation
572
+ * @returns {Promise<Object[]>} Observations
573
+ */
574
+ async function benchmarkMemoryGrowth(maxMemoryMB, timeout) {
575
+ const observations = [];
576
+
577
+ try {
578
+ guardWasmSupport();
579
+ guardMemoryLimit(maxMemoryMB);
580
+ guardWasmBytes(WASM_MEMORY_MODULE);
581
+
582
+ const maxPages = Math.floor((maxMemoryMB * 1024 * 1024) / (64 * 1024)); // 64KB per page
583
+
584
+ const { instance } = await withTimeout(async () => {
585
+ return WebAssembly.instantiate(WASM_MEMORY_MODULE);
586
+ }, timeout);
587
+
588
+ const memory = instance.exports.memory;
589
+ const initialPages = memory.buffer.byteLength / (64 * 1024);
590
+
591
+ observations.push(createObservation('wasm.memory.initial.pages', initialPages, 'pages', 'success'));
592
+ observations.push(createObservation('wasm.memory.initial.bytes', memory.buffer.byteLength, 'bytes', 'success'));
593
+
594
+ // Try to grow memory
595
+ let currentPages = initialPages;
596
+ let maxAchievedPages = initialPages;
597
+
598
+ try {
599
+ while (currentPages < maxPages) {
600
+ await withTimeout(async () => {
601
+ memory.grow(1);
602
+ }, timeout);
603
+ currentPages++;
604
+ maxAchievedPages = currentPages;
605
+
606
+ // Stop if we hit the module's internal limit
607
+ if (currentPages >= 10) {
608
+ break; // Our test module has max=10 pages
609
+ }
610
+ }
611
+ } catch (error) {
612
+ // Growth failed, record what we achieved
613
+ }
614
+
615
+ observations.push(createObservation('wasm.memory.max.pages', maxAchievedPages, 'pages', 'success'));
616
+ observations.push(createObservation('wasm.memory.max.bytes', maxAchievedPages * 64 * 1024, 'bytes', 'success'));
617
+ } catch (error) {
618
+ observations.push(createObservation('wasm.memory.growth', 0, 'pages', 'error', error.message));
619
+ }
620
+
621
+ return observations;
622
+ }
623
+
624
+ // =============================================================================
625
+ // Main Probe Function
626
+ // =============================================================================
627
+
628
+ /**
629
+ * Probe WebAssembly capabilities and performance
630
+ *
631
+ * Returns comprehensive WASM runtime observations including:
632
+ * - Feature support (instantiate, compile, memory, table, global, SIMD, threads)
633
+ * - Performance benchmarks (compile time, instantiate time, call overhead)
634
+ * - Memory characteristics (initial pages, max pages, growth limits)
635
+ *
636
+ * All operations are guarded with timeouts and resource limits.
637
+ *
638
+ * @param {Object} [config={}] - Probe configuration
639
+ * @param {number} [config.samples=100] - Number of samples for benchmarks
640
+ * @param {number} [config.timeout=5000] - Timeout per operation (ms)
641
+ * @param {number} [config.maxMemoryMB=1024] - Max memory for growth test (MB)
642
+ * @param {boolean} [config.detectSIMD=true] - Enable SIMD detection
643
+ * @param {boolean} [config.detectThreads=true] - Enable threads detection
644
+ * @returns {Promise<Object[]>} Array of observations
645
+ *
646
+ * @example
647
+ * const observations = await probeWasm({
648
+ * samples: 100,
649
+ * timeout: 5000,
650
+ * maxMemoryMB: 1024
651
+ * });
652
+ *
653
+ * // Find specific metric
654
+ * const compileTime = observations.find(o => o.metric === 'wasm.compile.time.mean');
655
+ * console.log(`WASM compile time: ${compileTime.value}ms`);
656
+ *
657
+ * // Filter by status
658
+ * const errors = observations.filter(o => o.status === 'error');
659
+ * console.log(`Failed probes: ${errors.length}`);
660
+ */
661
+ export async function probeWasm(config = {}) {
662
+ const validatedConfig = WasmProbeConfigSchema.parse(config);
663
+ const { samples, timeout, maxMemoryMB, detectSIMD, detectThreads } = validatedConfig;
664
+
665
+ const observations = [];
666
+
667
+ // Guard: Check WASM support before running any probes
668
+ try {
669
+ guardWasmSupport();
670
+ } catch (error) {
671
+ observations.push(createObservation('wasm.environment', false, 'boolean', 'error', error.message));
672
+ return observations;
673
+ }
674
+
675
+ observations.push(createObservation('wasm.environment', true, 'boolean', 'success'));
676
+
677
+ // Capability probes
678
+ observations.push(await probeInstantiationSupport(timeout));
679
+ observations.push(await probeCompileSupport(timeout));
680
+ observations.push(...(await probeMemorySupport(timeout)));
681
+ observations.push(await probeTableSupport(timeout));
682
+ observations.push(await probeGlobalSupport(timeout));
683
+
684
+ if (detectSIMD) {
685
+ observations.push(await probeSIMDSupport(timeout));
686
+ }
687
+
688
+ if (detectThreads) {
689
+ observations.push(await probeThreadsSupport(timeout));
690
+ }
691
+
692
+ // Performance benchmarks (only if basic support confirmed)
693
+ const instantiateSupported = observations.find(
694
+ o => o.metric === 'wasm.support.instantiate' && o.value === true
695
+ );
696
+
697
+ if (instantiateSupported) {
698
+ observations.push(...(await benchmarkCompileTime(samples, timeout)));
699
+ observations.push(...(await benchmarkInstantiateTime(samples, timeout)));
700
+ observations.push(...(await benchmarkCallOverhead(samples, timeout)));
701
+ observations.push(...(await benchmarkMemoryGrowth(maxMemoryMB, timeout)));
702
+ }
703
+
704
+ return observations;
705
+ }
706
+
707
+ // =============================================================================
708
+ // Module Exports
709
+ // =============================================================================
710
+
711
+ export default {
712
+ probeWasm,
713
+ ObservationSchema,
714
+ WasmProbeConfigSchema,
715
+ };