/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.benchmark;
import org.elasticsearch.client.benchmark.ops.bulk.BulkBenchmarkTask;
import org.elasticsearch.client.benchmark.ops.bulk.BulkRequestExecutor;
import org.elasticsearch.client.benchmark.ops.search.SearchBenchmarkTask;
import org.elasticsearch.client.benchmark.ops.search.SearchRequestExecutor;
import org.elasticsearch.common.SuppressForbidden;
import java.io.Closeable;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public abstract class AbstractBenchmark<T extends Closeable> {
private static final int SEARCH_BENCHMARK_ITERATIONS = 10_000;
protected abstract T client(String benchmarkTargetHost) throws Exception;
protected abstract BulkRequestExecutor bulkRequestExecutor(T client, String indexName, String typeName);
protected abstract SearchRequestExecutor searchRequestExecutor(T client, String indexName);
@SuppressForbidden(reason = "system out is ok for a command line tool")
public final void run(String[] args) throws Exception {
if (args.length < 1) {
System.err.println("usage: [search|bulk]");
System.exit(1);
}
switch (args[0]) {
case "search":
runSearchBenchmark(args);
break;
case "bulk":
runBulkIndexBenchmark(args);
break;
default:
System.err.println("Unknown benchmark type [" + args[0] + "]");
System.exit(1);
}
}
@SuppressForbidden(reason = "system out is ok for a command line tool")
private void runBulkIndexBenchmark(String[] args) throws Exception {
if (args.length != 7) {
System.err.println(
"usage: 'bulk' benchmarkTargetHostIp indexFilePath indexName typeName numberOfDocuments bulkSize");
System.exit(1);
}
String benchmarkTargetHost = args[1];
String indexFilePath = args[2];
String indexName = args[3];
String typeName = args[4];
int totalDocs = Integer.valueOf(args[5]);
int bulkSize = Integer.valueOf(args[6]);
int totalIterationCount = (int) Math.floor(totalDocs / bulkSize);
// consider 40% of all iterations as warmup iterations
int warmupIterations = (int) (0.4d * totalIterationCount);
int iterations = totalIterationCount - warmupIterations;
T client = client(benchmarkTargetHost);
BenchmarkRunner benchmark = new BenchmarkRunner(warmupIterations, iterations,
new BulkBenchmarkTask(
bulkRequestExecutor(client, indexName, typeName), indexFilePath, warmupIterations, iterations, bulkSize));
try {
runTrials(() -> {
runGc();
benchmark.run();
});
} finally {
client.close();
}
}
@SuppressForbidden(reason = "system out is ok for a command line tool")
private void runSearchBenchmark(String[] args) throws Exception {
if (args.length != 5) {
System.err.println(
"usage: 'search' benchmarkTargetHostIp indexName searchRequestBody throughputRates");
System.exit(1);
}
String benchmarkTargetHost = args[1];
String indexName = args[2];
String searchBody = args[3];
List<Integer> throughputRates = Arrays.asList(args[4].split(",")).stream().map(Integer::valueOf).collect(Collectors.toList());
T client = client(benchmarkTargetHost);
try {
runTrials(() -> {
for (int throughput : throughputRates) {
//GC between trials to reduce the likelihood of a GC occurring in the middle of a trial.
runGc();
BenchmarkRunner benchmark = new BenchmarkRunner(SEARCH_BENCHMARK_ITERATIONS, SEARCH_BENCHMARK_ITERATIONS,
new SearchBenchmarkTask(
searchRequestExecutor(client, indexName), searchBody, SEARCH_BENCHMARK_ITERATIONS,
SEARCH_BENCHMARK_ITERATIONS, throughput));
System.out.printf("Target throughput = %d ops / s%n", throughput);
benchmark.run();
}
});
} finally {
client.close();
}
}
@SuppressForbidden(reason = "system out is ok for a command line tool")
private void runTrials(Runnable runner) {
int totalWarmupTrialRuns = 1;
for (int run = 1; run <= totalWarmupTrialRuns; run++) {
System.out.println("======================");
System.out.println(" Warmup trial run " + run + "/" + totalWarmupTrialRuns);
System.out.println("======================");
runner.run();
}
int totalTrialRuns = 5;
for (int run = 1; run <= totalTrialRuns; run++) {
System.out.println("================");
System.out.println(" Trial run " + run + "/" + totalTrialRuns);
System.out.println("================");
runner.run();
}
}
/**
* Requests a full GC and checks whether the GC did actually run after a request. It retries up to 5 times in case the GC did not
* run in time.
*/
@SuppressForbidden(reason = "we need to request a system GC for the benchmark")
private void runGc() {
long previousCollections = getTotalGcCount();
int attempts = 0;
do {
// request a full GC ...
System.gc();
// ... and give GC a chance to run
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
attempts++;
} while (previousCollections == getTotalGcCount() || attempts < 5);
}
private long getTotalGcCount() {
List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
return gcMxBeans.stream().mapToLong(GarbageCollectorMXBean::getCollectionCount).sum();
}
}