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