public static void main(String[] args) throws Exception {
    System.setProperty("es.logger.prefix", "");
    Natives.tryMlockall();

    Settings settings =
        settingsBuilder()
            .put("gateway.type", "local")
            .put(DiskThresholdDecider.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED, "false")
            .put(SETTING_NUMBER_OF_SHARDS, 1)
            .put(SETTING_NUMBER_OF_REPLICAS, 0)
            .put(TransportModule.TRANSPORT_TYPE_KEY, "local")
            .build();

    String clusterName = ReplicaRecoveryBenchmark.class.getSimpleName();
    Node node1 =
        nodeBuilder().clusterName(clusterName).settings(settingsBuilder().put(settings)).node();

    final ESLogger logger = ESLoggerFactory.getLogger("benchmark");

    final Client client1 = node1.client();
    client1
        .admin()
        .cluster()
        .prepareUpdateSettings()
        .setPersistentSettings("logger.indices.recovery: TRACE")
        .get();
    final BackgroundIndexer indexer =
        new BackgroundIndexer(
            INDEX_NAME, TYPE_NAME, client1, 0, CONCURRENT_INDEXERS, false, new Random());
    indexer.setMinFieldSize(10);
    indexer.setMaxFieldSize(150);
    try {
      client1.admin().indices().prepareDelete(INDEX_NAME).get();
    } catch (IndexMissingException e) {
    }
    client1.admin().indices().prepareCreate(INDEX_NAME).get();
    indexer.start(DOC_COUNT / 2);
    while (indexer.totalIndexedDocs() < DOC_COUNT / 2) {
      Thread.sleep(5000);
      logger.info("--> indexed {} of {}", indexer.totalIndexedDocs(), DOC_COUNT);
    }
    client1.admin().indices().prepareFlush().get();
    indexer.continueIndexing(DOC_COUNT / 2);
    while (indexer.totalIndexedDocs() < DOC_COUNT) {
      Thread.sleep(5000);
      logger.info("--> indexed {} of {}", indexer.totalIndexedDocs(), DOC_COUNT);
    }

    logger.info("--> starting another node and allocating a shard on it");

    Node node2 =
        nodeBuilder().clusterName(clusterName).settings(settingsBuilder().put(settings)).node();

    client1
        .admin()
        .indices()
        .prepareUpdateSettings(INDEX_NAME)
        .setSettings(IndexMetaData.SETTING_NUMBER_OF_REPLICAS + ": 1")
        .get();

    final AtomicBoolean end = new AtomicBoolean(false);

    final Thread backgroundLogger =
        new Thread(
            new Runnable() {

              long lastTime = System.currentTimeMillis();
              long lastDocs = indexer.totalIndexedDocs();
              long lastBytes = 0;
              long lastTranslogOps = 0;

              @Override
              public void run() {
                while (true) {
                  try {
                    Thread.sleep(5000);
                  } catch (InterruptedException e) {

                  }
                  if (end.get()) {
                    return;
                  }
                  long currentTime = System.currentTimeMillis();
                  long currentDocs = indexer.totalIndexedDocs();
                  RecoveryResponse recoveryResponse =
                      client1
                          .admin()
                          .indices()
                          .prepareRecoveries(INDEX_NAME)
                          .setActiveOnly(true)
                          .get();
                  List<ShardRecoveryResponse> indexRecoveries =
                      recoveryResponse.shardResponses().get(INDEX_NAME);
                  long translogOps;
                  long bytes;
                  if (indexRecoveries.size() > 0) {
                    translogOps =
                        indexRecoveries.get(0).recoveryState().getTranslog().recoveredOperations();
                    bytes =
                        recoveryResponse
                            .shardResponses()
                            .get(INDEX_NAME)
                            .get(0)
                            .recoveryState()
                            .getIndex()
                            .recoveredBytes();
                  } else {
                    bytes = lastBytes = 0;
                    translogOps = lastTranslogOps = 0;
                  }
                  float seconds = (currentTime - lastTime) / 1000.0F;
                  logger.info(
                      "--> indexed [{}];[{}] doc/s, recovered [{}] MB/s , translog ops [{}]/s ",
                      currentDocs,
                      (currentDocs - lastDocs) / seconds,
                      (bytes - lastBytes) / 1024.0F / 1024F / seconds,
                      (translogOps - lastTranslogOps) / seconds);
                  lastBytes = bytes;
                  lastTranslogOps = translogOps;
                  lastTime = currentTime;
                  lastDocs = currentDocs;
                }
              }
            });

    backgroundLogger.start();

    client1.admin().cluster().prepareHealth().setWaitForGreenStatus().get();

    logger.info("--> green. starting relocation cycles");

    long startDocIndexed = indexer.totalIndexedDocs();
    indexer.continueIndexing(DOC_COUNT * 50);

    long totalRecoveryTime = 0;
    long startTime = System.currentTimeMillis();
    long[] recoveryTimes = new long[3];
    for (int iteration = 0; iteration < 3; iteration++) {
      logger.info("--> removing replicas");
      client1
          .admin()
          .indices()
          .prepareUpdateSettings(INDEX_NAME)
          .setSettings(IndexMetaData.SETTING_NUMBER_OF_REPLICAS + ": 0")
          .get();
      logger.info("--> adding replica again");
      long recoveryStart = System.currentTimeMillis();
      client1
          .admin()
          .indices()
          .prepareUpdateSettings(INDEX_NAME)
          .setSettings(IndexMetaData.SETTING_NUMBER_OF_REPLICAS + ": 1")
          .get();
      client1
          .admin()
          .cluster()
          .prepareHealth(INDEX_NAME)
          .setWaitForGreenStatus()
          .setTimeout("15m")
          .get();
      long recoveryTime = System.currentTimeMillis() - recoveryStart;
      totalRecoveryTime += recoveryTime;
      recoveryTimes[iteration] = recoveryTime;
      logger.info("--> recovery done in [{}]", new TimeValue(recoveryTime));

      // sleep some to let things clean up
      Thread.sleep(10000);
    }

    long endDocIndexed = indexer.totalIndexedDocs();
    long totalTime = System.currentTimeMillis() - startTime;
    indexer.stop();

    end.set(true);

    backgroundLogger.interrupt();

    backgroundLogger.join();

    logger.info(
        "average doc/s [{}], average relocation time [{}], taking [{}], [{}], [{}]",
        (endDocIndexed - startDocIndexed) * 1000.0 / totalTime,
        new TimeValue(totalRecoveryTime / 3),
        TimeValue.timeValueMillis(recoveryTimes[0]),
        TimeValue.timeValueMillis(recoveryTimes[1]),
        TimeValue.timeValueMillis(recoveryTimes[2]));

    client1.close();
    node1.close();
    node2.close();
  }