public void run() {
      operations.initTimers();

      try {
        SimpleClient sclient = null;
        ThriftClient tclient = null;
        JavaDriverClient jclient = null;

        switch (settings.mode.api) {
          case JAVA_DRIVER_NATIVE:
            jclient = settings.getJavaDriverClient();
            break;
          case SIMPLE_NATIVE:
            sclient = settings.getSimpleNativeClient();
            break;
          case THRIFT:
          case THRIFT_SMART:
            tclient = settings.getThriftClient();
            break;
          default:
            throw new IllegalStateException();
        }

        while (true) {
          Operation op = operations.next();
          if (!op.ready(workManager, rateLimiter)) break;

          try {
            switch (settings.mode.api) {
              case JAVA_DRIVER_NATIVE:
                op.run(jclient);
                break;
              case SIMPLE_NATIVE:
                op.run(sclient);
                break;
              case THRIFT:
              case THRIFT_SMART:
              default:
                op.run(tclient);
            }
          } catch (Exception e) {
            if (output == null) {
              System.err.println(e.getMessage());
              success = false;
              System.exit(-1);
            }

            e.printStackTrace(output);
            success = false;
            workManager.stop();
            metrics.cancel();
            return;
          }
        }
      } finally {
        done.countDown();
        operations.closeTimers();
      }
    }
  // TODO : permit varying more than just thread count
  // TODO : vary thread count based on percentage improvement of previous increment, not by fixed
  // amounts
  private boolean runMulti(boolean auto, RateLimiter rateLimiter) {
    if (settings.command.targetUncertainty >= 0)
      output.println(
          "WARNING: uncertainty mode (err<) results in uneven workload between thread runs, so should be used for high level analysis only");
    int prevThreadCount = -1;
    int threadCount = settings.rate.minThreads;
    List<StressMetrics> results = new ArrayList<>();
    List<String> runIds = new ArrayList<>();
    do {
      output.println(String.format("Running with %d threadCount", threadCount));

      if (settings.command.truncate == SettingsCommand.TruncateWhen.ALWAYS)
        settings.command.truncateTables(settings);

      StressMetrics result =
          run(
              settings.command.getFactory(settings),
              threadCount,
              settings.command.count,
              settings.command.duration,
              rateLimiter,
              settings.command.durationUnits,
              output);
      if (result == null) return false;
      results.add(result);

      if (prevThreadCount > 0)
        System.out.println(
            String.format(
                "Improvement over %d threadCount: %.0f%%",
                prevThreadCount, 100 * averageImprovement(results, 1)));

      runIds.add(threadCount + " threadCount");
      prevThreadCount = threadCount;
      if (threadCount < 16) threadCount *= 2;
      else threadCount *= 1.5;

      if (!results.isEmpty() && threadCount > settings.rate.maxThreads) break;

      if (settings.command.type.updates) {
        // pause an arbitrary period of time to let the commit log flush, etc. shouldn't make much
        // difference
        // as we only increase load, never decrease it
        output.println("Sleeping for 15s");
        try {
          Thread.sleep(15 * 1000);
        } catch (InterruptedException e) {
          return false;
        }
      }
      // run until we have not improved throughput significantly for previous three runs
    } while (!auto
        || (hasAverageImprovement(results, 3, 0)
            && hasAverageImprovement(results, 5, settings.command.targetUncertainty)));

    // summarise all results
    StressMetrics.summarise(runIds, results, output, settings.samples.historyCount);
    return true;
  }
 public Consumer(
     OpDistributionFactory operations,
     CountDownLatch done,
     WorkManager workManager,
     StressMetrics metrics,
     RateLimiter rateLimiter,
     int sampleCount) {
   this.done = done;
   this.rateLimiter = rateLimiter;
   this.workManager = workManager;
   this.metrics = metrics;
   this.operations = operations.get(metrics.getTiming(), sampleCount);
 }
  private StressMetrics run(
      OpDistributionFactory operations,
      int threadCount,
      long opCount,
      long duration,
      RateLimiter rateLimiter,
      TimeUnit durationUnits,
      PrintStream output) {
    output.println(
        String.format(
            "Running %s with %d threads %s",
            operations.desc(),
            threadCount,
            durationUnits != null
                ? duration + " " + durationUnits.toString().toLowerCase()
                : opCount > 0
                    ? "for " + opCount + " iteration"
                    : "until stderr of mean < " + settings.command.targetUncertainty));
    final WorkManager workManager;
    if (opCount < 0) workManager = new WorkManager.ContinuousWorkManager();
    else workManager = new WorkManager.FixedWorkManager(opCount);

    final StressMetrics metrics = new StressMetrics(output, settings.log.intervalMillis, settings);

    final CountDownLatch done = new CountDownLatch(threadCount);
    final Consumer[] consumers = new Consumer[threadCount];
    for (int i = 0; i < threadCount; i++) {
      consumers[i] =
          new Consumer(
              operations,
              done,
              workManager,
              metrics,
              rateLimiter,
              settings.samples.liveCount / threadCount);
    }

    // starting worker threadCount
    for (int i = 0; i < threadCount; i++) consumers[i].start();

    metrics.start();

    if (durationUnits != null) {
      Uninterruptibles.sleepUninterruptibly(duration, durationUnits);
      workManager.stop();
    } else if (opCount <= 0) {
      try {
        metrics.waitUntilConverges(
            settings.command.targetUncertainty,
            settings.command.minimumUncertaintyMeasurements,
            settings.command.maximumUncertaintyMeasurements);
      } catch (InterruptedException e) {
      }
      workManager.stop();
    }

    try {
      done.await();
      metrics.stop();
    } catch (InterruptedException e) {
    }

    if (metrics.wasCancelled()) return null;

    metrics.summarise();

    boolean success = true;
    for (Consumer consumer : consumers) success &= consumer.success;

    if (!success) return null;

    return metrics;
  }