fix: workaround import issue with `node:os` module in node 16.x.x
[poolifier.git] / src / utils.ts
1 import * as os from 'node:os'
2 import type {
3 MeasurementStatisticsRequirements,
4 WorkerChoiceStrategyOptions
5 } from './pools/selection-strategies/selection-strategies-types'
6 import type { KillBehavior } from './worker/worker-options'
7
8 /**
9 * An intentional empty function.
10 */
11 export const EMPTY_FUNCTION: () => void = Object.freeze(() => {
12 /* Intentionally empty */
13 })
14
15 /**
16 * Default worker choice strategy options.
17 */
18 export const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrategyOptions =
19 {
20 runTime: { median: false },
21 waitTime: { median: false },
22 elu: { median: false }
23 }
24
25 /**
26 * Default measurement statistics requirements.
27 */
28 export const DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS: MeasurementStatisticsRequirements =
29 {
30 aggregate: false,
31 average: false,
32 median: false
33 }
34
35 /**
36 * Returns safe host OS optimized estimate of the default amount of parallelism a pool should use.
37 * Always returns a value greater than zero.
38 *
39 * @returns The host OS optimized maximum pool size.
40 */
41 export const availableParallelism = (): number => {
42 let availableParallelism = 1
43 try {
44 availableParallelism = os.availableParallelism()
45 } catch {
46 const numberOfCpus = os.cpus()
47 if (Array.isArray(numberOfCpus) && numberOfCpus.length > 0) {
48 availableParallelism = numberOfCpus.length
49 }
50 }
51 return availableParallelism
52 }
53
54 /**
55 * Computes the median of the given data set.
56 *
57 * @param dataSet - Data set.
58 * @returns The median of the given data set.
59 */
60 export const median = (dataSet: number[]): number => {
61 if (Array.isArray(dataSet) && dataSet.length === 0) {
62 return 0
63 }
64 if (Array.isArray(dataSet) && dataSet.length === 1) {
65 return dataSet[0]
66 }
67 const sortedDataSet = dataSet.slice().sort((a, b) => a - b)
68 return (
69 (sortedDataSet[(sortedDataSet.length - 1) >> 1] +
70 sortedDataSet[sortedDataSet.length >> 1]) /
71 2
72 )
73 }
74
75 /**
76 * Rounds the given number to the given scale.
77 * The rounding is done using the "round half away from zero" method.
78 *
79 * @param num - The number to round.
80 * @param scale - The scale to round to.
81 * @returns The rounded number.
82 */
83 export const round = (num: number, scale = 2): number => {
84 const rounder = Math.pow(10, scale)
85 return Math.round(num * rounder * (1 + Number.EPSILON)) / rounder
86 }
87
88 /**
89 * Is the given object a plain object?
90 *
91 * @param obj - The object to check.
92 * @returns `true` if the given object is a plain object, `false` otherwise.
93 */
94 export const isPlainObject = (obj: unknown): boolean =>
95 typeof obj === 'object' &&
96 obj !== null &&
97 obj?.constructor === Object &&
98 Object.prototype.toString.call(obj) === '[object Object]'
99
100 /**
101 * Detects whether the given value is a kill behavior or not.
102 *
103 * @typeParam KB - Which specific KillBehavior type to test against.
104 * @param killBehavior - Which kind of kill behavior to detect.
105 * @param value - Any value.
106 * @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
107 */
108 export const isKillBehavior = <KB extends KillBehavior>(
109 killBehavior: KB,
110 value: unknown
111 ): value is KB => {
112 return value === killBehavior
113 }
114
115 /**
116 * Detects whether the given value is an asynchronous function or not.
117 *
118 * @param fn - Any value.
119 * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
120 */
121 export const isAsyncFunction = (
122 fn: unknown
123 ): fn is (...args: unknown[]) => Promise<unknown> => {
124 return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction'
125 }