Merge branch 'master' into worker-info
[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 // /**
132 // * Finds a free worker node key.
133 // *
134 // * @returns The free worker node key or `-1` if there is no free worker node.
135 // */
136 // protected findFreeWorkerNodeKey (): number {
137 // if (this.toggleFindLastFreeWorkerNodeKey) {
138 // this.toggleFindLastFreeWorkerNodeKey = false
139 // return this.findLastFreeWorkerNodeKey()
140 // }
141 // this.toggleFindLastFreeWorkerNodeKey = true
142 // return this.findFirstFreeWorkerNodeKey()
143 // }
144
145 /**
146 * Gets the worker task runtime.
147 * If the task statistics require the average runtime, the average runtime is returned.
148 * If the task statistics require the median runtime , the median runtime is returned.
149 *
150 * @param workerNodeKey - The worker node key.
151 * @returns The worker task runtime.
152 */
153 protected getWorkerTaskRunTime (workerNodeKey: number): number {
154 return this.taskStatisticsRequirements.runTime.median
155 ? this.pool.workerNodes[workerNodeKey].usage.runTime.median
156 : this.pool.workerNodes[workerNodeKey].usage.runTime.average
157 }
158
159 /**
160 * Gets the worker task wait time.
161 * If the task statistics require the average wait time, the average wait time is returned.
162 * If the task statistics require the median wait time, the median wait time is returned.
163 *
164 * @param workerNodeKey - The worker node key.
165 * @returns The worker task wait time.
166 */
167 protected getWorkerTaskWaitTime (workerNodeKey: number): number {
168 return this.taskStatisticsRequirements.waitTime.median
169 ? this.pool.workerNodes[workerNodeKey].usage.waitTime.median
170 : this.pool.workerNodes[workerNodeKey].usage.waitTime.average
171 }
172
173 /**
174 * Gets the worker task ELU.
175 * If the task statistics require the average ELU, the average ELU is returned.
176 * If the task statistics require the median ELU, the median ELU is returned.
177 *
178 * @param workerNodeKey - The worker node key.
179 * @returns The worker task ELU.
180 */
181 protected getWorkerTaskElu (workerNodeKey: number): number {
182 return this.taskStatisticsRequirements.elu.median
183 ? this.pool.workerNodes[workerNodeKey].usage.elu.active.median
184 : this.pool.workerNodes[workerNodeKey].usage.elu.active.average
185 }
186
187 protected computeDefaultWorkerWeight (): number {
188 let cpusCycleTimeWeight = 0
189 for (const cpu of cpus()) {
190 // CPU estimated cycle time
191 const numberOfDigits = cpu.speed.toString().length - 1
192 const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
193 cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
194 }
195 return Math.round(cpusCycleTimeWeight / cpus().length)
196 }
197
198 // /**
199 // * Finds the first free worker node key based on the number of tasks the worker has applied.
200 // *
201 // * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
202 // *
203 // * If no free worker is found, `-1` is returned.
204 // *
205 // * @returns A worker node key if there is one, `-1` otherwise.
206 // */
207 // private findFirstFreeWorkerNodeKey (): number {
208 // return this.pool.workerNodes.findIndex(workerNode => {
209 // return workerNode.usage.tasks.executing === 0
210 // })
211 // }
212
213 // /**
214 // * Finds the last free worker node key based on the number of tasks the worker has applied.
215 // *
216 // * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
217 // *
218 // * If no free worker is found, `-1` is returned.
219 // *
220 // * @returns A worker node key if there is one, `-1` otherwise.
221 // */
222 // private findLastFreeWorkerNodeKey (): number {
223 // // It requires node >= 18.0.0:
224 // // return this.pool.workerNodes.findLastIndex(workerNode => {
225 // // return workerNode.usage.tasks.executing === 0
226 // // })
227 // for (
228 // let workerNodeKey = this.pool.workerNodes.length - 1;
229 // workerNodeKey >= 0;
230 // workerNodeKey--
231 // ) {
232 // if (this.pool.workerNodes[workerNodeKey].usage.tasks.executing === 0) {
233 // return workerNodeKey
234 // }
235 // }
236 // return -1
237 // }
238 }