feat: worker node readiness aware worker choice strategies
[poolifier.git] / src / pools / selection-strategies / abstract-worker-choice-strategy.ts
1 import { cpus } from 'node:os'
2 import {
3 DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
4 DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
5 } from '../../utils'
6 import type { IPool } from '../pool'
7 import type { IWorker } from '../worker'
8 import type {
9 IWorkerChoiceStrategy,
10 StrategyPolicy,
11 TaskStatisticsRequirements,
12 WorkerChoiceStrategyOptions
13 } from './selection-strategies-types'
14
15 /**
16 * Worker choice strategy abstract base class.
17 *
18 * @typeParam Worker - Type of worker which manages the strategy.
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.
21 */
22 export abstract class AbstractWorkerChoiceStrategy<
23 Worker extends IWorker,
24 Data = unknown,
25 Response = unknown
26 > implements IWorkerChoiceStrategy {
27 // /**
28 // * Toggles finding the last free worker node key.
29 // */
30 // private toggleFindLastFreeWorkerNodeKey: boolean = false
31
32 /**
33 * Id of the next worker node.
34 */
35 protected nextWorkerNodeId: number = 0
36
37 /** @inheritDoc */
38 public readonly strategyPolicy: StrategyPolicy = {
39 useDynamicWorker: false
40 }
41
42 /** @inheritDoc */
43 public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
44 runTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
45 waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
46 elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
47 }
48
49 /**
50 * Constructs a worker choice strategy bound to the pool.
51 *
52 * @param pool - The pool instance.
53 * @param opts - The worker choice strategy options.
54 */
55 public constructor (
56 protected readonly pool: IPool<Worker, Data, Response>,
57 protected opts: WorkerChoiceStrategyOptions = DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
58 ) {
59 this.choose = this.choose.bind(this)
60 }
61
62 protected setTaskStatisticsRequirements (
63 opts: WorkerChoiceStrategyOptions
64 ): void {
65 if (
66 this.taskStatisticsRequirements.runTime.average &&
67 opts.runTime?.median === true
68 ) {
69 this.taskStatisticsRequirements.runTime.average = false
70 this.taskStatisticsRequirements.runTime.median = opts.runTime
71 .median as boolean
72 }
73 if (
74 this.taskStatisticsRequirements.runTime.median &&
75 opts.runTime?.median === false
76 ) {
77 this.taskStatisticsRequirements.runTime.average = true
78 this.taskStatisticsRequirements.runTime.median = opts.runTime
79 .median as boolean
80 }
81 if (
82 this.taskStatisticsRequirements.waitTime.average &&
83 opts.waitTime?.median === true
84 ) {
85 this.taskStatisticsRequirements.waitTime.average = false
86 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
87 .median as boolean
88 }
89 if (
90 this.taskStatisticsRequirements.waitTime.median &&
91 opts.waitTime?.median === false
92 ) {
93 this.taskStatisticsRequirements.waitTime.average = true
94 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
95 .median as boolean
96 }
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 }
111 }
112
113 /** @inheritDoc */
114 public abstract reset (): boolean
115
116 /** @inheritDoc */
117 public abstract update (workerNodeKey: number): boolean
118
119 /** @inheritDoc */
120 public abstract choose (): number
121
122 /** @inheritDoc */
123 public abstract remove (workerNodeKey: number): boolean
124
125 /** @inheritDoc */
126 public setOptions (opts: WorkerChoiceStrategyOptions): void {
127 this.opts = opts ?? DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
128 this.setTaskStatisticsRequirements(this.opts)
129 }
130
131 protected workerNodeReady (workerNodeKey: number): boolean {
132 return this.pool.workerNodes[workerNodeKey].info.ready
133 }
134
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 // }
148
149 /**
150 * Gets the worker task runtime.
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.
153 *
154 * @param workerNodeKey - The worker node key.
155 * @returns The worker task runtime.
156 */
157 protected getWorkerTaskRunTime (workerNodeKey: number): number {
158 return this.taskStatisticsRequirements.runTime.median
159 ? this.pool.workerNodes[workerNodeKey].usage.runTime?.median ?? 0
160 : this.pool.workerNodes[workerNodeKey].usage.runTime?.average ?? 0
161 }
162
163 /**
164 * Gets the worker task wait time.
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.
167 *
168 * @param workerNodeKey - The worker node key.
169 * @returns The worker task wait time.
170 */
171 protected getWorkerTaskWaitTime (workerNodeKey: number): number {
172 return this.taskStatisticsRequirements.waitTime.median
173 ? this.pool.workerNodes[workerNodeKey].usage.waitTime?.median ?? 0
174 : this.pool.workerNodes[workerNodeKey].usage.waitTime?.average ?? 0
175 }
176
177 /**
178 * Gets the worker task ELU.
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.
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
187 ? this.pool.workerNodes[workerNodeKey].usage.elu.active?.median ?? 0
188 : this.pool.workerNodes[workerNodeKey].usage.elu.active?.average ?? 0
189 }
190
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
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 => {
213 // return workerNode.usage.tasks.executing === 0
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:
228 // // return this.pool.workerNodes.findLastIndex(workerNode => {
229 // // return workerNode.usage.tasks.executing === 0
230 // // })
231 // for (
232 // let workerNodeKey = this.pool.workerNodes.length - 1;
233 // workerNodeKey >= 0;
234 // workerNodeKey--
235 // ) {
236 // if (this.pool.workerNodes[workerNodeKey].usage.tasks.executing === 0) {
237 // return workerNodeKey
238 // }
239 // }
240 // return -1
241 // }
242 }