1 import { cpus
} from
'node:os'
3 DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
,
4 DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
6 import type { IPool
} from
'../pool'
7 import type { IWorker
} from
'../worker'
11 TaskStatisticsRequirements
,
12 WorkerChoiceStrategyOptions
13 } from
'./selection-strategies-types'
16 * Worker choice strategy abstract base class.
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.
22 export abstract class AbstractWorkerChoiceStrategy
<
23 Worker
extends IWorker
,
26 > implements IWorkerChoiceStrategy
{
28 // * Toggles finding the last free worker node key.
30 // private toggleFindLastFreeWorkerNodeKey: boolean = false
33 * Id of the next worker node.
35 protected nextWorkerNodeId
: number = 0
38 public readonly strategyPolicy
: StrategyPolicy
= {
39 useDynamicWorker
: false
43 public readonly taskStatisticsRequirements
: TaskStatisticsRequirements
= {
44 runTime
: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
,
45 waitTime
: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
,
46 elu
: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
50 * Constructs a worker choice strategy bound to the pool.
52 * @param pool - The pool instance.
53 * @param opts - The worker choice strategy options.
56 protected readonly pool
: IPool
<Worker
, Data
, Response
>,
57 protected opts
: WorkerChoiceStrategyOptions
= DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
59 this.choose
= this.choose
.bind(this)
62 protected setTaskStatisticsRequirements (
63 opts
: WorkerChoiceStrategyOptions
66 this.taskStatisticsRequirements
.runTime
.average
&&
67 opts
.runTime
?.median
=== true
69 this.taskStatisticsRequirements
.runTime
.average
= false
70 this.taskStatisticsRequirements
.runTime
.median
= opts
.runTime
74 this.taskStatisticsRequirements
.runTime
.median
&&
75 opts
.runTime
?.median
=== false
77 this.taskStatisticsRequirements
.runTime
.average
= true
78 this.taskStatisticsRequirements
.runTime
.median
= opts
.runTime
82 this.taskStatisticsRequirements
.waitTime
.average
&&
83 opts
.waitTime
?.median
=== true
85 this.taskStatisticsRequirements
.waitTime
.average
= false
86 this.taskStatisticsRequirements
.waitTime
.median
= opts
.waitTime
90 this.taskStatisticsRequirements
.waitTime
.median
&&
91 opts
.waitTime
?.median
=== false
93 this.taskStatisticsRequirements
.waitTime
.average
= true
94 this.taskStatisticsRequirements
.waitTime
.median
= opts
.waitTime
98 this.taskStatisticsRequirements
.elu
.average
&&
99 opts
.elu
?.median
=== true
101 this.taskStatisticsRequirements
.elu
.average
= false
102 this.taskStatisticsRequirements
.elu
.median
= opts
.elu
.median
as boolean
105 this.taskStatisticsRequirements
.elu
.median
&&
106 opts
.elu
?.median
=== false
108 this.taskStatisticsRequirements
.elu
.average
= true
109 this.taskStatisticsRequirements
.elu
.median
= opts
.elu
.median
as boolean
114 public abstract reset (): boolean
117 public abstract update (workerNodeKey
: number): boolean
120 public abstract choose (): number
123 public abstract remove (workerNodeKey
: number): boolean
126 public setOptions (opts
: WorkerChoiceStrategyOptions
): void {
127 this.opts
= opts
?? DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
128 this.setTaskStatisticsRequirements(this.opts
)
131 protected workerNodeReady (workerNodeKey
: number): boolean {
132 return this.pool
.workerNodes
[workerNodeKey
].info
.ready
136 // * Finds a free worker node key.
138 // * @returns The free worker node key or `-1` if there is no free worker node.
140 // protected findFreeWorkerNodeKey (): number {
141 // if (this.toggleFindLastFreeWorkerNodeKey) {
142 // this.toggleFindLastFreeWorkerNodeKey = false
143 // return this.findLastFreeWorkerNodeKey()
145 // this.toggleFindLastFreeWorkerNodeKey = true
146 // return this.findFirstFreeWorkerNodeKey()
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.
154 * @param workerNodeKey - The worker node key.
155 * @returns The worker task runtime.
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
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.
168 * @param workerNodeKey - The worker node key.
169 * @returns The worker task wait time.
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
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.
182 * @param workerNodeKey - The worker node key.
183 * @returns The worker task ELU.
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
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
)
199 return Math.round(cpusCycleTimeWeight
/ cpus().length
)
203 // * Finds the first free worker node key based on the number of tasks the worker has applied.
205 // * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
207 // * If no free worker is found, `-1` is returned.
209 // * @returns A worker node key if there is one, `-1` otherwise.
211 // private findFirstFreeWorkerNodeKey (): number {
212 // return this.pool.workerNodes.findIndex(workerNode => {
213 // return workerNode.usage.tasks.executing === 0
218 // * Finds the last free worker node key based on the number of tasks the worker has applied.
220 // * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
222 // * If no free worker is found, `-1` is returned.
224 // * @returns A worker node key if there is one, `-1` otherwise.
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
232 // let workerNodeKey = this.pool.workerNodes.length - 1;
233 // workerNodeKey >= 0;
236 // if (this.pool.workerNodes[workerNodeKey].usage.tasks.executing === 0) {
237 // return workerNodeKey