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 // * Computes the retry delay in milliseconds using an exponential back off algorithm.
65 // * @param retryNumber - The number of retries that have already been attempted
66 // * @param maxDelayRatio - The maximum ratio of the delay that can be randomized
67 // * @returns Delay in milliseconds
70 // export const exponentialDelay = (
72 // maxDelayRatio = 0.2
74 // const delay = Math.pow(2, retryNumber) * 100
75 // const randomSum = delay * maxDelayRatio * Math.random() // 0-(maxDelayRatio*100)% of the delay
76 // return delay + randomSum
80 * Computes the average of the given data set.
82 * @param dataSet - Data set.
83 * @returns The average of the given data set.
86 export const average
= (dataSet
: number[]): number => {
87 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
90 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
94 dataSet
.reduce((accumulator
, number) => accumulator
+ number, 0) /
100 * Computes the median of the given data set.
102 * @param dataSet - Data set.
103 * @returns The median of the given data set.
106 export const median
= (dataSet
: number[]): number => {
107 if (Array.isArray(dataSet
) && dataSet
.length
=== 0) {
110 if (Array.isArray(dataSet
) && dataSet
.length
=== 1) {
113 const sortedDataSet
= dataSet
.slice().sort((a
, b
) => a
- b
)
115 (sortedDataSet
[(sortedDataSet
.length
- 1) >> 1] +
116 sortedDataSet
[sortedDataSet
.length
>> 1]) /
122 * Rounds the given number to the given scale.
123 * The rounding is done using the "round half away from zero" method.
125 * @param num - The number to round.
126 * @param scale - The scale to round to.
127 * @returns The rounded number.
129 export const round
= (num
: number, scale
= 2): number => {
130 const rounder
= Math.pow(10, scale
)
131 return Math.round(num
* rounder
* (1 + Number.EPSILON
)) / rounder
135 * Is the given object a plain object?
137 * @param obj - The object to check.
138 * @returns `true` if the given object is a plain object, `false` otherwise.
140 export const isPlainObject
= (obj
: unknown
): boolean =>
141 typeof obj
=== 'object' &&
143 obj
?.constructor
=== Object &&
144 Object.prototype
.toString
.call(obj
) === '[object Object]'
147 * Detects whether the given value is a kill behavior or not.
149 * @typeParam KB - Which specific KillBehavior type to test against.
150 * @param killBehavior - Which kind of kill behavior to detect.
151 * @param value - Any value.
152 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
155 export const isKillBehavior
= <KB
extends KillBehavior
>(
159 return value
=== killBehavior
163 * Detects whether the given value is an asynchronous function or not.
165 * @param fn - Any value.
166 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
168 export const isAsyncFunction
= (
170 ): fn
is (...args
: unknown
[]) => Promise
<unknown
> => {
171 return typeof fn
=== 'function' && fn
.constructor
.name
=== 'AsyncFunction'
175 * Updates the given measurement statistics.
177 * @param measurementStatistics - The measurement statistics to update.
178 * @param measurementRequirements - The measurement statistics requirements.
179 * @param measurementValue - The measurement value.
180 * @param numberOfMeasurements - The number of measurements.
183 export const updateMeasurementStatistics
= (
184 measurementStatistics
: MeasurementStatistics
,
185 measurementRequirements
: MeasurementStatisticsRequirements
,
186 measurementValue
: number
188 if (measurementRequirements
.aggregate
) {
189 measurementStatistics
.aggregate
=
190 (measurementStatistics
.aggregate
?? 0) + measurementValue
191 measurementStatistics
.minimum
= Math.min(
193 measurementStatistics
.minimum
?? Infinity
195 measurementStatistics
.maximum
= Math.max(
197 measurementStatistics
.maximum
?? -Infinity
200 (measurementRequirements
.average
|| measurementRequirements
.median
) &&
201 measurementValue
!= null
203 measurementStatistics
.history
.push(measurementValue
)
204 if (measurementRequirements
.average
) {
205 measurementStatistics
.average
= average(measurementStatistics
.history
)
207 if (measurementRequirements
.median
) {
208 measurementStatistics
.median
= median(measurementStatistics
.history
)
215 * Executes a function once at a time.
217 * @param fn - The function to execute.
218 * @param context - The context to bind the function to.
219 * @returns The function to execute.
221 export const once
= (
222 // eslint-disable-next-line @typescript-eslint/no-explicit-any
223 fn
: (...args
: any[]) => void,
225 // eslint-disable-next-line @typescript-eslint/no-explicit-any
226 ): ((...args
: any[]) => void) => {
228 // eslint-disable-next-line @typescript-eslint/no-explicit-any
229 return function (...args
: any[]): void {
232 fn
.apply(context
, args
)