@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.
- package/README.md +414 -0
- package/package.json +81 -0
- package/src/agents/index.mjs +1402 -0
- package/src/artifact.mjs +405 -0
- package/src/cli.mjs +932 -0
- package/src/config.mjs +115 -0
- package/src/guards.mjs +1213 -0
- package/src/index.mjs +347 -0
- package/src/merge.mjs +196 -0
- package/src/observation.mjs +193 -0
- package/src/orchestrator.mjs +315 -0
- package/src/probe.mjs +58 -0
- package/src/probes/CONCURRENCY-PROBE.md +256 -0
- package/src/probes/README.md +275 -0
- package/src/probes/concurrency.mjs +1175 -0
- package/src/probes/filesystem.mjs +731 -0
- package/src/probes/filesystem.test.mjs +244 -0
- package/src/probes/network.mjs +503 -0
- package/src/probes/performance.mjs +816 -0
- package/src/probes/persistence.mjs +785 -0
- package/src/probes/runtime.mjs +589 -0
- package/src/probes/tooling.mjs +454 -0
- package/src/probes/tooling.test.mjs +372 -0
- package/src/probes/verify-execution.mjs +131 -0
- package/src/probes/verify-guards.mjs +73 -0
- package/src/probes/wasm.mjs +715 -0
- package/src/receipt.mjs +197 -0
- package/src/receipts/index.mjs +813 -0
- package/src/reporter.example.mjs +223 -0
- package/src/reporter.mjs +555 -0
- package/src/reporters/markdown.mjs +355 -0
- package/src/reporters/rdf.mjs +383 -0
- package/src/storage/index.mjs +827 -0
- package/src/types.mjs +1028 -0
- package/src/utils/errors.mjs +397 -0
- package/src/utils/index.mjs +32 -0
- package/src/utils/logger.mjs +236 -0
- package/src/vocabulary.ttl +169 -0
|
@@ -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
|
+
};
|