X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=tests%2Fpools%2Fselection-strategies%2Fselection-strategies.test.js;h=3329e1dc718f3565202e1344479248013a6171c8;hb=2324f8c9233f6e036eee418327c46bb7e71356b9;hp=d609915842011ebb8651c95c8ae98bf06a5064c3;hpb=4a59691c9f48b3a6602510aeece809e2a0fe14c1;p=poolifier.git diff --git a/tests/pools/selection-strategies/selection-strategies.test.js b/tests/pools/selection-strategies/selection-strategies.test.js index d6099158..3329e1dc 100644 --- a/tests/pools/selection-strategies/selection-strategies.test.js +++ b/tests/pools/selection-strategies/selection-strategies.test.js @@ -1,12 +1,12 @@ const { expect } = require('expect') const { - WorkerChoiceStrategies, + DynamicClusterPool, DynamicThreadPool, + FixedClusterPool, FixedThreadPool, - FixedClusterPool + WorkerChoiceStrategies } = require('../../../lib') const { CircularArray } = require('../../../lib/circular-array') -const TestUtils = require('../../test-utils') describe('Selection strategies test suite', () => { const min = 0 @@ -66,6 +66,43 @@ describe('Selection strategies test suite', () => { expect(pool.workerChoiceStrategyContext.workerChoiceStrategy).toBe( workerChoiceStrategy ) + expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ + retries: 6, + runTime: { median: false }, + waitTime: { median: false }, + elu: { median: false } + }) + expect(pool.workerChoiceStrategyContext.opts).toStrictEqual({ + retries: 6, + runTime: { median: false }, + waitTime: { median: false }, + elu: { median: false } + }) + await pool.destroy() + } + for (const workerChoiceStrategy of Object.values(WorkerChoiceStrategies)) { + const pool = new DynamicClusterPool( + min, + max, + './tests/worker-files/cluster/testWorker.js' + ) + pool.setWorkerChoiceStrategy(workerChoiceStrategy, { retries: 3 }) + expect(pool.opts.workerChoiceStrategy).toBe(workerChoiceStrategy) + expect(pool.workerChoiceStrategyContext.workerChoiceStrategy).toBe( + workerChoiceStrategy + ) + expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ + retries: 3, + runTime: { median: false }, + waitTime: { median: false }, + elu: { median: false } + }) + expect(pool.workerChoiceStrategyContext.opts).toStrictEqual({ + retries: 3, + runTime: { median: false }, + waitTime: { median: false }, + elu: { median: false } + }) await pool.destroy() } }) @@ -76,47 +113,94 @@ describe('Selection strategies test suite', () => { './tests/worker-files/thread/testWorker.js' ) for (const workerChoiceStrategy of Object.values(WorkerChoiceStrategies)) { - if (workerChoiceStrategy === WorkerChoiceStrategies.ROUND_ROBIN) { + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).previousWorkerNodeKey + ).toBe(0) + if ( + workerChoiceStrategy === WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN + ) { + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).defaultWorkerWeight + ).toBeGreaterThan(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).nextWorkerNodeId + ).workerNodeVirtualTaskRunTime ).toBe(0) - } else if (workerChoiceStrategy === WorkerChoiceStrategies.FAIR_SHARE) { + } else if ( + workerChoiceStrategy === + WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN + ) { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).workersVirtualTaskEndTimestamp - ).toBeInstanceOf(Array) + ).defaultWorkerWeight + ).toBeGreaterThan(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length + ).workerNodeVirtualTaskRunTime ).toBe(0) - } else if ( - workerChoiceStrategy === WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN - ) { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).currentWorkerNodeId + ).roundId ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).defaultWorkerWeight - ).toBeGreaterThan(0) + ).workerNodeId + ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).workerVirtualTaskRunTime - ).toBe(0) + ).roundWeights + ).toStrictEqual([ + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).defaultWorkerWeight + ]) } } await pool.destroy() }) - it('Verify ROUND_ROBIN strategy default tasks usage statistics requirements', async () => { + it('Verify ROUND_ROBIN strategy default policy', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN + let pool = new FixedThreadPool( + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + await pool.destroy() + pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify ROUND_ROBIN strategy default tasks statistics requirements', async () => { const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN let pool = new FixedThreadPool( max, @@ -136,7 +220,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) await pool.destroy() pool = new DynamicThreadPool( @@ -158,17 +246,22 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) // We need to clean up the resources after our test await pool.destroy() }) it('Verify ROUND_ROBIN strategy can be run in a fixed pool', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN const pool = new FixedThreadPool( max, './tests/worker-files/thread/testWorker.js', - { workerChoiceStrategy: WorkerChoiceStrategies.ROUND_ROBIN } + { workerChoiceStrategy } ) // TODO: Create a better test to cover `RoundRobinWorkerChoiceStrategy#choose` const promises = new Set() @@ -178,43 +271,52 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { executed: maxMultiplier, executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, runTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) } expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - WorkerChoiceStrategies.ROUND_ROBIN - ).nextWorkerNodeId + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toBe(pool.workerNodes.length - 1) // We need to clean up the resources after our test await pool.destroy() }) it('Verify ROUND_ROBIN strategy can be run in a dynamic pool', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN const pool = new DynamicThreadPool( min, max, './tests/worker-files/thread/testWorker.js', - { workerChoiceStrategy: WorkerChoiceStrategies.ROUND_ROBIN } + { workerChoiceStrategy } ) // TODO: Create a better test to cover `RoundRobinWorkerChoiceStrategy#choose` const promises = new Set() @@ -224,33 +326,45 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, runTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) } expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - WorkerChoiceStrategies.ROUND_ROBIN - ).nextWorkerNodeId + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toBe(pool.workerNodes.length - 1) // We need to clean up the resources after our test await pool.destroy() }) @@ -290,14 +404,24 @@ describe('Selection strategies test suite', () => { ) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).nextWorkerNodeId + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBeDefined() pool.setWorkerChoiceStrategy(workerChoiceStrategy) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).nextWorkerNodeId + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBe(0) await pool.destroy() pool = new DynamicThreadPool( @@ -308,20 +432,56 @@ describe('Selection strategies test suite', () => { ) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).nextWorkerNodeId + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBeDefined() pool.setWorkerChoiceStrategy(workerChoiceStrategy) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).nextWorkerNodeId + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBe(0) // We need to clean up the resources after our test await pool.destroy() }) - it('Verify LEAST_USED strategy default tasks usage statistics requirements', async () => { + it('Verify LEAST_USED strategy default policy', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED + let pool = new FixedThreadPool( + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + await pool.destroy() + pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify LEAST_USED strategy default tasks statistics requirements', async () => { const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED let pool = new FixedThreadPool( max, @@ -341,7 +501,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) await pool.destroy() pool = new DynamicThreadPool( @@ -363,7 +527,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) // We need to clean up the resources after our test await pool.destroy() @@ -383,28 +551,45 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, runTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) // We need to clean up the resources after our test await pool.destroy() }) @@ -424,34 +609,76 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, runTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify LEAST_BUSY strategy default policy', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY + let pool = new FixedThreadPool( + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + await pool.destroy() + pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) // We need to clean up the resources after our test await pool.destroy() }) - it('Verify LEAST_BUSY strategy default tasks usage statistics requirements', async () => { + it('Verify LEAST_BUSY strategy default tasks statistics requirements', async () => { const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY let pool = new FixedThreadPool( max, @@ -471,7 +698,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) await pool.destroy() pool = new DynamicThreadPool( @@ -493,7 +724,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) // We need to clean up the resources after our test await pool.destroy() @@ -513,36 +748,55 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: 0, - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, - waitTime: { - aggregate: expect.any(Number), - average: 0, - median: 0, + }), + waitTime: expect.objectContaining({ history: expect.any(CircularArray) - }, - elu: undefined + }), + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) - expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0) - expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual( + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( max * maxMultiplier ) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThanOrEqual(0) - expect(workerNode.workerUsage.waitTime.aggregate).toBeGreaterThanOrEqual( - 0 - ) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.waitTime.aggregate == null) { + expect(workerNode.usage.waitTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0) + } } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) // We need to clean up the resources after our test await pool.destroy() }) @@ -562,39 +816,86 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: 0, - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, - waitTime: { - aggregate: expect.any(Number), - average: 0, - median: 0, + }), + waitTime: expect.objectContaining({ history: expect.any(CircularArray) - }, - elu: undefined + }), + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) - expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0) - expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual( + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( max * maxMultiplier ) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0) - expect(workerNode.workerUsage.waitTime.aggregate).toBeGreaterThan(0) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.waitTime.aggregate == null) { + expect(workerNode.usage.waitTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0) + } } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) // We need to clean up the resources after our test await pool.destroy() }) - it('Verify LEAST_ELU strategy default tasks usage statistics requirements', async () => { + it('Verify LEAST_ELU strategy default policy', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU + let pool = new FixedThreadPool( + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + await pool.destroy() + pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify LEAST_ELU strategy default tasks statistics requirements', async () => { const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU let pool = new FixedThreadPool( max, @@ -604,13 +905,21 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.getTaskStatisticsRequirements() ).toStrictEqual({ - runTime: false, - avgRunTime: false, - medRunTime: false, - waitTime: false, - avgWaitTime: false, - medWaitTime: false, - elu: true + runTime: { + aggregate: false, + average: false, + median: false + }, + waitTime: { + aggregate: false, + average: false, + median: false + }, + elu: { + aggregate: true, + average: false, + median: false + } }) await pool.destroy() pool = new DynamicThreadPool( @@ -622,13 +931,21 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.getTaskStatisticsRequirements() ).toStrictEqual({ - runTime: false, - avgRunTime: false, - medRunTime: false, - waitTime: false, - avgWaitTime: false, - medWaitTime: false, - elu: true + runTime: { + aggregate: false, + average: false, + median: false + }, + waitTime: { + aggregate: false, + average: false, + median: false + }, + elu: { + aggregate: true, + average: false, + median: false + } }) // We need to clean up the resources after our test await pool.destroy() @@ -641,57 +958,173 @@ describe('Selection strategies test suite', () => { { workerChoiceStrategy: WorkerChoiceStrategies.LEAST_ELU } ) // TODO: Create a better test to cover `LeastEluWorkerChoiceStrategy#choose` + const promises = new Set() const maxMultiplier = 2 for (let i = 0; i < max * maxMultiplier; i++) { - await pool.execute() - if (i !== max * maxMultiplier - 1) await TestUtils.sleep(500) + promises.add(pool.execute()) } + await Promise.all(promises) for (const workerNode of pool.workerNodes) { - const expectedWorkerUsage = { + expect(workerNode.usage).toStrictEqual({ tasks: { executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, runTime: { - aggregation: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, waitTime: { - aggregation: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) - } - } - if (workerNode.workerUsage.elu === undefined) { - expect(workerNode.workerUsage).toStrictEqual({ - ...expectedWorkerUsage, - elu: undefined + history: new CircularArray() + }, + elu: expect.objectContaining({ + idle: expect.objectContaining({ + history: expect.any(CircularArray) + }), + active: expect.objectContaining({ + history: expect.any(CircularArray) + }) }) + }) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) + if (workerNode.usage.elu.active.aggregate == null) { + expect(workerNode.usage.elu.active.aggregate).toBeUndefined() } else { - expect(workerNode.workerUsage).toStrictEqual({ - ...expectedWorkerUsage, - elu: { - active: expect.any(Number), - idle: 0, - utilization: 1 - } - }) + expect(workerNode.usage.elu.active.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.elu.idle.aggregate == null) { + expect(workerNode.usage.elu.idle.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.idle.aggregate).toBeGreaterThanOrEqual(0) } - expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0) - expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual( + if (workerNode.usage.elu.utilization == null) { + expect(workerNode.usage.elu.utilization).toBeUndefined() + } else { + expect(workerNode.usage.elu.utilization).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.elu.utilization).toBeLessThanOrEqual(1) + } + } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify LEAST_ELU strategy can be run in a dynamic pool', async () => { + const pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy: WorkerChoiceStrategies.LEAST_ELU } + ) + // TODO: Create a better test to cover `LeastEluWorkerChoiceStrategy#choose` + const promises = new Set() + const maxMultiplier = 2 + for (let i = 0; i < max * maxMultiplier; i++) { + promises.add(pool.execute()) + } + await Promise.all(promises) + for (const workerNode of pool.workerNodes) { + expect(workerNode.usage).toStrictEqual({ + tasks: { + executed: expect.any(Number), + executing: 0, + queued: 0, + maxQueued: 0, + stolen: 0, + failed: 0 + }, + runTime: { + history: new CircularArray() + }, + waitTime: { + history: new CircularArray() + }, + elu: expect.objectContaining({ + idle: expect.objectContaining({ + history: expect.any(CircularArray) + }), + active: expect.objectContaining({ + history: expect.any(CircularArray) + }) + }) + }) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( max * maxMultiplier ) + if (workerNode.usage.elu.active.aggregate == null) { + expect(workerNode.usage.elu.active.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.active.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.elu.idle.aggregate == null) { + expect(workerNode.usage.elu.idle.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.idle.aggregate).toBeGreaterThanOrEqual(0) + } + if (workerNode.usage.elu.utilization == null) { + expect(workerNode.usage.elu.utilization).toBeUndefined() + } else { + expect(workerNode.usage.elu.utilization).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.elu.utilization).toBeLessThanOrEqual(1) + } } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) // We need to clean up the resources after our test await pool.destroy() }) - it('Verify FAIR_SHARE strategy default tasks usage statistics requirements', async () => { + it('Verify FAIR_SHARE strategy default policy', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE + let pool = new FixedThreadPool( + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + await pool.destroy() + pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify FAIR_SHARE strategy default tasks statistics requirements', async () => { const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE let pool = new FixedThreadPool( max, @@ -711,7 +1144,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: true, + average: true, + median: false + } }) await pool.destroy() pool = new DynamicThreadPool( @@ -733,7 +1170,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: true, + average: true, + median: false + } }) // We need to clean up the resources after our test await pool.destroy() @@ -753,35 +1194,72 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: expect.any(Number), - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: expect.objectContaining({ + idle: expect.objectContaining({ + history: expect.any(CircularArray) + }), + active: expect.objectContaining({ + history: expect.any(CircularArray) + }) + }) }) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0) - expect(workerNode.workerUsage.runTime.average).toBeGreaterThan(0) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.runTime.average == null) { + expect(workerNode.usage.runTime.average).toBeUndefined() + } else { + expect(workerNode.usage.runTime.average).toBeGreaterThan(0) + } + if (workerNode.usage.elu.active.aggregate == null) { + expect(workerNode.usage.elu.active.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.active.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.elu.idle.aggregate == null) { + expect(workerNode.usage.elu.idle.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.idle.aggregate).toBeGreaterThanOrEqual(0) + } + if (workerNode.usage.elu.utilization == null) { + expect(workerNode.usage.elu.utilization).toBeUndefined() + } else { + expect(workerNode.usage.elu.utilization).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.elu.utilization).toBeLessThanOrEqual(1) + } + expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0) } expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(pool.workerNodes.length) + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) // We need to clean up the resources after our test await pool.destroy() }) @@ -801,35 +1279,72 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: expect.any(Number), - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: expect.objectContaining({ + idle: expect.objectContaining({ + history: expect.any(CircularArray) + }), + active: expect.objectContaining({ + history: expect.any(CircularArray) + }) + }) }) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0) - expect(workerNode.workerUsage.runTime.average).toBeGreaterThan(0) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.runTime.average == null) { + expect(workerNode.usage.runTime.average).toBeUndefined() + } else { + expect(workerNode.usage.runTime.average).toBeGreaterThan(0) + } + if (workerNode.usage.elu.active.aggregate == null) { + expect(workerNode.usage.elu.active.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.active.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.elu.idle.aggregate == null) { + expect(workerNode.usage.elu.idle.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.idle.aggregate).toBeGreaterThanOrEqual(0) + } + if (workerNode.usage.elu.utilization == null) { + expect(workerNode.usage.elu.utilization).toBeUndefined() + } else { + expect(workerNode.usage.elu.utilization).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.elu.utilization).toBeLessThanOrEqual(1) + } + expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0) } expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(pool.workerNodes.length) + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) // We need to clean up the resources after our test await pool.destroy() }) @@ -854,35 +1369,72 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: 0, - median: expect.any(Number), + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: expect.objectContaining({ + idle: expect.objectContaining({ + history: expect.any(CircularArray) + }), + active: expect.objectContaining({ + history: expect.any(CircularArray) + }) + }) }) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0) - expect(workerNode.workerUsage.runTime.median).toBeGreaterThan(0) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.runTime.median == null) { + expect(workerNode.usage.runTime.median).toBeUndefined() + } else { + expect(workerNode.usage.runTime.median).toBeGreaterThan(0) + } + if (workerNode.usage.elu.active.aggregate == null) { + expect(workerNode.usage.elu.active.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.active.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.elu.idle.aggregate == null) { + expect(workerNode.usage.elu.idle.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.elu.idle.aggregate).toBeGreaterThanOrEqual(0) + } + if (workerNode.usage.elu.utilization == null) { + expect(workerNode.usage.elu.utilization).toBeUndefined() + } else { + expect(workerNode.usage.elu.utilization).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.elu.utilization).toBeLessThanOrEqual(1) + } + expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeGreaterThan(0) } expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(pool.workerNodes.length) + ).nextWorkerNodeKey + ).toEqual(expect.any(Number)) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) // We need to clean up the resources after our test await pool.destroy() }) @@ -893,75 +1445,61 @@ describe('Selection strategies test suite', () => { max, './tests/worker-files/thread/testWorker.js' ) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp - ).toBeInstanceOf(Array) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(0) - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp[0] = performance.now() - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(1) + for (const workerNode of pool.workerNodes) { + workerNode.strategyData = { + virtualTaskEndTimestamp: performance.now() + } + } pool.setWorkerChoiceStrategy(workerChoiceStrategy) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp - ).toBeInstanceOf(Array) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(0) + for (const workerNode of pool.workerNodes) { + expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeUndefined() + } await pool.destroy() pool = new DynamicThreadPool( min, max, './tests/worker-files/thread/testWorker.js' ) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp - ).toBeInstanceOf(Array) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(0) - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp[0] = performance.now() - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(1) + for (const workerNode of pool.workerNodes) { + workerNode.strategyData = { + virtualTaskEndTimestamp: performance.now() + } + } pool.setWorkerChoiceStrategy(workerChoiceStrategy) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp - ).toBeInstanceOf(Array) - expect( - pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workersVirtualTaskEndTimestamp.length - ).toBe(0) + for (const workerNode of pool.workerNodes) { + expect(workerNode.strategyData.virtualTaskEndTimestamp).toBeUndefined() + } + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify WEIGHTED_ROUND_ROBIN strategy default policy', async () => { + const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN + let pool = new FixedThreadPool( + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + await pool.destroy() + pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) // We need to clean up the resources after our test await pool.destroy() }) - it('Verify WEIGHTED_ROUND_ROBIN strategy default tasks usage statistics requirements', async () => { + it('Verify WEIGHTED_ROUND_ROBIN strategy default tasks statistics requirements', async () => { const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN let pool = new FixedThreadPool( max, @@ -981,7 +1519,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) await pool.destroy() pool = new DynamicThreadPool( @@ -1003,7 +1545,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) // We need to clean up the resources after our test await pool.destroy() @@ -1023,34 +1569,55 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: expect.any(Number), - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) - expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0) - expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual( + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( max * maxMultiplier ) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThanOrEqual(0) - expect(workerNode.workerUsage.runTime.average).toBeGreaterThanOrEqual(0) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.runTime.average == null) { + expect(workerNode.usage.runTime.average).toBeUndefined() + } else { + expect(workerNode.usage.runTime.average).toBeGreaterThan(0) + } } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy @@ -1059,7 +1626,7 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).workerVirtualTaskRunTime + ).workerNodeVirtualTaskRunTime ).toBeGreaterThanOrEqual(0) // We need to clean up the resources after our test await pool.destroy() @@ -1080,34 +1647,55 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: expect.any(Number), - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) - expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0) - expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual( + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( max * maxMultiplier ) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0) - expect(workerNode.workerUsage.runTime.average).toBeGreaterThan(0) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.runTime.average == null) { + expect(workerNode.usage.runTime.average).toBeUndefined() + } else { + expect(workerNode.usage.runTime.average).toBeGreaterThan(0) + } } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy @@ -1116,7 +1704,7 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).workerVirtualTaskRunTime + ).workerNodeVirtualTaskRunTime ).toBeGreaterThanOrEqual(0) // We need to clean up the resources after our test await pool.destroy() @@ -1142,34 +1730,55 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: expect.any(Number), - average: 0, - median: expect.any(Number), + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) - expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0) - expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual( + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( max * maxMultiplier ) - expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0) - expect(workerNode.workerUsage.runTime.median).toBeGreaterThan(0) + if (workerNode.usage.runTime.aggregate == null) { + expect(workerNode.usage.runTime.aggregate).toBeUndefined() + } else { + expect(workerNode.usage.runTime.aggregate).toBeGreaterThan(0) + } + if (workerNode.usage.runTime.median == null) { + expect(workerNode.usage.runTime.median).toBeUndefined() + } else { + expect(workerNode.usage.runTime.median).toBeGreaterThan(0) + } } + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy @@ -1178,7 +1787,7 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).workerVirtualTaskRunTime + ).workerNodeVirtualTaskRunTime ).toBeGreaterThanOrEqual(0) // We need to clean up the resources after our test await pool.destroy() @@ -1193,7 +1802,12 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).currentWorkerNodeId + ).nextWorkerNodeKey + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).previousWorkerNodeKey ).toBeDefined() expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1203,13 +1817,18 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).workerVirtualTaskRunTime + ).workerNodeVirtualTaskRunTime ).toBeDefined() pool.setWorkerChoiceStrategy(workerChoiceStrategy) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentWorkerNodeId + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1218,8 +1837,8 @@ describe('Selection strategies test suite', () => { ).toBeGreaterThan(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workerVirtualTaskRunTime + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).workerNodeVirtualTaskRunTime ).toBe(0) await pool.destroy() pool = new DynamicThreadPool( @@ -1230,7 +1849,12 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).currentWorkerNodeId + ).nextWorkerNodeKey + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).previousWorkerNodeKey ).toBeDefined() expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1240,13 +1864,18 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).workerVirtualTaskRunTime + ).workerNodeVirtualTaskRunTime ).toBeDefined() pool.setWorkerChoiceStrategy(workerChoiceStrategy) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentWorkerNodeId + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1255,14 +1884,41 @@ describe('Selection strategies test suite', () => { ).toBeGreaterThan(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).workerVirtualTaskRunTime + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).workerNodeVirtualTaskRunTime ).toBe(0) // We need to clean up the resources after our test await pool.destroy() }) - it('Verify INTERLEAVED_WEIGHTED_ROUND_ROBIN strategy default tasks usage statistics requirements', async () => { + it('Verify INTERLEAVED_WEIGHTED_ROUND_ROBIN strategy default policy', async () => { + const workerChoiceStrategy = + WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN + let pool = new FixedThreadPool( + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + await pool.destroy() + pool = new DynamicThreadPool( + min, + max, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy } + ) + expect(pool.workerChoiceStrategyContext.getStrategyPolicy()).toStrictEqual({ + dynamicWorkerUsage: false, + dynamicWorkerReady: true + }) + // We need to clean up the resources after our test + await pool.destroy() + }) + + it('Verify INTERLEAVED_WEIGHTED_ROUND_ROBIN strategy default tasks statistics requirements', async () => { const workerChoiceStrategy = WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN let pool = new FixedThreadPool( @@ -1274,8 +1930,8 @@ describe('Selection strategies test suite', () => { pool.workerChoiceStrategyContext.getTaskStatisticsRequirements() ).toStrictEqual({ runTime: { - aggregate: false, - average: false, + aggregate: true, + average: true, median: false }, waitTime: { @@ -1283,7 +1939,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) await pool.destroy() pool = new DynamicThreadPool( @@ -1296,8 +1956,8 @@ describe('Selection strategies test suite', () => { pool.workerChoiceStrategyContext.getTaskStatisticsRequirements() ).toStrictEqual({ runTime: { - aggregate: false, - average: false, + aggregate: true, + average: true, median: false }, waitTime: { @@ -1305,7 +1965,11 @@ describe('Selection strategies test suite', () => { average: false, median: false }, - elu: false + elu: { + aggregate: false, + average: false, + median: false + } }) // We need to clean up the resources after our test await pool.destroy() @@ -1328,27 +1992,34 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: 0, - average: 0, - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) } expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1358,13 +2029,23 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentRoundId + ).roundId + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).workerNodeId ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentWorkerNodeId + ).nextWorkerNodeKey ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy @@ -1396,27 +2077,34 @@ describe('Selection strategies test suite', () => { } await Promise.all(promises) for (const workerNode of pool.workerNodes) { - expect(workerNode.workerUsage).toStrictEqual({ + expect(workerNode.usage).toStrictEqual({ tasks: { - executed: maxMultiplier, + executed: expect.any(Number), executing: 0, queued: 0, + maxQueued: 0, + stolen: 0, failed: 0 }, - runTime: { - aggregate: 0, - average: 0, - median: 0, + runTime: expect.objectContaining({ history: expect.any(CircularArray) - }, + }), waitTime: { - aggregate: 0, - average: 0, - median: 0, - history: expect.any(CircularArray) + history: new CircularArray() }, - elu: undefined + elu: { + idle: { + history: new CircularArray() + }, + active: { + history: new CircularArray() + } + } }) + expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0) + expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual( + max * maxMultiplier + ) } expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1426,13 +2114,23 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentRoundId + ).roundId + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).workerNodeId ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentWorkerNodeId + ).nextWorkerNodeKey ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey + ).toEqual(expect.any(Number)) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy @@ -1456,12 +2154,22 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).currentRoundId + ).roundId + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).workerNodeId + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).nextWorkerNodeKey ).toBeDefined() expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).currentWorkerNodeId + ).previousWorkerNodeKey ).toBeDefined() expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1476,13 +2184,23 @@ describe('Selection strategies test suite', () => { pool.setWorkerChoiceStrategy(workerChoiceStrategy) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy - ).currentRoundId + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).roundId ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentWorkerNodeId + ).workerNodeId + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1491,7 +2209,7 @@ describe('Selection strategies test suite', () => { ).toBeGreaterThan(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy + pool.workerChoiceStrategyContext.workerChoiceStrategy ).roundWeights ).toStrictEqual([ pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1507,12 +2225,22 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).currentRoundId + ).roundId + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).workerNodeId + ).toBeDefined() + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + workerChoiceStrategy + ).nextWorkerNodeKey ).toBeDefined() expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( workerChoiceStrategy - ).currentWorkerNodeId + ).previousWorkerNodeKey ).toBeDefined() expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1528,7 +2256,22 @@ describe('Selection strategies test suite', () => { expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( pool.workerChoiceStrategyContext.workerChoiceStrategy - ).currentWorkerNodeId + ).roundId + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).workerNodeId + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).nextWorkerNodeKey + ).toBe(0) + expect( + pool.workerChoiceStrategyContext.workerChoiceStrategies.get( + pool.workerChoiceStrategyContext.workerChoiceStrategy + ).previousWorkerNodeKey ).toBe(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( @@ -1537,7 +2280,7 @@ describe('Selection strategies test suite', () => { ).toBeGreaterThan(0) expect( pool.workerChoiceStrategyContext.workerChoiceStrategies.get( - workerChoiceStrategy + pool.workerChoiceStrategyContext.workerChoiceStrategy ).roundWeights ).toStrictEqual([ pool.workerChoiceStrategyContext.workerChoiceStrategies.get(