function main()

in perftest/jscrund.js [392:846]


function main() {
  var config_file_exists = false;

  /* Default options: */
  var options = {
    'doClass' : 'A',
    'adapter' : 'ndb',
    'database': 'jscrund',
    'modes': 'indy,each,bulk',
    'tests': null,
    'iterations': [4000],
    'stats': false,
    'spi': false,
    'log': false,
    'nRuns': 1,
    'setProp' : {},
    'delay_pre' : 0,
    'delay_post' : 0,
    'B_varchar_size' : 10,
    'deployment' : 'test'
  };

  /* Options from config file */
  try {
    var config_file = require("./jscrund.config");
    config_file_exists = true;
    for(var i in config_file.options) {
      if(config_file.options.hasOwnProperty(i)) {
        options[i]  = config_file.options[i];
      }
    }
  }
  catch(e) {
    if (e.message.indexOf('Cannot find module') === -1) {
      console.log(e);
      console.log(e.name, 'reading jscrund.config:', e.message, '\nPlease correct this error and try again.\n');
      process.exit(0);
    }
  }

  /* Options from command line */
  parse_command_line(options);

  if (options.exit) {
    usage();
    process.exit(0);
  }

  /* Global udebug level; may have been set by options */
  DEBUG  = JSCRUND.udebug.is_debug();
  DETAIL = JSCRUND.udebug.is_detail();

  /* Create the string value for varchar tests */
  if(options.B_varchar_size > 0) {
    options.B_varchar_value = "";
    for(var i = 0 ; i < options.B_varchar_size ; i++) {
      options.B_varchar_value += String.fromCharCode(48 + (i % 64));
    }
  }

  /* Fetch the backend implementation */
  if(options.spi) {
    JSCRUND.spiAdapter = require('./jscrund_dbspi');
    JSCRUND.implementation = new JSCRUND.spiAdapter.implementation();
  } else if(options.adapter == 'sql') {
    JSCRUND.sqlAdapter = require('./jscrund_sql');
    JSCRUND.implementation = new JSCRUND.sqlAdapter.implementation();
  } else if(options.adapter == 'null') {
    JSCRUND.nullAdapter = require('./jscrund_null');
    JSCRUND.implementation = new JSCRUND.nullAdapter.implementation();
  } else {
    JSCRUND.implementation = new JSCRUND.mysqljs.implementation();
  }

  /* Get connection properties */
  var properties;
  if(typeof JSCRUND.implementation.getConnectionProperties === 'function') {
    properties = JSCRUND.implementation.getConnectionProperties();
  } else {
    properties = new JSCRUND.mynode.ConnectionProperties(options.adapter, options.deployment);
  }

  /* Then mix in properties from the command line */
  properties.database = options.database;
  for(i in options.setProp) {
    if(options.setProp.hasOwnProperty(i)) {
      properties[i] = options.setProp[i];
    }
  }

  /* Finally store the complete properties object in the options */
  options.properties = properties;

  /* Force GC is available if node is run with the --expose-gc option
  */
  options.use_gc = ( typeof global.gc === 'function' );

  var logFile = new ResultLog(options.log);
  new JSCRUND.mynode.TableMapping("a").applyToClass(A);
  new JSCRUND.mynode.TableMapping("b").applyToClass(B);
  options.annotations = [ A, B ];

  var generateAllParameters = function(numberOfParameters) {
    var result = [];
    for (var i = 0; i < numberOfParameters; ++i) {
      result[i] = generateParameters(i);
    }
    return result;
  };

  var generateParameters = function(i) {
    return {'key': generateKey(i), 'object': generateObject(i)};
  };

  var generateKey = function(i) {
    return i;
  };

  var generateObject = function(i) {
    var result;
    if(options.doClass == "A") {
      result = new A();
    } else if(options.doClass == "B") {
      result = new B();
    } else {
      assert(false);
    }
    result.init(i);
    return result;
  };


  // mainTestLoop (-r 10)
  //   batchSizeLoop (-i 1,10,100)
  //     ModeLoop      (--modes=indy,each)
  //       TestsLoop     (--tests=persist,remove)

  var runTests = function(options) {

    var timer = new Timer();

    var modeNames = options.modes.split('\,');    
    var modeNumber = 0;
    var mode;
    var modeName;

    var testNames;
    var testNumber = 0;
    var operation;
    var testName;

    var key;
    var object;

    var numberOfBatchLoops = options.iterations.length;
    var batchLoopNumber = 0;
    var numberOfIterations;
    var iteration;

    var nRun = 0;
    var nRuns = (options.nRuns < 0 ? Infinity : options.nRuns);
    var nReport = 0;

    var operationsDoneCallback;
    var testsDoneCallback;
    var resultStats = [];

    var parameters;

    /* Which tests to run */
    if(options.tests) {  // Explicit test names
      testNames = options.tests.split('\,');
    } else {
      switch(options.doClass) {
        case 'B':
          testNames = [ 'persist', 'setVarchar', 'find', 'clearVarchar', 'remove' ];
          break;
        case 'A':
        default:
          testNames = [ 'persist', 'find', 'remove' ];
          break;
      };
    }

    /** Recursively call the operation numberOfIterations times in autocommit mode
     * and then call the operationsDoneCallback
     */
    var indyOperationsLoop = function(err) {
      if(DETAIL) JSCRUND.udebug.log_detail('jscrund.indyOperationsLoop iteration:', iteration, 'err:', err);
      // check result
      if (err) {
        appendError(err);
      }
      // call implementation operation
      if (iteration < numberOfIterations) {
        operation.apply(JSCRUND.implementation, [parameters[iteration], indyOperationsLoop]);
        iteration++;
      } else {
        if(DETAIL) JSCRUND.udebug.log_detail('jscrund.indyOperationsLoop iteration:', iteration, 'complete.');
        timer.stop();
        resultStats.push({
          name: testName + "," + modeName,
          time: timer.interval
        });
        setImmediate(operationsDoneCallback);
      }
    };

    /** Call indyOperationsLoop for all of the tests in testNames
     */
    var indyTestsLoop = function() {
      testName = testNames[testNumber];
      operation = JSCRUND.implementation[testName];
      operationsDoneCallback = indyTestsLoop;
      if (testNumber < testNames.length) {
        if(options.use_gc) global.gc();  // Full GC between tests
        testNumber++;
        if(DETAIL) JSCRUND.udebug.log_detail('jscrund.indyTestsLoop', testNumber, 'of', testNames.length, ':', testName);
        iteration = 0;
        timer.start(modeName, testName, numberOfIterations);
        setImmediate(indyOperationsLoop, null);
      } else {
        // done with all indy tests
        // stop timer and report
        setImmediate(testsDoneCallback);
      }
    };

    /** Finish the each operations loop after commit.
     */
    var eachCommitDoneCallback = function(err) {
      // check result
      if (err) {
        appendError(err);
      }
      timer.stop();
      resultStats.push({
        name: testName + "," + modeName,
        time: timer.interval
      });
      setImmediate(operationsDoneCallback);
    };

    /** Call the operation numberOfIterations times within a transaction
     * and then call the operationsDoneCallback
     */
    var eachOperationsLoop = function(err) {
      if(DETAIL) JSCRUND.udebug.log_detail('jscrund.eachOperationsLoop iteration:', iteration, 'err:', err);
      // check result
      if (err) {
        appendError(err);
      }
      // call implementation operation
      if (iteration < numberOfIterations) {
        operation.apply(JSCRUND.implementation, [parameters[iteration], eachOperationsLoop]);
        iteration++;
      } else {
        if(DETAIL) JSCRUND.udebug.log_detail('jscrund.eachOperationLoop iteration:', iteration, 'complete.');
        JSCRUND.implementation.commit(eachCommitDoneCallback);
      }
    };

    /** Call eachOperationsLoop for all of the tests in testNames
     */
    var eachTestsLoop = function() {
      testName = testNames[testNumber];
      operation = JSCRUND.implementation[testName];
      operationsDoneCallback = eachTestsLoop;
      if (testNumber < testNames.length) {
        if(options.use_gc) global.gc();  // Full GC between tests
        testNumber++;
        if(DETAIL) JSCRUND.udebug.log_detail('jscrund.eachTestsLoop', testNumber, 'of', testNames.length, ':', testName);
        iteration = 0;
        timer.start(modeName, testName, numberOfIterations);
        JSCRUND.implementation.begin(function(err) {
          setImmediate(eachOperationsLoop, null);
        });
      } else {
        // done with all each tests
        // stop timer and report
        testsDoneCallback();
      }
    };

    /** Check the results of a bulk execute.
     */
    var bulkCheckBatchCallback = function(err) {
      if(DETAIL) JSCRUND.udebug.log_detail('jscrund.bulkCheckBatchCallback', err);
      // check result
      if (err) {
        appendError(err);
      }
      timer.stop();
      resultStats.push({
        name: testName + "," + modeName,
        time: timer.interval
      });
      setImmediate(bulkTestsLoop);
    };

    /** Check the results of a bulk operation. It will be executed
     * numberOfIterations times per test.
     */
    var bulkCheckOperationCallback = function(err) {
      if(DETAIL) JSCRUND.udebug.log_detail('jscrund.bulkCheckOperationCallback', err);
      if (err) {
        appendError(err);
      }
    };

    /** Construct one batch and execute it for all of the tests in testNames
     */
    var bulkTestsLoop = function() {
      testName = testNames[testNumber];
      operation = JSCRUND.implementation[testName];
      operationsDoneCallback = bulkTestsLoop;
      if (testNumber < testNames.length) {
        if(options.use_gc) global.gc();  // Full GC between tests
        testNumber++;
        if(DETAIL) JSCRUND.udebug.log_detail('jscrund.bulkTestsLoop', testNumber, 'of', testNames.length, ':', testName);
        timer.start(modeName, testName, numberOfIterations);
        JSCRUND.implementation.createBatch(function(err) {
          for (iteration = 0; iteration < numberOfIterations; ++iteration) {
            operation.apply(JSCRUND.implementation, [parameters[iteration], bulkCheckOperationCallback]);
          }
          JSCRUND.implementation.executeBatch(bulkCheckBatchCallback);
        });
      } else {
        // done with all bulk tests
        testsDoneCallback();
      }
    };

    /** Run all modes specified in --modes: default is indy, each, bulk.
     * 
     */
    var modeLoop = function() {
      modeName = modeNames[modeNumber];
      mode = modes[modeNumber];
      testNumber = 0;
      if (modeNumber < modes.length) {
        modeNumber++;
         console.log('\njscrund.modeLoop ', modeName, "[ size",numberOfIterations,"]");
        mode.apply(runTests);
      } else {
        if (JSCRUND.errors.length !== 0) {
          console.log(JSCRUND.errors);
        }
        report();
        batchSizeLoop();
      }
    };

    function report() {
      var opNames, opTimes, r, adapter;

      adapter = options.adapter + (options.spi ? "(spi)" : "");
      opNames = "rtime[ms]," + adapter + '\t';
      opTimes = "" + numberOfIterations + '\t';

      while (r = resultStats.shift()) {
        opNames += r.name + '\t';
        opTimes += r.time + '\t';
      }

      if(! nReport++) {
        logFile.write(opNames + '\n');
      }

      logFile.write(opTimes + '\n');
    }

    /** Iterate over batch sizes
    */
    function batchSizeLoop() {
      if(batchLoopNumber < numberOfBatchLoops) {
        numberOfIterations = options.iterations[batchLoopNumber];
        batchLoopNumber++;
        parameters = generateAllParameters(numberOfIterations);
        modeNumber = 0;
        setImmediate(modeLoop);
      } else {
        mainTestLoop();
      }
    }

    /** Run test with all modes.
    */
    var mainTestLoop = function() {
      if (nRun++ >= nRuns) {
        console.log('\ndone: ' + nRuns + ' runs.');
        console.log("\nappending results to file: " + logFile.name);
        logFile.close();
        if (options.stats) {
          JSCRUND.stats.peek();
        }
        JSCRUND.implementation.close(function(err) {
          if(options.delay_post > 0) {
            console.log("Delaying", options.delay_post, "seconds");
            setTimeout(process.exit, 1000 * options.delay_post);
          } else { 
            process.exit(err ? 1 : 0);
          }
        });
      } else {
        console.log('\nRun #' + nRun + ' of ' + nRuns);
        batchLoopNumber = 0;
        if(nRun === 2 && (options.delay_pre > 0)) { 
          console.log("Waiting", options.delay_pre, "seconds...");
          setTimeout(batchSizeLoop, 1000 * options.delay_pre);
          options.delay_pre = 0;
        } else {
          batchSizeLoop();
        }
      }
    };

    // runTests starts here
    var modeTable = {
        'indy': indyTestsLoop,
        'each': eachTestsLoop,
        'bulk': bulkTestsLoop
    };
    var modes = [];
    for (var m = 0; m < modeNames.length; ++m) {
      modes.push(modeTable[modeNames[m]]);
    }

    console.log('running tests with options:\n', options);
    JSCRUND.implementation.initialize(options, function(err) {
      // initialization complete
      if (err) {
        console.log('Error initializing JSCRUND.implementation:', err);
        process.exit(1);
      } else {
        testsDoneCallback = modeLoop;
        mainTestLoop();
      }
    });
  };

  // create database
  JSCRUND.metadataManager = require("jones-ndb").getDBMetadataManager(properties);

  JSCRUND.metadataManager.runSQL(path.join(__dirname, "./create.sql"), function(err) {
    if (err) {
      console.log('Error creating tables.', err);
      process.exit(1);
    }
    
    // if database create successful, run tests
    runTests(options);
  });

}