diff --git a/client/benchmark/README.md b/client/benchmark/README.md index 96eac6bf5161..06211b9d8fe8 100644 --- a/client/benchmark/README.md +++ b/client/benchmark/README.md @@ -1,34 +1,53 @@ -Steps to execute the benchmark: +### Steps to execute the benchmark -1. Start Elasticsearch on the target host (ideally *not* on the same machine) -2. Create an empty index with the mapping you want to benchmark -3. Build an uberjar with `gradle :client:benchmark:shadowJar` and execute it. -4. Delete the index -5. Repeat steps 2. - 4. for multiple iterations. The first iterations are intended as warmup for Elasticsearch itself. Always start the same benchmark in step 3! -4. After the benchmark: Shutdown Elasticsearch and delete the data directory +1. Build `client-benchmark-noop-api-plugin` with `gradle :client:client-benchmark-noop-api-plugin:assemble` +2. Install it on the target host with `bin/elasticsearch-plugin install file:///full/path/to/client-benchmark-noop-api-plugin.zip` +3. Start Elasticsearch on the target host (ideally *not* on the same machine) +4. Build an uberjar with `gradle :client:benchmark:shadowJar` and execute it. Repeat all steps above for the other benchmark candidate. -Example benchmark: +### Example benchmark -* Download benchmark data from http://benchmarks.elastic.co/corpora/geonames/documents.json.bz2 and decompress -* Use the mapping file https://github.com/elastic/rally-tracks/blob/master/geonames/mappings.json to create the index +In general, you should define a few GC-related settings `-Xms8192M -Xmx8192M -XX:+UseConcMarkSweepGC -verbose:gc -XX:+PrintGCDetails` and keep an eye on GC activity. You can also define `-XX:+PrintCompilation` to see JIT activity. -Example command line parameter list: +#### Bulk indexing + +Download benchmark data from http://benchmarks.elastic.co/corpora/geonames/documents.json.bz2 and decompress them. + +Example command line parameters: ``` -rest 192.168.2.2 /home/your_user_name/.rally/benchmarks/data/geonames/documents.json geonames type 8647880 5000 "{ \"query\": { \"match_phrase\": { \"name\": \"Sankt Georgen\" } } }\"" +rest bulk 192.168.2.2 ./documents.json geonames type 8647880 5000 ``` The parameters are in order: * Client type: Use either "rest" or "transport" +* Benchmark type: Use either "bulk" or "search" * Benchmark target host IP (the host where Elasticsearch is running) * full path to the file that should be bulk indexed * name of the index * name of the (sole) type in the index * number of documents in the file * bulk size -* a search request body (remember to escape double quotes). The `TransportClientBenchmark` uses `QueryBuilders.wrapperQuery()` internally which automatically adds a root key `query`, so it must not be present in the command line parameter. - -You should also define a few GC-related settings `-Xms4096M -Xmx4096M -XX:+UseConcMarkSweepGC -verbose:gc -XX:+PrintGCDetails` and keep an eye on GC activity. You can also define `-XX:+PrintCompilation` to see JIT activity. + + +#### Bulk indexing + +Example command line parameters: + +``` +rest search 192.168.2.2 geonames "{ \"query\": { \"match_phrase\": { \"name\": \"Sankt Georgen\" } } }\"" 500,1000,1100,1200 +``` + +The parameters are in order: + +* Client type: Use either "rest" or "transport" +* Benchmark type: Use either "bulk" or "search" +* Benchmark target host IP (the host where Elasticsearch is running) +* name of the index +* a search request body (remember to escape double quotes). The `TransportClientBenchmark` uses `QueryBuilders.wrapperQuery()` internally which automatically adds a root key `query`, so it must not be present in the command line parameter. +* A comma-separated list of target throughput rates + + diff --git a/client/benchmark/build.gradle b/client/benchmark/build.gradle index eb0480a92e72..bd4abddbd1dd 100644 --- a/client/benchmark/build.gradle +++ b/client/benchmark/build.gradle @@ -50,6 +50,8 @@ dependencies { compile 'org.apache.commons:commons-math3:3.2' compile("org.elasticsearch.client:rest:${version}") + // bottleneck should be the client, not Elasticsearch + compile project(path: ':client:client-benchmark-noop-api-plugin') // for transport client compile("org.elasticsearch:elasticsearch:${version}") compile("org.elasticsearch.client:transport:${version}") diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/AbstractBenchmark.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/AbstractBenchmark.java index d4608c052ea7..23cb29563b5e 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/AbstractBenchmark.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/AbstractBenchmark.java @@ -27,7 +27,11 @@ 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 { private static final int SEARCH_BENCHMARK_ITERATIONS = 10_000; @@ -40,52 +44,111 @@ public abstract class AbstractBenchmark { @SuppressForbidden(reason = "system out is ok for a command line tool") public final void run(String[] args) throws Exception { - if (args.length < 6) { - System.err.println( - "usage: benchmarkTargetHostIp indexFilePath indexName typeName numberOfDocuments bulkSize [search request body]"); + if (args.length < 1) { + System.err.println("usage: [search|bulk]"); System.exit(1); } - String benchmarkTargetHost = args[0]; - String indexFilePath = args[1]; - String indexName = args[2]; - String typeName = args[3]; - int totalDocs = Integer.valueOf(args[4]); - int bulkSize = Integer.valueOf(args[5]); + 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; - String searchBody = (args.length == 7) ? args[6] : null; T client = client(benchmarkTargetHost); BenchmarkRunner benchmark = new BenchmarkRunner(warmupIterations, iterations, new BulkBenchmarkTask( - bulkRequestExecutor(client, indexName, typeName), indexFilePath, warmupIterations + iterations, bulkSize)); + bulkRequestExecutor(client, indexName, typeName), indexFilePath, warmupIterations, iterations, bulkSize)); try { - benchmark.run(); - if (searchBody != null) { - for (int run = 1; run <= 5; run++) { - System.out.println("============="); - System.out.println(" Trial run " + run); - System.out.println("============="); - - for (int throughput = 100; throughput <= 100_000; throughput *= 10) { - //GC between trials to reduce the likelihood of a GC occurring in the middle of a trial. - runGc(); - BenchmarkRunner searchBenchmark = new BenchmarkRunner(SEARCH_BENCHMARK_ITERATIONS, SEARCH_BENCHMARK_ITERATIONS, - new SearchBenchmarkTask( - searchRequestExecutor(client, indexName), searchBody, 2 * SEARCH_BENCHMARK_ITERATIONS, throughput)); - System.out.printf("Target throughput = %d ops / s%n", throughput); - searchBenchmark.run(); - } - } - } + 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 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(); + } } /** diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkMain.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkMain.java index 55e09fb438b6..317f0bf47977 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkMain.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkMain.java @@ -37,7 +37,7 @@ public class BenchmarkMain { benchmark = new RestClientBenchmark(); break; default: - System.err.println("Unknown benchmark type [" + type + "]"); + System.err.println("Unknown client type [" + type + "]"); System.exit(1); } benchmark.run(Arrays.copyOfRange(args, 1, args.length)); diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkRunner.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkRunner.java index 655b5815f35e..dfb1984f4f03 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkRunner.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/BenchmarkRunner.java @@ -40,8 +40,8 @@ public final class BenchmarkRunner { } @SuppressForbidden(reason = "system out is ok for a command line tool") - public void run() throws Exception { - SampleRecorder recorder = new SampleRecorder(warmupIterations, iterations); + public void run() { + SampleRecorder recorder = new SampleRecorder(iterations); System.out.printf("Running %s with %d warmup iterations and %d iterations.%n", task.getClass().getSimpleName(), warmupIterations, iterations); @@ -52,6 +52,8 @@ public final class BenchmarkRunner { } catch (InterruptedException ex) { Thread.currentThread().interrupt(); return; + } catch (Exception ex) { + throw new RuntimeException(ex); } List samples = recorder.getSamples(); @@ -62,17 +64,24 @@ public final class BenchmarkRunner { } for (Metrics metrics : summaryMetrics) { - System.out.printf(Locale.ROOT, "Operation: %s%n", metrics.operation); - String stats = String.format(Locale.ROOT, - "Throughput = %f ops/s, p90 = %f ms, p95 = %f ms, p99 = %f ms, p99.9 = %f ms, p99.99 = %f ms", - metrics.throughput, - metrics.serviceTimeP90, metrics.serviceTimeP95, - metrics.serviceTimeP99, metrics.serviceTimeP999, - metrics.serviceTimeP9999); - System.out.println(repeat(stats.length(), '-')); - System.out.println(stats); + String throughput = String.format(Locale.ROOT, "Throughput [ops/s]: %f", metrics.throughput); + String serviceTimes = String.format(Locale.ROOT, + "Service time [ms]: p50 = %f, p90 = %f, p95 = %f, p99 = %f, p99.9 = %f, p99.99 = %f", + metrics.serviceTimeP50, metrics.serviceTimeP90, metrics.serviceTimeP95, + metrics.serviceTimeP99, metrics.serviceTimeP999, metrics.serviceTimeP9999); + String latencies = String.format(Locale.ROOT, + "Latency [ms]: p50 = %f, p90 = %f, p95 = %f, p99 = %f, p99.9 = %f, p99.99 = %f", + metrics.latencyP50, metrics.latencyP90, metrics.latencyP95, + metrics.latencyP99, metrics.latencyP999, metrics.latencyP9999); + + int lineLength = Math.max(serviceTimes.length(), latencies.length()); + + System.out.println(repeat(lineLength, '-')); + System.out.println(throughput); + System.out.println(serviceTimes); + System.out.println(latencies); System.out.printf("success count = %d, error count = %d%n", metrics.successCount, metrics.errorCount); - System.out.println(repeat(stats.length(), '-')); + System.out.println(repeat(lineLength, '-')); } } diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Metrics.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Metrics.java index 9108afe44462..e099c531db15 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Metrics.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Metrics.java @@ -23,23 +23,38 @@ public final class Metrics { public final long successCount; public final long errorCount; public final double throughput; + public final double serviceTimeP50; public final double serviceTimeP90; public final double serviceTimeP95; public final double serviceTimeP99; public final double serviceTimeP999; public final double serviceTimeP9999; + public final double latencyP50; + public final double latencyP90; + public final double latencyP95; + public final double latencyP99; + public final double latencyP999; + public final double latencyP9999; public Metrics(String operation, long successCount, long errorCount, double throughput, - double serviceTimeP90, double serviceTimeP95, double serviceTimeP99, - double serviceTimeP999, double serviceTimeP9999) { + double serviceTimeP50, double serviceTimeP90, double serviceTimeP95, double serviceTimeP99, + double serviceTimeP999, double serviceTimeP9999, double latencyP50, double latencyP90, + double latencyP95, double latencyP99, double latencyP999, double latencyP9999) { this.operation = operation; this.successCount = successCount; this.errorCount = errorCount; this.throughput = throughput; + this.serviceTimeP50 = serviceTimeP50; this.serviceTimeP90 = serviceTimeP90; this.serviceTimeP95 = serviceTimeP95; this.serviceTimeP99 = serviceTimeP99; this.serviceTimeP999 = serviceTimeP999; this.serviceTimeP9999 = serviceTimeP9999; + this.latencyP50 = latencyP50; + this.latencyP90 = latencyP90; + this.latencyP95 = latencyP95; + this.latencyP99 = latencyP99; + this.latencyP999 = latencyP999; + this.latencyP9999 = latencyP9999; } } diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/MetricsCalculator.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/MetricsCalculator.java index 5b455127f52f..a0be3d901d3c 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/MetricsCalculator.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/MetricsCalculator.java @@ -50,13 +50,16 @@ public final class MetricsCalculator { for (Map.Entry> operationAndMetrics : samplesPerOperation.entrySet()) { List samples = operationAndMetrics.getValue(); double[] serviceTimes = new double[samples.size()]; + double[] latencies = new double[samples.size()]; int it = 0; long firstStart = Long.MAX_VALUE; long latestEnd = Long.MIN_VALUE; for (Sample sample : samples) { firstStart = Math.min(sample.getStartTimestamp(), firstStart); latestEnd = Math.max(sample.getStopTimestamp(), latestEnd); - serviceTimes[it++] = sample.getServiceTime(); + serviceTimes[it] = sample.getServiceTime(); + latencies[it] = sample.getLatency(); + it++; } metrics.add(new Metrics(operationAndMetrics.getKey(), @@ -65,11 +68,18 @@ public final class MetricsCalculator { // throughput calculation is based on the total (Wall clock) time it took to generate all samples calculateThroughput(samples.size(), latestEnd - firstStart), // convert ns -> ms without losing precision + StatUtils.percentile(serviceTimes, 50.0d) / TimeUnit.MILLISECONDS.toNanos(1L), StatUtils.percentile(serviceTimes, 90.0d) / TimeUnit.MILLISECONDS.toNanos(1L), StatUtils.percentile(serviceTimes, 95.0d) / TimeUnit.MILLISECONDS.toNanos(1L), StatUtils.percentile(serviceTimes, 99.0d) / TimeUnit.MILLISECONDS.toNanos(1L), StatUtils.percentile(serviceTimes, 99.9d) / TimeUnit.MILLISECONDS.toNanos(1L), - StatUtils.percentile(serviceTimes, 99.99d) / TimeUnit.MILLISECONDS.toNanos(1L))); + StatUtils.percentile(serviceTimes, 99.99d) / TimeUnit.MILLISECONDS.toNanos(1L), + StatUtils.percentile(latencies, 50.0d) / TimeUnit.MILLISECONDS.toNanos(1L), + StatUtils.percentile(latencies, 90.0d) / TimeUnit.MILLISECONDS.toNanos(1L), + StatUtils.percentile(latencies, 95.0d) / TimeUnit.MILLISECONDS.toNanos(1L), + StatUtils.percentile(latencies, 99.0d) / TimeUnit.MILLISECONDS.toNanos(1L), + StatUtils.percentile(latencies, 99.9d) / TimeUnit.MILLISECONDS.toNanos(1L), + StatUtils.percentile(latencies, 99.99d) / TimeUnit.MILLISECONDS.toNanos(1L))); } return metrics; } diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Sample.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Sample.java index 59cd6bfd101a..114baa553345 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Sample.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/Sample.java @@ -20,12 +20,14 @@ package org.elasticsearch.client.benchmark.metrics; public final class Sample { private final String operation; + private final long expectedStartTimestamp; private final long startTimestamp; private final long stopTimestamp; private final boolean success; - public Sample(String operation, long startTimestamp, long stopTimestamp, boolean success) { + public Sample(String operation, long expectedStartTimestamp, long startTimestamp, long stopTimestamp, boolean success) { this.operation = operation; + this.expectedStartTimestamp = expectedStartTimestamp; this.startTimestamp = startTimestamp; this.stopTimestamp = stopTimestamp; this.success = success; @@ -48,7 +50,10 @@ public final class Sample { } public long getServiceTime() { - // this is *not* latency, we're not including wait time in the queue (on purpose) return stopTimestamp - startTimestamp; } + + public long getLatency() { + return stopTimestamp - expectedStartTimestamp; + } } diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/SampleRecorder.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/SampleRecorder.java index d9f24aea004d..63e1627f0451 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/SampleRecorder.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/metrics/SampleRecorder.java @@ -28,21 +28,14 @@ import java.util.List; * This class is NOT threadsafe. */ public final class SampleRecorder { - private final int warmupIterations; private final List samples; - private int currentIteration; - public SampleRecorder(int warmupIterations, int iterations) { - this.warmupIterations = warmupIterations; + public SampleRecorder(int iterations) { this.samples = new ArrayList<>(iterations); } public void addSample(Sample sample) { - currentIteration++; - // only add samples after warmup - if (currentIteration > warmupIterations) { - samples.add(sample); - } + samples.add(sample); } public List getSamples() { diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/bulk/BulkBenchmarkTask.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/bulk/BulkBenchmarkTask.java index 5844103fd1eb..daf7213ed511 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/bulk/BulkBenchmarkTask.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/bulk/BulkBenchmarkTask.java @@ -43,15 +43,18 @@ import java.util.concurrent.TimeUnit; public class BulkBenchmarkTask implements BenchmarkTask { private final BulkRequestExecutor requestExecutor; private final String indexFilePath; - private final int totalIterations; + private final int warmupIterations; + private final int measurementIterations; private final int bulkSize; private LoadGenerator generator; private ExecutorService executorService; - public BulkBenchmarkTask(BulkRequestExecutor requestExecutor, String indexFilePath, int totalIterations, int bulkSize) { + public BulkBenchmarkTask(BulkRequestExecutor requestExecutor, String indexFilePath, int warmupIterations, int measurementIterations, + int bulkSize) { this.requestExecutor = requestExecutor; this.indexFilePath = indexFilePath; - this.totalIterations = totalIterations; + this.warmupIterations = warmupIterations; + this.measurementIterations = measurementIterations; this.bulkSize = bulkSize; } @@ -60,7 +63,7 @@ public class BulkBenchmarkTask implements BenchmarkTask { public void setUp(SampleRecorder sampleRecorder) { BlockingQueue> bulkQueue = new ArrayBlockingQueue<>(256); - BulkIndexer runner = new BulkIndexer(bulkQueue, totalIterations, sampleRecorder, requestExecutor); + BulkIndexer runner = new BulkIndexer(bulkQueue, warmupIterations, measurementIterations, sampleRecorder, requestExecutor); executorService = Executors.newSingleThreadExecutor((r) -> new Thread(r, "bulk-index-runner")); executorService.submit(runner); @@ -135,21 +138,23 @@ public class BulkBenchmarkTask implements BenchmarkTask { private static final ESLogger logger = ESLoggerFactory.getLogger(BulkIndexer.class.getName()); private final BlockingQueue> bulkData; - private final int totalIterations; + private final int warmupIterations; + private final int measurementIterations; private final BulkRequestExecutor bulkRequestExecutor; private final SampleRecorder sampleRecorder; - public BulkIndexer(BlockingQueue> bulkData, int totalIterations, SampleRecorder sampleRecorder, - BulkRequestExecutor bulkRequestExecutor) { + public BulkIndexer(BlockingQueue> bulkData, int warmupIterations, int measurementIterations, + SampleRecorder sampleRecorder, BulkRequestExecutor bulkRequestExecutor) { this.bulkData = bulkData; - this.totalIterations = totalIterations; + this.warmupIterations = warmupIterations; + this.measurementIterations = measurementIterations; this.bulkRequestExecutor = bulkRequestExecutor; this.sampleRecorder = sampleRecorder; } @Override public void run() { - for (int iteration = 0; iteration < totalIterations; iteration++) { + for (int iteration = 0; iteration < warmupIterations + measurementIterations; iteration++) { boolean success = false; List currentBulk; try { @@ -158,8 +163,7 @@ public class BulkBenchmarkTask implements BenchmarkTask { Thread.currentThread().interrupt(); return; } - // Yes, this approach is prone to coordinated omission *but* we have to consider that we want to benchmark a closed system - // with backpressure here instead of an open system. So this is actually correct in this case. + //measure only service time, latency is not that interesting for a throughput benchmark long start = System.nanoTime(); try { success = bulkRequestExecutor.bulkIndex(currentBulk); @@ -167,7 +171,9 @@ public class BulkBenchmarkTask implements BenchmarkTask { logger.warn("Error while executing bulk request", ex); } long stop = System.nanoTime(); - sampleRecorder.addSample(new Sample("bulk", start, stop, success)); + if (iteration < warmupIterations) { + sampleRecorder.addSample(new Sample("bulk", start, start, stop, success)); + } } } } diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/search/SearchBenchmarkTask.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/search/SearchBenchmarkTask.java index a71221610b2d..4f370a520af1 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/search/SearchBenchmarkTask.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/ops/search/SearchBenchmarkTask.java @@ -25,20 +25,20 @@ import org.elasticsearch.client.benchmark.metrics.SampleRecorder; import java.util.concurrent.TimeUnit; public class SearchBenchmarkTask implements BenchmarkTask { - private static final long MICROS_PER_SEC = TimeUnit.SECONDS.toMicros(1L); - private static final long NANOS_PER_MICRO = TimeUnit.MICROSECONDS.toNanos(1L); - private final SearchRequestExecutor searchRequestExecutor; private final String searchRequestBody; - private final int iterations; + private final int warmupIterations; + private final int measurementIterations; private final int targetThroughput; private SampleRecorder sampleRecorder; - public SearchBenchmarkTask(SearchRequestExecutor searchRequestExecutor, String body, int iterations, int targetThroughput) { + public SearchBenchmarkTask(SearchRequestExecutor searchRequestExecutor, String body, int warmupIterations, + int measurementIterations, int targetThroughput) { this.searchRequestExecutor = searchRequestExecutor; this.searchRequestBody = body; - this.iterations = iterations; + this.warmupIterations = warmupIterations; + this.measurementIterations = measurementIterations; this.targetThroughput = targetThroughput; } @@ -49,28 +49,25 @@ public class SearchBenchmarkTask implements BenchmarkTask { @Override public void run() throws Exception { - for (int iteration = 0; iteration < this.iterations; iteration++) { - final long start = System.nanoTime(); - boolean success = searchRequestExecutor.search(searchRequestBody); - final long stop = System.nanoTime(); - sampleRecorder.addSample(new Sample("search", start, stop, success)); - - int waitTime = (int) Math.floor(MICROS_PER_SEC / targetThroughput - (stop - start) / NANOS_PER_MICRO); - if (waitTime > 0) { - waitMicros(waitTime); - } - } + runIterations(warmupIterations, false); + runIterations(measurementIterations, true); } - private void waitMicros(int waitTime) throws InterruptedException { - // Thread.sleep() time is not very accurate (it's most of the time around 1 - 2 ms off) - // we busy spin all the time to avoid introducing additional measurement artifacts (noticed 100% skew on 99.9th percentile) - // this approach is not suitable for low throughput rates (in the second range) though - if (waitTime > 0) { - long end = System.nanoTime() + 1000L * waitTime; - while (end > System.nanoTime()) { + private void runIterations(int iterations, boolean addSample) { + long interval = TimeUnit.SECONDS.toNanos(1L) / targetThroughput; + + long totalStart = System.nanoTime(); + for (int iteration = 0; iteration < iterations; iteration++) { + long expectedStart = totalStart + iteration * interval; + while (System.nanoTime() < expectedStart) { // busy spin } + long start = System.nanoTime(); + boolean success = searchRequestExecutor.search(searchRequestBody); + long stop = System.nanoTime(); + if (addSample) { + sampleRecorder.addSample(new Sample("search", expectedStart, start, stop, success)); + } } } diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/rest/RestClientBenchmark.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/rest/RestClientBenchmark.java index bf661fa661fd..b342d93fba5a 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/rest/RestClientBenchmark.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/rest/RestClientBenchmark.java @@ -19,14 +19,20 @@ package org.elasticsearch.client.benchmark.rest; import org.apache.http.HttpEntity; +import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.apache.http.message.BasicHeader; import org.apache.http.nio.entity.NStringEntity; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.benchmark.AbstractBenchmark; import org.elasticsearch.client.benchmark.ops.bulk.BulkRequestExecutor; import org.elasticsearch.client.benchmark.ops.search.SearchRequestExecutor; @@ -45,7 +51,12 @@ public final class RestClientBenchmark extends AbstractBenchmark { @Override protected RestClient client(String benchmarkTargetHost) { - return RestClient.builder(new HttpHost(benchmarkTargetHost, 9200)).build(); + return RestClient + .builder(new HttpHost(benchmarkTargetHost, 9200)) + .setHttpClientConfigCallback(b -> b.setDefaultHeaders( + Collections.singleton(new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "gzip")))) + .setRequestConfigCallback(b -> b.setContentCompressionEnabled(true)) + .build(); } @Override @@ -77,7 +88,7 @@ public final class RestClientBenchmark extends AbstractBenchmark { } HttpEntity entity = new NStringEntity(bulkRequestBody.toString(), ContentType.APPLICATION_JSON); try { - Response response = client.performRequest("POST", "/geonames/type/_bulk", Collections.emptyMap(), entity); + Response response = client.performRequest("POST", "/geonames/type/_noop_bulk", Collections.emptyMap(), entity); return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK; } catch (Exception e) { throw new ElasticsearchException(e); @@ -91,7 +102,7 @@ public final class RestClientBenchmark extends AbstractBenchmark { private RestSearchRequestExecutor(RestClient client, String indexName) { this.client = client; - this.endpoint = "/" + indexName + "/_search"; + this.endpoint = "/" + indexName + "/_noop_search"; } @Override diff --git a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/transport/TransportClientBenchmark.java b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/transport/TransportClientBenchmark.java index c52414cf3a42..c38234ef3024 100644 --- a/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/transport/TransportClientBenchmark.java +++ b/client/benchmark/src/main/java/org/elasticsearch/client/benchmark/transport/TransportClientBenchmark.java @@ -19,7 +19,6 @@ package org.elasticsearch.client.benchmark.transport; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchResponse; @@ -30,6 +29,11 @@ import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.plugin.noop.NoopPlugin; +import org.elasticsearch.plugin.noop.action.bulk.NoopBulkAction; +import org.elasticsearch.plugin.noop.action.bulk.NoopBulkRequestBuilder; +import org.elasticsearch.plugin.noop.action.search.NoopSearchAction; +import org.elasticsearch.plugin.noop.action.search.NoopSearchRequestBuilder; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.transport.client.PreBuiltTransportClient; @@ -46,7 +50,7 @@ public final class TransportClientBenchmark extends AbstractBenchmark bulkData) { - BulkRequestBuilder builder = client.prepareBulk(); + NoopBulkRequestBuilder builder = NoopBulkAction.INSTANCE.newRequestBuilder(client); for (String bulkItem : bulkData) { builder.add(new IndexRequest(indexName, typeName).source(bulkItem.getBytes(StandardCharsets.UTF_8))); } @@ -103,8 +107,11 @@ public final class TransportClientBenchmark extends AbstractBenchmark, ? extends ActionResponse>> getActions() { + return Arrays.asList( + new ActionHandler<>(NoopBulkAction.INSTANCE, TransportNoopBulkAction.class), + new ActionHandler<>(NoopSearchAction.INSTANCE, TransportNoopSearchAction.class) + ); + } + + @Override + public List> getRestHandlers() { + return Arrays.asList(RestNoopBulkAction.class, RestNoopSearchAction.class); + } +} diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/NoopBulkAction.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/NoopBulkAction.java new file mode 100644 index 000000000000..7f5ec6edd8e4 --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/NoopBulkAction.java @@ -0,0 +1,44 @@ +/* + * 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.plugin.noop.action.bulk; + +import org.elasticsearch.action.Action; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.client.ElasticsearchClient; + +public class NoopBulkAction extends Action { + public static final String NAME = "mock:data/write/bulk"; + + public static final NoopBulkAction INSTANCE = new NoopBulkAction(); + + private NoopBulkAction() { + super(NAME); + } + + @Override + public NoopBulkRequestBuilder newRequestBuilder(ElasticsearchClient client) { + return new NoopBulkRequestBuilder(client, this); + } + + @Override + public BulkResponse newResponse() { + return new BulkResponse(null, 0); + } +} diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/NoopBulkRequestBuilder.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/NoopBulkRequestBuilder.java new file mode 100644 index 000000000000..ceaf9f8cc9d1 --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/NoopBulkRequestBuilder.java @@ -0,0 +1,153 @@ +/* + * 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.plugin.noop.action.bulk; + +import org.elasticsearch.action.ActionRequestBuilder; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.delete.DeleteRequestBuilder; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.action.support.WriteRequestBuilder; +import org.elasticsearch.action.support.replication.ReplicationRequest; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.action.update.UpdateRequestBuilder; +import org.elasticsearch.client.ElasticsearchClient; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.unit.TimeValue; + +public class NoopBulkRequestBuilder extends ActionRequestBuilder + implements WriteRequestBuilder { + + public NoopBulkRequestBuilder(ElasticsearchClient client, NoopBulkAction action) { + super(client, action, new BulkRequest()); + } + + /** + * Adds an {@link IndexRequest} to the list of actions to execute. Follows the same behavior of {@link IndexRequest} + * (for example, if no id is provided, one will be generated, or usage of the create flag). + */ + public NoopBulkRequestBuilder add(IndexRequest request) { + super.request.add(request); + return this; + } + + /** + * Adds an {@link IndexRequest} to the list of actions to execute. Follows the same behavior of {@link IndexRequest} + * (for example, if no id is provided, one will be generated, or usage of the create flag). + */ + public NoopBulkRequestBuilder add(IndexRequestBuilder request) { + super.request.add(request.request()); + return this; + } + + /** + * Adds an {@link DeleteRequest} to the list of actions to execute. + */ + public NoopBulkRequestBuilder add(DeleteRequest request) { + super.request.add(request); + return this; + } + + /** + * Adds an {@link DeleteRequest} to the list of actions to execute. + */ + public NoopBulkRequestBuilder add(DeleteRequestBuilder request) { + super.request.add(request.request()); + return this; + } + + + /** + * Adds an {@link UpdateRequest} to the list of actions to execute. + */ + public NoopBulkRequestBuilder add(UpdateRequest request) { + super.request.add(request); + return this; + } + + /** + * Adds an {@link UpdateRequest} to the list of actions to execute. + */ + public NoopBulkRequestBuilder add(UpdateRequestBuilder request) { + super.request.add(request.request()); + return this; + } + + /** + * Adds a framed data in binary format + */ + public NoopBulkRequestBuilder add(byte[] data, int from, int length) throws Exception { + request.add(data, from, length, null, null); + return this; + } + + /** + * Adds a framed data in binary format + */ + public NoopBulkRequestBuilder add(byte[] data, int from, int length, @Nullable String defaultIndex, @Nullable String defaultType) + throws Exception { + request.add(data, from, length, defaultIndex, defaultType); + return this; + } + + /** + * Sets the number of shard copies that must be active before proceeding with the write. + * See {@link ReplicationRequest#waitForActiveShards(ActiveShardCount)} for details. + */ + public NoopBulkRequestBuilder setWaitForActiveShards(ActiveShardCount waitForActiveShards) { + request.waitForActiveShards(waitForActiveShards); + return this; + } + + /** + * A shortcut for {@link #setWaitForActiveShards(ActiveShardCount)} where the numerical + * shard count is passed in, instead of having to first call {@link ActiveShardCount#from(int)} + * to get the ActiveShardCount. + */ + public NoopBulkRequestBuilder setWaitForActiveShards(final int waitForActiveShards) { + return setWaitForActiveShards(ActiveShardCount.from(waitForActiveShards)); + } + + /** + * A timeout to wait if the index operation can't be performed immediately. Defaults to 1m. + */ + public final NoopBulkRequestBuilder setTimeout(TimeValue timeout) { + request.timeout(timeout); + return this; + } + + /** + * A timeout to wait if the index operation can't be performed immediately. Defaults to 1m. + */ + public final NoopBulkRequestBuilder setTimeout(String timeout) { + request.timeout(timeout); + return this; + } + + /** + * The number of actions currently in the bulk. + */ + public int numberOfActions() { + return request.numberOfActions(); + } +} + diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/RestNoopBulkAction.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/RestNoopBulkAction.java new file mode 100644 index 000000000000..814c05889b2b --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/RestNoopBulkAction.java @@ -0,0 +1,117 @@ +/* + * 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.plugin.noop.action.bulk; + +import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.action.bulk.BulkItemResponse; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkShardRequest; +import org.elasticsearch.action.support.ActiveShardCount; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.Requests; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.BytesRestResponse; +import org.elasticsearch.rest.RestChannel; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestResponse; +import org.elasticsearch.rest.action.RestBuilderListener; + +import static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestRequest.Method.PUT; +import static org.elasticsearch.rest.RestStatus.OK; + +public class RestNoopBulkAction extends BaseRestHandler { + @Inject + public RestNoopBulkAction(Settings settings, RestController controller) { + super(settings); + + controller.registerHandler(POST, "/_noop_bulk", this); + controller.registerHandler(PUT, "/_noop_bulk", this); + controller.registerHandler(POST, "/{index}/_noop_bulk", this); + controller.registerHandler(PUT, "/{index}/_noop_bulk", this); + controller.registerHandler(POST, "/{index}/{type}/_noop_bulk", this); + controller.registerHandler(PUT, "/{index}/{type}/_noop_bulk", this); + } + + @Override + public void handleRequest(final RestRequest request, final RestChannel channel, final NodeClient client) throws Exception { + BulkRequest bulkRequest = Requests.bulkRequest(); + String defaultIndex = request.param("index"); + String defaultType = request.param("type"); + String defaultRouting = request.param("routing"); + String fieldsParam = request.param("fields"); + String defaultPipeline = request.param("pipeline"); + String[] defaultFields = fieldsParam != null ? Strings.commaDelimitedListToStringArray(fieldsParam) : null; + + String waitForActiveShards = request.param("wait_for_active_shards"); + if (waitForActiveShards != null) { + bulkRequest.waitForActiveShards(ActiveShardCount.parseString(waitForActiveShards)); + } + bulkRequest.timeout(request.paramAsTime("timeout", BulkShardRequest.DEFAULT_TIMEOUT)); + bulkRequest.setRefreshPolicy(request.param("refresh")); + bulkRequest.add(request.content(), defaultIndex, defaultType, defaultRouting, defaultFields, defaultPipeline, null, true); + + // short circuit the call to the transport layer + BulkRestBuilderListener listener = new BulkRestBuilderListener(channel, request); + listener.onResponse(bulkRequest); + + } + + private static class BulkRestBuilderListener extends RestBuilderListener { + private final BulkItemResponse ITEM_RESPONSE = new BulkItemResponse(1, "update", + new UpdateResponse(new ShardId("mock", "", 1), "mock_type", "1", 1L, DocWriteResponse.Result.CREATED)); + + private final RestRequest request; + + + public BulkRestBuilderListener(RestChannel channel, RestRequest request) { + super(channel); + this.request = request; + } + + @Override + public RestResponse buildResponse(BulkRequest bulkRequest, XContentBuilder builder) throws Exception { + builder.startObject(); + builder.field(Fields.TOOK, 0); + builder.field(Fields.ERRORS, false); + builder.startArray(Fields.ITEMS); + for (int idx = 0; idx < bulkRequest.numberOfActions(); idx++) { + builder.startObject(); + ITEM_RESPONSE.toXContent(builder, request); + builder.endObject(); + } + builder.endArray(); + builder.endObject(); + return new BytesRestResponse(OK, builder); + } + } + + static final class Fields { + static final String ITEMS = "items"; + static final String ERRORS = "errors"; + static final String TOOK = "took"; + } +} diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/TransportNoopBulkAction.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/TransportNoopBulkAction.java new file mode 100644 index 000000000000..dcc225c26030 --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/bulk/TransportNoopBulkAction.java @@ -0,0 +1,56 @@ +/* + * 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.plugin.noop.action.bulk; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.action.bulk.BulkItemResponse; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +public class TransportNoopBulkAction extends HandledTransportAction { + private static final BulkItemResponse ITEM_RESPONSE = new BulkItemResponse(1, "update", + new UpdateResponse(new ShardId("mock", "", 1), "mock_type", "1", 1L, DocWriteResponse.Result.CREATED)); + + @Inject + public TransportNoopBulkAction(Settings settings, ThreadPool threadPool, TransportService transportService, + ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { + super(settings, NoopBulkAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, BulkRequest::new); + } + + @Override + protected void doExecute(BulkRequest request, ActionListener listener) { + final int itemCount = request.subRequests().size(); + // simulate at least a realistic amount of data that gets serialized + BulkItemResponse[] bulkItemResponses = new BulkItemResponse[itemCount]; + for (int idx = 0; idx < itemCount; idx++) { + bulkItemResponses[idx] = ITEM_RESPONSE; + } + listener.onResponse(new BulkResponse(bulkItemResponses, 0)); + } +} diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/NoopSearchAction.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/NoopSearchAction.java new file mode 100644 index 000000000000..b24190b6946d --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/NoopSearchAction.java @@ -0,0 +1,43 @@ +/* + * 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.plugin.noop.action.search; + +import org.elasticsearch.action.Action; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.ElasticsearchClient; + +public class NoopSearchAction extends Action { + public static final NoopSearchAction INSTANCE = new NoopSearchAction(); + public static final String NAME = "mock:data/read/search"; + + public NoopSearchAction() { + super(NAME); + } + + @Override + public NoopSearchRequestBuilder newRequestBuilder(ElasticsearchClient client) { + return new NoopSearchRequestBuilder(client, this); + } + + @Override + public SearchResponse newResponse() { + return new SearchResponse(); + } +} diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/NoopSearchRequestBuilder.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/NoopSearchRequestBuilder.java new file mode 100644 index 000000000000..eec9254cc3ab --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/NoopSearchRequestBuilder.java @@ -0,0 +1,504 @@ +/* + * 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.plugin.noop.action.search; + +import org.elasticsearch.action.ActionRequestBuilder; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.client.ElasticsearchClient; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.script.Script; +import org.elasticsearch.search.Scroll; +import org.elasticsearch.search.aggregations.AggregationBuilder; +import org.elasticsearch.search.aggregations.PipelineAggregationBuilder; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; +import org.elasticsearch.search.rescore.RescoreBuilder; +import org.elasticsearch.search.slice.SliceBuilder; +import org.elasticsearch.search.sort.SortBuilder; +import org.elasticsearch.search.sort.SortOrder; +import org.elasticsearch.search.suggest.SuggestBuilder; + +import java.util.Arrays; +import java.util.List; + +public class NoopSearchRequestBuilder extends ActionRequestBuilder { + + public NoopSearchRequestBuilder(ElasticsearchClient client, NoopSearchAction action) { + super(client, action, new SearchRequest()); + } + + /** + * Sets the indices the search will be executed on. + */ + public NoopSearchRequestBuilder setIndices(String... indices) { + request.indices(indices); + return this; + } + + /** + * The document types to execute the search against. Defaults to be executed against + * all types. + */ + public NoopSearchRequestBuilder setTypes(String... types) { + request.types(types); + return this; + } + + /** + * The search type to execute, defaults to {@link org.elasticsearch.action.search.SearchType#DEFAULT}. + */ + public NoopSearchRequestBuilder setSearchType(SearchType searchType) { + request.searchType(searchType); + return this; + } + + /** + * The a string representation search type to execute, defaults to {@link org.elasticsearch.action.search.SearchType#DEFAULT}. Can be + * one of "dfs_query_then_fetch"/"dfsQueryThenFetch", "dfs_query_and_fetch"/"dfsQueryAndFetch", + * "query_then_fetch"/"queryThenFetch", and "query_and_fetch"/"queryAndFetch". + */ + public NoopSearchRequestBuilder setSearchType(String searchType) { + request.searchType(searchType); + return this; + } + + /** + * If set, will enable scrolling of the search request. + */ + public NoopSearchRequestBuilder setScroll(Scroll scroll) { + request.scroll(scroll); + return this; + } + + /** + * If set, will enable scrolling of the search request for the specified timeout. + */ + public NoopSearchRequestBuilder setScroll(TimeValue keepAlive) { + request.scroll(keepAlive); + return this; + } + + /** + * If set, will enable scrolling of the search request for the specified timeout. + */ + public NoopSearchRequestBuilder setScroll(String keepAlive) { + request.scroll(keepAlive); + return this; + } + + /** + * An optional timeout to control how long search is allowed to take. + */ + public NoopSearchRequestBuilder setTimeout(TimeValue timeout) { + sourceBuilder().timeout(timeout); + return this; + } + + /** + * An optional document count, upon collecting which the search + * query will early terminate + */ + public NoopSearchRequestBuilder setTerminateAfter(int terminateAfter) { + sourceBuilder().terminateAfter(terminateAfter); + return this; + } + + /** + * A comma separated list of routing values to control the shards the search will be executed on. + */ + public NoopSearchRequestBuilder setRouting(String routing) { + request.routing(routing); + return this; + } + + /** + * The routing values to control the shards that the search will be executed on. + */ + public NoopSearchRequestBuilder setRouting(String... routing) { + request.routing(routing); + return this; + } + + /** + * Sets the preference to execute the search. Defaults to randomize across shards. Can be set to + * _local to prefer local shards, _primary to execute only on primary shards, or + * a custom value, which guarantees that the same order will be used across different requests. + */ + public NoopSearchRequestBuilder setPreference(String preference) { + request.preference(preference); + return this; + } + + /** + * Specifies what type of requested indices to ignore and wildcard indices expressions. + *

