feat: optimize worker choice strategies implementation
[poolifier.git] / src / pools / selection-strategies / abstract-worker-choice-strategy.ts
CommitLineData
0bbf65c3 1import { cpus } from 'node:os'
3c93feb9
JB
2import {
3 DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
4 DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
5} from '../../utils'
08f3f44c 6import type { IPool } from '../pool'
f06e48d8 7import type { IWorker } from '../worker'
10fcfaf4
JB
8import type {
9 IWorkerChoiceStrategy,
57441b79 10 MeasurementStatisticsRequirements,
6c6afb84 11 StrategyPolicy,
87de9ff5 12 TaskStatisticsRequirements,
da309861 13 WorkerChoiceStrategyOptions
10fcfaf4 14} from './selection-strategies-types'
bdaf31cd
JB
15
16/**
9cd39dd4 17 * Worker choice strategy abstract base class.
bdaf31cd 18 *
38e795c1 19 * @typeParam Worker - Type of worker which manages the strategy.
e102732c
JB
20 * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
21 * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
bdaf31cd
JB
22 */
23export abstract class AbstractWorkerChoiceStrategy<
f06e48d8 24 Worker extends IWorker,
b2b1d84e
JB
25 Data = unknown,
26 Response = unknown
17393ac8 27> implements IWorkerChoiceStrategy {
d33be430 28 /**
9b106837 29 * The next worker node key.
d33be430 30 */
b1aae695 31 protected nextWorkerNodeKey: number | undefined = 0
d33be430 32
7c7bb289
JB
33 /**
34 * The previous worker node key.
35 */
36 protected previousWorkerNodeKey: number = 0
37
6c6afb84
JB
38 /** @inheritDoc */
39 public readonly strategyPolicy: StrategyPolicy = {
b1aae695 40 dynamicWorkerUsage: false,
f6bc9f26 41 dynamicWorkerReady: true
6c6afb84
JB
42 }
43
afc003b2 44 /** @inheritDoc */
87de9ff5 45 public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
3c93feb9
JB
46 runTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
47 waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
48 elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
10fcfaf4 49 }
bdaf31cd
JB
50
51 /**
6533c3e6 52 * Constructs a worker choice strategy bound to the pool.
bdaf31cd 53 *
38e795c1 54 * @param pool - The pool instance.
da309861 55 * @param opts - The worker choice strategy options.
bdaf31cd
JB
56 */
57 public constructor (
c4855468 58 protected readonly pool: IPool<Worker, Data, Response>,
a20f0ba5 59 protected opts: WorkerChoiceStrategyOptions = DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
b8f3418c 60 ) {
8990357d 61 this.opts = { ...DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS, ...opts }
7254e419 62 this.choose = this.choose.bind(this)
b8f3418c 63 }
bdaf31cd 64
932fc8be
JB
65 protected setTaskStatisticsRequirements (
66 opts: WorkerChoiceStrategyOptions
67 ): void {
57441b79
JB
68 this.toggleMedianMeasurementStatisticsRequirements(
69 this.taskStatisticsRequirements.runTime,
70 opts.runTime?.median as boolean
71 )
72 this.toggleMedianMeasurementStatisticsRequirements(
73 this.taskStatisticsRequirements.waitTime,
74 opts.waitTime?.median as boolean
75 )
76 this.toggleMedianMeasurementStatisticsRequirements(
77 this.taskStatisticsRequirements.elu,
78 opts.elu?.median as boolean
79 )
80 }
81
82 private toggleMedianMeasurementStatisticsRequirements (
83 measurementStatisticsRequirements: MeasurementStatisticsRequirements,
84 toggleMedian: boolean
85 ): void {
86 if (measurementStatisticsRequirements.average && toggleMedian) {
87 measurementStatisticsRequirements.average = false
88 measurementStatisticsRequirements.median = toggleMedian
5df69fab 89 }
57441b79
JB
90 if (measurementStatisticsRequirements.median && !toggleMedian) {
91 measurementStatisticsRequirements.average = true
92 measurementStatisticsRequirements.median = toggleMedian
5df69fab 93 }
da309861
JB
94 }
95
39a43af7
JB
96 protected resetWorkerNodeKeyProperties (): void {
97 this.nextWorkerNodeKey = 0
98 this.previousWorkerNodeKey = 0
99 }
100
afc003b2 101 /** @inheritDoc */
a6f7f1b4 102 public abstract reset (): boolean
ea7a90d3 103
138d29a8 104 /** @inheritDoc */
a4958de2 105 public abstract update (workerNodeKey: number): boolean
138d29a8 106
afc003b2 107 /** @inheritDoc */
b1aae695 108 public abstract choose (): number | undefined
97a2abc3 109
afc003b2 110 /** @inheritDoc */
f06e48d8 111 public abstract remove (workerNodeKey: number): boolean
a20f0ba5
JB
112
113 /** @inheritDoc */
114 public setOptions (opts: WorkerChoiceStrategyOptions): void {
8990357d 115 this.opts = { ...DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS, ...opts }
65ab8dcc 116 this.setTaskStatisticsRequirements(this.opts)
a20f0ba5 117 }
cb70b19d 118
d4ddb68b
JB
119 /**
120 * Whether the worker node is ready or not.
121 *
122 * @param workerNodeKey - The worker node key.
123 * @returns Whether the worker node is ready or not.
124 */
8990357d 125 private isWorkerNodeReady (workerNodeKey: number): boolean {
19dbc45b
JB
126 return this.pool.workerNodes[workerNodeKey].info.ready
127 }
128
e2b31e32
JB
129 /**
130 * Whether the worker node has back pressure or not (i.e. its tasks queue is full).
131 *
132 * @param workerNodeKey - The worker node key.
133 * @returns `true` if the worker node has back pressure, `false` otherwise.
134 */
8990357d 135 private hasWorkerNodeBackPressure (workerNodeKey: number): boolean {
e2b31e32
JB
136 return this.pool.hasWorkerNodeBackPressure(workerNodeKey)
137 }
138
8990357d
JB
139 /**
140 * Whether the worker node is eligible or not.
141 * A worker node is eligible if it is ready and does not have back pressure.
142 *
143 * @param workerNodeKey - The worker node key.
144 * @returns `true` if the worker node is eligible, `false` otherwise.
145 * @see {@link isWorkerNodeReady}
146 * @see {@link hasWorkerNodeBackPressure}
147 */
148 protected isWorkerNodeEligible (workerNodeKey: number): boolean {
149 return (
150 this.isWorkerNodeReady(workerNodeKey) &&
151 !this.hasWorkerNodeBackPressure(workerNodeKey)
152 )
153 }
154
f6b641d6 155 /**
f3a91bac 156 * Gets the worker node task runtime.
932fc8be
JB
157 * If the task statistics require the average runtime, the average runtime is returned.
158 * If the task statistics require the median runtime , the median runtime is returned.
f6b641d6
JB
159 *
160 * @param workerNodeKey - The worker node key.
f3a91bac 161 * @returns The worker node task runtime.
f6b641d6 162 */
f3a91bac 163 protected getWorkerNodeTaskRunTime (workerNodeKey: number): number {
932fc8be 164 return this.taskStatisticsRequirements.runTime.median
f3a91bac
JB
165 ? this.pool.workerNodes[workerNodeKey]?.usage?.runTime?.median ?? 0
166 : this.pool.workerNodes[workerNodeKey]?.usage?.runTime?.average ?? 0
f6b641d6
JB
167 }
168
ef680bb8 169 /**
f3a91bac 170 * Gets the worker node task wait time.
932fc8be
JB
171 * If the task statistics require the average wait time, the average wait time is returned.
172 * If the task statistics require the median wait time, the median wait time is returned.
ef680bb8
JB
173 *
174 * @param workerNodeKey - The worker node key.
f3a91bac 175 * @returns The worker node task wait time.
ef680bb8 176 */
f3a91bac 177 protected getWorkerNodeTaskWaitTime (workerNodeKey: number): number {
932fc8be 178 return this.taskStatisticsRequirements.waitTime.median
f3a91bac
JB
179 ? this.pool.workerNodes[workerNodeKey]?.usage?.waitTime?.median ?? 0
180 : this.pool.workerNodes[workerNodeKey]?.usage?.waitTime?.average ?? 0
ef680bb8
JB
181 }
182
5df69fab 183 /**
f3a91bac 184 * Gets the worker node task ELU.
9adcefab
JB
185 * If the task statistics require the average ELU, the average ELU is returned.
186 * If the task statistics require the median ELU, the median ELU is returned.
5df69fab
JB
187 *
188 * @param workerNodeKey - The worker node key.
f3a91bac 189 * @returns The worker node task ELU.
5df69fab 190 */
f3a91bac 191 protected getWorkerNodeTaskElu (workerNodeKey: number): number {
5df69fab 192 return this.taskStatisticsRequirements.elu.median
f3a91bac
JB
193 ? this.pool.workerNodes[workerNodeKey]?.usage?.elu?.active?.median ?? 0
194 : this.pool.workerNodes[workerNodeKey]?.usage?.elu?.active?.average ?? 0
5df69fab
JB
195 }
196
baca80f7
JB
197 /**
198 * Sets safely the previous worker node key.
199 *
200 * @param workerNodeKey - The worker node key.
201 */
202 protected setPreviousWorkerNodeKey (workerNodeKey: number | undefined): void {
203 this.previousWorkerNodeKey = workerNodeKey ?? this.previousWorkerNodeKey
204 }
205
b1aae695 206 /**
fce028d6 207 * Check the next worker node eligibility.
b1aae695 208 */
86ed0598 209 protected checkNextWorkerNodeEligibility (): void {
48e6ef5a
JB
210 if (!this.isWorkerNodeEligible(this.nextWorkerNodeKey as number)) {
211 this.nextWorkerNodeKey = undefined
48e6ef5a
JB
212 }
213 }
214
0bbf65c3
JB
215 protected computeDefaultWorkerWeight (): number {
216 let cpusCycleTimeWeight = 0
217 for (const cpu of cpus()) {
218 // CPU estimated cycle time
219 const numberOfDigits = cpu.speed.toString().length - 1
220 const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
221 cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
222 }
223 return Math.round(cpusCycleTimeWeight / cpus().length)
224 }
bdaf31cd 225}