fix: fix task wait time computation
[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 = {
c6bd2650 29 runTime: false,
78099a15 30 avgRunTime: false,
0567595a
JB
31 medRunTime: false,
32 waitTime: false,
33 avgWaitTime: false,
62c15a68
JB
34 medWaitTime: false,
35 elu: false
10fcfaf4 36 }
bdaf31cd
JB
37
38 /**
6533c3e6 39 * Constructs a worker choice strategy bound to the pool.
bdaf31cd 40 *
38e795c1 41 * @param pool - The pool instance.
da309861 42 * @param opts - The worker choice strategy options.
bdaf31cd
JB
43 */
44 public constructor (
c4855468 45 protected readonly pool: IPool<Worker, Data, Response>,
a20f0ba5 46 protected opts: WorkerChoiceStrategyOptions = DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
b8f3418c 47 ) {
7254e419 48 this.choose = this.choose.bind(this)
b8f3418c 49 }
bdaf31cd 50
b6b32453 51 protected setTaskStatistics (opts: WorkerChoiceStrategyOptions): void {
87de9ff5
JB
52 if (
53 this.taskStatisticsRequirements.avgRunTime &&
54 opts.medRunTime === true
55 ) {
56 this.taskStatisticsRequirements.avgRunTime = false
57 this.taskStatisticsRequirements.medRunTime = opts.medRunTime as boolean
da309861 58 }
87de9ff5
JB
59 if (
60 this.taskStatisticsRequirements.medRunTime &&
61 opts.medRunTime === false
62 ) {
63 this.taskStatisticsRequirements.avgRunTime = true
64 this.taskStatisticsRequirements.medRunTime = opts.medRunTime as boolean
a20f0ba5 65 }
87de9ff5
JB
66 if (
67 this.taskStatisticsRequirements.avgWaitTime &&
68 opts.medWaitTime === true
69 ) {
70 this.taskStatisticsRequirements.avgWaitTime = false
71 this.taskStatisticsRequirements.medWaitTime = opts.medWaitTime as boolean
0567595a 72 }
87de9ff5
JB
73 if (
74 this.taskStatisticsRequirements.medWaitTime &&
75 opts.medWaitTime === false
76 ) {
77 this.taskStatisticsRequirements.avgWaitTime = true
78 this.taskStatisticsRequirements.medWaitTime = opts.medWaitTime as boolean
0567595a 79 }
da309861
JB
80 }
81
afc003b2 82 /** @inheritDoc */
a6f7f1b4 83 public abstract reset (): boolean
ea7a90d3 84
138d29a8 85 /** @inheritDoc */
a4958de2 86 public abstract update (workerNodeKey: number): boolean
138d29a8 87
afc003b2 88 /** @inheritDoc */
c923ce56 89 public abstract choose (): number
97a2abc3 90
afc003b2 91 /** @inheritDoc */
f06e48d8 92 public abstract remove (workerNodeKey: number): boolean
a20f0ba5
JB
93
94 /** @inheritDoc */
95 public setOptions (opts: WorkerChoiceStrategyOptions): void {
d01e4d67 96 opts = opts ?? DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
b6b32453 97 this.setTaskStatistics(opts)
a20f0ba5
JB
98 this.opts = opts
99 }
cb70b19d
JB
100
101 /**
102 * Finds a free worker node key.
103 *
104 * @returns The free worker node key or `-1` if there is no free worker node.
105 */
106 protected findFreeWorkerNodeKey (): number {
107 if (this.toggleFindLastFreeWorkerNodeKey) {
108 this.toggleFindLastFreeWorkerNodeKey = false
e0ae6100 109 return this.findLastFreeWorkerNodeKey()
cb70b19d
JB
110 }
111 this.toggleFindLastFreeWorkerNodeKey = true
e0ae6100
JB
112 return this.findFirstFreeWorkerNodeKey()
113 }
114
f6b641d6 115 /**
e6606302 116 * Gets the worker task runtime.
87de9ff5
JB
117 * If the task statistics require `avgRunTime`, the average runtime is returned.
118 * If the task statistics require `medRunTime`, the median runtime is returned.
f6b641d6
JB
119 *
120 * @param workerNodeKey - The worker node key.
e6606302 121 * @returns The worker task runtime.
f6b641d6
JB
122 */
123 protected getWorkerTaskRunTime (workerNodeKey: number): number {
87de9ff5 124 return this.taskStatisticsRequirements.medRunTime
a4e07f72
JB
125 ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
126 : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
f6b641d6
JB
127 }
128
ef680bb8
JB
129 /**
130 * Gets the worker task wait time.
87de9ff5
JB
131 * If the task statistics require `avgWaitTime`, the average wait time is returned.
132 * If the task statistics require `medWaitTime`, the median wait time is returned.
ef680bb8
JB
133 *
134 * @param workerNodeKey - The worker node key.
135 * @returns The worker task wait time.
136 */
137 protected getWorkerWaitTime (workerNodeKey: number): number {
87de9ff5 138 return this.taskStatisticsRequirements.medWaitTime
a4e07f72
JB
139 ? this.pool.workerNodes[workerNodeKey].workerUsage.runTime.median
140 : this.pool.workerNodes[workerNodeKey].workerUsage.runTime.average
ef680bb8
JB
141 }
142
0bbf65c3
JB
143 protected computeDefaultWorkerWeight (): number {
144 let cpusCycleTimeWeight = 0
145 for (const cpu of cpus()) {
146 // CPU estimated cycle time
147 const numberOfDigits = cpu.speed.toString().length - 1
148 const cpuCycleTime = 1 / (cpu.speed / Math.pow(10, numberOfDigits))
149 cpusCycleTimeWeight += cpuCycleTime * Math.pow(10, numberOfDigits)
150 }
151 return Math.round(cpusCycleTimeWeight / cpus().length)
152 }
153
e0ae6100
JB
154 /**
155 * Finds the first free worker node key based on the number of tasks the worker has applied.
156 *
1c6fe997 157 * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
e0ae6100
JB
158 *
159 * If no free worker is found, `-1` is returned.
160 *
161 * @returns A worker node key if there is one, `-1` otherwise.
162 */
163 private findFirstFreeWorkerNodeKey (): number {
164 return this.pool.workerNodes.findIndex(workerNode => {
a4e07f72 165 return workerNode.workerUsage.tasks.executing === 0
e0ae6100
JB
166 })
167 }
168
169 /**
170 * Finds the last free worker node key based on the number of tasks the worker has applied.
171 *
1c6fe997 172 * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
e0ae6100
JB
173 *
174 * If no free worker is found, `-1` is returned.
175 *
176 * @returns A worker node key if there is one, `-1` otherwise.
177 */
178 private findLastFreeWorkerNodeKey (): number {
0e7c56b0 179 // It requires node >= 18.0.0:
e0ae6100 180 // return this.workerNodes.findLastIndex(workerNode => {
a4e07f72 181 // return workerNode.workerUsage.tasks.executing === 0
e0ae6100 182 // })
0682ba15
JB
183 for (
184 let workerNodeKey = this.pool.workerNodes.length - 1;
185 workerNodeKey >= 0;
186 workerNodeKey--
187 ) {
a4e07f72
JB
188 if (
189 this.pool.workerNodes[workerNodeKey].workerUsage.tasks.executing === 0
190 ) {
0682ba15 191 return workerNodeKey
e0ae6100
JB
192 }
193 }
194 return -1
cb70b19d 195 }
bdaf31cd 196}