text: // Performance benchmark
  
  const USAGE = `
  Usage: babel-node ./perf/index.js [--compare referencePath] [--only benchmarkName] [--output outputPath]
  
  	--compare referencePath	Compare with results stored in the JSON at referencePath
  	--only benchmarkName	Only run the designated benchmark (named after the benchmark directory)
  	--output outputPath	Output the benchmarks results as JSON at outputPath
  `
  
  const Benchmark = require('benchmark')
  const jsdomGlobal = require('jsdom-global')
  
  // Setup virtual DOM for rendering tests, before loading React (so it
  // see the fake DOM), but after loading BenchmarkJS so that it does
  // not think we are running inside a browser.
  jsdomGlobal()
  
  const fs = require('fs')
  const readMetadata = require('read-metadata')
  const { Raw } = require('..')
  const memoize = require('../lib/utils/memoize')
  
  const { resolve } = require('path')
  
  const DEFAULT_BENCHMARK = {
    setup(state) { return state },
    teardown() {},
    run(state) {}
  }
  
  const BENCHMARK_OPTIONS = {
    // To ensure a better accuracy, force a minimum number of samples
    minSamples: 80 // default 10
  }
  
  // Because BenchmarkJS does not support scoped variables well, use
  // globals...  Each benchmark has its own namespace scope, that can be
  // accessed through the `getScope` global function
  const scopes = {}
  global.currentBenchmark = undefined // The benchmark being run
  global.setScope = function (benchmarkName, scope) {
    scopes[benchmarkName] = scope
  }
  global.getScope = function () {
    return scopes[global.currentBenchmark]
  }
  
  // --------------------------------------------------
  // Run benchmarks
  // --------------------------------------------------
  
  function runBenchmarks() {
    print('Benchmarks\n')
  
    // Command line options
    const { outputPath, reference, only, help } = parseCommandLineOptions(process)
    if (help) return printUsage()
  
    let suite = new Benchmark.Suite()
    let results = {} // Can be saved as JSON
  
    // For each benchmark
    const suiteDir = resolve(__dirname, './benchmarks')
    const benchmarks = fs.readdirSync(suiteDir)
    for (const benchmarkName of benchmarks) {
      if (benchmarkName[0] == '.') continue
      if (only && benchmarkName != only) continue
  
      const benchmarkDir = resolve(suiteDir, benchmarkName)
  
      // Read benchmark specification
      const benchmark = Object.assign({}, DEFAULT_BENCHMARK, require(benchmarkDir))
      // Parse input Slate.State
      const input = readMetadata.sync(resolve(benchmarkDir, 'input.yaml'))
  
      // Setup global scope for this benchmark
      global.setScope(benchmarkName, {
        Raw,
        memoize,
        benchmark,
        input
      })
  
      // Add it to the benchmark suite
      suite.add(Object.assign({}, BENCHMARK_OPTIONS, {
        name: benchmarkName,
  
        onStart() {
          print(indent(1), benchmarkName)
          // Use this test's scope
          global.currentBenchmark = benchmarkName
        },
  
        // Time spent in setup is not taken into account
        setup() {
          const scope = global.getScope()
          // Each benchmark is given the chance to do its own setup
          const state = scope.benchmark.setup( // eslint-disable-line no-unused-vars
            scope.Raw.deserialize(scope.input, { terse: true })
          )
        },
  
        // Because of the way BenchmarkJS compiles the functions,
        // the variables declared in `setup` are visible to `fn`
  
        fn() {
          scope.benchmark.run(state) // eslint-disable-line no-undef
          // Clear memoized values between each run
          scope.memoize.__clear() // eslint-disable-line no-undef
        },
  
        onComplete() {
          global.getScope().benchmark.teardown()
        }
      }))
    }
  
    suite
    // On benchmark completion
      .on('cycle', (event) => {
        const result = serializeResult(event)
        results[result.name] = result
        compareResult(result, reference)
      })
    // On suite completion
      .on('complete', (event) => {
        if (outputPath) {
          save(results, outputPath)
          print(`\nSaved results as JSON to ${outputPath}`)
        }
      })
    // Run async to properly flush logs
      .run({ 'async': true })
  }
  
  /**
   * @param {Node.Process} process
   * @return {Object} { reference: JSON?, outputPath: String?, only: String? }
   */
  
  function parseCommandLineOptions(process) {
    let outputPath
    let reference
    let only
    let help = false
  
    const options = process.argv.slice(2)
  
    for (let i = 0; i < options.length; i++) {
      let option = options[i]
  
      switch (option) {
  
      case '-h':
      case '--help':
        help = true
        break
  
      case '--output':
        outputPath = options[i + 1]
        i++
        break
  
      case '--only':
        only = options[i + 1]
        i++
        break
  
      case '--compare':
        let refPath = resolve(process.cwd(), options[i + 1])
        if (exists(refPath)) {
          let fileContents = fs.readFileSync(refPath, 'utf-8')
          reference = JSON.parse(fileContents)
        }
  
        i++
        break
  
      default:
        printUsage()
        throw new Error(`Invalid argument ${option}`)
      }
    }
  
    return {
      outputPath,
      reference,
      only,
      help
    }
  }
  
  function printUsage() {
    print(USAGE)
  }
  
  function exists(filepath) {
    try {
      fs.statSync(filepath)
      return true
    } catch (e) {
      return false
    }
  }
  
  function save(results, path) {
    path = resolve(process.cwd(), path)
    fs.writeFileSync(path, JSON.stringify(results, null, 2))
  }
  
  function serializeResult(event) {
    const { target } = event
    const { error, name } = target
  
    const result = {
      name
    }
  
    if (target.error) {
      Object.assign(result, { error })
    }
  
    else {
      const { hz } = target
      const { mean, rme, sample } = target.stats
      const stats = {
        rme,
        mean,
        sample
      }
  
      Object.assign(result, {
        hz,
        stats
      })
    }
  
    return result
  }
  
  /**
   * Pretty print a benchmark result, along with its reference.
   * Mean difference, and rme computations inspired from
   * https://github.com/facebook/immutable-js/blob/master/resources/bench.js
   *
   * @param {Object} result
   * @param {Object} reference (optional)
   */
  
  function compareResult(result, reference = {}) {
    const { name } = result
    const ref = reference[name]
    const errored = ref && (ref.error || result.error)
  
    print(indent(2), 'Current:	', formatPerf(result))
  
    if (ref) {
      print(indent(2), 'Reference:	', formatPerf(ref))
    }
  
    // Print comparison
  
    if (ref && !errored) {
      print(indent(2), `comparison: ${compare(result, ref)}`)
    }
  
    // Print difference as percentage
    if (ref && !errored) {
      const newMean = 1 / result.stats.mean
      const prevMean = 1 / ref.stats.mean
      const diffMean = 100 * (newMean - prevMean) / prevMean
  
      print(indent(2), `diff: ${signed(diffMean.toFixed(2))}%`) // diff: -3.45%
    }
  
    // Print relative mean error
    if (ref && !errored) {
      const aRme = 100 * Math.sqrt(
        (square(result.stats.rme / 100) + square(ref.stats.rme / 100)) / 2
      )
  
      print(indent(2), `rme: \xb1${aRme.toFixed(2)}%`) // rme: ±6.22%
    } else if (!result.error) {
      print(indent(2), `rme: \xb1${result.stats.rme.toFixed(2)}%`) // rme: ±6.22%
    }
  
    print('') // newline
  }
  
  /**
   * Pretty format a benchmark's ops/sec along with its sample size
   * @param {Object} result
   * @return {String}
   */
  
  function formatPerf(result) {
    if (result.error) return result.error
    const { hz } = result
    const runs = result.stats.sample.length
    const opsSec = Benchmark.formatNumber(`${hz.toFixed(hz < 100 ? 2 : 0)}`)
    return `${opsSec} ops/sec (${runs} runs sampled)`
  }
  
  /**
   * @param {Object} newResult
   * @param {Object} oldResult
   * @return {String} Faster, Slower, or Indeterminate
   */
  
  function compare(newResult, oldResult) {
    const comparison = (new Benchmark()).compare.call(newResult, oldResult)
  
    switch (comparison) {
    case 1:
      return 'Faster'
    case -1:
      return 'Slower'
    default:
      return 'Indeterminate'
    }
  }
  
  function indent(level = 0) {
    return Array(level + 1).join('  ')
  }
  
  function square(x) {
    return x * x
  }
  
  function signed(x) {
    return x > 0 ? `+${x}` : `${x}`
  }
  
  function print(...strs) {
    console.log(...strs) // eslint-disable-line no-console
  }
  
  // --------------------------------------------------
  // Main
  // --------------------------------------------------
  runBenchmarks()
  