text: var assert = require('assert');
  var fs = require('fs');
  var path = require('path');
  var child_process = require('child_process');
  
  var silent = +process.env.NODE_BENCH_SILENT;
  
  exports.PORT = process.env.PORT || 12346;
  
  // If this is the main module, then run the benchmarks
  if (module === require.main) {
    hasWrk();
    var type = process.argv[2];
    var testFilter = process.argv[3];
    if (!type) {
      console.error('usage:\n ./iojs benchmark/common.js <type> [testFilter]');
      process.exit(1);
    }
  
    var dir = path.join(__dirname, type);
    var tests = fs.readdirSync(dir);
  
    if (testFilter) {
      var filteredTests = tests.filter(function(item){
        if (item.lastIndexOf(testFilter) >= 0) {
          return item;
        }
      });
  
      if (filteredTests.length === 0) {
        console.error('%s is not found in \n %j', testFilter, tests);
        return;
      }
      tests = filteredTests;
    }
  
    runBenchmarks();
  }
  
  function hasWrk() {
    var result = child_process.spawnSync('wrk', ['-h']);
    if (result.error && result.error.code === 'ENOENT') {
      console.error('Couldn\'t locate `wrk` which is needed for running ' +
        'benchmarks. Check benchmark/README.md for further instructions.');
        process.exit(-1);
    }
  }
  
  function runBenchmarks() {
    var test = tests.shift();
    if (!test)
      return;
  
    if (test.match(/^[\._]/))
      return process.nextTick(runBenchmarks);
  
    console.error(type + '/' + test);
    test = path.resolve(dir, test);
  
    var a = (process.execArgv || []).concat(test);
    var child = child_process.spawn(process.execPath, a, { stdio: 'inherit' });
    child.on('close', function(code) {
      if (code) {
        process.exit(code);
      } else {
        console.log('');
        runBenchmarks();
      }
    });
  }
  
  exports.createBenchmark = function(fn, options) {
    return new Benchmark(fn, options);
  };
  
  function Benchmark(fn, options) {
    this.fn = fn;
    this.options = options;
    this.config = parseOpts(options);
    this._name = require.main.filename.split(/benchmark[\/\\]/).pop();
    this._start = [0,0];
    this._started = false;
  
    var self = this;
  
    hasWrk();
    process.nextTick(function() {
      self._run();
    });
  }
  
  // benchmark an http server.
  Benchmark.prototype.http = function(p, args, cb) {
    var self = this;
    var regexp = /Requests\/sec:[ \t]+([0-9\.]+)/;
    var url = 'http://127.0.0.1:' + exports.PORT + p;
  
    args = args.concat(url);
  
    var out = '';
    var child = child_process.spawn('wrk', args);
  
    child.stdout.setEncoding('utf8');
  
    child.stdout.on('data', function(chunk) {
      out += chunk;
    });
  
    child.on('close', function(code) {
      if (cb)
        cb(code);
  
      if (code) {
        console.error('wrk failed with ' + code);
        process.exit(code)
      }
      var match = out.match(regexp);
      var qps = match && +match[1];
      if (!qps) {
        console.error('%j', out);
        console.error('wrk produced strange output');
        process.exit(1);
      }
      self.report(+qps);
    });
  };
  
  Benchmark.prototype._run = function() {
    if (this.config)
      return this.fn(this.config);
  
    // one more more options weren't set.
    // run with all combinations
    var main = require.main.filename;
    var settings = [];
    var queueLen = 1;
    var options = this.options;
  
    var queue = Object.keys(options).reduce(function(set, key) {
      var vals = options[key];
      assert(Array.isArray(vals));
  
      // match each item in the set with each item in the list
      var newSet = new Array(set.length * vals.length);
      var j = 0;
      set.forEach(function(s) {
        vals.forEach(function(val) {
          newSet[j++] = s.concat(key + '=' + val);
        });
      });
      return newSet;
    }, [[main]]);
  
    var node = process.execPath;
    var i = 0;
    function run() {
      var argv = queue[i++];
      if (!argv)
        return;
      argv = process.execArgv.concat(argv);
      var child = child_process.spawn(node, argv, { stdio: 'inherit' });
      child.on('close', function(code, signal) {
        if (code)
          console.error('child process exited with code ' + code);
        else
          run();
      });
    }
    run();
  };
  
  function parseOpts(options) {
    // verify that there's an option provided for each of the options
    // if they're not *all* specified, then we return null.
    var keys = Object.keys(options);
    var num = keys.length;
    var conf = {};
    for (var i = 2; i < process.argv.length; i++) {
      var match = process.argv[i].match(/^(.+)=(.+)$/);
      if (!match || !match[1] || !match[2] || !options[match[1]]) {
        return null;
      } else {
        conf[match[1]] = isFinite(match[2]) ? +match[2] : match[2]
        num--;
      }
    }
    // still go ahead and set whatever WAS set, if it was.
    if (num !== 0) {
      Object.keys(conf).forEach(function(k) {
        options[k] = [conf[k]];
      });
    }
    return num === 0 ? conf : null;
  };
  
  Benchmark.prototype.start = function() {
    if (this._started)
      throw new Error('Called start more than once in a single benchmark');
  
    this._started = true;
    this._start = process.hrtime();
  };
  
  Benchmark.prototype.end = function(operations) {
    var elapsed = process.hrtime(this._start);
  
    if (!this._started)
      throw new Error('called end without start');
    if (typeof operations !== 'number')
      throw new Error('called end() without specifying operation count');
  
    var time = elapsed[0] + elapsed[1]/1e9;
    var rate = operations/time;
    this.report(rate);
  };
  
  Benchmark.prototype.report = function(value) {
    var heading = this.getHeading();
    if (!silent)
      console.log('%s: %s', heading, value.toFixed(5));
  
    process.exit(0);
  };
  
  Benchmark.prototype.getHeading = function() {
    var conf = this.config;
    return this._name + ' ' + Object.keys(conf).map(function(key) {
      return key + '=' + conf[key];
    }).join(' ');
  };
  