+ * For example indices that don't exist. + */ + public NoopSearchRequestBuilder setIndicesOptions(IndicesOptions indicesOptions) { + request().indicesOptions(indicesOptions); + return this; + } + + /** + * Constructs a new search source builder with a search query. + * + * @see org.elasticsearch.index.query.QueryBuilders + */ + public NoopSearchRequestBuilder setQuery(QueryBuilder queryBuilder) { + sourceBuilder().query(queryBuilder); + return this; + } + + /** + * Sets a filter that will be executed after the query has been executed and only has affect on the search hits + * (not aggregations). This filter is always executed as last filtering mechanism. + */ + public NoopSearchRequestBuilder setPostFilter(QueryBuilder postFilter) { + sourceBuilder().postFilter(postFilter); + return this; + } + + /** + * Sets the minimum score below which docs will be filtered out. + */ + public NoopSearchRequestBuilder setMinScore(float minScore) { + sourceBuilder().minScore(minScore); + return this; + } + + /** + * From index to start the search from. Defaults to 0. + */ + public NoopSearchRequestBuilder setFrom(int from) { + sourceBuilder().from(from); + return this; + } + + /** + * The number of search hits to return. Defaults to 10. + */ + public NoopSearchRequestBuilder setSize(int size) { + sourceBuilder().size(size); + return this; + } + + /** + * Should each {@link org.elasticsearch.search.SearchHit} be returned with an + * explanation of the hit (ranking). + */ + public NoopSearchRequestBuilder setExplain(boolean explain) { + sourceBuilder().explain(explain); + return this; + } + + /** + * Should each {@link org.elasticsearch.search.SearchHit} be returned with its + * version. + */ + public NoopSearchRequestBuilder setVersion(boolean version) { + sourceBuilder().version(version); + return this; + } + + /** + * Sets the boost a specific index will receive when the query is executed against it. + * + * @param index The index to apply the boost against + * @param indexBoost The boost to apply to the index + */ + public NoopSearchRequestBuilder addIndexBoost(String index, float indexBoost) { + sourceBuilder().indexBoost(index, indexBoost); + return this; + } + + /** + * The stats groups this request will be aggregated under. + */ + public NoopSearchRequestBuilder setStats(String... statsGroups) { + sourceBuilder().stats(Arrays.asList(statsGroups)); + return this; + } + + /** + * The stats groups this request will be aggregated under. + */ + public NoopSearchRequestBuilder setStats(List statsGroups) { + sourceBuilder().stats(statsGroups); + return this; + } + + /** + * Sets no fields to be loaded, resulting in only id and type to be returned per field. + */ + public NoopSearchRequestBuilder setNoStoredFields() { + sourceBuilder().noStoredFields(); + return this; + } + + /** + * Indicates whether the response should contain the stored _source for every hit + */ + public NoopSearchRequestBuilder setFetchSource(boolean fetch) { + sourceBuilder().fetchSource(fetch); + return this; + } + + /** + * Indicate that _source should be returned with every hit, with an "include" and/or "exclude" set which can include simple wildcard + * elements. + * + * @param include An optional include (optionally wildcarded) pattern to filter the returned _source + * @param exclude An optional exclude (optionally wildcarded) pattern to filter the returned _source + */ + public NoopSearchRequestBuilder setFetchSource(@Nullable String include, @Nullable String exclude) { + sourceBuilder().fetchSource(include, exclude); + return this; + } + + /** + * Indicate that _source should be returned with every hit, with an "include" and/or "exclude" set which can include simple wildcard + * elements. + * + * @param includes An optional list of include (optionally wildcarded) pattern to filter the returned _source + * @param excludes An optional list of exclude (optionally wildcarded) pattern to filter the returned _source + */ + public NoopSearchRequestBuilder setFetchSource(@Nullable String[] includes, @Nullable String[] excludes) { + sourceBuilder().fetchSource(includes, excludes); + return this; + } + + /** + * Adds a docvalue based field to load and return. The field does not have to be stored, + * but its recommended to use non analyzed or numeric fields. + * + * @param name The field to get from the docvalue + */ + public NoopSearchRequestBuilder addDocValueField(String name) { + sourceBuilder().docValueField(name); + return this; + } + + /** + * Adds a stored field to load and return (note, it must be stored) as part of the search request. + * If none are specified, the source of the document will be return. + */ + public NoopSearchRequestBuilder addStoredField(String field) { + sourceBuilder().storedField(field); + return this; + } + + + /** + * Adds a script based field to load and return. The field does not have to be stored, + * but its recommended to use non analyzed or numeric fields. + * + * @param name The name that will represent this value in the return hit + * @param script The script to use + */ + public NoopSearchRequestBuilder addScriptField(String name, Script script) { + sourceBuilder().scriptField(name, script); + return this; + } + + /** + * Adds a sort against the given field name and the sort ordering. + * + * @param field The name of the field + * @param order The sort ordering + */ + public NoopSearchRequestBuilder addSort(String field, SortOrder order) { + sourceBuilder().sort(field, order); + return this; + } + + /** + * Adds a generic sort builder. + * + * @see org.elasticsearch.search.sort.SortBuilders + */ + public NoopSearchRequestBuilder addSort(SortBuilder sort) { + sourceBuilder().sort(sort); + return this; + } + + /** + * Set the sort values that indicates which docs this request should "search after". + */ + public NoopSearchRequestBuilder searchAfter(Object[] values) { + sourceBuilder().searchAfter(values); + return this; + } + + public NoopSearchRequestBuilder slice(SliceBuilder builder) { + sourceBuilder().slice(builder); + return this; + } + + /** + * Applies when sorting, and controls if scores will be tracked as well. Defaults to + * false. + */ + public NoopSearchRequestBuilder setTrackScores(boolean trackScores) { + sourceBuilder().trackScores(trackScores); + return this; + } + + + /** + * Sets the fields to load and return as part of the search request. If none + * are specified, the source of the document will be returned. + */ + public NoopSearchRequestBuilder storedFields(String... fields) { + sourceBuilder().storedFields(Arrays.asList(fields)); + return this; + } + + /** + * Adds an aggregation to the search operation. + */ + public NoopSearchRequestBuilder addAggregation(AggregationBuilder aggregation) { + sourceBuilder().aggregation(aggregation); + return this; + } + + /** + * Adds an aggregation to the search operation. + */ + public NoopSearchRequestBuilder addAggregation(PipelineAggregationBuilder aggregation) { + sourceBuilder().aggregation(aggregation); + return this; + } + + public NoopSearchRequestBuilder highlighter(HighlightBuilder highlightBuilder) { + sourceBuilder().highlighter(highlightBuilder); + return this; + } + + /** + * Delegates to {@link org.elasticsearch.search.builder.SearchSourceBuilder#suggest(SuggestBuilder)} + */ + public NoopSearchRequestBuilder suggest(SuggestBuilder suggestBuilder) { + sourceBuilder().suggest(suggestBuilder); + return this; + } + + /** + * Clears all rescorers on the builder and sets the first one. To use multiple rescore windows use + * {@link #addRescorer(org.elasticsearch.search.rescore.RescoreBuilder, int)}. + * + * @param rescorer rescorer configuration + * @return this for chaining + */ + public NoopSearchRequestBuilder setRescorer(RescoreBuilder rescorer) { + sourceBuilder().clearRescorers(); + return addRescorer(rescorer); + } + + /** + * Clears all rescorers on the builder and sets the first one. To use multiple rescore windows use + * {@link #addRescorer(org.elasticsearch.search.rescore.RescoreBuilder, int)}. + * + * @param rescorer rescorer configuration + * @param window rescore window + * @return this for chaining + */ + public NoopSearchRequestBuilder setRescorer(RescoreBuilder rescorer, int window) { + sourceBuilder().clearRescorers(); + return addRescorer(rescorer.windowSize(window)); + } + + /** + * Adds a new rescorer. + * + * @param rescorer rescorer configuration + * @return this for chaining + */ + public NoopSearchRequestBuilder addRescorer(RescoreBuilder rescorer) { + sourceBuilder().addRescorer(rescorer); + return this; + } + + /** + * Adds a new rescorer. + * + * @param rescorer rescorer configuration + * @param window rescore window + * @return this for chaining + */ + public NoopSearchRequestBuilder addRescorer(RescoreBuilder rescorer, int window) { + sourceBuilder().addRescorer(rescorer.windowSize(window)); + return this; + } + + /** + * Clears all rescorers from the builder. + * + * @return this for chaining + */ + public NoopSearchRequestBuilder clearRescorers() { + sourceBuilder().clearRescorers(); + return this; + } + + /** + * Sets the source of the request as a SearchSourceBuilder. + */ + public NoopSearchRequestBuilder setSource(SearchSourceBuilder source) { + request.source(source); + return this; + } + + /** + * Sets if this request should use the request cache or not, assuming that it can (for + * example, if "now" is used, it will never be cached). By default (not set, or null, + * will default to the index level setting if request cache is enabled or not). + */ + public NoopSearchRequestBuilder setRequestCache(Boolean requestCache) { + request.requestCache(requestCache); + return this; + } + + /** + * Should the query be profiled. Defaults to false + */ + public NoopSearchRequestBuilder setProfile(boolean profile) { + sourceBuilder().profile(profile); + return this; + } + + @Override + public String toString() { + if (request.source() != null) { + return request.source().toString(); + } + return new SearchSourceBuilder().toString(); + } + + private SearchSourceBuilder sourceBuilder() { + if (request.source() == null) { + request.source(new SearchSourceBuilder()); + } + return request.source(); + } +} diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/RestNoopSearchAction.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/RestNoopSearchAction.java new file mode 100644 index 000000000000..3520876af04b --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/RestNoopSearchAction.java @@ -0,0 +1,54 @@ +/* + * 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.plugin.noop.action.search; + +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestChannel; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestStatusToXContentListener; + +import java.io.IOException; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; + +public class RestNoopSearchAction extends BaseRestHandler { + + @Inject + public RestNoopSearchAction(Settings settings, RestController controller) { + super(settings); + controller.registerHandler(GET, "/_noop_search", this); + controller.registerHandler(POST, "/_noop_search", this); + controller.registerHandler(GET, "/{index}/_noop_search", this); + controller.registerHandler(POST, "/{index}/_noop_search", this); + controller.registerHandler(GET, "/{index}/{type}/_noop_search", this); + controller.registerHandler(POST, "/{index}/{type}/_noop_search", this); + } + + @Override + public void handleRequest(final RestRequest request, final RestChannel channel, final NodeClient client) throws IOException { + SearchRequest searchRequest = new SearchRequest(); + client.execute(NoopSearchAction.INSTANCE, searchRequest, new RestStatusToXContentListener<>(channel)); + } +} diff --git a/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/TransportNoopSearchAction.java b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/TransportNoopSearchAction.java new file mode 100644 index 000000000000..c4397684bc41 --- /dev/null +++ b/client/client-benchmark-noop-api-plugin/src/main/java/org/elasticsearch/plugin/noop/action/search/TransportNoopSearchAction.java @@ -0,0 +1,58 @@ +/* + * 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.plugin.noop.action.search; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.ShardSearchFailure; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.internal.InternalSearchHit; +import org.elasticsearch.search.internal.InternalSearchHits; +import org.elasticsearch.search.internal.InternalSearchResponse; +import org.elasticsearch.search.profile.SearchProfileShardResults; +import org.elasticsearch.search.suggest.Suggest; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.util.Collections; + +public class TransportNoopSearchAction extends HandledTransportAction { + @Inject + public TransportNoopSearchAction(Settings settings, ThreadPool threadPool, TransportService transportService, ActionFilters + actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { + super(settings, NoopSearchAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, + SearchRequest::new); + } + + @Override + protected void doExecute(SearchRequest request, ActionListener listener) { + listener.onResponse(new SearchResponse(new InternalSearchResponse( + new InternalSearchHits( + new InternalSearchHit[0], 0L, 0.0f), + new InternalAggregations(Collections.emptyList()), + new Suggest(Collections.emptyList()), + new SearchProfileShardResults(Collections.emptyMap()), false, false), "", 1, 1, 0, new ShardSearchFailure[0])); + } +} diff --git a/settings.gradle b/settings.gradle index 8aeb694b51d0..904fb69469d9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,6 +9,7 @@ List projects = [ 'client:sniffer', 'client:transport', 'client:test', + 'client:client-benchmark-noop-api-plugin', 'client:benchmark', 'benchmarks', 'distribution:integ-test-zip',