test: add pool creation and destroy test
[poolifier.git] / src / utils.ts
CommitLineData
aa4bf4b2 1import * as os from 'node:os'
98446b39 2import { webcrypto } from 'node:crypto'
75de9f41
JB
3import { Worker as ClusterWorker } from 'node:cluster'
4import { Worker as ThreadWorker } from 'node:worker_threads'
3c93feb9
JB
5import type {
6 MeasurementStatisticsRequirements,
7 WorkerChoiceStrategyOptions
8} from './pools/selection-strategies/selection-strategies-types'
59317253 9import type { KillBehavior } from './worker/worker-options'
bfc75cca 10import { type IWorker, type WorkerType, WorkerTypes } from './pools/worker'
bbeadd16 11
ff128cc9
JB
12/**
13 * Default task name.
14 */
15export const DEFAULT_TASK_NAME = 'default'
16
6e9d10db
JB
17/**
18 * An intentional empty function.
19 */
4f3c3d89 20export const EMPTY_FUNCTION: () => void = Object.freeze(() => {
6e9d10db 21 /* Intentionally empty */
4f3c3d89 22})
78099a15
JB
23
24/**
bbeadd16
JB
25 * Default worker choice strategy options.
26 */
27export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrategyOptions =
28 {
8c0b113f 29 retries: 6,
932fc8be 30 runTime: { median: false },
5df69fab
JB
31 waitTime: { median: false },
32 elu: { median: false }
bbeadd16
JB
33 }
34
3c93feb9
JB
35/**
36 * Default measurement statistics requirements.
37 */
38export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS: MeasurementStatisticsRequirements =
39 {
40 aggregate: false,
41 average: false,
42 median: false
43 }
44
51474716 45/**
ab80dc46
JB
46 * Returns safe host OS optimized estimate of the default amount of parallelism a pool should use.
47 * Always returns a value greater than zero.
48 *
49 * @returns The host OS optimized maximum pool size.
51474716
JB
50 */
51export const availableParallelism = (): number => {
52 let availableParallelism = 1
53 try {
aa4bf4b2 54 availableParallelism = os.availableParallelism()
51474716 55 } catch {
562a4037
JB
56 const cpus = os.cpus()
57 if (Array.isArray(cpus) && cpus.length > 0) {
58 availableParallelism = cpus.length
51474716
JB
59 }
60 }
61 return availableParallelism
62}
63
9fe8fd69
JB
64/**
65 * Returns the worker type of the given worker.
66 *
67 * @param worker - The worker to get the type of.
68 * @returns The worker type of the given worker.
69 * @internal
70 */
187601ff 71export const getWorkerType = (worker: IWorker): WorkerType | undefined => {
9fe8fd69
JB
72 if (worker instanceof ThreadWorker) {
73 return WorkerTypes.thread
74 } else if (worker instanceof ClusterWorker) {
75 return WorkerTypes.cluster
76 }
77}
78
79/**
80 * Returns the worker id of the given worker.
81 *
82 * @param worker - The worker to get the id of.
83 * @returns The worker id of the given worker.
84 * @internal
85 */
187601ff 86export const getWorkerId = (worker: IWorker): number | undefined => {
9fe8fd69
JB
87 if (worker instanceof ThreadWorker) {
88 return worker.threadId
89 } else if (worker instanceof ClusterWorker) {
90 return worker.id
91 }
92}
93
68cbdc84
JB
94/**
95 * Sleeps for the given amount of milliseconds.
96 *
97 * @param ms - The amount of milliseconds to sleep.
98 * @returns A promise that resolves after the given amount of milliseconds.
57a29f75 99 * @internal
68cbdc84
JB
100 */
101export const sleep = async (ms: number): Promise<void> => {
041dc05b 102 await new Promise(resolve => {
68cbdc84
JB
103 setTimeout(resolve, ms)
104 })
105}
106
107/**
108 * Computes the retry delay in milliseconds using an exponential back off algorithm.
109 *
110 * @param retryNumber - The number of retries that have already been attempted
147be6fe 111 * @param delayFactor - The base delay factor in milliseconds
68cbdc84
JB
112 * @returns Delay in milliseconds
113 * @internal
114 */
115export const exponentialDelay = (
116 retryNumber = 0,
147be6fe 117 delayFactor = 100
68cbdc84 118): number => {
147be6fe
JB
119 const delay = Math.pow(2, retryNumber) * delayFactor
120 const randomSum = delay * 0.2 * secureRandom() // 0-20% of the delay
68cbdc84
JB
121 return delay + randomSum
122}
8990357d 123
dc021bcc
JB
124/**
125 * Computes the average of the given data set.
126 *
127 * @param dataSet - Data set.
128 * @returns The average of the given data set.
129 * @internal
130 */
131export const average = (dataSet: number[]): number => {
132 if (Array.isArray(dataSet) && dataSet.length === 0) {
133 return 0
134 }
135 if (Array.isArray(dataSet) && dataSet.length === 1) {
136 return dataSet[0]
137 }
138 return (
139 dataSet.reduce((accumulator, number) => accumulator + number, 0) /
140 dataSet.length
141 )
142}
143
bbeadd16 144/**
afe0d5bf 145 * Computes the median of the given data set.
78099a15
JB
146 *
147 * @param dataSet - Data set.
148 * @returns The median of the given data set.
4bffc062 149 * @internal
78099a15
JB
150 */
151export const median = (dataSet: number[]): number => {
4a45e8d2
JB
152 if (Array.isArray(dataSet) && dataSet.length === 0) {
153 return 0
154 }
78099a15
JB
155 if (Array.isArray(dataSet) && dataSet.length === 1) {
156 return dataSet[0]
157 }
c6f42dd6
JB
158 const sortedDataSet = dataSet.slice().sort((a, b) => a - b)
159 return (
160 (sortedDataSet[(sortedDataSet.length - 1) >> 1] +
161 sortedDataSet[sortedDataSet.length >> 1]) /
162 2
163 )
78099a15 164}
0d80593b 165
afe0d5bf
JB
166/**
167 * Rounds the given number to the given scale.
64383951 168 * The rounding is done using the "round half away from zero" method.
afe0d5bf
JB
169 *
170 * @param num - The number to round.
171 * @param scale - The scale to round to.
172 * @returns The rounded number.
57a29f75 173 * @internal
afe0d5bf
JB
174 */
175export const round = (num: number, scale = 2): number => {
176 const rounder = Math.pow(10, scale)
177 return Math.round(num * rounder * (1 + Number.EPSILON)) / rounder
178}
179
3c653a03
JB
180/**
181 * Is the given object a plain object?
182 *
183 * @param obj - The object to check.
184 * @returns `true` if the given object is a plain object, `false` otherwise.
57a29f75 185 * @internal
3c653a03 186 */
0d80593b
JB
187export const isPlainObject = (obj: unknown): boolean =>
188 typeof obj === 'object' &&
189 obj !== null &&
190 obj?.constructor === Object &&
191 Object.prototype.toString.call(obj) === '[object Object]'
59317253
JB
192
193/**
194 * Detects whether the given value is a kill behavior or not.
195 *
196 * @typeParam KB - Which specific KillBehavior type to test against.
197 * @param killBehavior - Which kind of kill behavior to detect.
198 * @param value - Any value.
199 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
4bffc062 200 * @internal
59317253
JB
201 */
202export const isKillBehavior = <KB extends KillBehavior>(
203 killBehavior: KB,
204 value: unknown
205): value is KB => {
206 return value === killBehavior
207}
49d1b48c
JB
208
209/**
210 * Detects whether the given value is an asynchronous function or not.
211 *
212 * @param fn - Any value.
213 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
57a29f75 214 * @internal
49d1b48c
JB
215 */
216export const isAsyncFunction = (
217 fn: unknown
218): fn is (...args: unknown[]) => Promise<unknown> => {
219 return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction'
220}
e4f20deb 221
68cbdc84 222/**
57a29f75 223 * Generates a cryptographically secure random number in the [0,1[ range
68cbdc84
JB
224 *
225 * @returns A number in the [0,1[ range
57a29f75 226 * @internal
68cbdc84 227 */
970b38d6 228export const secureRandom = (): number => {
98446b39 229 return webcrypto.getRandomValues(new Uint32Array(1))[0] / 0x100000000
68cbdc84 230}
68e7ed58 231
57a29f75
JB
232/**
233 * Returns the minimum of the given numbers.
234 * If no numbers are given, `Infinity` is returned.
235 *
236 * @param args - The numbers to get the minimum of.
237 * @returns The minimum of the given numbers.
238 * @internal
239 */
90d6701c
JB
240export const min = (...args: number[]): number =>
241 args.reduce((minimum, num) => (minimum < num ? minimum : num), Infinity)
242
57a29f75
JB
243/**
244 * Returns the maximum of the given numbers.
245 * If no numbers are given, `-Infinity` is returned.
246 *
247 * @param args - The numbers to get the maximum of.
248 * @returns The maximum of the given numbers.
249 * @internal
250 */
90d6701c
JB
251export const max = (...args: number[]): number =>
252 args.reduce((maximum, num) => (maximum > num ? maximum : num), -Infinity)