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