feat: add statistics accounting to ELU fields
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 9 Jun 2023 16:52:39 +0000 (18:52 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 9 Jun 2023 16:52:39 +0000 (18:52 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
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/least-busy-worker-choice-strategy.ts
src/pools/selection-strategies/least-elu-worker-choice-strategy.ts
src/pools/selection-strategies/selection-strategies-types.ts
src/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.ts
src/pools/worker.ts
src/utils.ts
tests/pools/abstract/abstract-pool.test.js
tests/pools/selection-strategies/selection-strategies.test.js

index 3c690fb819e9c718163e86badcde28786a5dd849..f082b8d144993b28621cc58e2941551c8da4a264 100644 (file)
@@ -555,25 +555,46 @@ export abstract class AbstractPool<
   }
 
   private updateEluWorkerUsage (
-    workerTasksUsage: WorkerUsage,
+    workerUsage: WorkerUsage,
     message: MessageValue<Response>
   ): void {
-    if (this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu) {
+    if (
+      this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu
+        .aggregate
+    ) {
+      if (workerUsage.elu != null && message.taskPerformance?.elu != null) {
+        workerUsage.elu.idle.aggregate =
+          workerUsage.elu.idle.aggregate + message.taskPerformance.elu.idle
+        workerUsage.elu.active.aggregate =
+          workerUsage.elu.active.aggregate + message.taskPerformance.elu.active
+        workerUsage.elu.utilization =
+          (workerUsage.elu.utilization +
+            message.taskPerformance.elu.utilization) /
+          2
+      } else if (message.taskPerformance?.elu != null) {
+        workerUsage.elu.idle.aggregate = message.taskPerformance.elu.idle
+        workerUsage.elu.active.aggregate = message.taskPerformance.elu.active
+        workerUsage.elu.utilization = message.taskPerformance.elu.utilization
+      }
       if (
-        workerTasksUsage.elu != null &&
+        this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu
+          .average &&
+        workerUsage.tasks.executed !== 0
+      ) {
+        workerUsage.elu.idle.average =
+          workerUsage.elu.idle.aggregate / workerUsage.tasks.executed
+        workerUsage.elu.active.average =
+          workerUsage.elu.active.aggregate / workerUsage.tasks.executed
+      }
+      if (
+        this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu
+          .median &&
         message.taskPerformance?.elu != null
       ) {
-        workerTasksUsage.elu = {
-          idle: workerTasksUsage.elu.idle + message.taskPerformance.elu.idle,
-          active:
-            workerTasksUsage.elu.active + message.taskPerformance.elu.active,
-          utilization:
-            (workerTasksUsage.elu.utilization +
-              message.taskPerformance.elu.utilization) /
-            2
-        }
-      } else if (message.taskPerformance?.elu != null) {
-        workerTasksUsage.elu = message.taskPerformance.elu
+        workerUsage.elu.idle.history.push(message.taskPerformance.elu.idle)
+        workerUsage.elu.active.history.push(message.taskPerformance.elu.active)
+        workerUsage.elu.idle.median = median(workerUsage.elu.idle.history)
+        workerUsage.elu.active.median = median(workerUsage.elu.active.history)
       }
     }
   }
@@ -829,7 +850,7 @@ export abstract class AbstractPool<
           this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
             .runTime.aggregate,
         elu: this.workerChoiceStrategyContext.getTaskStatisticsRequirements()
-          .elu
+          .elu.aggregate
       }
     })
   }
@@ -849,7 +870,21 @@ export abstract class AbstractPool<
         median: 0,
         history: new CircularArray()
       },
-      elu: undefined
+      elu: {
+        idle: {
+          aggregate: 0,
+          average: 0,
+          median: 0,
+          history: new CircularArray()
+        },
+        active: {
+          aggregate: 0,
+          average: 0,
+          median: 0,
+          history: new CircularArray()
+        },
+        utilization: 0
+      }
     }
   }
 
index 18b47a1d9d38bcbb1ac4361dd4938d0106d47fe7..8f71b502fd9ee32aa46b4cb9190909ecabc2ff58 100644 (file)
@@ -36,7 +36,11 @@ export abstract class AbstractWorkerChoiceStrategy<
       average: false,
       median: false
     },
