perf: fix continuous benchmarking
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 13 May 2024 14:50:34 +0000 (16:50 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Mon, 13 May 2024 14:50:34 +0000 (16:50 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
.eslintrc.cjs
.github/workflows/internal-benchmark.yml
.gitignore
benchmarks/README.md
benchmarks/benchmarks-utils.mjs
benchmarks/internal/bench.mjs
package.json
pnpm-lock.yaml

index af3c350bef18760c226e5afcd7f97962f3235bf1..80b400b6572173a3817bd88651cd85b5b9369e62 100644 (file)
@@ -35,6 +35,7 @@ module.exports = defineConfig({
           'argv',
           'axios',
           'benoit',
+          'bmf',
           'browserslist',
           'builtins',
           'christopher',
index 936f0ad721c40e54ce1351dbe357106483a5678b..75b97cffcdc6697a40ee27c8bb724856b363b500 100644 (file)
@@ -38,6 +38,7 @@ jobs:
           --else-if-branch "$GITHUB_BASE_REF" \
           --else-if-branch master \
           --hash "$GITHUB_SHA" \
+          --file benchmark-report.json \
           --err \
           --github-actions ${{ secrets.GITHUB_TOKEN }} \
           "pnpm benchmark:tatami-ng:prod"
index d014f1b3a8032abb61dabb3f3ba3b85fe29debf1..9f9e3aa7268dd38e26f362ece005fbf116f9a8f3 100644 (file)
@@ -78,3 +78,4 @@ dist
 tmp
 .history/
 reports/
+benchmark-report.json
index 27467511aaee25a534213894915b3f31a89038e9..319cafcf4f3759f105e0359b0b31d70ea8788848 100644 (file)
@@ -24,7 +24,6 @@ See the dedicated repository [README.md](https://github.com/poolifier/benchmark#
 
 To run the internal benchmark, you just need to navigate to the root of poolifier cloned repository and run:
 
-- `pnpm benchmark:benchmark.js` or
 - `pnpm benchmark:tatami-ng`
 
 ### [Results](https://bencher.dev/perf/poolifier)
index 7d395d96aca6898289497e3a3da5c1d7517d75d7..8620dd681b10be4b6019dfc3d2be0dcfcf770311 100644 (file)
@@ -1,7 +1,5 @@
 import { strictEqual } from 'node:assert'
-import { env } from 'node:process'
 
-import Benchmark from 'benchmark'
 import { bench, clear, group, run } from 'tatami-ng'
 
 import {
@@ -76,180 +74,6 @@ const runPoolifierPool = async (pool, { taskExecutions, workerData }) => {
   })
 }
 
-export const runPoolifierBenchmarkBenchmarkJs = async (
-  name,
-  workerType,
-  poolType,
-  poolSize,
-  poolOptions,
-  { taskExecutions, workerData }
-) => {
-  return await new Promise((resolve, reject) => {
-    const pool = buildPoolifierPool(workerType, poolType, poolSize, poolOptions)
-    let workerChoiceStrategy
-    let enableTasksQueue
-    let workerChoiceStrategyOptions
-    if (poolOptions != null) {
-      ({
-        workerChoiceStrategy,
-        enableTasksQueue,
-        workerChoiceStrategyOptions
-      } = poolOptions)
-    }
-    const measurement = workerChoiceStrategyOptions?.measurement
-    new Benchmark(
-      `${name} with ${workerChoiceStrategy ?? pool.opts.workerChoiceStrategy}${
-        measurement != null ? `, with ${measurement}` : ''
-      } and ${enableTasksQueue ? 'with' : 'without'} tasks queue`,
-      async () => {
-        await runPoolifierPool(pool, {
-          taskExecutions,
-          workerData
-        })
-      },
-      {
-        onStart: () => {
-          if (workerChoiceStrategy != null) {
-            strictEqual(pool.opts.workerChoiceStrategy, workerChoiceStrategy)
-          }
-          if (enableTasksQueue != null) {
-            strictEqual(pool.opts.enableTasksQueue, enableTasksQueue)
-          }
-          if (measurement != null) {
-            strictEqual(
-              pool.opts.workerChoiceStrategyOptions.measurement,
-              measurement
-            )
-          }
-        },
-        onComplete: event => {
-          console.info(event.target.toString())
-          if (pool.started && !pool.destroying) {
-            pool.destroy().then(resolve).catch(reject)
-          } else {
-            resolve()
-          }
-        },
-        onError: event => {
-          if (pool.started && !pool.destroying) {
-            pool
-              .destroy()
-              .then(() => {
-                return reject(event.target.error)
-              })
-              .catch(() => {})
-          } else {
-            reject(event.target.error)
-          }
-        }
-      }
-    ).run({ async: true })
-  })
-}
-
-export const runPoolifierBenchmarkBenchmarkJsSuite = async (
-  name,
-  workerType,
-  poolType,
-  poolSize,
-  { taskExecutions, workerData }
-) => {
-  return await new Promise((resolve, reject) => {
-    const pool = buildPoolifierPool(workerType, poolType, poolSize)
-    const suite = new Benchmark.Suite(name, {
-      onComplete: () => {
-        if (pool.started && !pool.destroying) {
-          pool.destroy().then(resolve).catch(reject)
-        } else {
-          resolve()
-        }
-      },
-      onCycle: event => {
-        console.info(event.target.toString())
-      },
-      onError: event => {
-        if (pool.started && !pool.destroying) {
-          pool
-            .destroy()
-            .then(() => {
-              return reject(event.target.error)
-            })
-            .catch(() => {})
-        } else {
-          reject(event.target.error)
-        }
-      }
-    })
-    for (const workerChoiceStrategy of Object.values(WorkerChoiceStrategies)) {
-      for (const enableTasksQueue of [false, true]) {
-        if (workerChoiceStrategy === WorkerChoiceStrategies.FAIR_SHARE) {
-          for (const measurement of [Measurements.runTime, Measurements.elu]) {
-            suite.add(
-              `${name} with ${workerChoiceStrategy}, with ${measurement} and ${
-                enableTasksQueue ? 'with' : 'without'
-              } tasks queue`,
-              async () => {
-                await runPoolifierPool(pool, {
-                  taskExecutions,
-                  workerData
-                })
-              },
-              {
-                onStart: () => {
-                  pool.setWorkerChoiceStrategy(workerChoiceStrategy, {
-                    measurement
-                  })
-                  pool.enableTasksQueue(enableTasksQueue)
-                  strictEqual(
-                    pool.opts.workerChoiceStrategy,
-                    workerChoiceStrategy
-                  )
-                  strictEqual(pool.opts.enableTasksQueue, enableTasksQueue)
-                  strictEqual(
-                    pool.opts.workerChoiceStrategyOptions.measurement,
-                    measurement
-                  )
-                }
-              }
-            )
-          }
-        } else {
-          suite.add(
-            `${name} with ${workerChoiceStrategy} and ${
-              enableTasksQueue ? 'with' : 'without'
-            } tasks queue`,
-            async () => {
-              await runPoolifierPool(pool, {
-                taskExecutions,
-                workerData
-              })
-            },
-            {
-              onStart: () => {
-                pool.setWorkerChoiceStrategy(workerChoiceStrategy)
-                pool.enableTasksQueue(enableTasksQueue)
-                strictEqual(
-                  pool.opts.workerChoiceStrategy,
-                  workerChoiceStrategy
-                )
-                strictEqual(pool.opts.enableTasksQueue, enableTasksQueue)
-              }
-            }
-          )
-        }
-      }
-    }
-    suite
-      .on('complete', function () {
-        console.info(
-          'Fastest is ' +
-            LIST_FORMATTER.format(this.filter('fastest').map('name'))
-        )
-      })
-      .run({ async: true })
-  })
-}
-
 export const runPoolifierBenchmarkTatamiNg = async (
   name,
   workerType,
@@ -322,19 +146,32 @@ export const runPoolifierBenchmarkTatamiNg = async (
         }
       }
     }
-    await run({
-      json: env.CI != null ? 'bmf' : false
-    })
+    const report = await run()
     clear()
     await pool.destroy()
+    return report
   } catch (error) {
     console.error(error)
   }
 }
 
-const LIST_FORMATTER = new Intl.ListFormat('en-US', {
-  style: 'long',
-  type: 'conjunction'
-})
+export const convertTatamiNgToBmf = report => {
+  return report.benchmarks
+    .map(({ name, stats }) => {
+      return {
+        [name]: {
+          latency: {
+            value: stats?.avg,
+            lower_value: stats?.min,
+            upper_value: stats?.max
+          },
+          throughput: {
+            value: stats?.iter
+          }
+        }
+      }
+    })
+    .reduce((obj, item) => Object.assign(obj, item), {})
+}
 
 export { executeTaskFunction }
index da0c023d611cf47904148f9c3541d05a4d400b99..0e3ee055a474e5a439c5a9b1fcde3de218271193 100644 (file)
@@ -1,4 +1,5 @@
-import { exit } from 'node:process'
+import { writeFileSync } from 'node:fs'
+import { env, exit } from 'node:process'
 // eslint-disable-next-line n/no-unsupported-features/node-builtins
 import { parseArgs } from 'node:util'
 
@@ -9,7 +10,7 @@ import {
 } from '../../lib/index.mjs'
 import { TaskFunctions } from '../benchmarks-types.cjs'
 import {
-  runPoolifierBenchmarkBenchmarkJsSuite,
+  convertTatamiNgToBmf,
   runPoolifierBenchmarkTatamiNg
 } from '../benchmarks-utils.mjs'
 
@@ -19,6 +20,8 @@ const workerData = {
   function: TaskFunctions.factorial,
   taskSize: 1000
 }
+const benchmarkReportFile = 'benchmark-report.json'
+let benchmarkReport
 
 switch (
   parseArgs({
@@ -34,89 +37,66 @@ switch (
   }).values.type
 ) {
   case 'tatami-ng':
-    await runPoolifierBenchmarkTatamiNg(
-      'FixedThreadPool',
-      WorkerTypes.thread,
-      PoolTypes.fixed,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
-    )
-    await runPoolifierBenchmarkTatamiNg(
-      'DynamicThreadPool',
-      WorkerTypes.thread,
-      PoolTypes.dynamic,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
-    )
-    await runPoolifierBenchmarkTatamiNg(
-      'FixedClusterPool',
-      WorkerTypes.cluster,
-      PoolTypes.fixed,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
-    )
-    await runPoolifierBenchmarkTatamiNg(
-      'DynamicClusterPool',
-      WorkerTypes.cluster,
-      PoolTypes.dynamic,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
-    )
-    break
-  case 'benchmark.js':
   default:
-    await runPoolifierBenchmarkBenchmarkJsSuite(
-      'FixedThreadPool',
-      WorkerTypes.thread,
-      PoolTypes.fixed,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
-    )
-    await runPoolifierBenchmarkBenchmarkJsSuite(
-      'DynamicThreadPool',
-      WorkerTypes.thread,
-      PoolTypes.dynamic,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
-    )
-    await runPoolifierBenchmarkBenchmarkJsSuite(
-      'FixedClusterPool',
-      WorkerTypes.cluster,
-      PoolTypes.fixed,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
-    )
-    await runPoolifierBenchmarkBenchmarkJsSuite(
-      'DynamicClusterPool',
-      WorkerTypes.cluster,
-      PoolTypes.dynamic,
-      poolSize,
-      {
-        taskExecutions,
-        workerData
-      }
+    benchmarkReport = convertTatamiNgToBmf(
+      await runPoolifierBenchmarkTatamiNg(
+        'FixedThreadPool',
+        WorkerTypes.thread,
+        PoolTypes.fixed,
+        poolSize,
+        {
+          taskExecutions,
+          workerData
+        }
+      )
     )
+    benchmarkReport = {
+      ...benchmarkReport,
+      ...convertTatamiNgToBmf(
+        await runPoolifierBenchmarkTatamiNg(
+          'DynamicThreadPool',
+          WorkerTypes.thread,
+          PoolTypes.dynamic,
+          poolSize,
+          {
+            taskExecutions,
+            workerData
+          }
+        )
+      )
+    }
+    benchmarkReport = {
+      ...benchmarkReport,
+      ...convertTatamiNgToBmf(
+        await runPoolifierBenchmarkTatamiNg(
+          'FixedClusterPool',
+          WorkerTypes.cluster,
+          PoolTypes.fixed,
+          poolSize,
+          {
+            taskExecutions,
+            workerData
+          }
+        )
+      )
+    }
+    benchmarkReport = {
+      ...benchmarkReport,
+      ...convertTatamiNgToBmf(
+        await runPoolifierBenchmarkTatamiNg(
+          'DynamicClusterPool',
+          WorkerTypes.cluster,
+          PoolTypes.dynamic,
+          poolSize,
+          {
+            taskExecutions,
+            workerData
+          }
+        )
+      )
+    }
+    env.CI != null &&
+      writeFileSync(benchmarkReportFile, JSON.stringify(benchmarkReport))
     break
 }
 
index bf763edc6cd297974cd31142a194695f89ca62cb..ff866a520bcbf7887da6e846160b5da20eb9590c 100644 (file)
@@ -21,9 +21,6 @@
     "build:prod": "rollup --config",
     "build:typedoc": "rollup --config --environment DOCUMENTATION,BUILD:development",
     "build:analyze": "rollup --config --environment ANALYZE,BUILD:development",
-    "benchmark:benchmark.js": "pnpm build && node --enable-source-maps benchmarks/internal/bench.mjs -t benchmark.js",
-    "benchmark:benchmark.js:prod": "pnpm build:prod && node --enable-source-maps benchmarks/internal/bench.mjs -t benchmark.js",
-    "benchmark:benchmark.js:debug": "pnpm build && node --enable-source-maps --inspect benchmarks/internal/bench.mjs -t benchmark.js",
     "benchmark:tatami-ng": "pnpm build && node --enable-source-maps benchmarks/internal/bench.mjs -t tatami-ng",
     "benchmark:tatami-ng:prod": "pnpm build:prod && node --enable-source-maps benchmarks/internal/bench.mjs -t tatami-ng",
     "benchmark:tatami-ng:debug": "pnpm build && node --enable-source-maps --inspect benchmarks/internal/bench.mjs -t tatami-ng",
     "@types/node": "^20.12.11",
     "@typescript-eslint/eslint-plugin": "^7.8.0",
     "@typescript-eslint/parser": "^7.8.0",
-    "benchmark": "^2.1.4",
     "c8": "^9.1.0",
     "cross-env": "^7.0.3",
     "eslint": "^8.57.0",
     "rollup-plugin-delete": "^2.0.0",
     "rollup-plugin-dts": "^6.1.0",
     "sinon": "^17.0.2",
-    "tatami-ng": "^0.4.6",
+    "tatami-ng": "^0.4.8",
     "typedoc": "^0.25.13",
     "typescript": "~5.4.5"
   }
index f9cbefb144a6cff42965c2411a21798ad09a27aa..65b9a37a6e232d3f01f01c706866de88ab0ee25d 100644 (file)
@@ -41,9 +41,6 @@ importers:
       '@typescript-eslint/parser':
         specifier: ^7.8.0
         version: 7.8.0(eslint@8.57.0)(typescript@5.4.5)
-      benchmark:
-        specifier: ^2.1.4
-        version: 2.1.4
       c8:
         specifier: ^9.1.0
         version: 9.1.0
@@ -129,8 +126,8 @@ importers:
         specifier: ^17.0.2
         version: 17.0.2
       tatami-ng:
-        specifier: ^0.4.6
-        version: 0.4.6(typescript@5.4.5)
+        specifier: ^0.4.8
+        version: 0.4.8(typescript@5.4.5)
       typedoc:
         specifier: ^0.25.13
         version: 0.25.13(typescript@5.4.5)
@@ -851,9 +848,6 @@ packages:
   before-after-hook@2.2.3:
     resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
 
-  benchmark@2.1.4:
-    resolution: {integrity: sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==}
-
   binary-extensions@2.3.0:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
@@ -2509,9 +2503,6 @@ packages:
     engines: {node: '>=0.10'}
     hasBin: true
 
-  platform@1.3.6:
-    resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
-
   possible-typed-array-names@1.0.0:
     resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
     engines: {node: '>= 0.4'}
@@ -2914,8 +2905,8 @@ packages:
     resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
     engines: {node: '>=6'}
 
-  tatami-ng@0.4.6:
-    resolution: {integrity: sha512-DR59y8nC6r+u9YbYY5objx8o+DJah0NgxktO9Ib6jMk/tETzsCfqaPOOMt6GhWi/msZ2i6mFV6D2pur5HTBkzw==}
+  tatami-ng@0.4.8:
+    resolution: {integrity: sha512-RoAvJYBdweev0gMbphpea/kC+ht9hcatUzTj4XPP5kRpkjrsRUu9oAGAxVFPISl/s+iVvC76ChiDaDNHl5sVJg==}
     peerDependencies:
       typescript: ^5.0.0
 
@@ -3944,11 +3935,6 @@ snapshots:
 
   before-after-hook@2.2.3: {}
 
-  benchmark@2.1.4:
-    dependencies:
-      lodash: 4.17.21
-      platform: 1.3.6
-
   binary-extensions@2.3.0: {}
 
   bl@4.1.0:
@@ -5784,8 +5770,6 @@ snapshots:
 
   pidtree@0.6.0: {}
 
-  platform@1.3.6: {}
-
   possible-typed-array-names@1.0.0: {}
 
   prelude-ls@1.2.1: {}
@@ -6242,7 +6226,7 @@ snapshots:
 
   tapable@2.2.1: {}
 
-  tatami-ng@0.4.6(typescript@5.4.5):
+  tatami-ng@0.4.8(typescript@5.4.5):
     dependencies:
       typescript: 5.4.5