fix: fix worker choice strategies behavior
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 2 May 2024 11:13:11 +0000 (13:13 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Thu, 2 May 2024 11:13:11 +0000 (13:13 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
12 files changed:
CHANGELOG.md
docs/api.md
src/pools/abstract-pool.ts
src/pools/selection-strategies/abstract-worker-choice-strategy.ts
src/pools/selection-strategies/fair-share-worker-choice-strategy.ts
src/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.ts
src/pools/selection-strategies/least-busy-worker-choice-strategy.ts
src/pools/selection-strategies/least-used-worker-choice-strategy.ts
src/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.ts
tests/pools/abstract-pool.test.mjs
tests/pools/selection-strategies/selection-strategies.test.mjs
tests/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.test.mjs

index e9d570eb2bdc15dc0b287f7c4f9ac6d9e7a2411a..27bbb98753b512b0df974a9d18f72e77f8ba8414 100644 (file)
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Fixed
+
+- Ensure dynamic worker node are initialized with sensible worker node usage default values to avoid worker choice strategies biased decisions.
+- Account for tasks wait time in task execution time computation in worker choice strategies to avoid biased decisions under load with several prioritized task functions and tasks queue enabled.
+
 ## [4.0.0] - 2024-04-30
 
 ### Changed
index dd9f833c711b602948d2afa0dda5f2729f7473cb..1b888173d13db4519a17dd786364ca3cd6247d8b 100644 (file)
@@ -98,7 +98,7 @@ An object with these properties:
 - `workerChoiceStrategy` (optional) - The default worker choice strategy to use in this pool:
 
   - `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in a round robin fashion
-  - `WorkerChoiceStrategies.LEAST_USED`: Submit tasks to the worker with the minimum number of executed, executing and queued tasks
+  - `WorkerChoiceStrategies.LEAST_USED`: Submit tasks to the worker with the minimum number of executing and queued tasks
   - `WorkerChoiceStrategies.LEAST_BUSY`: Submit tasks to the worker with the minimum tasks total execution and wait time
   - `WorkerChoiceStrategies.LEAST_ELU`: Submit tasks to the worker with the minimum event loop utilization (ELU)
   - `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`: Submit tasks to worker by using a [weighted round robin scheduling algorithm](./worker-choice-strategies.md#weighted-round-robin) based on tasks execution time
index 4f60dff0279643a864b8c570eaddf3e3af9e7546..677d33b60d7c2b9a6736229004bb528008edf25d 100644 (file)
@@ -168,7 +168,7 @@ export abstract class AbstractPool<
     this.enqueueTask = this.enqueueTask.bind(this)
 
     if (this.opts.enableEvents === true) {
-      this.initializeEventEmitter()
+      this.initEventEmitter()
     }
     this.workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext<
     Worker,
@@ -281,7 +281,7 @@ export abstract class AbstractPool<
     }
   }
 
-  private initializeEventEmitter (): void {
+  private initEventEmitter (): void {
     this.emitter = new EventEmitterAsyncResource({
       name: `poolifier:${this.type}-${this.worker}-pool`
     })
@@ -1061,7 +1061,7 @@ export abstract class AbstractPool<
   /**
    * Starts the minimum number of workers.
    */
-  private startMinimumNumberOfWorkers (): void {
+  private startMinimumNumberOfWorkers (initWorkerNodeUsage = false): void {
     this.startingMinimumNumberOfWorkers = true
     while (
       this.workerNodes.reduce(
@@ -1070,7 +1070,9 @@ export abstract class AbstractPool<
         0
       ) < this.minimumNumberOfWorkers
     ) {
-      this.createAndSetupWorkerNode()
+      const workerNodeKey = this.createAndSetupWorkerNode()
+      initWorkerNodeUsage &&
+        this.initWorkerNodeUsage(this.workerNodes[workerNodeKey])
     }
     this.startingMinimumNumberOfWorkers = false
   }
@@ -1338,6 +1340,44 @@ export abstract class AbstractPool<
     transferList?: readonly TransferListItem[]
   ): void
 
+  /**
+   * Initializes the worker node usage with sensible default values gathered during runtime.
+   *
+   * @param workerNode - The worker node.
+   */
+  private initWorkerNodeUsage (workerNode: IWorkerNode<Worker, Data>): void {
+    if (
+      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .runTime.aggregate === true
+    ) {
+      workerNode.usage.runTime.aggregate = min(
+        ...this.workerNodes.map(
+          workerNode => workerNode.usage.runTime.aggregate ?? Infinity
+        )
+      )
+    }
+    if (
+      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .waitTime.aggregate === true
+    ) {
+      workerNode.usage.waitTime.aggregate = min(
+        ...this.workerNodes.map(
+          workerNode => workerNode.usage.waitTime.aggregate ?? Infinity
+        )
+      )
+    }
+    if (
+      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements().elu
+        .aggregate === true
+    ) {
+      workerNode.usage.elu.active.aggregate = min(
+        ...this.workerNodes.map(
+          workerNode => workerNode.usage.elu.active.aggregate ?? Infinity
+        )
+      )
+    }
+  }
+
   /**
    * Creates a new, completely set up worker node.
    *
@@ -1368,7 +1408,7 @@ export abstract class AbstractPool<
         if (workerNode.info.dynamic) {
           this.createAndSetupDynamicWorkerNode()
         } else if (!this.startingMinimumNumberOfWorkers) {
-          this.startMinimumNumberOfWorkers()
+          this.startMinimumNumberOfWorkers(true)
         }
       }
       if (
@@ -1394,7 +1434,7 @@ export abstract class AbstractPool<
         !this.startingMinimumNumberOfWorkers &&
         !this.destroying
       ) {
-        this.startMinimumNumberOfWorkers()
+        this.startMinimumNumberOfWorkers(true)
       }
     })
     const workerNodeKey = this.addWorkerNode(workerNode)
@@ -1459,6 +1499,7 @@ export abstract class AbstractPool<
     ) {
       workerNode.info.ready = true
     }
+    this.initWorkerNodeUsage(workerNode)
     this.checkAndEmitDynamicWorkerCreationEvents()
     return workerNodeKey
   }
index 849b8a4f96feed20bcdb6bf22ba7330c8964517f..2b40f0c6de497a630808fbffe6a9ec3f59039e91 100644 (file)
@@ -133,7 +133,7 @@ export abstract class AbstractWorkerChoiceStrategy<
   /**
    * Gets the worker node task runtime.
    * If the task statistics require the average runtime, the average runtime is returned.
-   * If the task statistics require the median runtime , the median runtime is returned.
+   * If the task statistics require the median runtime, the median runtime is returned.
    *
    * @param workerNodeKey - The worker node key.
    * @returns The worker node task runtime.
index ee617d36253cac9c6e8b0970725b139c1ad5b1a5..2f2686e9bb0f5f6a6c18aa5ce51c913e9d3af7c5 100644 (file)
@@ -1,5 +1,4 @@
 import type { IPool } from '../pool.js'
-import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import type { IWorker } from '../worker.js'
 import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import {
@@ -31,7 +30,11 @@ export class FairShareWorkerChoiceStrategy<
       average: true,
       median: false
     },
-    waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
+    waitTime: {
+      aggregate: true,
+      average: true,
+      median: false
+    },
     elu: {
       aggregate: true,
       average: true,
@@ -120,12 +123,13 @@ export class FairShareWorkerChoiceStrategy<
     workerNodeKey: number,
     workerNodeVirtualTaskStartTimestamp: number
   ): number {
-    const workerNodeTaskRunTime =
+    const workerNodeTaskExecutionTime =
+      this.getWorkerNodeTaskWaitTime(workerNodeKey) +
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.opts!.measurement === Measurements.elu
+      (this.opts!.measurement === Measurements.elu
         ? this.getWorkerNodeTaskElu(workerNodeKey)
-        : this.getWorkerNodeTaskRunTime(workerNodeKey)
-    return workerNodeVirtualTaskStartTimestamp + workerNodeTaskRunTime
+        : this.getWorkerNodeTaskRunTime(workerNodeKey))
+    return workerNodeVirtualTaskStartTimestamp + workerNodeTaskExecutionTime
   }
 
   private getWorkerNodeVirtualTaskStartTimestamp (
index b1abe98772d88ce295b17aa6be653a332dd11b04..4048924c5f9e4b86e72c42697d06a15e271aa08b 100644 (file)
@@ -29,7 +29,11 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
       average: true,
       median: false
     },
-    waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
+    waitTime: {
+      aggregate: true,
+      average: true,
+      median: false
+    },
     elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
   }
 
@@ -46,9 +50,9 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
    */
   private workerNodeId = 0
   /**
-   * Worker node virtual task runtime.
+   * Worker node virtual execution time.
    */
-  private workerNodeVirtualTaskRunTime = 0
+  private workerNodeVirtualTaskExecutionTime = 0
 
   /** @inheritDoc */
   public constructor (
@@ -65,7 +69,7 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
     this.resetWorkerNodeKeyProperties()
     this.roundId = 0
     this.workerNodeId = 0
-    this.workerNodeVirtualTaskRunTime = 0
+    this.workerNodeVirtualTaskExecutionTime = 0
     return true
   }
 
@@ -90,19 +94,19 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
         this.workerNodeId = workerNodeKey
         if (
           this.workerNodeId !== this.nextWorkerNodeKey &&
-          this.workerNodeVirtualTaskRunTime !== 0
+          this.workerNodeVirtualTaskExecutionTime !== 0
         ) {
-          this.workerNodeVirtualTaskRunTime = 0
+          this.workerNodeVirtualTaskExecutionTime = 0
         }
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         const workerWeight = this.opts!.weights![workerNodeKey]
         if (
           this.isWorkerNodeReady(workerNodeKey) &&
           workerWeight >= this.roundWeights[roundIndex] &&
-          this.workerNodeVirtualTaskRunTime < workerWeight
+          this.workerNodeVirtualTaskExecutionTime < workerWeight
         ) {
-          this.workerNodeVirtualTaskRunTime =
-            this.workerNodeVirtualTaskRunTime +
+          this.workerNodeVirtualTaskExecutionTime +=
+            this.getWorkerNodeTaskWaitTime(workerNodeKey) +
             this.getWorkerNodeTaskRunTime(workerNodeKey)
           this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey)
           this.nextWorkerNodeKey = workerNodeKey
@@ -135,7 +139,7 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
     if (this.pool.workerNodes.length === 0) {
       this.resetWorkerNodeKeyProperties()
       this.workerNodeId = 0
-      this.workerNodeVirtualTaskRunTime = 0
+      this.workerNodeVirtualTaskExecutionTime = 0
       return true
     }
     if (
index 8db32ac7f1421330e36d9bfb81bddf53392a666c..a9fe3d1c3f0eb779ea47d6907cbb8aed464e46d9 100644 (file)
@@ -75,10 +75,10 @@ export class LeastBusyWorkerChoiceStrategy<
     return this.pool.workerNodes.reduce(
       (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
         return this.isWorkerNodeReady(workerNodeKey) &&
-          (workerNode.usage.runTime.aggregate ?? 0) +
-            (workerNode.usage.waitTime.aggregate ?? 0) <
-            (workerNodes[minWorkerNodeKey].usage.runTime.aggregate ?? 0) +
-              (workerNodes[minWorkerNodeKey].usage.waitTime.aggregate ?? 0)
+          (workerNode.usage.waitTime.aggregate ?? 0) +
+            (workerNode.usage.runTime.aggregate ?? 0) <
+            (workerNodes[minWorkerNodeKey].usage.waitTime.aggregate ?? 0) +
+              (workerNodes[minWorkerNodeKey].usage.runTime.aggregate ?? 0)
           ? workerNodeKey
           : minWorkerNodeKey
       },
index e45d57549580b883c1f6a185dc7701b861004336..cd93d752d7f5009154dc796ee1d1c824b81075af 100644 (file)
@@ -57,11 +57,8 @@ export class LeastUsedWorkerChoiceStrategy<
     return this.pool.workerNodes.reduce(
       (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
         return this.isWorkerNodeReady(workerNodeKey) &&
-          workerNode.usage.tasks.executed +
-            workerNode.usage.tasks.executing +
-            workerNode.usage.tasks.queued <
-            workerNodes[minWorkerNodeKey].usage.tasks.executed +
-              workerNodes[minWorkerNodeKey].usage.tasks.executing +
+          workerNode.usage.tasks.executing + workerNode.usage.tasks.queued <
+            workerNodes[minWorkerNodeKey].usage.tasks.executing +
               workerNodes[minWorkerNodeKey].usage.tasks.queued
           ? workerNodeKey
           : minWorkerNodeKey
index f04847782f540e9edea4603b47afe4c70aa4e299..6ce55c9a2554c0e951d67b503642f800205b9805 100644 (file)
@@ -30,14 +30,18 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
       average: true,
       median: false
     },
-    waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
+    waitTime: {
+      aggregate: true,
+      average: true,
+      median: false
+    },
     elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
   }
 
   /**
-   * Worker node virtual task runtime.
+   * Worker node virtual execution time.
    */
-  private workerNodeVirtualTaskRunTime = 0
+  private workerNodeVirtualTaskExecutionTime = 0
 
   /** @inheritDoc */
   public constructor (
@@ -51,7 +55,7 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
   /** @inheritDoc */
   public reset (): boolean {
     this.resetWorkerNodeKeyProperties()
-    this.workerNodeVirtualTaskRunTime = 0
+    this.workerNodeVirtualTaskExecutionTime = 0
     return true
   }
 
@@ -75,7 +79,7 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
       return true
     }
     if (this.nextWorkerNodeKey === workerNodeKey) {
-      this.workerNodeVirtualTaskRunTime = 0
+      this.workerNodeVirtualTaskExecutionTime = 0
       if (this.nextWorkerNodeKey > this.pool.workerNodes.length - 1) {
         this.nextWorkerNodeKey = this.pool.workerNodes.length - 1
       }
@@ -93,9 +97,11 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
     const workerWeight =
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       this.opts!.weights![this.nextWorkerNodeKey ?? this.previousWorkerNodeKey]
-    if (this.workerNodeVirtualTaskRunTime < workerWeight) {
-      this.workerNodeVirtualTaskRunTime =
-        this.workerNodeVirtualTaskRunTime +
+    if (this.workerNodeVirtualTaskExecutionTime < workerWeight) {
+      this.workerNodeVirtualTaskExecutionTime +=
+        this.getWorkerNodeTaskWaitTime(
+          this.nextWorkerNodeKey ?? this.previousWorkerNodeKey
+        ) +
         this.getWorkerNodeTaskRunTime(
           this.nextWorkerNodeKey ?? this.previousWorkerNodeKey
         )
@@ -104,7 +110,7 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
         this.nextWorkerNodeKey === this.pool.workerNodes.length - 1
           ? 0
           : (this.nextWorkerNodeKey ?? this.previousWorkerNodeKey) + 1
-      this.workerNodeVirtualTaskRunTime = 0
+      this.workerNodeVirtualTaskExecutionTime = 0
     }
     return this.nextWorkerNodeKey
   }
index 313cc77a13317d369ee40f57d09a1f59d85a5ebf..42e2959be9230185635759f07d6916769f030ae3 100644 (file)
@@ -468,8 +468,8 @@ describe('Abstract pool test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -507,8 +507,8 @@ describe('Abstract pool test suite', () => {
         median: true
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -546,8 +546,8 @@ describe('Abstract pool test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
index 345c2af8bc6f349d88198e1e92000a81390929d2..e4d1dd07ece0a5aeb8702a31c5386eb45d468ae0 100644 (file)
@@ -112,7 +112,7 @@ describe('Selection strategies test suite', () => {
         expect(
           pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
             workerChoiceStrategy
-          ).workerNodeVirtualTaskRunTime
+          ).workerNodeVirtualTaskExecutionTime
         ).toBe(0)
       } else if (
         workerChoiceStrategy ===
@@ -121,7 +121,7 @@ describe('Selection strategies test suite', () => {
         expect(
           pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
             workerChoiceStrategy
-          ).workerNodeVirtualTaskRunTime
+          ).workerNodeVirtualTaskExecutionTime
         ).toBe(0)
         expect(
           pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
@@ -1116,8 +1116,8 @@ describe('Selection strategies test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -1142,8 +1142,8 @@ describe('Selection strategies test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -1183,9 +1183,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: expect.objectContaining({
           idle: expect.objectContaining({
             history: expect.any(CircularArray)
@@ -1209,6 +1209,16 @@ describe('Selection strategies test suite', () => {
       } else {
         expect(workerNode.usage.runTime.average).toBeGreaterThan(0)
       }
+      if (workerNode.usage.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.average == null) {
+        expect(workerNode.usage.waitTime.average).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.average).toBeGreaterThan(0)
+      }
       if (workerNode.usage.elu.active.aggregate == null) {
         expect(workerNode.usage.elu.active.aggregate).toBeUndefined()
       } else {
@@ -1269,9 +1279,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: expect.objectContaining({
           idle: expect.objectContaining({
             history: expect.any(CircularArray)
@@ -1295,6 +1305,16 @@ describe('Selection strategies test suite', () => {
       } else {
         expect(workerNode.usage.runTime.average).toBeGreaterThan(0)
       }
+      if (workerNode.usage.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.average == null) {
+        expect(workerNode.usage.waitTime.average).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.average).toBeGreaterThan(0)
+      }
       if (workerNode.usage.elu.active.aggregate == null) {
         expect(workerNode.usage.elu.active.aggregate).toBeUndefined()
       } else {
@@ -1360,9 +1380,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: expect.objectContaining({
           idle: expect.objectContaining({
             history: expect.any(CircularArray)
@@ -1386,6 +1406,16 @@ describe('Selection strategies test suite', () => {
       } else {
         expect(workerNode.usage.runTime.median).toBeGreaterThan(0)
       }
+      if (workerNode.usage.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.median == null) {
+        expect(workerNode.usage.waitTime.median).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.median).toBeGreaterThan(0)
+      }
       if (workerNode.usage.elu.active.aggregate == null) {
         expect(workerNode.usage.elu.active.aggregate).toBeUndefined()
       } else {
@@ -1494,8 +1524,8 @@ describe('Selection strategies test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -1520,8 +1550,8 @@ describe('Selection strategies test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -1561,9 +1591,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: {
           idle: {
             history: new CircularArray()
@@ -1587,6 +1617,16 @@ describe('Selection strategies test suite', () => {
       } else {
         expect(workerNode.usage.runTime.average).toBeGreaterThan(0)
       }
+      if (workerNode.usage.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.average == null) {
+        expect(workerNode.usage.waitTime.average).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.average).toBeGreaterThan(0)
+      }
     }
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
@@ -1601,7 +1641,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
-      ).workerNodeVirtualTaskRunTime
+      ).workerNodeVirtualTaskExecutionTime
     ).toBeGreaterThanOrEqual(0)
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1635,9 +1675,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: {
           idle: {
             history: new CircularArray()
@@ -1661,6 +1701,16 @@ describe('Selection strategies test suite', () => {
       } else {
         expect(workerNode.usage.runTime.average).toBeGreaterThan(0)
       }
+      if (workerNode.usage.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.average == null) {
+        expect(workerNode.usage.waitTime.average).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.average).toBeGreaterThan(0)
+      }
     }
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
@@ -1675,7 +1725,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
-      ).workerNodeVirtualTaskRunTime
+      ).workerNodeVirtualTaskExecutionTime
     ).toBeGreaterThanOrEqual(0)
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1714,9 +1764,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: {
           idle: {
             history: new CircularArray()
@@ -1740,6 +1790,16 @@ describe('Selection strategies test suite', () => {
       } else {
         expect(workerNode.usage.runTime.median).toBeGreaterThan(0)
       }
+      if (workerNode.usage.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.median == null) {
+        expect(workerNode.usage.waitTime.median).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.median).toBeGreaterThan(0)
+      }
     }
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
@@ -1754,7 +1814,7 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
         pool.workerChoiceStrategiesContext.defaultWorkerChoiceStrategy
-      ).workerNodeVirtualTaskRunTime
+      ).workerNodeVirtualTaskExecutionTime
     ).toBeGreaterThanOrEqual(0)
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1872,8 +1932,8 @@ describe('Selection strategies test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -1898,8 +1958,8 @@ describe('Selection strategies test suite', () => {
         median: false
       },
       waitTime: {
-        aggregate: false,
-        average: false,
+        aggregate: true,
+        average: true,
         median: false
       },
       elu: {
@@ -1942,9 +2002,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: {
           idle: {
             history: new CircularArray()
@@ -1958,6 +2018,26 @@ describe('Selection strategies test suite', () => {
       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.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.average == null) {
+        expect(workerNode.usage.waitTime.average).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.average).toBeGreaterThan(0)
+      }
     }
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
@@ -2026,9 +2106,9 @@ describe('Selection strategies test suite', () => {
         runTime: expect.objectContaining({
           history: expect.any(CircularArray)
         }),
-        waitTime: {
-          history: new CircularArray()
-        },
+        waitTime: expect.objectContaining({
+          history: expect.any(CircularArray)
+        }),
         elu: {
           idle: {
             history: new CircularArray()
@@ -2042,6 +2122,26 @@ describe('Selection strategies test suite', () => {
       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.waitTime.aggregate == null) {
+        expect(workerNode.usage.waitTime.aggregate).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.aggregate).toBeGreaterThan(0)
+      }
+      if (workerNode.usage.waitTime.average == null) {
+        expect(workerNode.usage.waitTime.average).toBeUndefined()
+      } else {
+        expect(workerNode.usage.waitTime.average).toBeGreaterThan(0)
+      }
     }
     expect(
       pool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
index 80c5ec7c65b7f808e230870060bd6270da111100..b3ea806ef68ac2b4a57bed494209618ab449346a 100644 (file)
@@ -33,7 +33,7 @@ describe('Weighted round robin strategy worker choice strategy test suite', () =
     expect(strategy.reset()).toBe(true)
     expect(strategy.nextWorkerNodeKey).toBe(0)
     expect(strategy.previousWorkerNodeKey).toBe(0)
-    expect(strategy.workerNodeVirtualTaskRunTime).toBe(0)
+    expect(strategy.workerNodeVirtualTaskExecutionTime).toBe(0)
   })
 
   it('Verify that IWRR reset() resets internals', () => {
@@ -53,6 +53,6 @@ describe('Weighted round robin strategy worker choice strategy test suite', () =
     expect(strategy.previousWorkerNodeKey).toBe(0)
     expect(strategy.roundId).toBe(0)
     expect(strategy.workerNodeId).toBe(0)
-    expect(strategy.workerNodeVirtualTaskRunTime).toBe(0)
+    expect(strategy.workerNodeVirtualTaskExecutionTime).toBe(0)
   })
 })