-    elu: false
+    elu: {
+      aggregate: false,
+      average: false,
+      median: false
+    }
   }
 
   /**
@@ -87,6 +91,20 @@ export abstract class AbstractWorkerChoiceStrategy<
       this.taskStatisticsRequirements.waitTime.median = opts.waitTime
         .median as boolean
     }
+    if (
+      this.taskStatisticsRequirements.elu.average &&
+      opts.elu?.median === true
+    ) {
+      this.taskStatisticsRequirements.elu.average = false
+      this.taskStatisticsRequirements.elu.median = opts.elu.median as boolean
+    }
+    if (
+      this.taskStatisticsRequirements.elu.median &&
+      opts.elu?.median === false
+    ) {
+      this.taskStatisticsRequirements.elu.average = true
+      this.taskStatisticsRequirements.elu.median = opts.elu.median as boolean
+    }
   }
 
   /** @inheritDoc */
@@ -144,12 +162,26 @@ export abstract class AbstractWorkerChoiceStrategy<
    * @param workerNodeKey - The worker node key.
    * @returns The worker task wait time.
    */
-  protected getWorkerWaitTime (workerNodeKey: number): number {
+  protected getWorkerTaskWaitTime (workerNodeKey: number): number {
     return this.taskStatisticsRequirements.waitTime.median
       ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
       : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
   }
 
+  /**
+   * Gets the worker task ELU.
+   * If the task statistics require the ELU, the average ELU is returned.
+   * If the task statistics require the ELU, the median ELU is returned.
+   *
+   * @param workerNodeKey - The worker node key.
+   * @returns The worker task ELU.
+   */
+  protected getWorkerTaskElu (workerNodeKey: number): number {
+    return this.taskStatisticsRequirements.elu.median
+      ? this.pool.workerNodes[workerNodeKey].workerUsage.elu.active.median
+      : this.pool.workerNodes[workerNodeKey].workerUsage.elu.active.average
+  }
+
   protected computeDefaultWorkerWeight (): number {
     let cpusCycleTimeWeight = 0
     for (const cpu of cpus()) {
index 4ea68d40cd127632de1fed3b94568aa531ebe8cb..52b25fb947cd38c08c024ab5c816b90f5adeae0a 100644 (file)
@@ -35,7 +35,11 @@ export class FairShareWorkerChoiceStrategy<
       average: false,
       median: false
     },
-    elu: false
+    elu: {
+      aggregate: false,
+      average: false,
+      median: false
+    }
   }
 
   /**
index 8afd2c36edda1bcb7fe94dcb2b6d1f51c36eac9e..58fbb51ecfd5012db79b6bc3cad5a714ba93d733 100644 (file)
@@ -34,7 +34,11 @@ export class LeastBusyWorkerChoiceStrategy<
       average: false,
       median: false
     },
-    elu: false
+    elu: {
+      aggregate: false,
+      average: false,
+      median: false
+    }
   }
 
   /** @inheritDoc */
index 9f6ddbf3f407658ea32046af66dfe294bb8f45df..35ab69d0ca38595c2f81be566a0b7352bf900e4e 100644 (file)
@@ -34,7 +34,11 @@ export class LeastEluWorkerChoiceStrategy<
       average: false,
       median: false
     },
-    elu: true
+    elu: {
+      aggregate: true,
+      average: false,
+      median: false
+    }
   }
 
   /** @inheritDoc */
