refactor: cleanup circular array UT
[poolifier.git] / src / utils.ts
CommitLineData
aa4bf4b2 1import * as os from 'node:os'
3c93feb9
JB
2import type {
3 MeasurementStatisticsRequirements,
4 WorkerChoiceStrategyOptions
5} from './pools/selection-strategies/selection-strategies-types'
59317253 6import type { KillBehavior } from './worker/worker-options'
e4f20deb 7import type { MeasurementStatistics } from './pools/worker'
bbeadd16 8
ff128cc9
JB
9/**
10 * Default task name.
11 */
12export const DEFAULT_TASK_NAME = 'default'
13
6e9d10db
JB
14/**
15 * An intentional empty function.
16 */
4f3c3d89 17export const EMPTY_FUNCTION: () => void = Object.freeze(() => {
6e9d10db 18 /* Intentionally empty */
4f3c3d89 19})
78099a15
JB
20
21/**
bbeadd16
JB
22 * Default worker choice strategy options.
23 */
24export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrategyOptions =
25 {
8990357d 26 choiceRetries: 6,
932fc8be 27 runTime: { median: false },
5df69fab
JB
28 waitTime: { median: false },
29 elu: { median: false }
bbeadd16
JB
30 }
31
3c93feb9
JB
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
51474716 42/**
ab80dc46
JB
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.
4bffc062 47 * @internal
51474716
JB
48 */
49export const availableParallelism = (): number => {
50 let availableParallelism = 1
51 try {
aa4bf4b2 52 availableParallelism = os.availableParallelism()
51474716 53 } catch {
aa4bf4b2 54 const numberOfCpus = os.cpus()
2845f2a5
JB
55 if (Array.isArray(numberOfCpus) && numberOfCpus.length > 0) {
56 availableParallelism = numberOfCpus.length
51474716
JB
57 }
58 }
59 return availableParallelism
60}
61
9a9f863c
JB
62// /**
63// * Computes the retry delay in milliseconds using an exponential back off algorithm.
64// *
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
4bffc062 68// * @internal
9a9f863c
JB
69// */
70// export const exponentialDelay = (
71// retryNumber = 0,
72// maxDelayRatio = 0.2
73// ): number => {
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
77// }
8990357d 78
dc021bcc
JB
79/**
80 * Computes the average of the given data set.
81 *
82 * @param dataSet - Data set.
83 * @returns The average of the given data set.
84 * @internal
85 */
86export const average = (dataSet: number[]): number => {
87 if (Array.isArray(dataSet) && dataSet.length === 0) {
88 return 0
89 }
90 if (Array.isArray(dataSet) && dataSet.length === 1) {
91 return dataSet[0]
92 }
93 return (
94 dataSet.reduce((accumulator, number) => accumulator + number, 0) /
95 dataSet.length
96 )
97}
98
bbeadd16 99/**
afe0d5bf 100 * Computes the median of the given data set.
78099a15
JB
101 *
102 * @param dataSet - Data set.
103 * @returns The median of the given data set.
4bffc062 104 * @internal
78099a15
JB
105 */
106export const median = (dataSet: number[]): number => {
4a45e8d2
JB
107 if (Array.isArray(dataSet) && dataSet.length === 0) {
108 return 0
109 }
78099a15
JB
110 if (Array.isArray(dataSet) && dataSet.length === 1) {
111 return dataSet[0]
112 }
c6f42dd6
JB
113 const sortedDataSet = dataSet.slice().sort((a, b) => a - b)
114 return (
115 (sortedDataSet[(sortedDataSet.length - 1) >> 1] +
116 sortedDataSet[sortedDataSet.length >> 1]) /
117 2
118 )
78099a15 119}
0d80593b 120
afe0d5bf
JB
121/**
122 * Rounds the given number to the given scale.
64383951 123 * The rounding is done using the "round half away from zero" method.
afe0d5bf
JB
124 *
125 * @param num - The number to round.
126 * @param scale - The scale to round to.
127 * @returns The rounded number.
128 */
129export const round = (num: number, scale = 2): number => {
130 const rounder = Math.pow(10, scale)
131 return Math.round(num * rounder * (1 + Number.EPSILON)) / rounder
132}
133
3c653a03
JB
134/**
135 * Is the given object a plain object?
136 *
137 * @param obj - The object to check.
138 * @returns `true` if the given object is a plain object, `false` otherwise.
139 */
0d80593b
JB
140export const isPlainObject = (obj: unknown): boolean =>
141 typeof obj === 'object' &&
142 obj !== null &&
143 obj?.constructor === Object &&
144 Object.prototype.toString.call(obj) === '[object Object]'
59317253
JB
145
146/**
147 * Detects whether the given value is a kill behavior or not.
148 *
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`.
4bffc062 153 * @internal
59317253
JB
154 */
155export const isKillBehavior = <KB extends KillBehavior>(
156 killBehavior: KB,
157 value: unknown
158): value is KB => {
159 return value === killBehavior
160}
49d1b48c
JB
161
162/**
163 * Detects whether the given value is an asynchronous function or not.
164 *
165 * @param fn - Any value.
166 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
167 */
168export const isAsyncFunction = (
169 fn: unknown
170): fn is (...args: unknown[]) => Promise<unknown> => {
171 return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction'
172}
e4f20deb
JB
173
174/**
175 * Updates the given measurement statistics.
176 *
177 * @param measurementStatistics - The measurement statistics to update.
178 * @param measurementRequirements - The measurement statistics requirements.
179 * @param measurementValue - The measurement value.
008512c7 180 * @param numberOfMeasurements - The number of measurements.
4bffc062 181 * @internal
e4f20deb
JB
182 */
183export const updateMeasurementStatistics = (
184 measurementStatistics: MeasurementStatistics,
185 measurementRequirements: MeasurementStatisticsRequirements,
dc021bcc 186 measurementValue: number
e4f20deb
JB
187): void => {
188 if (measurementRequirements.aggregate) {
189 measurementStatistics.aggregate =
190 (measurementStatistics.aggregate ?? 0) + measurementValue
191 measurementStatistics.minimum = Math.min(
192 measurementValue,
193 measurementStatistics.minimum ?? Infinity
194 )
195 measurementStatistics.maximum = Math.max(
196 measurementValue,
197 measurementStatistics.maximum ?? -Infinity
198 )
dc021bcc
JB
199 if (
200 (measurementRequirements.average || measurementRequirements.median) &&
201 measurementValue != null
202 ) {
e4f20deb 203 measurementStatistics.history.push(measurementValue)
dc021bcc
JB
204 if (measurementRequirements.average) {
205 measurementStatistics.average = average(measurementStatistics.history)
206 }
207 if (measurementRequirements.median) {
208 measurementStatistics.median = median(measurementStatistics.history)
209 }
e4f20deb
JB
210 }
211 }
212}
c3f0a074
JB
213
214/**
215 * Executes a function once at a time.
216 *
217 * @param fn - The function to execute.
218 * @param context - The context to bind the function to.
219 * @returns The function to execute.
220 */
221export const once = (
222 // eslint-disable-next-line @typescript-eslint/no-explicit-any
223 fn: (...args: any[]) => void,
f7426dd9 224 context: unknown
c3f0a074
JB
225 // eslint-disable-next-line @typescript-eslint/no-explicit-any
226): ((...args: any[]) => void) => {
227 let called = false
228 // eslint-disable-next-line @typescript-eslint/no-explicit-any
229 return function (...args: any[]): void {
230 if (!called) {
231 called = true
232 fn.apply(context, args)
233 called = false
234 }
235 }
236}