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