@@ -62,7 +66,7 @@ export class LeastEluWorkerChoiceStrategy<
     let leastEluWorkerNodeKey!: number
     for (const [workerNodeKey, workerNode] of this.pool.workerNodes.entries()) {
       const workerUsage = workerNode.workerUsage
-      const workerElu = workerUsage.elu?.active ?? 0
+      const workerElu = workerUsage.elu?.active.aggregate ?? 0
       if (workerElu === 0) {
         return workerNodeKey
       } else if (workerElu < minWorkerElu) {
index 0fbb4d4d43165737d3bd3c0c7378b2c55d7e03d2..c578ddc3f9fb24e289f5caf8fa542ec402d63834 100644 (file)
@@ -67,6 +67,12 @@ export interface WorkerChoiceStrategyOptions {
    * @defaultValue \{ median: false \}
    */
   waitTime?: MeasurementOptions
+  /**
+   * Event loop utilization options.
+   *
+   * @defaultValue \{ median: false \}
+   */
+  elu?: MeasurementOptions
   /**
    * Worker weights to use for weighted round robin worker selection strategy.
    * Weight is the tasks maximum average or median runtime in milliseconds.
@@ -111,9 +117,9 @@ export interface TaskStatisticsRequirements {
    */
   waitTime: MeasurementStatisticsRequirements
   /**
-   * Event loop utilization.
+   * Tasks event loop utilization requirements.
    */
-  elu: boolean
+  elu: MeasurementStatisticsRequirements
 }
 
 /**
index 4fcbeeda43d6021cc0d71dfb4daa66b8a046f0de..1ce6041920cfa634071093561eabc32e405272d2 100644 (file)
@@ -35,7 +35,11 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
       average: false,
       median: false
     },
-    elu: false
+    elu: {
+      aggregate: false,
+      average: false,
+      median: false
+    }
   }
 
   /**
index 70e4a7edd0ee481a0b608bb53ae6463d3517c144..598e6be82ca22588c34f04d6a0c402359956b66d 100644 (file)
@@ -1,4 +1,3 @@
-import type { EventLoopUtilization } from 'node:perf_hooks'
 import type { CircularArray } from '../circular-array'
 import type { Queue } from '../queue'
 
@@ -80,6 +79,17 @@ export interface MeasurementStatistics {
   history: CircularArray<number>
 }
 
+/**
+ * Event loop utilization measurement statistics.
+ *
+ * @internal
+ */
+export interface EventLoopUtilizationMeasurementStatistics {
+  idle: MeasurementStatistics
+  active: MeasurementStatistics
+  utilization: number
+}
+
 /**
  * Task statistics.
  *
@@ -123,9 +133,9 @@ export interface WorkerUsage {
    */
   waitTime: MeasurementStatistics
   /**
-   * Event loop utilization.
+   * Tasks event loop utilization statistics.
    */
-  elu: EventLoopUtilization | undefined
+  elu: EventLoopUtilizationMeasurementStatistics
 }
 
 /**
index 7b921ffa4b94efcfd673b75248e44ee305466058..6e5f72527cf6d34433bb4c82722be4e68aa646d2 100644 (file)
@@ -13,7 +13,8 @@ export const EMPTY_FUNCTION: () => void = Object.freeze(() => {
 export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrategyOptions =
   {
     runTime: { median: false },
-    waitTime: { median: false }
+    waitTime: { median: false },
+    elu: { median: false }
   }
 
 /**
index 12237d93f7bba6c752852d7b49c72da8253a492c..25566a6a2ac115e581f435eb290861031a570530 100644 (file)
@@ -94,7 +94,8 @@ describe('Abstract pool test suite', () => {
     )
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
       runTime: { median: false },
-      waitTime: { median: false }
+      waitTime: { median: false },
+      elu: { median: false }
     })
     expect(pool.opts.messageHandler).toBeUndefined()
     expect(pool.opts.errorHandler).toBeUndefined()
@@ -184,13 +185,15 @@ describe('Abstract pool test suite', () => {
     )
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
       runTime: { median: false },
-      waitTime: { median: false }
+      waitTime: { median: false },
+      elu: { median: false }
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
         runTime: { median: false },
-        waitTime: { median: false }
+        waitTime: { median: false },
+        elu: { median: false }
       })
     }
     expect(
@@ -206,7 +209,11 @@ describe('Abstract pool test suite', () => {
         average: false,
         median: false
       },
-      elu: false
+      elu: {
+        aggregate: false,
+        average: false,
+        median: false
+      }
     })
     pool.setWorkerChoiceStrategyOptions({ runTime: { median: true } })
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
@@ -231,7 +238,11 @@ describe('Abstract pool test suite', () => {
         average: false,
         median: false
       },
-      elu: false
+      elu: {
+        aggregate: false,
+        average: false,
+        median: false
+      }
     })
     pool.setWorkerChoiceStrategyOptions({ runTime: { median: false } })
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
@@ -256,7 +267,11 @@ describe('Abstract pool test suite', () => {
         average: false,
         median: false
       },
-      elu: false
+      elu: {
+        aggregate: false,
+        average: false,
+        median: false
+      }
     })
     await pool.destroy()
   })
@@ -377,7 +392,21 @@ describe('Abstract pool test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     await pool.destroy()
@@ -426,7 +455,21 @@ describe('Abstract pool test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     await Promise.all(promises)
@@ -450,7 +493,21 @@ describe('Abstract pool test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     await pool.destroy()
@@ -488,7 +545,21 @@ describe('Abstract pool test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
@@ -516,7 +587,21 @@ describe('Abstract pool test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.runTime.history.length).toBe(0)
       expect(workerNode.workerUsage.waitTime.history.length).toBe(0)
index dbe286b40e5a46f7b262a700f130f4aad1bf82c4..9b581ff57a9f052d93bca4edfe67e46c266ce8b6 100644 (file)
@@ -135,7 +135,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(
@@ -157,7 +161,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()
@@ -196,7 +204,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     expect(
@@ -242,7 +264,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     expect(
@@ -340,7 +376,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(
@@ -362,7 +402,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()
@@ -401,7 +445,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     // We need to clean up the resources after our test
@@ -442,8 +500,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     // We need to clean up the resources after our test
@@ -470,7 +541,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(
@@ -492,7 +567,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()
@@ -531,7 +610,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
@@ -580,7 +673,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
@@ -613,7 +720,11 @@ describe('Selection strategies test suite', () => {
         average: false,
         median: false
       },
-      elu: true
+      elu: {
+        aggregate: true,
+        average: false,
+        median: false
+      }
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -635,7 +746,11 @@ describe('Selection strategies test suite', () => {
         average: false,
         median: false
       },
-      elu: true
+      elu: {
+        aggregate: true,
+        average: false,
+        median: false
+      }
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -655,7 +770,7 @@ describe('Selection strategies test suite', () => {
     }
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
-      const expectedWorkerUsage = {
+      expect(workerNode.workerUsage).toStrictEqual({
         tasks: {
           executed: expect.any(Number),
           executing: 0,
@@ -673,27 +788,29 @@ describe('Selection strategies test suite', () => {
           average: 0,
           median: 0,
           history: expect.any(CircularArray)
+        },
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: expect.any(Number),
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: expect.any(Number)
         }
-      }
-      if (workerNode.workerUsage.elu === undefined) {
-        expect(workerNode.workerUsage).toStrictEqual({
-          ...expectedWorkerUsage,
-          elu: undefined
-        })
-      } else {
-        expect(workerNode.workerUsage).toStrictEqual({
-          ...expectedWorkerUsage,
-          elu: {
-            active: expect.any(Number),
-            idle: 0,
-            utilization: 1
-          }
-        })
-      }
+      })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
         max * maxMultiplier
       )
+      expect(workerNode.workerUsage.elu.utilization).toBeGreaterThanOrEqual(0)
+      expect(workerNode.workerUsage.elu.utilization).toBeLessThanOrEqual(1)
     }
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -714,7 +831,7 @@ describe('Selection strategies test suite', () => {
     }
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
-      const expectedWorkerUsage = {
+      expect(workerNode.workerUsage).toStrictEqual({
         tasks: {
           executed: expect.any(Number),
           executing: 0,
@@ -732,27 +849,29 @@ describe('Selection strategies test suite', () => {
           average: 0,
           median: 0,
           history: expect.any(CircularArray)
+        },
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: expect.any(Number),
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: expect.any(Number)
         }
-      }
-      if (workerNode.workerUsage.elu === undefined) {
-        expect(workerNode.workerUsage).toStrictEqual({
-          ...expectedWorkerUsage,
-          elu: undefined
-        })
-      } else {
-        expect(workerNode.workerUsage).toStrictEqual({
-          ...expectedWorkerUsage,
-          elu: {
-            active: expect.any(Number),
-            idle: 0,
-            utilization: 1
-          }
-        })
-      }
+      })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
         max * maxMultiplier
       )
+      expect(workerNode.workerUsage.elu.utilization).toBeGreaterThanOrEqual(0)
+      expect(workerNode.workerUsage.elu.utilization).toBeLessThanOrEqual(1)
     }
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -778,7 +897,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(
@@ -800,7 +923,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()
@@ -839,7 +966,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0)
       expect(workerNode.workerUsage.runTime.average).toBeGreaterThan(0)
@@ -887,7 +1028,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0)
       expect(workerNode.workerUsage.runTime.average).toBeGreaterThan(0)
@@ -940,7 +1095,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.runTime.aggregate).toBeGreaterThan(0)
       expect(workerNode.workerUsage.runTime.median).toBeGreaterThan(0)
@@ -1048,7 +1217,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(
@@ -1070,7 +1243,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()
@@ -1109,7 +1286,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
@@ -1166,7 +1357,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
@@ -1228,7 +1433,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
       expect(workerNode.workerUsage.tasks.executed).toBeGreaterThan(0)
       expect(workerNode.workerUsage.tasks.executed).toBeLessThanOrEqual(
@@ -1350,7 +1569,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(
@@ -1372,7 +1595,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()
@@ -1414,7 +1641,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     expect(
@@ -1482,7 +1723,21 @@ describe('Selection strategies test suite', () => {
           median: 0,
           history: expect.any(CircularArray)
         },
-        elu: undefined
+        elu: {
+          idle: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          active: {
+            aggregate: 0,
+            average: 0,
+            median: 0,
+            history: expect.any(CircularArray)
+          },
+          utilization: 0
+        }
       })
     }
     expect(