feat: add utilization to pool information
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 1 Jul 2023 20:13:25 +0000 (22:13 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 1 Jul 2023 20:13:25 +0000 (22:13 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
CHANGELOG.md
src/pools/abstract-pool.ts
src/pools/pool.ts
src/utils.ts
tests/pools/abstract/abstract-pool.test.js
tests/utils.test.js

index f47aeb8f5355c78cd21015583fc8f58776037038..b1ca76b6b62cd8bcfbc2551869396f39831b5ae9 100644 (file)
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Add pool `utilization` ratio to pool information.
+
 ## [2.6.6] - 2023-07-01
 
 ### Added
index 71f765ac575da13933a1543cd069feb0b6b1984d..0f18de3fba17f63f0f6b0b8c76697852cc085baa 100644 (file)
@@ -5,7 +5,8 @@ import {
   DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS,
   EMPTY_FUNCTION,
   isPlainObject,
-  median
+  median,
+  round
 } from '../utils'
 import { KillBehaviors, isKillBehavior } from '../worker/worker-options'
 import { CircularArray } from '../circular-array'
@@ -76,6 +77,11 @@ export abstract class AbstractPool<
   Response
   >
 
+  /**
+   * The start timestamp of the pool.
+   */
+  private readonly startTimestamp
+
   /**
    * Constructs a new poolifier pool.
    *
@@ -115,9 +121,11 @@ export abstract class AbstractPool<
 
     this.setupHook()
 
-    for (let i = 1; i <= this.numberOfWorkers; i++) {
+    while (this.workerNodes.length < this.numberOfWorkers) {
       this.createAndSetupWorker()
     }
+
+    this.startTimestamp = performance.now()
   }
 
   private checkFilePath (filePath: string): void {
@@ -243,6 +251,7 @@ export abstract class AbstractPool<
       worker: this.worker,
       minSize: this.minSize,
       maxSize: this.maxSize,
+      utilization: round(this.utilization),
       workerNodes: this.workerNodes.length,
       idleWorkerNodes: this.workerNodes.reduce(
         (accumulator, workerNode) =>
@@ -284,6 +293,35 @@ export abstract class AbstractPool<
     }
   }
 
+  /**
+   * Gets the pool run time.
+   *
+   * @returns The pool run time in milliseconds.
+   */
+  private get runTime (): number {
+    return performance.now() - this.startTimestamp
+  }
+
+  /**
+   * Gets the approximate pool utilization.
+   *
+   * @returns The pool utilization.
+   */
+  private get utilization (): number {
+    const poolRunTimeCapacity = this.runTime * this.maxSize
+    const totalTasksRunTime = this.workerNodes.reduce(
+      (accumulator, workerNode) =>
+        accumulator + workerNode.usage.runTime.aggregate,
+      0
+    )
+    const totalTasksWaitTime = this.workerNodes.reduce(
+      (accumulator, workerNode) =>
+        accumulator + workerNode.usage.waitTime.aggregate,
+      0
+    )
+    return (totalTasksRunTime + totalTasksWaitTime) / poolRunTimeCapacity
+  }
+
   /**
    * Pool type.
    *
index ef93982fbf19ab4c3350af3150116e7cb27dfd40..7c6f6769212c45513453af5d3b373f2b752f4c62 100644 (file)
@@ -72,6 +72,7 @@ export interface PoolInfo {
   worker: WorkerType
   minSize: number
   maxSize: number
+  utilization: number
   workerNodes: number
   idleWorkerNodes: number
   busyWorkerNodes: number
index 87c4888db0770d15bc624371eccb716809938934..6d11f88cf6d964bfc9f36fe79b5c3eda3810d35b 100644 (file)
@@ -51,7 +51,7 @@ export const availableParallelism = (): number => {
 }
 
 /**
- * Compute the median of the given data set.
+ * Computes the median of the given data set.
  *
  * @param dataSet - Data set.
  * @returns The median of the given data set.
@@ -71,6 +71,18 @@ export const median = (dataSet: number[]): number => {
   )
 }
 
+/**
+ * Rounds the given number to the given scale.
+ *
+ * @param num - The number to round.
+ * @param scale - The scale to round to.
+ * @returns The rounded number.
+ */
+export const round = (num: number, scale = 2): number => {
+  const rounder = Math.pow(10, scale)
+  return Math.round(num * rounder * (1 + Number.EPSILON)) / rounder
+}
+
 /**
  * Is the given object a plain object?
  *
index adf2765f7e67bcb68ffb11a534574fa3e18c3907..023b56b414c4c3453d269c5f5605d0e69a8c4275 100644 (file)
@@ -397,6 +397,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.thread,
       minSize: numberOfWorkers,
       maxSize: numberOfWorkers,
+      utilization: 0,
       workerNodes: numberOfWorkers,
       idleWorkerNodes: numberOfWorkers,
       busyWorkerNodes: 0,
@@ -417,6 +418,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.cluster,
       minSize: numberOfWorkers,
       maxSize: numberOfWorkers * 2,
+      utilization: 0,
       workerNodes: numberOfWorkers,
       idleWorkerNodes: numberOfWorkers,
       busyWorkerNodes: 0,
@@ -715,6 +717,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.thread,
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
+      utilization: 0,
       workerNodes: expect.any(Number),
       idleWorkerNodes: expect.any(Number),
       busyWorkerNodes: expect.any(Number),
@@ -751,6 +754,7 @@ describe('Abstract pool test suite', () => {
       worker: WorkerTypes.thread,
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
+      utilization: 0,
       workerNodes: expect.any(Number),
       idleWorkerNodes: expect.any(Number),
       busyWorkerNodes: expect.any(Number),
index f62d49c68af0a9a3a187cbf06d7899b7685cc829..0bf5e50ca8a4989099500100ff2b097a96092713 100644 (file)
@@ -1,11 +1,20 @@
 const { expect } = require('expect')
-const { isPlainObject, median, availableParallelism } = require('../lib/utils')
+const {
+  availableParallelism,
+  isPlainObject,
+  median,
+  round
+} = require('../lib/utils')
 const {
   isKillBehavior,
   KillBehaviors
 } = require('../lib/worker/worker-options')
 
 describe('Utils test suite', () => {
+  it('Verify availableParallelism() behavior', () => {
+    expect(typeof availableParallelism() === 'number').toBe(true)
+  })
+
   it('Verify median() computation', () => {
     expect(median([])).toBe(0)
     expect(median([0.08])).toBe(0.08)
@@ -13,6 +22,20 @@ describe('Utils test suite', () => {
     expect(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.535)
   })
 
+  it('Verify round() behavior', () => {
+    expect(round(0)).toBe(0)
+    expect(round(0.5, 0)).toBe(1)
+    expect(round(0.5)).toBe(0.5)
+    expect(round(-0.5, 0)).toBe(-1)
+    expect(round(-0.5)).toBe(-0.5)
+    expect(round(1.005)).toBe(1.01)
+    expect(round(2.175)).toBe(2.18)
+    expect(round(5.015)).toBe(5.02)
+    expect(round(-1.005)).toBe(-1.01)
+    expect(round(-2.175)).toBe(-2.18)
+    expect(round(-5.015)).toBe(-5.02)
+  })
+
   it('Verify isPlainObject() behavior', () => {
     expect(isPlainObject(null)).toBe(false)
     expect(isPlainObject(undefined)).toBe(false)
@@ -60,8 +83,4 @@ describe('Utils test suite', () => {
     expect(isKillBehavior(KillBehaviors.HARD, null)).toBe(false)
     expect(isKillBehavior(KillBehaviors.SOFT, 'unknown')).toBe(false)
   })
-
-  it('Verify availableParallelism() behavior', () => {
-    expect(typeof availableParallelism() === 'number').toBe(true)
-  })
 })