docs: refine benchmarks README
[poolifier.git] / src / pools / selection-strategies / abstract-worker-choice-strategy.ts
CommitLineData
0bbf65c3 1import { cpus } from 'node:os'
bbeadd16 2import { DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS } from '../../utils'
08f3f44c 3import type { IPool } from '../pool'
f06e48d8 4import type { IWorker } from '../worker'
10fcfaf4
JB
5import type {
6 IWorkerChoiceStrategy,
6c6afb84 7 StrategyPolicy,
87de9ff5 8 TaskStatisticsRequirements,
da309861 9 WorkerChoiceStrategyOptions
10fcfaf4 10} from './selection-strategies-types'
bdaf31cd
JB
11
12/**
9cd39dd4 13 * Worker choice strategy abstract base class.
bdaf31cd 14 *
38e795c1
JB
15 * @typeParam Worker - Type of worker which manages the strategy.
16 * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
02706357 17 * @typeParam Response - Type of execution response. This can only be serializable data.
bdaf31cd
JB
18 */
19export abstract class AbstractWorkerChoiceStrategy<
f06e48d8 20 Worker extends IWorker,
b2b1d84e
JB
21 Data = unknown,
22 Response = unknown
17393ac8 23> implements IWorkerChoiceStrategy {
cb70b19d
JB
24 /**
25 * Toggles finding the last free worker node key.
26 */
27 private toggleFindLastFreeWorkerNodeKey: boolean = false
6c6afb84
JB
28
29 /** @inheritDoc */
30 public readonly strategyPolicy: StrategyPolicy = {
31 useDynamicWorker: false
32 }
33
afc003b2 34 /** @inheritDoc */
87de9ff5 35 public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
932fc8be
JB
36 runTime: {
37 aggregate: false,
38 average: false,
39 median: false
40 },
41 waitTime: {
42 aggregate: false,
43 average: false,
44 median: false
45 },
5df69fab
JB
46 elu: {
47 aggregate: false,
48 average: false,
49 median: false
50 }
10fcfaf4 51 }
bdaf31cd
JB
52
53 /**
6533c3e6 54 * Constructs a worker choice strategy bound to the pool.
bdaf31cd 55 *
38e795c1 56 * @param pool - The pool instance.
da309861 57 * @param opts - The worker choice strategy options.
bdaf31cd
JB
58 */
59 public constructor (
c4855468 60 protected readonly pool: IPool<Worker, Data, Response>,
a20f0ba5 61 protected opts: WorkerChoiceStrategyOptions = DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
b8f3418c 62 ) {
7254e419 63 this.choose = this.choose.bind(this)
b8f3418c 64 }
bdaf31cd 65
932fc8be
JB
66 protected setTaskStatisticsRequirements (
67 opts: WorkerChoiceStrategyOptions
68 ): void {
87de9ff5 69 if (
932fc8be
JB
70 this.taskStatisticsRequirements.runTime.average &&
71 opts.runTime?.median === true
87de9ff5 72 ) {
932fc8be
JB
73 this.taskStatisticsRequirements.runTime.average = false
74 this.taskStatisticsRequirements.runTime.median = opts.runTime
75 .median as boolean
da309861 76 }
87de9ff5 77 if (
932fc8be
JB
78 this.taskStatisticsRequirements.runTime.median &&
79 opts.runTime?.median === false
87de9ff5 80 ) {
932fc8be
JB
81 this.taskStatisticsRequirements.runTime.average = true
82 this.taskStatisticsRequirements.runTime.median = opts.runTime
83 .median as boolean
a20f0ba5 84 }
87de9ff5 85 if (
932fc8be
JB
86 this.taskStatisticsRequirements.waitTime.average &&
87 opts.waitTime?.median === true
87de9ff5 88 ) {
932fc8be
JB
89 this.taskStatisticsRequirements.waitTime.average = false
90 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
91 .median as boolean
0567595a 92 }
87de9ff5 93 if (
932fc8be
JB
94 this.taskStatisticsRequirements.waitTime.median &&
95 opts.waitTime?.median === false
87de9ff5 96 ) {
932fc8be
JB
97 this.taskStatisticsRequirements.waitTime.average = true
98 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
99 .median as boolean
0567595a 100 }
5df69fab
JB
101 if (
102 this.taskStatisticsRequirements.elu.average &&
103 opts.elu?.median === true
104 ) {
105 this.taskStatisticsRequirements.elu.average = false
106 this.taskStatisticsRequirements.elu.median = opts.elu.median as boolean
107 }
108 if (
109 this.taskStatisticsRequirements.elu.median &&
110 opts.elu?.median === false
111 ) {
112 this.taskStatisticsRequirements.elu.average = true
113 this.taskStatisticsRequirements.elu.median = opts.elu.median as boolean
114 }
da309861
JB
115 }
116
afc003b2 117 /** @inheritDoc */
a6f7f1b4 118 public abstract reset (): boolean
ea7a90d3 119
138d29a8 120 /** @inheritDoc */
a4958de2 121 public abstract update (workerNodeKey: number): boolean
138d29a8 122
afc003b2 123 /** @inheritDoc */
c923ce56 124 public abstract choose (): number
97a2abc3 125
afc003b2 126 /** @inheritDoc */
f06e48d8 127 public abstract remove (workerNodeKey: number): boolean
a20f0ba5
JB
128
129 /** @inheritDoc */
130 public setOptions (opts: WorkerChoiceStrategyOptions): void {
d01e4d67 131 opts = opts ?? DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
932fc8be 132 this.setTaskStatisticsRequirements(opts)
a20f0ba5
JB
133 this.opts = opts
134 }
cb70b19d
JB
135
136 /**
137 * Finds a free worker node key.
138 *
139 * @returns The free worker node key or `-1` if there is no free worker node.
140 */
141 protected findFreeWorkerNodeKey (): number {
142 if (this.toggleFindLastFreeWorkerNodeKey) {
143 this.toggleFindLastFreeWorkerNodeKey = false
e0ae6100 144 return this.findLastFreeWorkerNodeKey()
cb70b19d
JB
145 }
146 this.toggleFindLastFreeWorkerNodeKey = true
e0ae6100
JB
147 return this.findFirstFreeWorkerNodeKey()
148 }
149
f6b641d6 150 /**
e6606302 151 * Gets the worker task runtime.
932fc8be
JB
152 * If the task statistics require the average runtime, the average runtime is returned.
153 * If the task statistics require the median runtime , the median runtime is returned.
f6b641d6
JB
154 *
155 * @param workerNodeKey - The worker node key.
e6606302 156 * @returns The worker task runtime.
f6b641d6
JB
157 */
158 protected getWorkerTaskRunTime (workerNodeKey: number): number {
932fc8be 159 return this.taskStatisticsRequirements.runTime.median
a4e07f72
JB
160 ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
161 : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
f6b641d6
JB
162 }
163
ef680bb8
JB
164 /**
165 * Gets the worker task wait time.
932fc8be
JB
166 * If the task statistics require the average wait time, the average wait time is returned.
167 * If the task statistics require the median wait time, the median wait time is returned.
ef680bb8
JB
168 *
169 * @param workerNodeKey - The worker node key.
170 * @returns The worker task wait time.
171 */
5df69fab 172 protected getWorkerTaskWaitTime (workerNodeKey: number): number {
932fc8be 173 return this.taskStatisticsRequirements.waitTime.median
a4e07f72
JB
174 ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
175 : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
ef680bb8
JB
176 }
177
5df69fab
JB
178 /**
179 * Gets the worker task ELU.
9adcefab
JB
180 * If the task statistics require the average ELU, the average ELU is returned.
181 * If the task statistics require the median ELU, the median ELU is returned.
5df69fab
JB
182 *
183 * @param workerNodeKey - The worker node key.
184 * @returns The worker task ELU.
185 */
186 protected getWorkerTaskElu (workerNodeKey: number): number {
187 return this.taskStatisticsRequirements.elu.median
188 ? this.pool.workerNodes[workerNodeKey].workerUsage.elu.active.median
189 : this.pool.workerNodes[workerNodeKey].workerUsage.elu.active.average
190 }
191
0bbf65c3
JB
192 protected computeDefaultWorkerWeight (): number {
193 let cpusCycleTimeWeight = 0
194 for (const cpu of cpus()) {
195 // CPU estimated cycle time
196 const numberOfDigits = cpu.speed.toString().length - 1
197 const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
198 cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
199 }
200 return Math.round(cpusCycleTimeWeight / cpus().length)
201 }
202
e0ae6100
JB
203 /**
204 * Finds the first free worker node key based on the number of tasks the worker has applied.
205 *
1c6fe997 206 * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
e0ae6100
JB
207 *
208 * If no free worker is found, `-1` is returned.
209 *
210 * @returns A worker node key if there is one, `-1` otherwise.
211 */
212 private findFirstFreeWorkerNodeKey (): number {
213 return this.pool.workerNodes.findIndex(workerNode => {
a4e07f72 214 return workerNode.workerUsage.tasks.executing === 0
e0ae6100
JB
215 })
216 }
217
218 /**
219 * Finds the last free worker node key based on the number of tasks the worker has applied.
220 *
1c6fe997 221 * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
e0ae6100
JB
222 *
223 * If no free worker is found, `-1` is returned.
224 *
225 * @returns A worker node key if there is one, `-1` otherwise.
226 */
227 private findLastFreeWorkerNodeKey (): number {
0e7c56b0 228 // It requires node >= 18.0.0:
e0ae6100 229 // return this.workerNodes.findLastIndex(workerNode => {
a4e07f72 230 // return workerNode.workerUsage.tasks.executing === 0
e0ae6100 231 // })
0682ba15
JB
232 for (
233 let workerNodeKey = this.pool.workerNodes.length - 1;
234 workerNodeKey >= 0;
235 workerNodeKey--
236 ) {
a4e07f72
JB
237 if (
238 this.pool.workerNodes[workerNodeKey].workerUsage.tasks.executing === 0
239 ) {
0682ba15 240 return workerNodeKey
e0ae6100
JB
241 }
242 }
243 return -1
cb70b19d 244 }
bdaf31cd 245}