1 import * as os from
'node:os'
3 MeasurementStatisticsRequirements
,
4 WorkerChoiceStrategyOptions
5 } from
'./pools/selection-strategies/selection-strategies-types'
6 import type { KillBehavior
} from
'./worker/worker-options'
7 import type { MeasurementStatistics
} from
'./pools/worker'
12 export const DEFAULT_TASK_NAME
= 'default'
15 * An intentional empty function.
17 export const EMPTY_FUNCTION
: () => void = Object.freeze(() => {
18 /* Intentionally empty */
22 * Default worker choice strategy options.
24 export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
: WorkerChoiceStrategyOptions
=
27 runTime
: { median
: false },
28 waitTime
: { median
: false },
29 elu
: { median
: false }
33 * Default measurement statistics requirements.
35 export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
: MeasurementStatisticsRequirements
=
43 * Returns safe host OS optimized estimate of the default amount of parallelism a pool should use.
44 * Always returns a value greater than zero.
46 * @returns The host OS optimized maximum pool size.
49 export const availableParallelism
= (): number => {
50 let availableParallelism
= 1
52 availableParallelism
= os
.availableParallelism()
54 const numberOfCpus
= os
.cpus()
55 if (Array.isArray(numberOfCpus
) && numberOfCpus
.length
> 0) {
56 availableParallelism
= numberOfCpus
.length
59 return availableParallelism
63 * Sleeps for the given amount of milliseconds.
65 * @param ms - The amount of milliseconds to sleep.
66 * @returns A promise that resolves after the given amount of milliseconds.
68 export const sleep
= async (ms
: number): Promise
<void> => {
69 await new Promise((resolve
) => {
70 setTimeout(resolve
, ms
)
75 * Computes the retry delay in milliseconds using an exponential back off algorithm.
77 * @param retryNumber - The number of retries that have already been attempted
78 * @param delayFactor - The base delay factor in milliseconds
79 * @returns Delay in milliseconds
82 export const exponentialDelay
= (
86 const delay
= Math.pow(2, retryNumber
) * delayFactor
87 const randomSum
= delay
* 0.2 * secureRandom() // 0-20% of the delay
88 return delay
+ randomSum
92 * Computes the average of the given data set.
94 * @param dataSet - Data set.
95 * @returns The average of the given data set.
98 export const average
= (dataSet
: number[]): number => {
99 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
102 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
106 dataSet
.reduce((accumulator
, number) => accumulator
+ number, 0) /
112 * Computes the median of the given data set.
114 * @param dataSet - Data set.
115 * @returns The median of the given data set.
118 export const median
= (dataSet
: number[]): number => {
119 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
122 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
125 const sortedDataSet
= dataSet
.slice().sort((a
, b
) => a
- b
)
127 (sortedDataSet
[(sortedDataSet
.length
- 1) >> 1] +
128 sortedDataSet
[sortedDataSet
.length
>> 1]) /
134 * Rounds the given number to the given scale.
135 * The rounding is done using the "round half away from zero" method.
137 * @param num - The number to round.
138 * @param scale - The scale to round to.
139 * @returns The rounded number.
141 export const round
= (num
: number, scale
= 2): number => {
142 const rounder
= Math.pow(10, scale
)
143 return Math.round(num
* rounder
* (1 + Number.EPSILON
)) / rounder
147 * Is the given object a plain object?
149 * @param obj - The object to check.
150 * @returns `true` if the given object is a plain object, `false` otherwise.
152 export const isPlainObject
= (obj
: unknown
): boolean =>
153 typeof obj
=== 'object' &&
155 obj
?.constructor
=== Object &&
156 Object.prototype
.toString
.call(obj
) === '[object Object]'
159 * Detects whether the given value is a kill behavior or not.
161 * @typeParam KB - Which specific KillBehavior type to test against.
162 * @param killBehavior - Which kind of kill behavior to detect.
163 * @param value - Any value.
164 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
167 export const isKillBehavior
= <KB
extends KillBehavior
>(
171 return value
=== killBehavior
175 * Detects whether the given value is an asynchronous function or not.
177 * @param fn - Any value.
178 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
180 export const isAsyncFunction
= (
182 ): fn
is (...args
: unknown
[]) => Promise
<unknown
> => {
183 return typeof fn
=== 'function' && fn
.constructor
.name
=== 'AsyncFunction'
187 * Updates the given measurement statistics.
189 * @param measurementStatistics - The measurement statistics to update.
190 * @param measurementRequirements - The measurement statistics requirements.
191 * @param measurementValue - The measurement value.
192 * @param numberOfMeasurements - The number of measurements.
195 export const updateMeasurementStatistics
= (
196 measurementStatistics
: MeasurementStatistics
,
197 measurementRequirements
: MeasurementStatisticsRequirements
,
198 measurementValue
: number
200 if (measurementRequirements
.aggregate
) {
201 measurementStatistics
.aggregate
=
202 (measurementStatistics
.aggregate
?? 0) + measurementValue
203 measurementStatistics
.minimum
= Math.min(
205 measurementStatistics
.minimum
?? Infinity
207 measurementStatistics
.maximum
= Math.max(
209 measurementStatistics
.maximum
?? -Infinity
212 (measurementRequirements
.average
|| measurementRequirements
.median
) &&
213 measurementValue
!= null
215 measurementStatistics
.history
.push(measurementValue
)
216 if (measurementRequirements
.average
) {
217 measurementStatistics
.average
= average(measurementStatistics
.history
)
219 if (measurementRequirements
.median
) {
220 measurementStatistics
.median
= median(measurementStatistics
.history
)
227 * Executes a function once at a time.
229 * @param fn - The function to execute.
230 * @param context - The context to bind the function to.
231 * @returns The function to execute.
233 export const once
= (
234 // eslint-disable-next-line @typescript-eslint/no-explicit-any
235 fn
: (...args
: any[]) => void,
237 // eslint-disable-next-line @typescript-eslint/no-explicit-any
238 ): ((...args
: any[]) => void) => {
240 // eslint-disable-next-line @typescript-eslint/no-explicit-any
241 return function (...args
: any[]): void {
244 fn
.apply(context
, args
)
251 * Generate a cryptographically secure random number in the [0,1[ range
253 * @returns A number in the [0,1[ range
255 const secureRandom
= (): number => {
256 return crypto
.getRandomValues(new Uint32Array(1))[0] / 0x100000000