fix: get wait time statistics
[poolifier.git] / src / pools / selection-strategies / abstract-worker-choice-strategy.ts
1 import { cpus } from 'node:os'
2 import { DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS } from '../../utils'
3 import type { IPool } from '../pool'
4 import type { IWorker } from '../worker'
5 import type {
6 IWorkerChoiceStrategy,
7 StrategyPolicy,
8 TaskStatisticsRequirements,
9 WorkerChoiceStrategyOptions
10 } from './selection-strategies-types'
11
12 /**
13 * Worker choice strategy abstract base class.
14 *
15 * @typeParam Worker - Type of worker which manages the strategy.
16 * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
17 * @typeParam Response - Type of execution response. This can only be serializable data.
18 */
19 export abstract class AbstractWorkerChoiceStrategy<
20 Worker extends IWorker,
21 Data = unknown,
22 Response = unknown
23 > implements IWorkerChoiceStrategy {
24 // /**
25 // * Toggles finding the last free worker node key.
26 // */
27 // private toggleFindLastFreeWorkerNodeKey: boolean = false
28
29 /**
30 * Id of the next worker node.
31 */
32 protected nextWorkerNodeId: number = 0
33
34 /** @inheritDoc */
35 public readonly strategyPolicy: StrategyPolicy = {
36 useDynamicWorker: false
37 }
38
39 /** @inheritDoc */
40 public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
41 runTime: {
42 aggregate: false,
43 average: false,
44 median: false
45 },
46 waitTime: {
47 aggregate: false,
48 average: false,
49 median: false
50 },
51 elu: {
52 aggregate: false,
53 average: false,
54 median: false
55 }
56 }
57
58 /**
59 * Constructs a worker choice strategy bound to the pool.
60 *
61 * @param pool - The pool instance.
62 * @param opts - The worker choice strategy options.
63 */
64 public constructor (
65 protected readonly pool: IPool<Worker, Data, Response>,
66 protected opts: WorkerChoiceStrategyOptions = DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
67 ) {
68 this.choose = this.choose.bind(this)
69 }
70
71 protected setTaskStatisticsRequirements (
72 opts: WorkerChoiceStrategyOptions
73 ): void {
74 if (
75 this.taskStatisticsRequirements.runTime.average &&
76 opts.runTime?.median === true
77 ) {
78 this.taskStatisticsRequirements.runTime.average = false
79 this.taskStatisticsRequirements.runTime.median = opts.runTime
80 .median as boolean
81 }
82 if (
83 this.taskStatisticsRequirements.runTime.median &&
84 opts.runTime?.median === false
85 ) {
86 this.taskStatisticsRequirements.runTime.average = true
87 this.taskStatisticsRequirements.runTime.median = opts.runTime
88 .median as boolean
89 }
90 if (
91 this.taskStatisticsRequirements.waitTime.average &&
92 opts.waitTime?.median === true
93 ) {
94 this.taskStatisticsRequirements.waitTime.average = false
95 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
96 .median as boolean
97 }
98 if (
99 this.taskStatisticsRequirements.waitTime.median &&
100 opts.waitTime?.median === false
101 ) {
102 this.taskStatisticsRequirements.waitTime.average = true
103 this.taskStatisticsRequirements.waitTime.median = opts.waitTime
104 .median as boolean
105 }
106 if (
107 this.taskStatisticsRequirements.elu.average &&
108 opts.elu?.median === true
109 ) {
110 this.taskStatisticsRequirements.elu.average = false
111 this.taskStatisticsRequirements.elu.median = opts.elu.median as boolean
112 }
113 if (
114 this.taskStatisticsRequirements.elu.median &&
115 opts.elu?.median === false
116 ) {
117 this.taskStatisticsRequirements.elu.average = true
118 this.taskStatisticsRequirements.elu.median = opts.elu.median as boolean
119 }
120 }
121
122 /** @inheritDoc */
123 public abstract reset (): boolean
124
125 /** @inheritDoc */
126 public abstract update (workerNodeKey: number): boolean
127
128 /** @inheritDoc */
129 public abstract choose (): number
130
131 /** @inheritDoc */
132 public abstract remove (workerNodeKey: number): boolean
133
134 /** @inheritDoc */
135 public setOptions (opts: WorkerChoiceStrategyOptions): void {
136 opts = opts ?? DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
137 this.setTaskStatisticsRequirements(opts)
138 this.opts = opts
139 }
140
141 // /**
142 // * Finds a free worker node key.
143 // *
144 // * @returns The free worker node key or `-1` if there is no free worker node.
145 // */
146 // protected findFreeWorkerNodeKey (): number {
147 // if (this.toggleFindLastFreeWorkerNodeKey) {
148 // this.toggleFindLastFreeWorkerNodeKey = false
149 // return this.findLastFreeWorkerNodeKey()
150 // }
151 // this.toggleFindLastFreeWorkerNodeKey = true
152 // return this.findFirstFreeWorkerNodeKey()
153 // }
154
155 /**
156 * Gets the worker task runtime.
157 * If the task statistics require the average runtime, the average runtime is returned.
158 * If the task statistics require the median runtime , the median runtime is returned.
159 *
160 * @param workerNodeKey - The worker node key.
161 * @returns The worker task runtime.
162 */
163 protected getWorkerTaskRunTime (workerNodeKey: number): number {
164 return this.taskStatisticsRequirements.runTime.median
165 ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
166 : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
167 }
168
169 /**
170 * Gets the worker task wait time.
171 * If the task statistics require the average wait time, the average wait time is returned.
172 * If the task statistics require the median wait time, the median wait time is returned.
173 *
174 * @param workerNodeKey - The worker node key.
175 * @returns The worker task wait time.
176 */
177 protected getWorkerTaskWaitTime (workerNodeKey: number): number {
178 return this.taskStatisticsRequirements.waitTime.median
179 ? this.pool.workerNodes[workerNodeKey].workerUsage.waitTime.median
180 : this.pool.workerNodes[workerNodeKey].workerUsage.waitTime.average
181 }
182
183 /**
184 * Gets the worker task ELU.
185 * If the task statistics require the average ELU, the average ELU is returned.
186 * If the task statistics require the median ELU, the median ELU is returned.
187 *
188 * @param workerNodeKey - The worker node key.
189 * @returns The worker task ELU.
190 */
191 protected getWorkerTaskElu (workerNodeKey: number): number {
192 return this.taskStatisticsRequirements.elu.median
193 ? this.pool.workerNodes[workerNodeKey].workerUsage.elu.active.median
194 : this.pool.workerNodes[workerNodeKey].workerUsage.elu.active.average
195 }
196
197 protected computeDefaultWorkerWeight (): number {
198 let cpusCycleTimeWeight = 0
199 for (const cpu of cpus()) {
200 // CPU estimated cycle time
201 const numberOfDigits = cpu.speed.toString().length - 1
202 const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
203 cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
204 }
205 return Math.round(cpusCycleTimeWeight / cpus().length)
206 }
207
208 // /**
209 // * Finds the first free worker node key based on the number of tasks the worker has applied.
210 // *
211 // * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
212 // *
213 // * If no free worker is found, `-1` is returned.
214 // *
215 // * @returns A worker node key if there is one, `-1` otherwise.
216 // */
217 // private findFirstFreeWorkerNodeKey (): number {
218 // return this.pool.workerNodes.findIndex(workerNode => {
219 // return workerNode.workerUsage.tasks.executing === 0
220 // })
221 // }
222
223 // /**
224 // * Finds the last free worker node key based on the number of tasks the worker has applied.
225 // *
226 // * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
227 // *
228 // * If no free worker is found, `-1` is returned.
229 // *
230 // * @returns A worker node key if there is one, `-1` otherwise.
231 // */
232 // private findLastFreeWorkerNodeKey (): number {
233 // // It requires node >= 18.0.0:
234 // // return this.workerNodes.findLastIndex(workerNode => {
235 // // return workerNode.workerUsage.tasks.executing === 0
236 // // })
237 // for (
238 // let workerNodeKey = this.pool.workerNodes.length - 1;
239 // workerNodeKey >= 0;
240 // workerNodeKey--
241 // ) {
242 // if (
243 // this.pool.workerNodes[workerNodeKey].workerUsage.tasks.executing === 0
244 // ) {
245 // return workerNodeKey
246 // }
247 // }
248 // return -1
249 // }
250 }