refactor: align worker choice strategy options namespace
[poolifier.git] / src / pools / selection-strategies / abstract-worker-choice-strategy.ts
CommitLineData
0bbf65c3 1import { cpus } from 'node:os'
bbeadd16 2import { DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS } from '../../utils'
08f3f44c 3import type { IPool } from '../pool'
f06e48d8 4import type { IWorker } from '../worker'
10fcfaf4
JB
5import type {
6 IWorkerChoiceStrategy,
87de9ff5 7 TaskStatisticsRequirements,
da309861 8 WorkerChoiceStrategyOptions
10fcfaf4 9} from './selection-strategies-types'
bdaf31cd
JB
10
11/**
9cd39dd4 12 * Worker choice strategy abstract base class.
bdaf31cd 13 *
38e795c1
JB
14 * @typeParam Worker - Type of worker which manages the strategy.
15 * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
02706357 16 * @typeParam Response - Type of execution response. This can only be serializable data.
bdaf31cd
JB
17 */
18export abstract class AbstractWorkerChoiceStrategy<
f06e48d8 19 Worker extends IWorker,
b2b1d84e
JB
20 Data = unknown,
21 Response = unknown
17393ac8 22> implements IWorkerChoiceStrategy {
cb70b19d
JB
23 /**
24 * Toggles finding the last free worker node key.
25 */
26 private toggleFindLastFreeWorkerNodeKey: boolean = false
afc003b2 27 /** @inheritDoc */
87de9ff5 28 public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
932fc8be
JB
29 runTime: {
30 aggregate: false,
31 average: false,
32 median: false
33 },
34 waitTime: {
35 aggregate: false,
36 average: false,
37 median: false
38 },
62c15a68 39 elu: false
10fcfaf4 40 }
bdaf31cd
JB
41
42 /**
6533c3e6 43 * Constructs a worker choice strategy bound to the pool.
bdaf31cd 44 *
38e795c1 45 * @param pool - The pool instance.
da309861 46 * @param opts - The worker choice strategy options.
bdaf31cd
JB
47 */
48 public constructor (
c4855468 49 protected readonly pool: IPool<Worker, Data, Response>,
a20f0ba5 50 protected opts: WorkerChoiceStrategyOptions = DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
b8f3418c 51 ) {
7254e419 52 this.choose = this.choose.bind(this)
b8f3418c 53 }
bdaf31cd 54
932fc8be
JB
55 protected setTaskStatisticsRequirements (
56 opts: WorkerChoiceStrategyOptions
57 ): void {
87de9ff5 58 if (
932fc8be
JB
59 this.taskStatisticsRequirements.runTime.average &&
60 opts.runTime?.median === true
87de9ff5 61 ) {
932fc8be
JB
62 this.taskStatisticsRequirements.runTime.average = false
63 this.taskStatisticsRequirements.runTime.median = opts.runTime
64 .median as boolean
da309861 65 }
87de9ff5 66 if (
932fc8be
JB
67 this.taskStatisticsRequirements.runTime.median &&
68 opts.runTime?.median === false
87de9ff5 69 ) {
932fc8be
JB
70 this.taskStatisticsRequirements.runTime.average = true
71 this.taskStatisticsRequirements.runTime.median = opts.runTime
72 .median as boolean
a20f0ba5 73 }
87de9ff5 74 if (
932fc8be
JB
75 this.taskStatisticsRequirements.waitTime.average &&
76 opts.waitTime?.median === true
87de9ff5 77 ) {
932fc8be
JB
78 this.taskStatisticsRequirements.waitTime.average = false
79 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
80 .median as boolean
0567595a 81 }
87de9ff5 82 if (
932fc8be
JB
83 this.taskStatisticsRequirements.waitTime.median &&
84 opts.waitTime?.median === false
87de9ff5 85 ) {
932fc8be
JB
86 this.taskStatisticsRequirements.waitTime.average = true
87 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
88 .median as boolean
0567595a 89 }
da309861
JB
90 }
91
afc003b2 92 /** @inheritDoc */
a6f7f1b4 93 public abstract reset (): boolean
ea7a90d3 94
138d29a8 95 /** @inheritDoc */
a4958de2 96 public abstract update (workerNodeKey: number): boolean
138d29a8 97
afc003b2 98 /** @inheritDoc */
c923ce56 99 public abstract choose (): number
97a2abc3 100
afc003b2 101 /** @inheritDoc */
f06e48d8 102 public abstract remove (workerNodeKey: number): boolean
a20f0ba5
JB
103
104 /** @inheritDoc */
105 public setOptions (opts: WorkerChoiceStrategyOptions): void {
d01e4d67 106 opts = opts ?? DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
932fc8be 107 this.setTaskStatisticsRequirements(opts)
a20f0ba5
JB
108 this.opts = opts
109 }
cb70b19d
JB
110
111 /**
112 * Finds a free worker node key.
113 *
114 * @returns The free worker node key or `-1` if there is no free worker node.
115 */
116 protected findFreeWorkerNodeKey (): number {
117 if (this.toggleFindLastFreeWorkerNodeKey) {
118 this.toggleFindLastFreeWorkerNodeKey = false
e0ae6100 119 return this.findLastFreeWorkerNodeKey()
cb70b19d
JB
120 }
121 this.toggleFindLastFreeWorkerNodeKey = true
e0ae6100
JB
122 return this.findFirstFreeWorkerNodeKey()
123 }
124
f6b641d6 125 /**
e6606302 126 * Gets the worker task runtime.
932fc8be
JB
127 * If the task statistics require the average runtime, the average runtime is returned.
128 * If the task statistics require the median runtime , the median runtime is returned.
f6b641d6
JB
129 *
130 * @param workerNodeKey - The worker node key.
e6606302 131 * @returns The worker task runtime.
f6b641d6
JB
132 */
133 protected getWorkerTaskRunTime (workerNodeKey: number): number {
932fc8be 134 return this.taskStatisticsRequirements.runTime.median
a4e07f72
JB
135 ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
136 : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
f6b641d6
JB
137 }
138
ef680bb8
JB
139 /**
140 * Gets the worker task wait time.
932fc8be
JB
141 * If the task statistics require the average wait time, the average wait time is returned.
142 * If the task statistics require the median wait time, the median wait time is returned.
ef680bb8
JB
143 *
144 * @param workerNodeKey - The worker node key.
145 * @returns The worker task wait time.
146 */
147 protected getWorkerWaitTime (workerNodeKey: number): number {
932fc8be 148 return this.taskStatisticsRequirements.waitTime.median
a4e07f72
JB
149 ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
150 : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
ef680bb8
JB
151 }
152
0bbf65c3
JB
153 protected computeDefaultWorkerWeight (): number {
154 let cpusCycleTimeWeight = 0
155 for (const cpu of cpus()) {
156 // CPU estimated cycle time
157 const numberOfDigits = cpu.speed.toString().length - 1
158 const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
159 cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
160 }
161 return Math.round(cpusCycleTimeWeight / cpus().length)
162 }
163
e0ae6100
JB
164 /**
165 * Finds the first free worker node key based on the number of tasks the worker has applied.
166 *
1c6fe997 167 * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
e0ae6100
JB
168 *
169 * If no free worker is found, `-1` is returned.
170 *
171 * @returns A worker node key if there is one, `-1` otherwise.
172 */
173 private findFirstFreeWorkerNodeKey (): number {
174 return this.pool.workerNodes.findIndex(workerNode => {
a4e07f72 175 return workerNode.workerUsage.tasks.executing === 0
e0ae6100
JB
176 })
177 }
178
179 /**
180 * Finds the last free worker node key based on the number of tasks the worker has applied.
181 *
1c6fe997 182 * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
e0ae6100
JB
183 *
184 * If no free worker is found, `-1` is returned.
185 *
186 * @returns A worker node key if there is one, `-1` otherwise.
187 */
188 private findLastFreeWorkerNodeKey (): number {
0e7c56b0 189 // It requires node >= 18.0.0:
e0ae6100 190 // return this.workerNodes.findLastIndex(workerNode => {
a4e07f72 191 // return workerNode.workerUsage.tasks.executing === 0
e0ae6100 192 // })
0682ba15
JB
193 for (
194 let workerNodeKey = this.pool.workerNodes.length - 1;
195 workerNodeKey >= 0;
196 workerNodeKey--
197 ) {
a4e07f72
JB
198 if (
199 this.pool.workerNodes[workerNodeKey].workerUsage.tasks.executing === 0
200 ) {
0682ba15 201 return workerNodeKey
e0ae6100
JB
202 }
203 }
204 return -1
cb70b19d 205 }
bdaf31cd 206}