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