Преглед изворни кода

Merge pull request #14579 from overleaf/jpa-bench-bcrypt

[web] add script for benchmarking bcrypt performance

GitOrigin-RevId: c87ef9485323630ddb10b3bed2ed64f8f6812541
Jakob Ackermann пре 3 година
родитељ
комит
0c767f67c9
1 измењених фајлова са 121 додато и 0 уклоњено
  1. 121 0
      services/web/scripts/bench_bcrypt.js

+ 121 - 0
services/web/scripts/bench_bcrypt.js

@@ -0,0 +1,121 @@
+const minimist = require('minimist')
+const { promisify } = require('util')
+const bcrypt = require('bcrypt')
+const { promiseMapWithLimit } = require('../app/src/util/promises')
+const csv = require('csv/sync')
+
+const bcryptCompare = promisify(bcrypt.compare)
+const bcryptGenSalt = promisify(bcrypt.genSalt)
+const bcryptHash = promisify(bcrypt.hash)
+
+const argv = minimist(process.argv.slice(2), {
+  string: ['major', 'minor', 'concurrency', 'samples', 'password'],
+  bool: ['hash', 'compare', 'verbose', 'table', 'csv'],
+  default: {
+    major: '12,13,14,15',
+    minor: 'a',
+    concurrency: '1,2,4,10,20',
+    samples: 100,
+    password: 'x'.repeat(72),
+    hash: true,
+    compare: true,
+    verbose: true,
+    table: true,
+    csv: true,
+  },
+})
+
+const SAMPLES = parseInt(argv.samples, 10)
+const STATS = []
+
+function asListOfInt(s) {
+  return s.split(',').map(x => parseInt(x, 10))
+}
+
+async function computeHash(rounds, minor) {
+  const salt = await bcryptGenSalt(rounds, minor)
+  return await bcryptHash(argv.password, salt)
+}
+
+async function sample(concurrency, fn) {
+  const stats = await promiseMapWithLimit(
+    concurrency,
+    new Array(SAMPLES).fill(0),
+    async () => {
+      const t0 = process.hrtime.bigint()
+      await fn()
+      const t1 = process.hrtime.bigint()
+      return Number(t1 - t0) / 1e6
+    }
+  )
+  const sum = stats.reduce((a, b) => a + b, 0)
+  const avg = sum / SAMPLES
+  stats.sort((a, b) => a - b)
+  const median = stats[Math.ceil(SAMPLES / 2)]
+  const p95 = stats[Math.ceil(SAMPLES * 0.95)]
+  const min = stats[0]
+  const max = stats[stats.length - 1]
+  return Object.fromEntries(
+    Object.entries({
+      min,
+      avg,
+      median,
+      p95,
+      max,
+    }).map(([key, value]) => [key, Math.ceil(value) + 'ms'])
+  )
+}
+
+async function run(rounds, minor, concurrency) {
+  if (argv.hash) {
+    const stats = await sample(concurrency, async () => {
+      await computeHash(rounds, minor)
+    })
+    STATS.push({
+      kind: 'hash',
+      rounds,
+      concurrency,
+      ...stats,
+    })
+    if (argv.verbose) console.log(STATS[STATS.length - 1])
+  }
+  if (argv.compare) {
+    const hashedPassword = await computeHash(rounds, minor)
+    const stats = await sample(concurrency, async () => {
+      await bcryptCompare(argv.password, hashedPassword)
+    })
+    STATS.push({
+      kind: 'compare',
+      rounds,
+      concurrency,
+      ...stats,
+    })
+    if (argv.verbose) console.log(STATS[STATS.length - 1])
+  }
+}
+
+async function main() {
+  for (const rounds of asListOfInt(argv.major)) {
+    for (const minor of argv.minor.split(',')) {
+      for (const concurrency of asListOfInt(argv.concurrency)) {
+        await run(rounds, minor, concurrency)
+      }
+    }
+  }
+
+  STATS.forEach(s => {
+    s.samples = SAMPLES
+  })
+
+  if (argv.table) console.table(STATS)
+  if (argv.csv) console.log(csv.stringify(STATS, { header: true }))
+}
+
+main()
+  .then(() => {
+    process.exit(0)
+  })
+  .catch(err => {
+    console.error(err)
+    process.exit(1)
+  })