Merge branch 'master' into feature/task-functions
[poolifier.git] / benchmarks / benchmarks-utils.mjs
1 import crypto from 'node:crypto'
2 import assert from 'node:assert'
3 import fs from 'node:fs'
4 import Benchmark from 'benchmark'
5 import {
6 DynamicClusterPool,
7 DynamicThreadPool,
8 FixedClusterPool,
9 FixedThreadPool,
10 Measurements,
11 PoolTypes,
12 WorkerChoiceStrategies,
13 WorkerTypes
14 } from '../lib/index.mjs'
15 import { TaskFunctions } from './benchmarks-types.mjs'
16
17 export const buildPoolifierPool = (
18 workerType,
19 poolType,
20 poolSize,
21 poolOptions
22 ) => {
23 switch (poolType) {
24 case PoolTypes.fixed:
25 switch (workerType) {
26 case WorkerTypes.thread:
27 return new FixedThreadPool(
28 poolSize,
29 './benchmarks/internal/thread-worker.mjs',
30 poolOptions
31 )
32 case WorkerTypes.cluster:
33 return new FixedClusterPool(
34 poolSize,
35 './benchmarks/internal/cluster-worker.mjs',
36 poolOptions
37 )
38 }
39 break
40 case PoolTypes.dynamic:
41 switch (workerType) {
42 case WorkerTypes.thread:
43 return new DynamicThreadPool(
44 Math.floor(poolSize / 2),
45 poolSize,
46 './benchmarks/internal/thread-worker.mjs',
47 poolOptions
48 )
49 case WorkerTypes.cluster:
50 return new DynamicClusterPool(
51 Math.floor(poolSize / 2),
52 poolSize,
53 './benchmarks/internal/cluster-worker.mjs',
54 poolOptions
55 )
56 }
57 break
58 }
59 }
60
61 export const runPoolifierPool = async (
62 pool,
63 { taskExecutions, workerData }
64 ) => {
65 return await new Promise((resolve, reject) => {
66 let executions = 0
67 for (let i = 1; i <= taskExecutions; i++) {
68 pool
69 .execute(workerData)
70 .then(() => {
71 ++executions
72 if (executions === taskExecutions) {
73 resolve({ ok: 1 })
74 }
75 return null
76 })
77 .catch(err => {
78 console.error(err)
79 reject(err)
80 })
81 }
82 })
83 }
84
85 export const runPoolifierPoolBenchmark = async (
86 name,
87 pool,
88 { taskExecutions, workerData }
89 ) => {
90 return await new Promise((resolve, reject) => {
91 try {
92 const suite = new Benchmark.Suite(name)
93 for (const workerChoiceStrategy of Object.values(
94 WorkerChoiceStrategies
95 )) {
96 for (const enableTasksQueue of [false, true]) {
97 if (workerChoiceStrategy === WorkerChoiceStrategies.FAIR_SHARE) {
98 for (const measurement of [
99 Measurements.runTime,
100 Measurements.elu
101 ]) {
102 suite.add(
103 `${name}|${workerChoiceStrategy}|${measurement}|${
104 enableTasksQueue ? 'with' : 'without'
105 } tasks queue`,
106 async () => {
107 pool.setWorkerChoiceStrategy(workerChoiceStrategy, {
108 measurement
109 })
110 pool.enableTasksQueue(enableTasksQueue)
111 assert.strictEqual(
112 pool.opts.workerChoiceStrategy,
113 workerChoiceStrategy
114 )
115 assert.strictEqual(
116 pool.opts.enableTasksQueue,
117 enableTasksQueue
118 )
119 assert.strictEqual(
120 pool.opts.workerChoiceStrategyOptions.measurement,
121 measurement
122 )
123 await runPoolifierPool(pool, {
124 taskExecutions,
125 workerData
126 })
127 }
128 )
129 }
130 } else {
131 suite.add(
132 `${name}|${workerChoiceStrategy}|${
133 enableTasksQueue ? 'with' : 'without'
134 } tasks queue`,
135 async () => {
136 pool.setWorkerChoiceStrategy(workerChoiceStrategy)
137 pool.enableTasksQueue(enableTasksQueue)
138 assert.strictEqual(
139 pool.opts.workerChoiceStrategy,
140 workerChoiceStrategy
141 )
142 assert.strictEqual(pool.opts.enableTasksQueue, enableTasksQueue)
143 await runPoolifierPool(pool, {
144 taskExecutions,
145 workerData
146 })
147 }
148 )
149 }
150 }
151 }
152 suite
153 .on('cycle', event => {
154 console.info(event.target.toString())
155 })
156 .on('complete', async function () {
157 console.info(
158 'Fastest is ' +
159 LIST_FORMATTER.format(this.filter('fastest').map('name'))
160 )
161 await pool.destroy()
162 resolve()
163 })
164 .run({ async: true })
165 } catch (error) {
166 reject(error)
167 }
168 })
169 }
170
171 export const LIST_FORMATTER = new Intl.ListFormat('en-US', {
172 style: 'long',
173 type: 'conjunction'
174 })
175
176 export const executeAsyncFn = async fn => {
177 try {
178 await fn()
179 } catch (e) {
180 console.error(e)
181 // eslint-disable-next-line n/no-process-exit
182 process.exit(1)
183 }
184 }
185
186 export const generateRandomInteger = (
187 max = Number.MAX_SAFE_INTEGER,
188 min = 0
189 ) => {
190 if (max < min || max < 0 || min < 0) {
191 throw new RangeError('Invalid interval')
192 }
193 max = Math.floor(max)
194 if (min != null && min !== 0) {
195 min = Math.ceil(min)
196 return Math.floor(Math.random() * (max - min + 1)) + min
197 }
198 return Math.floor(Math.random() * (max + 1))
199 }
200
201 const jsonIntegerSerialization = n => {
202 for (let i = 0; i < n; i++) {
203 const o = {
204 a: i
205 }
206 JSON.stringify(o)
207 }
208 return { ok: 1 }
209 }
210
211 /**
212 * Intentionally inefficient implementation.
213 * @param {number} n - The number of fibonacci numbers to generate.
214 * @returns {number} - The nth fibonacci number.
215 */
216 const fibonacci = n => {
217 if (n <= 1) return n
218 return fibonacci(n - 1) + fibonacci(n - 2)
219 }
220
221 /**
222 * Intentionally inefficient implementation.
223 * @param {number} n - The number to calculate the factorial of.
224 * @returns {number} - The factorial of n.
225 */
226 const factorial = n => {
227 if (n === 0) {
228 return 1
229 }
230 return factorial(n - 1) * n
231 }
232
233 const readWriteFiles = (
234 n,
235 baseDirectory = `/tmp/poolifier-benchmarks/${crypto.randomInt(
236 281474976710655
237 )}`
238 ) => {
239 if (fs.existsSync(baseDirectory) === true) {
240 fs.rmSync(baseDirectory, { recursive: true })
241 }
242 fs.mkdirSync(baseDirectory, { recursive: true })
243 for (let i = 0; i < n; i++) {
244 const filePath = `${baseDirectory}/${i}`
245 fs.writeFileSync(filePath, i.toString(), {
246 encoding: 'utf8',
247 flag: 'a'
248 })
249 fs.readFileSync(filePath, 'utf8')
250 }
251 fs.rmSync(baseDirectory, { recursive: true })
252 return { ok: 1 }
253 }
254
255 export const executeTaskFunction = data => {
256 switch (data.function) {
257 case TaskFunctions.jsonIntegerSerialization:
258 return jsonIntegerSerialization(data.taskSize || 1000)
259 case TaskFunctions.fibonacci:
260 return fibonacci(data.taskSize || 1000)
261 case TaskFunctions.factorial:
262 return factorial(data.taskSize || 1000)
263 case TaskFunctions.readWriteFiles:
264 return readWriteFiles(data.taskSize || 1000)
265 default:
266 throw new Error('Unknown task function')
267 }
268 }