1 import * as os from
'node:os'
2 import { webcrypto
} from
'node:crypto'
4 MeasurementStatisticsRequirements
,
5 WorkerChoiceStrategyOptions
6 } from
'./pools/selection-strategies/selection-strategies-types'
7 import type { KillBehavior
} from
'./worker/worker-options'
8 import type { MeasurementStatistics
} from
'./pools/worker'
13 export const DEFAULT_TASK_NAME
= 'default'
16 * An intentional empty function.
18 export const EMPTY_FUNCTION
: () => void = Object.freeze(() => {
19 /* Intentionally empty */
23 * Default worker choice strategy options.
25 export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS
: WorkerChoiceStrategyOptions
=
28 runTime
: { median
: false },
29 waitTime
: { median
: false },
30 elu
: { median
: false }
34 * Default measurement statistics requirements.
36 export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS
: MeasurementStatisticsRequirements
=
44 * Returns safe host OS optimized estimate of the default amount of parallelism a pool should use.
45 * Always returns a value greater than zero.
47 * @returns The host OS optimized maximum pool size.
50 export const availableParallelism
= (): number => {
51 let availableParallelism
= 1
53 availableParallelism
= os
.availableParallelism()
55 const numberOfCpus
= os
.cpus()
56 if (Array.isArray(numberOfCpus
) && numberOfCpus
.length
> 0) {
57 availableParallelism
= numberOfCpus
.length
60 return availableParallelism
64 * Sleeps for the given amount of milliseconds.
66 * @param ms - The amount of milliseconds to sleep.
67 * @returns A promise that resolves after the given amount of milliseconds.
69 export const sleep
= async (ms
: number): Promise
<void> => {
70 await new Promise((resolve
) => {
71 setTimeout(resolve
, ms
)
76 * Computes the retry delay in milliseconds using an exponential back off algorithm.
78 * @param retryNumber - The number of retries that have already been attempted
79 * @param delayFactor - The base delay factor in milliseconds
80 * @returns Delay in milliseconds
83 export const exponentialDelay
= (
87 const delay
= Math.pow(2, retryNumber
) * delayFactor
88 const randomSum
= delay
* 0.2 * secureRandom() // 0-20% of the delay
89 return delay
+ randomSum
93 * Computes the average of the given data set.
95 * @param dataSet - Data set.
96 * @returns The average of the given data set.
99 export const average
= (dataSet
: number[]): number => {
100 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
103 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
107 dataSet
.reduce((accumulator
, number) => accumulator
+ number, 0) /
113 * Computes the median of the given data set.
115 * @param dataSet - Data set.
116 * @returns The median of the given data set.
119 export const median
= (dataSet
: number[]): number => {
120 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
123 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
126 const sortedDataSet
= dataSet
.slice().sort((a
, b
) => a
- b
)
128 (sortedDataSet
[(sortedDataSet
.length
- 1) >> 1] +
129 sortedDataSet
[sortedDataSet
.length
>> 1]) /
135 * Rounds the given number to the given scale.
136 * The rounding is done using the "round half away from zero" method.
138 * @param num - The number to round.
139 * @param scale - The scale to round to.
140 * @returns The rounded number.
142 export const round
= (num
: number, scale
= 2): number => {
143 const rounder
= Math.pow(10, scale
)
144 return Math.round(num
* rounder
* (1 + Number.EPSILON
)) / rounder
148 * Is the given object a plain object?
150 * @param obj - The object to check.
151 * @returns `true` if the given object is a plain object, `false` otherwise.
153 export const isPlainObject
= (obj
: unknown
): boolean =>
154 typeof obj
=== 'object' &&
156 obj
?.constructor
=== Object &&
157 Object.prototype
.toString
.call(obj
) === '[object Object]'
160 * Detects whether the given value is a kill behavior or not.
162 * @typeParam KB - Which specific KillBehavior type to test against.
163 * @param killBehavior - Which kind of kill behavior to detect.
164 * @param value - Any value.
165 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
168 export const isKillBehavior
= <KB
extends KillBehavior
>(
172 return value
=== killBehavior
176 * Detects whether the given value is an asynchronous function or not.
178 * @param fn - Any value.
179 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
181 export const isAsyncFunction
= (
183 ): fn
is (...args
: unknown
[]) => Promise
<unknown
> => {
184 return typeof fn
=== 'function' && fn
.constructor
.name
=== 'AsyncFunction'
188 * Updates the given measurement statistics.
190 * @param measurementStatistics - The measurement statistics to update.
191 * @param measurementRequirements - The measurement statistics requirements.
192 * @param measurementValue - The measurement value.
193 * @param numberOfMeasurements - The number of measurements.
196 export const updateMeasurementStatistics
= (
197 measurementStatistics
: MeasurementStatistics
,
198 measurementRequirements
: MeasurementStatisticsRequirements
,
199 measurementValue
: number
201 if (measurementRequirements
.aggregate
) {
202 measurementStatistics
.aggregate
=
203 (measurementStatistics
.aggregate
?? 0) + measurementValue
204 measurementStatistics
.minimum
= Math.min(
206 measurementStatistics
.minimum
?? Infinity
208 measurementStatistics
.maximum
= Math.max(
210 measurementStatistics
.maximum
?? -Infinity
213 (measurementRequirements
.average
|| measurementRequirements
.median
) &&
214 measurementValue
!= null
216 measurementStatistics
.history
.push(measurementValue
)
217 if (measurementRequirements
.average
) {
218 measurementStatistics
.average
= average(measurementStatistics
.history
)
220 if (measurementRequirements
.median
) {
221 measurementStatistics
.median
= median(measurementStatistics
.history
)
228 * Executes a function once at a time.
230 * @param fn - The function to execute.
231 * @param context - The context to bind the function to.
232 * @returns The function to execute.
234 export const once
= (
235 // eslint-disable-next-line @typescript-eslint/no-explicit-any
236 fn
: (...args
: any[]) => void,
238 // eslint-disable-next-line @typescript-eslint/no-explicit-any
239 ): ((...args
: any[]) => void) => {
241 // eslint-disable-next-line @typescript-eslint/no-explicit-any
242 return function (...args
: any[]): void {
245 fn
.apply(context
, args
)
252 * Generate a cryptographically secure random number in the [0,1[ range
254 * @returns A number in the [0,1[ range
256 export const secureRandom
= (): number => {
257 return webcrypto
.getRandomValues(new Uint32Array(1))[0] / 0x100000000