bench_bcrypt.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. const minimist = require('minimist')
  2. const { promisify } = require('util')
  3. const bcrypt = require('bcrypt')
  4. const { promiseMapWithLimit } = require('../app/src/util/promises')
  5. const csv = require('csv/sync')
  6. const bcryptCompare = promisify(bcrypt.compare)
  7. const bcryptGenSalt = promisify(bcrypt.genSalt)
  8. const bcryptHash = promisify(bcrypt.hash)
  9. const argv = minimist(process.argv.slice(2), {
  10. string: ['major', 'minor', 'concurrency', 'samples', 'password'],
  11. bool: ['hash', 'compare', 'verbose', 'table', 'csv'],
  12. default: {
  13. major: '12,13,14,15',
  14. minor: 'a',
  15. concurrency: '1,2,4,10,20',
  16. samples: 100,
  17. password: 'x'.repeat(72),
  18. hash: true,
  19. compare: true,
  20. verbose: true,
  21. table: true,
  22. csv: true,
  23. },
  24. })
  25. const SAMPLES = parseInt(argv.samples, 10)
  26. const STATS = []
  27. function asListOfInt(s) {
  28. return s.split(',').map(x => parseInt(x, 10))
  29. }
  30. async function computeHash(rounds, minor) {
  31. const salt = await bcryptGenSalt(rounds, minor)
  32. return await bcryptHash(argv.password, salt)
  33. }
  34. async function sample(concurrency, fn) {
  35. const stats = await promiseMapWithLimit(
  36. concurrency,
  37. new Array(SAMPLES).fill(0),
  38. async () => {
  39. const t0 = process.hrtime.bigint()
  40. await fn()
  41. const t1 = process.hrtime.bigint()
  42. return Number(t1 - t0) / 1e6
  43. }
  44. )
  45. const sum = stats.reduce((a, b) => a + b, 0)
  46. const avg = sum / SAMPLES
  47. stats.sort((a, b) => a - b)
  48. const median = stats[Math.ceil(SAMPLES / 2)]
  49. const p95 = stats[Math.ceil(SAMPLES * 0.95)]
  50. const min = stats[0]
  51. const max = stats[stats.length - 1]
  52. return Object.fromEntries(
  53. Object.entries({
  54. min,
  55. avg,
  56. median,
  57. p95,
  58. max,
  59. }).map(([key, value]) => [key, Math.ceil(value) + 'ms'])
  60. )
  61. }
  62. async function run(rounds, minor, concurrency) {
  63. if (argv.hash) {
  64. const stats = await sample(concurrency, async () => {
  65. await computeHash(rounds, minor)
  66. })
  67. STATS.push({
  68. kind: 'hash',
  69. rounds,
  70. concurrency,
  71. ...stats,
  72. })
  73. if (argv.verbose) console.log(STATS[STATS.length - 1])
  74. }
  75. if (argv.compare) {
  76. const hashedPassword = await computeHash(rounds, minor)
  77. const stats = await sample(concurrency, async () => {
  78. await bcryptCompare(argv.password, hashedPassword)
  79. })
  80. STATS.push({
  81. kind: 'compare',
  82. rounds,
  83. concurrency,
  84. ...stats,
  85. })
  86. if (argv.verbose) console.log(STATS[STATS.length - 1])
  87. }
  88. }
  89. async function main() {
  90. for (const rounds of asListOfInt(argv.major)) {
  91. for (const minor of argv.minor.split(',')) {
  92. for (const concurrency of asListOfInt(argv.concurrency)) {
  93. await run(rounds, minor, concurrency)
  94. }
  95. }
  96. }
  97. STATS.forEach(s => {
  98. s.samples = SAMPLES
  99. })
  100. if (argv.table) console.table(STATS)
  101. if (argv.csv) console.log(csv.stringify(STATS, { header: true }))
  102. }
  103. main()
  104. .then(() => {
  105. process.exit(0)
  106. })
  107. .catch(err => {
  108. console.error(err)
  109. process.exit(1)
  110. })