build(deps): bump poolifier
[poolifier.git] / src / utils.ts
... / ...
CommitLineData
1import * as os from 'node:os'
2import type {
3 MeasurementStatisticsRequirements,
4 WorkerChoiceStrategyOptions
5} from './pools/selection-strategies/selection-strategies-types'
6import type { KillBehavior } from './worker/worker-options'
7import type { MeasurementStatistics } from './pools/worker'
8
9/**
10 * Default task name.
11 */
12export const DEFAULT_TASK_NAME = 'default'
13
14/**
15 * An intentional empty function.
16 */
17export const EMPTY_FUNCTION: () => void = Object.freeze(() => {
18 /* Intentionally empty */
19})
20
21/**
22 * Default worker choice strategy options.
23 */
24export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrategyOptions =
25 {
26 choiceRetries: 6,
27 runTime: { median: false },
28 waitTime: { median: false },
29 elu: { median: false }
30 }
31
32/**
33 * Default measurement statistics requirements.
34 */
35export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS: MeasurementStatisticsRequirements =
36 {
37 aggregate: false,
38 average: false,
39 median: false
40 }
41
42/**
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.
45 *
46 * @returns The host OS optimized maximum pool size.
47 */
48export const availableParallelism = (): number => {
49 let availableParallelism = 1
50 try {
51 availableParallelism = os.availableParallelism()
52 } catch {
53 const numberOfCpus = os.cpus()
54 if (Array.isArray(numberOfCpus) && numberOfCpus.length > 0) {
55 availableParallelism = numberOfCpus.length
56 }
57 }
58 return availableParallelism
59}
60
61// /**
62// * Computes the retry delay in milliseconds using an exponential back off algorithm.
63// *
64// * @param retryNumber - The number of retries that have already been attempted
65// * @param maxDelayRatio - The maximum ratio of the delay that can be randomized
66// * @returns Delay in milliseconds
67// */
68// export const exponentialDelay = (
69// retryNumber = 0,
70// maxDelayRatio = 0.2
71// ): number => {
72// const delay = Math.pow(2, retryNumber) * 100
73// const randomSum = delay * maxDelayRatio * Math.random() // 0-(maxDelayRatio*100)% of the delay
74// return delay + randomSum
75// }
76
77/**
78 * Computes the median of the given data set.
79 *
80 * @param dataSet - Data set.
81 * @returns The median of the given data set.
82 */
83export const median = (dataSet: number[]): number => {
84 if (Array.isArray(dataSet) && dataSet.length === 0) {
85 return 0
86 }
87 if (Array.isArray(dataSet) && dataSet.length === 1) {
88 return dataSet[0]
89 }
90 const sortedDataSet = dataSet.slice().sort((a, b) => a - b)
91 return (
92 (sortedDataSet[(sortedDataSet.length - 1) >> 1] +
93 sortedDataSet[sortedDataSet.length >> 1]) /
94 2
95 )
96}
97
98/**
99 * Rounds the given number to the given scale.
100 * The rounding is done using the "round half away from zero" method.
101 *
102 * @param num - The number to round.
103 * @param scale - The scale to round to.
104 * @returns The rounded number.
105 */
106export const round = (num: number, scale = 2): number => {
107 const rounder = Math.pow(10, scale)
108 return Math.round(num * rounder * (1 + Number.EPSILON)) / rounder
109}
110
111/**
112 * Is the given object a plain object?
113 *
114 * @param obj - The object to check.
115 * @returns `true` if the given object is a plain object, `false` otherwise.
116 */
117export const isPlainObject = (obj: unknown): boolean =>
118 typeof obj === 'object' &&
119 obj !== null &&
120 obj?.constructor === Object &&
121 Object.prototype.toString.call(obj) === '[object Object]'
122
123/**
124 * Detects whether the given value is a kill behavior or not.
125 *
126 * @typeParam KB - Which specific KillBehavior type to test against.
127 * @param killBehavior - Which kind of kill behavior to detect.
128 * @param value - Any value.
129 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
130 */
131export const isKillBehavior = <KB extends KillBehavior>(
132 killBehavior: KB,
133 value: unknown
134): value is KB => {
135 return value === killBehavior
136}
137
138/**
139 * Detects whether the given value is an asynchronous function or not.
140 *
141 * @param fn - Any value.
142 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
143 */
144export const isAsyncFunction = (
145 fn: unknown
146): fn is (...args: unknown[]) => Promise<unknown> => {
147 return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction'
148}
149
150/**
151 * Updates the given measurement statistics.
152 *
153 * @param measurementStatistics - The measurement statistics to update.
154 * @param measurementRequirements - The measurement statistics requirements.
155 * @param measurementValue - The measurement value.
156 * @param numberOfMeasurements - The number of measurements.
157 */
158export const updateMeasurementStatistics = (
159 measurementStatistics: MeasurementStatistics,
160 measurementRequirements: MeasurementStatisticsRequirements,
161 measurementValue: number,
162 numberOfMeasurements: number
163): void => {
164 if (measurementRequirements.aggregate) {
165 measurementStatistics.aggregate =
166 (measurementStatistics.aggregate ?? 0) + measurementValue
167 measurementStatistics.minimum = Math.min(
168 measurementValue,
169 measurementStatistics.minimum ?? Infinity
170 )
171 measurementStatistics.maximum = Math.max(
172 measurementValue,
173 measurementStatistics.maximum ?? -Infinity
174 )
175 if (measurementRequirements.average && numberOfMeasurements !== 0) {
176 measurementStatistics.average =
177 measurementStatistics.aggregate / numberOfMeasurements
178 }
179 if (measurementRequirements.median && measurementValue != null) {
180 measurementStatistics.history.push(measurementValue)
181 measurementStatistics.median = median(measurementStatistics.history)
182 }
183 }
184}
185
186/**
187 * Executes a function once at a time.
188 *
189 * @param fn - The function to execute.
190 * @param context - The context to bind the function to.
191 * @returns The function to execute.
192 */
193export const once = (
194 // eslint-disable-next-line @typescript-eslint/no-explicit-any
195 fn: (...args: any[]) => void,
196 context: unknown
197 // eslint-disable-next-line @typescript-eslint/no-explicit-any
198): ((...args: any[]) => void) => {
199 let called = false
200 // eslint-disable-next-line @typescript-eslint/no-explicit-any
201 return function (...args: any[]): void {
202 if (!called) {
203 called = true
204 fn.apply(context, args)
205 called = false
206 }
207 }
208}