Commit | Line | Data |
---|---|---|
0bbf65c3 | 1 | import { cpus } from 'node:os' |
3c93feb9 JB |
2 | import { |
3 | DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS, | |
4 | DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS | |
5 | } from '../../utils' | |
08f3f44c | 6 | import type { IPool } from '../pool' |
f06e48d8 | 7 | import type { IWorker } from '../worker' |
10fcfaf4 JB |
8 | import 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 | */ |
22 | export 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 | |
1d6d93ce JB |
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 | // } | |
e0ae6100 | 144 | |
f6b641d6 | 145 | /** |
e6606302 | 146 | * Gets the worker task runtime. |
932fc8be JB |
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. | |
f6b641d6 JB |
149 | * |
150 | * @param workerNodeKey - The worker node key. | |
e6606302 | 151 | * @returns The worker task runtime. |
f6b641d6 JB |
152 | */ |
153 | protected getWorkerTaskRunTime (workerNodeKey: number): number { | |
932fc8be | 154 | return this.taskStatisticsRequirements.runTime.median |
465b2940 JB |
155 | ? this.pool.workerNodes[workerNodeKey].usage.runTime.median |
156 | : this.pool.workerNodes[workerNodeKey].usage.runTime.average | |
f6b641d6 JB |
157 | } |
158 | ||
ef680bb8 JB |
159 | /** |
160 | * Gets the worker task wait time. | |
932fc8be JB |
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. | |
ef680bb8 JB |
163 | * |
164 | * @param workerNodeKey - The worker node key. | |
165 | * @returns The worker task wait time. | |
166 | */ | |
5df69fab | 167 | protected getWorkerTaskWaitTime (workerNodeKey: number): number { |
932fc8be | 168 | return this.taskStatisticsRequirements.waitTime.median |
465b2940 JB |
169 | ? this.pool.workerNodes[workerNodeKey].usage.waitTime.median |
170 | : this.pool.workerNodes[workerNodeKey].usage.waitTime.average | |
ef680bb8 JB |
171 | } |
172 | ||
5df69fab JB |
173 | /** |
174 | * Gets the worker task ELU. | |
9adcefab JB |
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. | |
5df69fab JB |
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 | |
465b2940 JB |
183 | ? this.pool.workerNodes[workerNodeKey].usage.elu.active.median |
184 | : this.pool.workerNodes[workerNodeKey].usage.elu.active.average | |
5df69fab JB |
185 | } |
186 | ||
0bbf65c3 JB |
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 | ||
1d6d93ce JB |
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 => { | |
465b2940 | 209 | // return workerNode.usage.tasks.executing === 0 |
1d6d93ce JB |
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: | |
465b2940 JB |
224 | // // return this.pool.workerNodes.findLastIndex(workerNode => { |
225 | // // return workerNode.usage.tasks.executing === 0 | |
1d6d93ce JB |
226 | // // }) |
227 | // for ( | |
228 | // let workerNodeKey = this.pool.workerNodes.length - 1; | |
229 | // workerNodeKey >= 0; | |
230 | // workerNodeKey-- | |
231 | // ) { | |
465b2940 | 232 | // if (this.pool.workerNodes[workerNodeKey].usage.tasks.executing === 0) { |
1d6d93ce JB |
233 | // return workerNodeKey |
234 | // } | |
235 | // } | |
236 | // return -1 | |
237 | // } | |
bdaf31cd | 238 | } |