@ruvector/edge-net 0.4.2 → 0.4.4

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,1609 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @ruvector/edge-net Distributed Worker System Test Suite
4
+ *
5
+ * Comprehensive battle-testing of the worker task distribution system:
6
+ * - Worker spawning and lifecycle
7
+ * - Task distribution across workers
8
+ * - Throughput and latency measurement
9
+ * - Failure handling (worker crashes)
10
+ * - Load balancing verification
11
+ * - Scheduler integration tests
12
+ *
13
+ * @module @ruvector/edge-net/tests/distributed-workers-test
14
+ */
15
+
16
+ import { RealWorkerPool, WorkerTaskTypes } from '../real-workers.js';
17
+ import { TaskScheduler, Task, TaskPriority, TaskStatus, WorkerInfo } from '../scheduler.js';
18
+ import { EventEmitter } from 'events';
19
+ import { performance } from 'perf_hooks';
20
+
21
+ // ============================================
22
+ // TEST UTILITIES
23
+ // ============================================
24
+
25
+ class TestMetrics {
26
+ constructor() {
27
+ this.tests = [];
28
+ this.passed = 0;
29
+ this.failed = 0;
30
+ this.startTime = performance.now();
31
+ }
32
+
33
+ record(name, passed, duration, details = {}) {
34
+ this.tests.push({
35
+ name,
36
+ passed,
37
+ duration,
38
+ details,
39
+ timestamp: new Date().toISOString(),
40
+ });
41
+ if (passed) {
42
+ this.passed++;
43
+ } else {
44
+ this.failed++;
45
+ }
46
+ }
47
+
48
+ report() {
49
+ const totalTime = performance.now() - this.startTime;
50
+ console.log('\n' + '='.repeat(60));
51
+ console.log('TEST RESULTS');
52
+ console.log('='.repeat(60));
53
+ console.log(`Total Tests: ${this.tests.length}`);
54
+ console.log(`Passed: ${this.passed}`);
55
+ console.log(`Failed: ${this.failed}`);
56
+ console.log(`Total Time: ${totalTime.toFixed(2)}ms`);
57
+ console.log('='.repeat(60));
58
+
59
+ // Show failed tests
60
+ const failed = this.tests.filter(t => !t.passed);
61
+ if (failed.length > 0) {
62
+ console.log('\nFailed Tests:');
63
+ for (const t of failed) {
64
+ console.log(` - ${t.name}: ${JSON.stringify(t.details)}`);
65
+ }
66
+ }
67
+
68
+ // Performance summary
69
+ console.log('\nPerformance Summary:');
70
+ for (const t of this.tests) {
71
+ const status = t.passed ? '[PASS]' : '[FAIL]';
72
+ console.log(` ${status} ${t.name}: ${t.duration.toFixed(2)}ms`);
73
+ if (t.details.throughput) {
74
+ console.log(` Throughput: ${t.details.throughput.toFixed(2)} tasks/sec`);
75
+ }
76
+ if (t.details.avgLatency) {
77
+ console.log(` Avg Latency: ${t.details.avgLatency.toFixed(2)}ms`);
78
+ }
79
+ }
80
+
81
+ return {
82
+ total: this.tests.length,
83
+ passed: this.passed,
84
+ failed: this.failed,
85
+ totalTime,
86
+ tests: this.tests,
87
+ };
88
+ }
89
+ }
90
+
91
+ async function delay(ms) {
92
+ return new Promise(resolve => setTimeout(resolve, ms));
93
+ }
94
+
95
+ function assert(condition, message) {
96
+ if (!condition) {
97
+ throw new Error(message || 'Assertion failed');
98
+ }
99
+ }
100
+
101
+ // ============================================
102
+ // WORKER POOL TESTS
103
+ // ============================================
104
+
105
+ async function testWorkerPoolInitialization(metrics) {
106
+ const testName = 'Worker Pool Initialization';
107
+ const start = performance.now();
108
+ let pool = null;
109
+
110
+ try {
111
+ pool = new RealWorkerPool({ size: 4 });
112
+ await pool.initialize();
113
+
114
+ const status = pool.getStatus();
115
+ assert(status.status === 'ready', 'Pool should be ready');
116
+ assert(status.workers.total === 4, 'Should have 4 workers');
117
+ assert(status.workers.idle === 4, 'All workers should be idle');
118
+
119
+ metrics.record(testName, true, performance.now() - start, {
120
+ workerCount: status.workers.total,
121
+ });
122
+ } catch (error) {
123
+ metrics.record(testName, false, performance.now() - start, {
124
+ error: error.message,
125
+ });
126
+ } finally {
127
+ if (pool) await pool.shutdown();
128
+ }
129
+ }
130
+
131
+ async function testSingleTaskExecution(metrics) {
132
+ const testName = 'Single Task Execution';
133
+ const start = performance.now();
134
+ let pool = null;
135
+
136
+ try {
137
+ pool = new RealWorkerPool({ size: 2 });
138
+ await pool.initialize();
139
+
140
+ const result = await pool.execute('compute', [1, 2, 3, 4, 5], { operation: 'sum' });
141
+
142
+ assert(result.computed === true, 'Task should be computed');
143
+ assert(result.result === 15, 'Sum should be 15');
144
+ assert(result.operation === 'sum', 'Operation should be sum');
145
+
146
+ metrics.record(testName, true, performance.now() - start, {
147
+ result: result.result,
148
+ });
149
+ } catch (error) {
150
+ metrics.record(testName, false, performance.now() - start, {
151
+ error: error.message,
152
+ });
153
+ } finally {
154
+ if (pool) await pool.shutdown();
155
+ }
156
+ }
157
+
158
+ async function testMultipleTaskTypes(metrics) {
159
+ const testName = 'Multiple Task Types';
160
+ const start = performance.now();
161
+ let pool = null;
162
+ const results = {};
163
+
164
+ try {
165
+ pool = new RealWorkerPool({ size: 4 });
166
+ await pool.initialize();
167
+
168
+ // Test embed
169
+ results.embed = await pool.execute('embed', 'Hello world');
170
+ assert(results.embed.embedding, 'Should have embedding');
171
+ assert(results.embed.dimensions === 384, 'Should have 384 dimensions');
172
+
173
+ // Test process
174
+ results.process = await pool.execute('process', { key: 'value' });
175
+ assert(results.process.processed === true, 'Should be processed');
176
+
177
+ // Test analyze
178
+ results.analyze = await pool.execute('analyze', ['item1', 'item2', 'item3']);
179
+ assert(results.analyze.stats.count === 3, 'Should have 3 items');
180
+
181
+ // Test transform
182
+ results.transform = await pool.execute('transform', 'hello', { transform: 'uppercase' });
183
+ assert(results.transform.transformed === 'HELLO', 'Should be uppercase');
184
+
185
+ // Test compute
186
+ results.compute = await pool.execute('compute', [10, 20, 30], { operation: 'mean' });
187
+ assert(results.compute.result === 20, 'Mean should be 20');
188
+
189
+ // Test aggregate
190
+ results.aggregate = await pool.execute('aggregate', [
191
+ { type: 'a', val: 1 },
192
+ { type: 'b', val: 2 },
193
+ { type: 'a', val: 3 },
194
+ ], { groupBy: 'type' });
195
+ assert(results.aggregate.aggregated === true, 'Should be aggregated');
196
+ assert(results.aggregate.groups.length === 2, 'Should have 2 groups');
197
+
198
+ metrics.record(testName, true, performance.now() - start, {
199
+ taskTypes: Object.keys(results).length,
200
+ });
201
+ } catch (error) {
202
+ metrics.record(testName, false, performance.now() - start, {
203
+ error: error.message,
204
+ });
205
+ } finally {
206
+ if (pool) await pool.shutdown();
207
+ }
208
+ }
209
+
210
+ async function testBatchExecution(metrics) {
211
+ const testName = 'Batch Task Execution';
212
+ const start = performance.now();
213
+ let pool = null;
214
+
215
+ try {
216
+ pool = new RealWorkerPool({ size: 4 });
217
+ await pool.initialize();
218
+
219
+ const batchSize = 100;
220
+ const data = Array.from({ length: batchSize }, (_, i) => `item-${i}`);
221
+
222
+ const results = await pool.executeBatch('process', data);
223
+
224
+ assert(results.length === batchSize, `Should have ${batchSize} results`);
225
+ const successCount = results.filter(r => r.processed).length;
226
+ assert(successCount === batchSize, 'All tasks should succeed');
227
+
228
+ const duration = performance.now() - start;
229
+ const throughput = (batchSize / duration) * 1000;
230
+
231
+ metrics.record(testName, true, duration, {
232
+ batchSize,
233
+ throughput,
234
+ avgLatency: duration / batchSize,
235
+ });
236
+ } catch (error) {
237
+ metrics.record(testName, false, performance.now() - start, {
238
+ error: error.message,
239
+ });
240
+ } finally {
241
+ if (pool) await pool.shutdown();
242
+ }
243
+ }
244
+
245
+ async function testConcurrentExecution(metrics) {
246
+ const testName = 'Concurrent Task Distribution';
247
+ const start = performance.now();
248
+ let pool = null;
249
+
250
+ try {
251
+ pool = new RealWorkerPool({ size: 4 });
252
+ await pool.initialize();
253
+
254
+ const taskCount = 50;
255
+ const promises = [];
256
+
257
+ // Submit all tasks concurrently
258
+ for (let i = 0; i < taskCount; i++) {
259
+ promises.push(
260
+ pool.execute('compute', [i, i + 1, i + 2], { operation: 'sum' })
261
+ );
262
+ }
263
+
264
+ const results = await Promise.all(promises);
265
+
266
+ assert(results.length === taskCount, 'All tasks should complete');
267
+
268
+ // Verify results
269
+ for (let i = 0; i < taskCount; i++) {
270
+ const expected = i + (i + 1) + (i + 2);
271
+ assert(results[i].result === expected, `Task ${i} should have correct result`);
272
+ }
273
+
274
+ const duration = performance.now() - start;
275
+ const throughput = (taskCount / duration) * 1000;
276
+
277
+ metrics.record(testName, true, duration, {
278
+ taskCount,
279
+ throughput,
280
+ avgLatency: duration / taskCount,
281
+ });
282
+ } catch (error) {
283
+ metrics.record(testName, false, performance.now() - start, {
284
+ error: error.message,
285
+ });
286
+ } finally {
287
+ if (pool) await pool.shutdown();
288
+ }
289
+ }
290
+
291
+ async function testWorkerPoolScaling(metrics) {
292
+ const testName = 'Worker Pool Scaling';
293
+ const start = performance.now();
294
+ const results = {};
295
+
296
+ try {
297
+ // Test with different pool sizes using larger workloads
298
+ // to overcome initialization overhead
299
+ for (const size of [1, 2, 4, 8]) {
300
+ const pool = new RealWorkerPool({ size });
301
+ await pool.initialize();
302
+
303
+ // Use larger task count to better measure scaling
304
+ const taskCount = 200;
305
+
306
+ // Warm up the pool first
307
+ await pool.execute('process', { warmup: true });
308
+
309
+ const taskStart = performance.now();
310
+
311
+ const promises = Array.from({ length: taskCount }, (_, i) =>
312
+ pool.execute('process', { index: i })
313
+ );
314
+
315
+ await Promise.all(promises);
316
+
317
+ const taskDuration = performance.now() - taskStart;
318
+ results[`size_${size}`] = {
319
+ throughput: (taskCount / taskDuration) * 1000,
320
+ avgLatency: taskDuration / taskCount,
321
+ };
322
+
323
+ await pool.shutdown();
324
+ }
325
+
326
+ // Verify scaling improves throughput (with tolerance for timing variations)
327
+ // Due to worker thread overhead, we only expect modest improvement
328
+ // The key is that more workers don't decrease throughput significantly
329
+ const scalingRatio = results.size_4.throughput / results.size_1.throughput;
330
+ const meetsExpectation = scalingRatio > 0.8; // Allow 20% tolerance
331
+
332
+ if (!meetsExpectation) {
333
+ console.log(` Scaling ratio: ${scalingRatio.toFixed(2)} (expected > 0.8)`);
334
+ console.log(` 1 worker: ${results.size_1.throughput.toFixed(0)} tasks/sec`);
335
+ console.log(` 4 workers: ${results.size_4.throughput.toFixed(0)} tasks/sec`);
336
+ }
337
+
338
+ metrics.record(testName, meetsExpectation, performance.now() - start, {
339
+ scalingRatio: scalingRatio.toFixed(2),
340
+ ...results,
341
+ });
342
+ } catch (error) {
343
+ metrics.record(testName, false, performance.now() - start, {
344
+ error: error.message,
345
+ });
346
+ }
347
+ }
348
+
349
+ async function testQueueOverflow(metrics) {
350
+ const testName = 'Queue Overflow Handling';
351
+ const start = performance.now();
352
+ let pool = null;
353
+
354
+ try {
355
+ pool = new RealWorkerPool({ size: 1, maxQueueSize: 10 });
356
+ await pool.initialize();
357
+
358
+ const promises = [];
359
+ let overflowCount = 0;
360
+
361
+ // Submit more tasks than queue can hold
362
+ for (let i = 0; i < 20; i++) {
363
+ promises.push(
364
+ pool.execute('compute', [i], { operation: 'sum' })
365
+ .catch(err => {
366
+ if (err.message === 'Task queue full') {
367
+ overflowCount++;
368
+ }
369
+ return { error: err.message };
370
+ })
371
+ );
372
+ }
373
+
374
+ await Promise.all(promises);
375
+
376
+ // Some tasks should have been rejected due to queue overflow
377
+ // But with async execution, we might not hit the limit if tasks complete fast
378
+ // So we just verify the system didn't crash
379
+ const status = pool.getStatus();
380
+ assert(status.status === 'ready', 'Pool should still be ready');
381
+
382
+ metrics.record(testName, true, performance.now() - start, {
383
+ overflowCount,
384
+ queueMaxSize: pool.maxQueueSize,
385
+ });
386
+ } catch (error) {
387
+ metrics.record(testName, false, performance.now() - start, {
388
+ error: error.message,
389
+ });
390
+ } finally {
391
+ if (pool) await pool.shutdown();
392
+ }
393
+ }
394
+
395
+ async function testTaskErrorHandling(metrics) {
396
+ const testName = 'Task Error Handling';
397
+ const start = performance.now();
398
+ let pool = null;
399
+
400
+ try {
401
+ pool = new RealWorkerPool({ size: 2 });
402
+ await pool.initialize();
403
+
404
+ // Test with invalid task type (should use custom handler)
405
+ const result = await pool.execute('invalid_type', { data: 'test' });
406
+ assert(result.custom === true, 'Invalid type should use custom handler');
407
+
408
+ // Test with various edge cases
409
+ const edgeCases = await Promise.all([
410
+ pool.execute('compute', [], { operation: 'sum' }), // Empty array
411
+ pool.execute('compute', null, { operation: 'sum' }), // Null data
412
+ pool.execute('transform', '', { transform: 'uppercase' }), // Empty string
413
+ ]);
414
+
415
+ assert(edgeCases.length === 3, 'All edge cases should complete');
416
+
417
+ metrics.record(testName, true, performance.now() - start, {
418
+ edgeCasesHandled: edgeCases.length,
419
+ });
420
+ } catch (error) {
421
+ metrics.record(testName, false, performance.now() - start, {
422
+ error: error.message,
423
+ });
424
+ } finally {
425
+ if (pool) await pool.shutdown();
426
+ }
427
+ }
428
+
429
+ // ============================================
430
+ // SCHEDULER TESTS
431
+ // ============================================
432
+
433
+ async function testSchedulerBasics(metrics) {
434
+ const testName = 'Scheduler Basic Operations';
435
+ const start = performance.now();
436
+ let scheduler = null;
437
+
438
+ try {
439
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
440
+ scheduler.start();
441
+
442
+ // Register a mock worker that completes tasks
443
+ const worker = scheduler.registerWorker({
444
+ id: 'worker-1',
445
+ capabilities: ['compute', 'process'],
446
+ maxConcurrent: 4,
447
+ });
448
+
449
+ assert(scheduler.workers.size === 1, 'Should have 1 worker');
450
+
451
+ // Submit a task
452
+ const taskPromise = scheduler.submit({
453
+ type: 'compute',
454
+ data: [1, 2, 3],
455
+ priority: TaskPriority.HIGH,
456
+ });
457
+
458
+ // Wait for task to be assigned
459
+ await delay(100);
460
+
461
+ const status = scheduler.getStatus();
462
+ assert(status.started === true, 'Scheduler should be started');
463
+
464
+ // Manually complete the task (simulating worker)
465
+ const runningTask = Array.from(scheduler.running.values())[0];
466
+ if (runningTask) {
467
+ scheduler.completeTask(runningTask.id, { result: 'success' });
468
+ }
469
+
470
+ // Wait for completion
471
+ const result = await Promise.race([
472
+ taskPromise,
473
+ delay(1000).then(() => ({ timeout: true })),
474
+ ]);
475
+
476
+ assert(!result.timeout, 'Task should complete before timeout');
477
+
478
+ metrics.record(testName, true, performance.now() - start, {
479
+ workersRegistered: scheduler.workers.size,
480
+ });
481
+ } catch (error) {
482
+ metrics.record(testName, false, performance.now() - start, {
483
+ error: error.message,
484
+ });
485
+ } finally {
486
+ if (scheduler) scheduler.stop();
487
+ }
488
+ }
489
+
490
+ async function testPriorityScheduling(metrics) {
491
+ const testName = 'Priority-Based Scheduling';
492
+ const start = performance.now();
493
+ let scheduler = null;
494
+
495
+ try {
496
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
497
+ scheduler.start();
498
+
499
+ // Register worker
500
+ scheduler.registerWorker({
501
+ id: 'worker-1',
502
+ capabilities: ['compute'],
503
+ maxConcurrent: 1, // Only 1 concurrent task to test priority
504
+ });
505
+
506
+ // Submit tasks with different priorities
507
+ const completionOrder = [];
508
+
509
+ const lowTask = new Task({
510
+ id: 'low-priority',
511
+ type: 'compute',
512
+ priority: TaskPriority.LOW,
513
+ });
514
+
515
+ const highTask = new Task({
516
+ id: 'high-priority',
517
+ type: 'compute',
518
+ priority: TaskPriority.HIGH,
519
+ });
520
+
521
+ const criticalTask = new Task({
522
+ id: 'critical-priority',
523
+ type: 'compute',
524
+ priority: TaskPriority.CRITICAL,
525
+ });
526
+
527
+ // Add all to pending queue
528
+ scheduler.pending.enqueue(lowTask);
529
+ scheduler.pending.enqueue(highTask);
530
+ scheduler.pending.enqueue(criticalTask);
531
+
532
+ // Check that critical is dequeued first
533
+ const first = scheduler.pending.dequeue();
534
+ const second = scheduler.pending.dequeue();
535
+ const third = scheduler.pending.dequeue();
536
+
537
+ assert(first.id === 'critical-priority', 'Critical should be first');
538
+ assert(second.id === 'high-priority', 'High should be second');
539
+ assert(third.id === 'low-priority', 'Low should be third');
540
+
541
+ metrics.record(testName, true, performance.now() - start, {
542
+ orderCorrect: true,
543
+ });
544
+ } catch (error) {
545
+ metrics.record(testName, false, performance.now() - start, {
546
+ error: error.message,
547
+ });
548
+ } finally {
549
+ if (scheduler) scheduler.stop();
550
+ }
551
+ }
552
+
553
+ async function testWorkerSelection(metrics) {
554
+ const testName = 'Worker Selection Algorithm';
555
+ const start = performance.now();
556
+ let scheduler = null;
557
+
558
+ try {
559
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
560
+ scheduler.start();
561
+
562
+ // Register workers with different characteristics
563
+ const worker1 = scheduler.registerWorker({
564
+ id: 'worker-slow',
565
+ capabilities: ['compute'],
566
+ maxConcurrent: 4,
567
+ });
568
+ worker1.metrics.avgExecutionTime = 500; // Slow worker
569
+ worker1.metrics.successRate = 0.9;
570
+
571
+ const worker2 = scheduler.registerWorker({
572
+ id: 'worker-fast',
573
+ capabilities: ['compute'],
574
+ maxConcurrent: 4,
575
+ });
576
+ worker2.metrics.avgExecutionTime = 100; // Fast worker
577
+ worker2.metrics.successRate = 1.0;
578
+
579
+ const worker3 = scheduler.registerWorker({
580
+ id: 'worker-busy',
581
+ capabilities: ['compute'],
582
+ maxConcurrent: 4,
583
+ });
584
+ worker3.activeTasks = new Set(['task1', 'task2', 'task3']); // Busy worker
585
+ worker3.metrics.avgExecutionTime = 100;
586
+ worker3.metrics.successRate = 1.0;
587
+
588
+ // Create a task
589
+ const task = new Task({
590
+ type: 'compute',
591
+ requiredCapabilities: ['compute'],
592
+ });
593
+
594
+ // Select worker
595
+ const selected = scheduler.selectWorker(task);
596
+
597
+ // Should select the fast, idle worker
598
+ assert(selected.id === 'worker-fast', 'Should select fast, idle worker');
599
+
600
+ // Test with affinity
601
+ const affinityTask = new Task({
602
+ type: 'compute',
603
+ preferredWorker: 'worker-slow',
604
+ });
605
+
606
+ const affinitySelected = scheduler.selectWorker(affinityTask);
607
+ assert(affinitySelected.id === 'worker-slow', 'Should respect affinity');
608
+
609
+ metrics.record(testName, true, performance.now() - start, {
610
+ selectedWorker: selected.id,
611
+ affinityWorker: affinitySelected.id,
612
+ });
613
+ } catch (error) {
614
+ metrics.record(testName, false, performance.now() - start, {
615
+ error: error.message,
616
+ });
617
+ } finally {
618
+ if (scheduler) scheduler.stop();
619
+ }
620
+ }
621
+
622
+ async function testCapabilityMatching(metrics) {
623
+ const testName = 'Capability-Based Worker Selection';
624
+ const start = performance.now();
625
+ let scheduler = null;
626
+
627
+ try {
628
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
629
+ scheduler.start();
630
+
631
+ // Register workers with different capabilities
632
+ scheduler.registerWorker({
633
+ id: 'worker-compute',
634
+ capabilities: ['compute', 'process'],
635
+ maxConcurrent: 4,
636
+ });
637
+
638
+ scheduler.registerWorker({
639
+ id: 'worker-inference',
640
+ capabilities: ['inference', 'embed'],
641
+ maxConcurrent: 4,
642
+ });
643
+
644
+ scheduler.registerWorker({
645
+ id: 'worker-all',
646
+ capabilities: ['compute', 'process', 'inference', 'embed'],
647
+ maxConcurrent: 4,
648
+ });
649
+
650
+ // Task requiring inference
651
+ const inferenceTask = new Task({
652
+ type: 'inference',
653
+ requiredCapabilities: ['inference'],
654
+ });
655
+
656
+ const selected = scheduler.selectWorker(inferenceTask);
657
+ assert(
658
+ selected.capabilities.includes('inference'),
659
+ 'Selected worker should have inference capability'
660
+ );
661
+
662
+ // Task requiring multiple capabilities
663
+ const multiTask = new Task({
664
+ type: 'complex',
665
+ requiredCapabilities: ['compute', 'inference'],
666
+ });
667
+
668
+ const multiSelected = scheduler.selectWorker(multiTask);
669
+ assert(
670
+ multiSelected.id === 'worker-all',
671
+ 'Should select worker with all required capabilities'
672
+ );
673
+
674
+ metrics.record(testName, true, performance.now() - start, {
675
+ inferenceWorker: selected.id,
676
+ multiCapWorker: multiSelected.id,
677
+ });
678
+ } catch (error) {
679
+ metrics.record(testName, false, performance.now() - start, {
680
+ error: error.message,
681
+ });
682
+ } finally {
683
+ if (scheduler) scheduler.stop();
684
+ }
685
+ }
686
+
687
+ async function testRetryMechanism(metrics) {
688
+ const testName = 'Task Retry Mechanism';
689
+ const start = performance.now();
690
+ let scheduler = null;
691
+
692
+ try {
693
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
694
+ scheduler.start();
695
+
696
+ // Register worker
697
+ const worker = scheduler.registerWorker({
698
+ id: 'worker-1',
699
+ capabilities: ['compute'],
700
+ maxConcurrent: 4,
701
+ });
702
+
703
+ // Create task with retries
704
+ const task = new Task({
705
+ id: 'retry-task',
706
+ type: 'compute',
707
+ maxRetries: 3,
708
+ retryDelay: 100,
709
+ });
710
+
711
+ // Submit task
712
+ scheduler.pending.enqueue(task);
713
+ scheduler.schedule();
714
+
715
+ // Wait for assignment
716
+ await delay(100);
717
+
718
+ // Simulate failures
719
+ let retryCount = 0;
720
+ while (scheduler.running.has('retry-task') && retryCount < 3) {
721
+ scheduler.failTask('retry-task', new Error('Simulated failure'));
722
+ retryCount++;
723
+
724
+ // Wait for retry
725
+ await delay(200);
726
+
727
+ // If task is retrying, it should be re-queued
728
+ if (task.status === TaskStatus.RETRYING || task.status === TaskStatus.QUEUED) {
729
+ scheduler.schedule();
730
+ await delay(100);
731
+ }
732
+ }
733
+
734
+ assert(task.retryCount >= 1, 'Task should have retried at least once');
735
+
736
+ metrics.record(testName, true, performance.now() - start, {
737
+ retryCount: task.retryCount,
738
+ finalStatus: task.status,
739
+ });
740
+ } catch (error) {
741
+ metrics.record(testName, false, performance.now() - start, {
742
+ error: error.message,
743
+ });
744
+ } finally {
745
+ if (scheduler) scheduler.stop();
746
+ }
747
+ }
748
+
749
+ async function testTaskCancellation(metrics) {
750
+ const testName = 'Task Cancellation';
751
+ const start = performance.now();
752
+ let scheduler = null;
753
+
754
+ try {
755
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
756
+ scheduler.start();
757
+
758
+ // Register worker
759
+ scheduler.registerWorker({
760
+ id: 'worker-1',
761
+ capabilities: ['compute'],
762
+ maxConcurrent: 4,
763
+ });
764
+
765
+ // Submit multiple tasks
766
+ const task1 = new Task({ id: 'task-1', type: 'compute' });
767
+ const task2 = new Task({ id: 'task-2', type: 'compute' });
768
+ const task3 = new Task({ id: 'task-3', type: 'compute' });
769
+
770
+ scheduler.pending.enqueue(task1);
771
+ scheduler.pending.enqueue(task2);
772
+ scheduler.pending.enqueue(task3);
773
+
774
+ // Cancel a pending task
775
+ const cancelledPending = scheduler.cancel('task-2');
776
+ assert(cancelledPending === true, 'Should cancel pending task');
777
+
778
+ // Schedule remaining
779
+ scheduler.schedule();
780
+ await delay(100);
781
+
782
+ // Cancel a running task
783
+ const runningTask = Array.from(scheduler.running.values())[0];
784
+ if (runningTask) {
785
+ const cancelledRunning = scheduler.cancel(runningTask.id);
786
+ assert(cancelledRunning === true, 'Should cancel running task');
787
+ }
788
+
789
+ metrics.record(testName, true, performance.now() - start, {
790
+ cancelledPending: cancelledPending,
791
+ });
792
+ } catch (error) {
793
+ metrics.record(testName, false, performance.now() - start, {
794
+ error: error.message,
795
+ });
796
+ } finally {
797
+ if (scheduler) scheduler.stop();
798
+ }
799
+ }
800
+
801
+ async function testWorkerFailure(metrics) {
802
+ const testName = 'Worker Failure Handling';
803
+ const start = performance.now();
804
+ let scheduler = null;
805
+
806
+ try {
807
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
808
+ scheduler.start();
809
+
810
+ // Register workers
811
+ const worker1 = scheduler.registerWorker({
812
+ id: 'worker-1',
813
+ capabilities: ['compute'],
814
+ maxConcurrent: 4,
815
+ });
816
+
817
+ const worker2 = scheduler.registerWorker({
818
+ id: 'worker-2',
819
+ capabilities: ['compute'],
820
+ maxConcurrent: 4,
821
+ });
822
+
823
+ // Submit tasks to worker-1
824
+ const task1 = new Task({ id: 'task-1', type: 'compute' });
825
+ const task2 = new Task({ id: 'task-2', type: 'compute' });
826
+
827
+ scheduler.pending.enqueue(task1);
828
+ scheduler.pending.enqueue(task2);
829
+ scheduler.schedule();
830
+
831
+ await delay(100);
832
+
833
+ // Simulate worker-1 going offline
834
+ scheduler.unregisterWorker('worker-1');
835
+
836
+ assert(scheduler.workers.size === 1, 'Should have 1 worker remaining');
837
+
838
+ // Tasks should be re-queued
839
+ // The scheduler should have moved tasks back to pending
840
+ scheduler.schedule();
841
+ await delay(100);
842
+
843
+ metrics.record(testName, true, performance.now() - start, {
844
+ remainingWorkers: scheduler.workers.size,
845
+ pendingTasks: scheduler.pending.size,
846
+ });
847
+ } catch (error) {
848
+ metrics.record(testName, false, performance.now() - start, {
849
+ error: error.message,
850
+ });
851
+ } finally {
852
+ if (scheduler) scheduler.stop();
853
+ }
854
+ }
855
+
856
+ async function testResourceAllocation(metrics) {
857
+ const testName = 'Resource Allocation';
858
+ const start = performance.now();
859
+ let scheduler = null;
860
+
861
+ try {
862
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
863
+ scheduler.start();
864
+
865
+ // Register worker with limited resources
866
+ const worker = scheduler.registerWorker({
867
+ id: 'worker-1',
868
+ capabilities: ['compute'],
869
+ maxConcurrent: 4,
870
+ cpu: 4,
871
+ memory: 4096,
872
+ });
873
+
874
+ // Create tasks with resource requirements
875
+ const smallTask = new Task({
876
+ id: 'small-task',
877
+ type: 'compute',
878
+ resources: { cpu: 1, memory: 256 },
879
+ });
880
+
881
+ const largeTask = new Task({
882
+ id: 'large-task',
883
+ type: 'compute',
884
+ resources: { cpu: 3, memory: 3000 },
885
+ });
886
+
887
+ const hugeTask = new Task({
888
+ id: 'huge-task',
889
+ type: 'compute',
890
+ resources: { cpu: 10, memory: 8000 }, // More than available
891
+ });
892
+
893
+ // Small task should be schedulable
894
+ assert(worker.hasResources(smallTask.resources), 'Worker should have resources for small task');
895
+
896
+ // Allocate small task
897
+ worker.allocate(smallTask);
898
+ assert(worker.resources.cpuUsed === 1, 'CPU should be allocated');
899
+ assert(worker.resources.memoryUsed === 256, 'Memory should be allocated');
900
+
901
+ // Large task should still fit
902
+ assert(worker.hasResources(largeTask.resources), 'Worker should have resources for large task');
903
+
904
+ worker.allocate(largeTask);
905
+ assert(worker.resources.cpuUsed === 4, 'All CPU should be allocated');
906
+ assert(worker.resources.memoryUsed === 3256, 'Memory should be allocated');
907
+
908
+ // Huge task should NOT fit
909
+ assert(!worker.hasResources(hugeTask.resources), 'Worker should NOT have resources for huge task');
910
+
911
+ // Release tasks
912
+ worker.release(smallTask);
913
+ worker.release(largeTask);
914
+
915
+ assert(worker.resources.cpuUsed === 0, 'CPU should be released');
916
+ assert(worker.resources.memoryUsed === 0, 'Memory should be released');
917
+
918
+ metrics.record(testName, true, performance.now() - start, {
919
+ resourcesManaged: true,
920
+ });
921
+ } catch (error) {
922
+ metrics.record(testName, false, performance.now() - start, {
923
+ error: error.message,
924
+ });
925
+ } finally {
926
+ if (scheduler) scheduler.stop();
927
+ }
928
+ }
929
+
930
+ // ============================================
931
+ // LOAD BALANCING TESTS
932
+ // ============================================
933
+
934
+ async function testLoadBalancing(metrics) {
935
+ const testName = 'Load Balancing Distribution';
936
+ const start = performance.now();
937
+ let scheduler = null;
938
+
939
+ try {
940
+ scheduler = new TaskScheduler({ schedulingInterval: 10 });
941
+ scheduler.start();
942
+
943
+ // Register multiple workers
944
+ const workerCount = 4;
945
+ const workers = [];
946
+ for (let i = 0; i < workerCount; i++) {
947
+ const worker = scheduler.registerWorker({
948
+ id: `worker-${i}`,
949
+ capabilities: ['compute'],
950
+ maxConcurrent: 10,
951
+ });
952
+ workers.push(worker);
953
+ }
954
+
955
+ // Track task assignments
956
+ const assignments = new Map();
957
+ scheduler.on('task-assigned', ({ taskId, workerId }) => {
958
+ const count = assignments.get(workerId) || 0;
959
+ assignments.set(workerId, count + 1);
960
+ });
961
+
962
+ // Submit many tasks
963
+ const taskCount = 100;
964
+ for (let i = 0; i < taskCount; i++) {
965
+ const task = new Task({
966
+ id: `task-${i}`,
967
+ type: 'compute',
968
+ });
969
+ scheduler.pending.enqueue(task);
970
+ }
971
+
972
+ // Let scheduler distribute tasks
973
+ for (let i = 0; i < 20; i++) {
974
+ scheduler.schedule();
975
+ await delay(10);
976
+
977
+ // Complete some tasks to free up workers
978
+ for (const [taskId, task] of scheduler.running) {
979
+ scheduler.completeTask(taskId, { result: 'ok' });
980
+ }
981
+ }
982
+
983
+ // Check distribution
984
+ const taskDistribution = {};
985
+ for (const [workerId, count] of assignments) {
986
+ taskDistribution[workerId] = count;
987
+ }
988
+
989
+ // All workers should have received tasks
990
+ const workersUsed = assignments.size;
991
+ assert(workersUsed > 1, 'Multiple workers should be used');
992
+
993
+ // Calculate distribution evenness (coefficient of variation)
994
+ const counts = Array.from(assignments.values());
995
+ const mean = counts.reduce((a, b) => a + b, 0) / counts.length;
996
+ const variance = counts.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / counts.length;
997
+ const stdDev = Math.sqrt(variance);
998
+ const cv = mean > 0 ? stdDev / mean : 0;
999
+
1000
+ metrics.record(testName, true, performance.now() - start, {
1001
+ workersUsed,
1002
+ distribution: taskDistribution,
1003
+ coefficientOfVariation: cv.toFixed(3),
1004
+ });
1005
+ } catch (error) {
1006
+ metrics.record(testName, false, performance.now() - start, {
1007
+ error: error.message,
1008
+ });
1009
+ } finally {
1010
+ if (scheduler) scheduler.stop();
1011
+ }
1012
+ }
1013
+
1014
+ // ============================================
1015
+ // PERFORMANCE TESTS
1016
+ // ============================================
1017
+
1018
+ async function testThroughputUnderLoad(metrics) {
1019
+ const testName = 'Throughput Under Load';
1020
+ const start = performance.now();
1021
+ let pool = null;
1022
+
1023
+ try {
1024
+ pool = new RealWorkerPool({ size: 8 });
1025
+ await pool.initialize();
1026
+
1027
+ const taskCount = 500;
1028
+ const batchSize = 50;
1029
+ let completed = 0;
1030
+ let totalLatency = 0;
1031
+
1032
+ const batches = [];
1033
+ for (let i = 0; i < taskCount; i += batchSize) {
1034
+ const batchStart = performance.now();
1035
+ const data = Array.from({ length: Math.min(batchSize, taskCount - i) }, (_, j) => ({
1036
+ index: i + j,
1037
+ data: `item-${i + j}`,
1038
+ }));
1039
+
1040
+ const results = await pool.executeBatch('process', data);
1041
+ const batchDuration = performance.now() - batchStart;
1042
+
1043
+ completed += results.length;
1044
+ totalLatency += batchDuration;
1045
+
1046
+ batches.push({
1047
+ size: results.length,
1048
+ duration: batchDuration,
1049
+ throughput: (results.length / batchDuration) * 1000,
1050
+ });
1051
+ }
1052
+
1053
+ const totalDuration = performance.now() - start;
1054
+ const avgThroughput = (completed / totalDuration) * 1000;
1055
+ const avgLatency = totalLatency / batches.length;
1056
+
1057
+ metrics.record(testName, true, totalDuration, {
1058
+ tasksCompleted: completed,
1059
+ throughput: avgThroughput,
1060
+ avgLatency,
1061
+ batchCount: batches.length,
1062
+ });
1063
+ } catch (error) {
1064
+ metrics.record(testName, false, performance.now() - start, {
1065
+ error: error.message,
1066
+ });
1067
+ } finally {
1068
+ if (pool) await pool.shutdown();
1069
+ }
1070
+ }
1071
+
1072
+ async function testLatencyDistribution(metrics) {
1073
+ const testName = 'Latency Distribution';
1074
+ const start = performance.now();
1075
+ let pool = null;
1076
+
1077
+ try {
1078
+ pool = new RealWorkerPool({ size: 4 });
1079
+ await pool.initialize();
1080
+
1081
+ const taskCount = 100;
1082
+ const latencies = [];
1083
+
1084
+ for (let i = 0; i < taskCount; i++) {
1085
+ const taskStart = performance.now();
1086
+ await pool.execute('compute', [i, i * 2], { operation: 'sum' });
1087
+ latencies.push(performance.now() - taskStart);
1088
+ }
1089
+
1090
+ // Calculate statistics
1091
+ latencies.sort((a, b) => a - b);
1092
+ const min = latencies[0];
1093
+ const max = latencies[latencies.length - 1];
1094
+ const mean = latencies.reduce((a, b) => a + b, 0) / latencies.length;
1095
+ const p50 = latencies[Math.floor(latencies.length * 0.5)];
1096
+ const p95 = latencies[Math.floor(latencies.length * 0.95)];
1097
+ const p99 = latencies[Math.floor(latencies.length * 0.99)];
1098
+
1099
+ metrics.record(testName, true, performance.now() - start, {
1100
+ min: min.toFixed(2),
1101
+ max: max.toFixed(2),
1102
+ mean: mean.toFixed(2),
1103
+ p50: p50.toFixed(2),
1104
+ p95: p95.toFixed(2),
1105
+ p99: p99.toFixed(2),
1106
+ });
1107
+ } catch (error) {
1108
+ metrics.record(testName, false, performance.now() - start, {
1109
+ error: error.message,
1110
+ });
1111
+ } finally {
1112
+ if (pool) await pool.shutdown();
1113
+ }
1114
+ }
1115
+
1116
+ async function testMapReducePattern(metrics) {
1117
+ const testName = 'Map-Reduce Pattern';
1118
+ const start = performance.now();
1119
+ let pool = null;
1120
+
1121
+ try {
1122
+ pool = new RealWorkerPool({ size: 4 });
1123
+ await pool.initialize();
1124
+
1125
+ // Map phase: process items
1126
+ const data = Array.from({ length: 100 }, (_, i) => ({
1127
+ value: i + 1,
1128
+ category: `cat-${i % 5}`,
1129
+ }));
1130
+
1131
+ const mapStart = performance.now();
1132
+ const mapped = await pool.map('process', data);
1133
+ const mapDuration = performance.now() - mapStart;
1134
+
1135
+ // Reduce phase: aggregate
1136
+ const reduceStart = performance.now();
1137
+ const reduced = await pool.reduce('aggregate', mapped, { groupBy: 'category' });
1138
+ const reduceDuration = performance.now() - reduceStart;
1139
+
1140
+ assert(reduced.aggregated === true, 'Should be aggregated');
1141
+
1142
+ const totalDuration = performance.now() - start;
1143
+
1144
+ metrics.record(testName, true, totalDuration, {
1145
+ mapDuration: mapDuration.toFixed(2),
1146
+ reduceDuration: reduceDuration.toFixed(2),
1147
+ itemsProcessed: data.length,
1148
+ });
1149
+ } catch (error) {
1150
+ metrics.record(testName, false, performance.now() - start, {
1151
+ error: error.message,
1152
+ });
1153
+ } finally {
1154
+ if (pool) await pool.shutdown();
1155
+ }
1156
+ }
1157
+
1158
+ // ============================================
1159
+ // INTEGRATION TESTS
1160
+ // ============================================
1161
+
1162
+ async function testSchedulerWorkerPoolIntegration(metrics) {
1163
+ const testName = 'Scheduler-WorkerPool Integration';
1164
+ const start = performance.now();
1165
+ let scheduler = null;
1166
+ let pool = null;
1167
+
1168
+ try {
1169
+ // Create both scheduler and pool
1170
+ scheduler = new TaskScheduler({ schedulingInterval: 50 });
1171
+ pool = new RealWorkerPool({ size: 4 });
1172
+
1173
+ await pool.initialize();
1174
+ scheduler.start();
1175
+
1176
+ // Register simulated workers that use the pool
1177
+ const workerCount = 4;
1178
+ for (let i = 0; i < workerCount; i++) {
1179
+ scheduler.registerWorker({
1180
+ id: `pool-worker-${i}`,
1181
+ capabilities: ['compute', 'process', 'embed'],
1182
+ maxConcurrent: 4,
1183
+ });
1184
+ }
1185
+
1186
+ // Submit tasks through scheduler
1187
+ const taskPromises = [];
1188
+ const taskCount = 20;
1189
+
1190
+ for (let i = 0; i < taskCount; i++) {
1191
+ const taskPromise = scheduler.submit({
1192
+ id: `integrated-task-${i}`,
1193
+ type: 'compute',
1194
+ data: [i, i + 1],
1195
+ priority: i % 4, // Vary priority
1196
+ });
1197
+
1198
+ // Process assigned tasks with pool
1199
+ taskPromise.taskId = `integrated-task-${i}`;
1200
+ taskPromises.push(taskPromise);
1201
+ }
1202
+
1203
+ // Process tasks as they're assigned
1204
+ await delay(100);
1205
+
1206
+ // Complete tasks using pool results
1207
+ for (const [taskId, task] of scheduler.running) {
1208
+ try {
1209
+ const result = await pool.execute(task.type, task.data, task.options);
1210
+ scheduler.completeTask(taskId, result);
1211
+ } catch (error) {
1212
+ scheduler.failTask(taskId, error);
1213
+ }
1214
+ }
1215
+
1216
+ // Wait for some completions
1217
+ await delay(200);
1218
+
1219
+ const status = scheduler.getStatus();
1220
+
1221
+ metrics.record(testName, true, performance.now() - start, {
1222
+ submitted: status.stats.submitted,
1223
+ completed: status.stats.completed,
1224
+ pending: status.pending,
1225
+ });
1226
+ } catch (error) {
1227
+ metrics.record(testName, false, performance.now() - start, {
1228
+ error: error.message,
1229
+ });
1230
+ } finally {
1231
+ if (scheduler) scheduler.stop();
1232
+ if (pool) await pool.shutdown();
1233
+ }
1234
+ }
1235
+
1236
+ async function testStressTest(metrics) {
1237
+ const testName = 'Stress Test (High Load)';
1238
+ const start = performance.now();
1239
+ let pool = null;
1240
+
1241
+ try {
1242
+ pool = new RealWorkerPool({ size: 8, maxQueueSize: 5000 });
1243
+ await pool.initialize();
1244
+
1245
+ const taskCount = 1000;
1246
+ const taskTypes = ['embed', 'process', 'compute', 'transform', 'analyze'];
1247
+ const promises = [];
1248
+
1249
+ // Submit mixed tasks rapidly
1250
+ for (let i = 0; i < taskCount; i++) {
1251
+ const taskType = taskTypes[i % taskTypes.length];
1252
+ let data;
1253
+
1254
+ switch (taskType) {
1255
+ case 'embed':
1256
+ data = `Text to embed ${i}`;
1257
+ break;
1258
+ case 'process':
1259
+ data = { index: i, value: `item-${i}` };
1260
+ break;
1261
+ case 'compute':
1262
+ data = [i, i * 2, i * 3];
1263
+ break;
1264
+ case 'transform':
1265
+ data = `transform-${i}`;
1266
+ break;
1267
+ case 'analyze':
1268
+ data = [`item-${i}`, `item-${i + 1}`];
1269
+ break;
1270
+ }
1271
+
1272
+ promises.push(
1273
+ pool.execute(taskType, data)
1274
+ .catch(err => ({ error: err.message }))
1275
+ );
1276
+ }
1277
+
1278
+ const results = await Promise.all(promises);
1279
+ const successCount = results.filter(r => !r.error).length;
1280
+ const errorCount = results.filter(r => r.error).length;
1281
+
1282
+ const duration = performance.now() - start;
1283
+ const throughput = (successCount / duration) * 1000;
1284
+
1285
+ const poolStatus = pool.getStatus();
1286
+
1287
+ metrics.record(testName, true, duration, {
1288
+ totalTasks: taskCount,
1289
+ succeeded: successCount,
1290
+ failed: errorCount,
1291
+ throughput,
1292
+ avgProcessingTime: poolStatus.stats.avgProcessingTime,
1293
+ });
1294
+ } catch (error) {
1295
+ metrics.record(testName, false, performance.now() - start, {
1296
+ error: error.message,
1297
+ });
1298
+ } finally {
1299
+ if (pool) await pool.shutdown();
1300
+ }
1301
+ }
1302
+
1303
+ // ============================================
1304
+ // METRICS COLLECTION FOR PERFORMANCE ANALYSIS
1305
+ // ============================================
1306
+
1307
+ class PerformanceCollector {
1308
+ constructor() {
1309
+ this.samples = [];
1310
+ this.histograms = new Map();
1311
+ this.counters = new Map();
1312
+ this.gauges = new Map();
1313
+ }
1314
+
1315
+ // Record a latency sample
1316
+ recordLatency(name, value) {
1317
+ if (!this.histograms.has(name)) {
1318
+ this.histograms.set(name, []);
1319
+ }
1320
+ this.histograms.get(name).push(value);
1321
+ }
1322
+
1323
+ // Increment a counter
1324
+ increment(name, value = 1) {
1325
+ const current = this.counters.get(name) || 0;
1326
+ this.counters.set(name, current + value);
1327
+ }
1328
+
1329
+ // Set a gauge value
1330
+ gauge(name, value) {
1331
+ this.gauges.set(name, value);
1332
+ }
1333
+
1334
+ // Record a sample with timestamp
1335
+ sample(name, value, labels = {}) {
1336
+ this.samples.push({
1337
+ name,
1338
+ value,
1339
+ labels,
1340
+ timestamp: Date.now(),
1341
+ });
1342
+ }
1343
+
1344
+ // Calculate percentile
1345
+ percentile(data, p) {
1346
+ const sorted = [...data].sort((a, b) => a - b);
1347
+ const index = Math.ceil((p / 100) * sorted.length) - 1;
1348
+ return sorted[Math.max(0, index)];
1349
+ }
1350
+
1351
+ // Get histogram statistics
1352
+ getHistogramStats(name) {
1353
+ const data = this.histograms.get(name);
1354
+ if (!data || data.length === 0) {
1355
+ return null;
1356
+ }
1357
+
1358
+ const sorted = [...data].sort((a, b) => a - b);
1359
+ const sum = data.reduce((a, b) => a + b, 0);
1360
+ const mean = sum / data.length;
1361
+ const variance = data.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / data.length;
1362
+
1363
+ return {
1364
+ count: data.length,
1365
+ min: sorted[0],
1366
+ max: sorted[sorted.length - 1],
1367
+ mean,
1368
+ stdDev: Math.sqrt(variance),
1369
+ p50: this.percentile(data, 50),
1370
+ p75: this.percentile(data, 75),
1371
+ p90: this.percentile(data, 90),
1372
+ p95: this.percentile(data, 95),
1373
+ p99: this.percentile(data, 99),
1374
+ };
1375
+ }
1376
+
1377
+ // Get summary report
1378
+ report() {
1379
+ const report = {
1380
+ timestamp: new Date().toISOString(),
1381
+ histograms: {},
1382
+ counters: Object.fromEntries(this.counters),
1383
+ gauges: Object.fromEntries(this.gauges),
1384
+ sampleCount: this.samples.length,
1385
+ };
1386
+
1387
+ for (const [name, _] of this.histograms) {
1388
+ report.histograms[name] = this.getHistogramStats(name);
1389
+ }
1390
+
1391
+ return report;
1392
+ }
1393
+
1394
+ // Export to JSON
1395
+ toJSON() {
1396
+ return JSON.stringify(this.report(), null, 2);
1397
+ }
1398
+
1399
+ // Print summary
1400
+ printSummary() {
1401
+ console.log('\n' + '='.repeat(60));
1402
+ console.log('PERFORMANCE METRICS SUMMARY');
1403
+ console.log('='.repeat(60));
1404
+
1405
+ // Counters
1406
+ if (this.counters.size > 0) {
1407
+ console.log('\nCounters:');
1408
+ for (const [name, value] of this.counters) {
1409
+ console.log(` ${name}: ${value}`);
1410
+ }
1411
+ }
1412
+
1413
+ // Gauges
1414
+ if (this.gauges.size > 0) {
1415
+ console.log('\nGauges:');
1416
+ for (const [name, value] of this.gauges) {
1417
+ console.log(` ${name}: ${value}`);
1418
+ }
1419
+ }
1420
+
1421
+ // Histograms
1422
+ if (this.histograms.size > 0) {
1423
+ console.log('\nLatency Distributions:');
1424
+ for (const [name, _] of this.histograms) {
1425
+ const stats = this.getHistogramStats(name);
1426
+ if (stats) {
1427
+ console.log(` ${name}:`);
1428
+ console.log(` count: ${stats.count}`);
1429
+ console.log(` min: ${stats.min.toFixed(2)}ms`);
1430
+ console.log(` max: ${stats.max.toFixed(2)}ms`);
1431
+ console.log(` mean: ${stats.mean.toFixed(2)}ms`);
1432
+ console.log(` stdDev: ${stats.stdDev.toFixed(2)}ms`);
1433
+ console.log(` p50: ${stats.p50.toFixed(2)}ms`);
1434
+ console.log(` p95: ${stats.p95.toFixed(2)}ms`);
1435
+ console.log(` p99: ${stats.p99.toFixed(2)}ms`);
1436
+ }
1437
+ }
1438
+ }
1439
+
1440
+ console.log('='.repeat(60));
1441
+ }
1442
+ }
1443
+
1444
+ // Global performance collector for tests
1445
+ const perfCollector = new PerformanceCollector();
1446
+
1447
+ // ============================================
1448
+ // BENCHMARK TEST WITH METRICS COLLECTION
1449
+ // ============================================
1450
+
1451
+ async function runPerformanceBenchmark() {
1452
+ console.log('\n' + '='.repeat(60));
1453
+ console.log('DETAILED PERFORMANCE BENCHMARK');
1454
+ console.log('='.repeat(60));
1455
+
1456
+ let pool = null;
1457
+
1458
+ try {
1459
+ pool = new RealWorkerPool({ size: 4 });
1460
+ await pool.initialize();
1461
+
1462
+ // Warm up
1463
+ await pool.execute('process', { warmup: true });
1464
+
1465
+ // Benchmark different task types
1466
+ const taskTypes = ['embed', 'process', 'compute', 'transform', 'analyze'];
1467
+
1468
+ for (const taskType of taskTypes) {
1469
+ const iterations = 100;
1470
+ console.log(`\nBenchmarking ${taskType}...`);
1471
+
1472
+ for (let i = 0; i < iterations; i++) {
1473
+ let data;
1474
+ switch (taskType) {
1475
+ case 'embed':
1476
+ data = `Sample text for embedding ${i}`;
1477
+ break;
1478
+ case 'process':
1479
+ data = { index: i, value: `item-${i}` };
1480
+ break;
1481
+ case 'compute':
1482
+ data = [i, i * 2, i * 3, i * 4];
1483
+ break;
1484
+ case 'transform':
1485
+ data = `transform-input-${i}`;
1486
+ break;
1487
+ case 'analyze':
1488
+ data = [`a-${i}`, `b-${i}`, `c-${i}`];
1489
+ break;
1490
+ }
1491
+
1492
+ const start = performance.now();
1493
+ await pool.execute(taskType, data);
1494
+ const latency = performance.now() - start;
1495
+
1496
+ perfCollector.recordLatency(`task.${taskType}.latency`, latency);
1497
+ perfCollector.increment(`task.${taskType}.count`);
1498
+ }
1499
+
1500
+ const stats = perfCollector.getHistogramStats(`task.${taskType}.latency`);
1501
+ console.log(` Mean: ${stats.mean.toFixed(2)}ms, P50: ${stats.p50.toFixed(2)}ms, P99: ${stats.p99.toFixed(2)}ms`);
1502
+ }
1503
+
1504
+ // Concurrent load test
1505
+ console.log('\nBenchmarking concurrent execution...');
1506
+ const concurrencyLevels = [1, 5, 10, 20, 50];
1507
+
1508
+ for (const concurrency of concurrencyLevels) {
1509
+ const iterations = 100;
1510
+ const start = performance.now();
1511
+
1512
+ // Submit in batches of `concurrency` size
1513
+ for (let batch = 0; batch < iterations / concurrency; batch++) {
1514
+ const promises = [];
1515
+ for (let i = 0; i < concurrency; i++) {
1516
+ promises.push(pool.execute('process', { batch, i }));
1517
+ }
1518
+ await Promise.all(promises);
1519
+ }
1520
+
1521
+ const duration = performance.now() - start;
1522
+ const throughput = (iterations / duration) * 1000;
1523
+
1524
+ perfCollector.sample('concurrent.throughput', throughput, { concurrency });
1525
+ perfCollector.gauge(`concurrent.${concurrency}.throughput`, throughput);
1526
+
1527
+ console.log(` Concurrency ${concurrency}: ${throughput.toFixed(0)} tasks/sec`);
1528
+ }
1529
+
1530
+ // Record pool stats
1531
+ const poolStatus = pool.getStatus();
1532
+ perfCollector.gauge('pool.tasksCompleted', poolStatus.stats.tasksCompleted);
1533
+ perfCollector.gauge('pool.avgProcessingTime', poolStatus.stats.avgProcessingTime);
1534
+
1535
+ } finally {
1536
+ if (pool) await pool.shutdown();
1537
+ }
1538
+
1539
+ // Print detailed summary
1540
+ perfCollector.printSummary();
1541
+ }
1542
+
1543
+ // ============================================
1544
+ // MAIN TEST RUNNER
1545
+ // ============================================
1546
+
1547
+ async function runAllTests() {
1548
+ console.log('='.repeat(60));
1549
+ console.log('@ruvector/edge-net Distributed Worker Test Suite');
1550
+ console.log('='.repeat(60));
1551
+ console.log(`Start Time: ${new Date().toISOString()}`);
1552
+ console.log('');
1553
+
1554
+ const metrics = new TestMetrics();
1555
+
1556
+ // Worker Pool Tests
1557
+ console.log('\n--- Worker Pool Tests ---');
1558
+ await testWorkerPoolInitialization(metrics);
1559
+ await testSingleTaskExecution(metrics);
1560
+ await testMultipleTaskTypes(metrics);
1561
+ await testBatchExecution(metrics);
1562
+ await testConcurrentExecution(metrics);
1563
+ await testWorkerPoolScaling(metrics);
1564
+ await testQueueOverflow(metrics);
1565
+ await testTaskErrorHandling(metrics);
1566
+
1567
+ // Scheduler Tests
1568
+ console.log('\n--- Scheduler Tests ---');
1569
+ await testSchedulerBasics(metrics);
1570
+ await testPriorityScheduling(metrics);
1571
+ await testWorkerSelection(metrics);
1572
+ await testCapabilityMatching(metrics);
1573
+ await testRetryMechanism(metrics);
1574
+ await testTaskCancellation(metrics);
1575
+ await testWorkerFailure(metrics);
1576
+ await testResourceAllocation(metrics);
1577
+
1578
+ // Load Balancing Tests
1579
+ console.log('\n--- Load Balancing Tests ---');
1580
+ await testLoadBalancing(metrics);
1581
+
1582
+ // Performance Tests
1583
+ console.log('\n--- Performance Tests ---');
1584
+ await testThroughputUnderLoad(metrics);
1585
+ await testLatencyDistribution(metrics);
1586
+ await testMapReducePattern(metrics);
1587
+
1588
+ // Integration Tests
1589
+ console.log('\n--- Integration Tests ---');
1590
+ await testSchedulerWorkerPoolIntegration(metrics);
1591
+ await testStressTest(metrics);
1592
+
1593
+ // Report results
1594
+ const report = metrics.report();
1595
+
1596
+ // Run detailed performance benchmark if --benchmark flag is provided
1597
+ if (process.argv.includes('--benchmark')) {
1598
+ await runPerformanceBenchmark();
1599
+ }
1600
+
1601
+ // Exit with appropriate code
1602
+ process.exit(report.failed > 0 ? 1 : 0);
1603
+ }
1604
+
1605
+ // Run tests
1606
+ runAllTests().catch(error => {
1607
+ console.error('Test suite failed:', error);
1608
+ process.exit(1);
1609
+ });