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