48311a3f04b773c0d6f540eaf659b2b00f3e08d7
[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 pool,
80 { taskExecutions, workerData }
81 ) => {
82 return await new Promise((resolve, reject) => {
83 try {
84 const suite = new Benchmark.Suite(name)
85 for (const workerChoiceStrategy of Object.values(
86 WorkerChoiceStrategies
87 )) {
88 for (const enableTasksQueue of [false, true]) {
89 if (workerChoiceStrategy === WorkerChoiceStrategies.FAIR_SHARE) {
90 for (const measurement of [
91 Measurements.runTime,
92 Measurements.elu
93 ]) {
94 suite.add(
95 `${name} with ${workerChoiceStrategy}, with ${measurement} and ${
96 enableTasksQueue ? 'with' : 'without'
97 } tasks queue`,
98 async () => {
99 pool.setWorkerChoiceStrategy(workerChoiceStrategy, {
100 measurement
101 })
102 pool.enableTasksQueue(enableTasksQueue)
103 assert.strictEqual(
104 pool.opts.workerChoiceStrategy,
105 workerChoiceStrategy
106 )
107 assert.strictEqual(
108 pool.opts.enableTasksQueue,
109 enableTasksQueue
110 )
111 assert.strictEqual(
112 pool.opts.workerChoiceStrategyOptions.measurement,
113 measurement
114 )
115 await runPoolifierPool(pool, {
116 taskExecutions,
117 workerData
118 })
119 }
120 )
121 }
122 } else {
123 suite.add(
124 `${name} with ${workerChoiceStrategy} and ${
125 enableTasksQueue ? 'with' : 'without'
126 } tasks queue`,
127 async () => {
128 pool.setWorkerChoiceStrategy(workerChoiceStrategy)
129 pool.enableTasksQueue(enableTasksQueue)
130 assert.strictEqual(
131 pool.opts.workerChoiceStrategy,
132 workerChoiceStrategy
133 )
134 assert.strictEqual(pool.opts.enableTasksQueue, enableTasksQueue)
135 await runPoolifierPool(pool, {
136 taskExecutions,
137 workerData
138 })
139 }
140 )
141 }
142 }
143 }
144 suite
145 .on('cycle', event => {
146 console.info(event.target.toString())
147 })
148 .on('complete', async function () {
149 console.info(
150 'Fastest is ' +
151 LIST_FORMATTER.format(this.filter('fastest').map('name'))
152 )
153 await pool.destroy()
154 pool = undefined
155 resolve()
156 })
157 .run({ async: true })
158 } catch (error) {
159 reject(error)
160 }
161 })
162 }
163
164 const LIST_FORMATTER = new Intl.ListFormat('en-US', {
165 style: 'long',
166 type: 'conjunction'
167 })
168
169 const generateRandomInteger = (max = Number.MAX_SAFE_INTEGER, min = 0) => {
170 if (max < min || max < 0 || min < 0) {
171 throw new RangeError('Invalid interval')
172 }
173 max = Math.floor(max)
174 if (min != null && min !== 0) {
175 min = Math.ceil(min)
176 return Math.floor(Math.random() * (max - min + 1)) + min
177 }
178 return Math.floor(Math.random() * (max + 1))
179 }
180
181 const jsonIntegerSerialization = n => {
182 for (let i = 0; i < n; i++) {
183 const o = {
184 a: i
185 }
186 JSON.stringify(o)
187 }
188 return { ok: 1 }
189 }
190
191 /**
192 * Intentionally inefficient implementation.
193 * @param {number} n - The number of fibonacci numbers to generate.
194 * @returns {number} - The nth fibonacci number.
195 */
196 const fibonacci = n => {
197 if (n <= 1) return n
198 return fibonacci(n - 1) + fibonacci(n - 2)
199 }
200
201 /**
202 * Intentionally inefficient implementation.
203 * @param {number} n - The number to calculate the factorial of.
204 * @returns {number} - The factorial of n.
205 */
206 const factorial = n => {
207 if (n === 0) {
208 return 1
209 }
210 return factorial(n - 1) * n
211 }
212
213 const readWriteFiles = (
214 n,
215 baseDirectory = `/tmp/poolifier-benchmarks/${crypto.randomInt(
216 281474976710655
217 )}`
218 ) => {
219 if (fs.existsSync(baseDirectory) === true) {
220 fs.rmSync(baseDirectory, { recursive: true })
221 }
222 fs.mkdirSync(baseDirectory, { recursive: true })
223 for (let i = 0; i < n; i++) {
224 const filePath = `${baseDirectory}/${i}`
225 fs.writeFileSync(filePath, i.toString(), {
226 encoding: 'utf8',
227 flag: 'a'
228 })
229 fs.readFileSync(filePath, 'utf8')
230 }
231 fs.rmSync(baseDirectory, { recursive: true })
232 return { ok: 1 }
233 }
234
235 const executeTaskFunction = data => {
236 switch (data.function) {
237 case TaskFunctions.jsonIntegerSerialization:
238 return jsonIntegerSerialization(data.taskSize || 1000)
239 case TaskFunctions.fibonacci:
240 return fibonacci(data.taskSize || 1000)
241 case TaskFunctions.factorial:
242 return factorial(data.taskSize || 1000)
243 case TaskFunctions.readWriteFiles:
244 return readWriteFiles(data.taskSize || 1000)
245 default:
246 throw new Error('Unknown task function')
247 }
248 }
249
250 module.exports = {
251 LIST_FORMATTER,
252 buildPoolifierPool,
253 executeTaskFunction,
254 generateRandomInteger,
255 runPoolifierPoolBenchmark
256 }