diff --git a/src/test/java/performance/ComplexQueryPerformance.java b/src/test/java/performance/ComplexQueryPerformance.java
new file mode 100644
index 0000000000..e8b2c3da0e
--- /dev/null
+++ b/src/test/java/performance/ComplexQueryPerformance.java
@@ -0,0 +1,272 @@
+package performance;
+
+import benchmark.BenchmarkUtils;
+import com.google.common.collect.ImmutableList;
+import graphql.ExecutionInput;
+import graphql.ExecutionResult;
+import graphql.GraphQL;
+import graphql.schema.DataFetcher;
+import graphql.schema.DataFetchingEnvironment;
+import graphql.schema.GraphQLSchema;
+import graphql.schema.idl.RuntimeWiring;
+import graphql.schema.idl.SchemaGenerator;
+import graphql.schema.idl.SchemaParser;
+import graphql.schema.idl.TypeDefinitionRegistry;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.profile.GCProfiler;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
+
+/**
+ * This benchmark is an attempt to have a more complex query that involves async and sync work together
+ * along with multiple threads happening.
+ *
+ * It can also be run in a forever mode say if you want to connect a profiler to it say
+ */
+@State(Scope.Benchmark)
+@Warmup(iterations = 2, time = 5)
+@Measurement(iterations = 2)
+@Fork(2)
+public class ComplexQueryPerformance {
+
+ @Param({"5", "10", "20"})
+ int howManyItems = 5;
+ int howLongToSleep = 5;
+ int howManyQueries = 10;
+ int howManyQueryThreads = 10;
+ int howManyFetcherThreads = 10;
+
+ ExecutorService queryExecutorService;
+ ExecutorService fetchersExecutorService;
+ GraphQL graphQL;
+ volatile boolean shutDown;
+
+ @Setup(Level.Trial)
+ public void setUp() {
+ shutDown = false;
+ queryExecutorService = Executors.newFixedThreadPool(howManyQueryThreads);
+ fetchersExecutorService = Executors.newFixedThreadPool(howManyFetcherThreads);
+ graphQL = buildGraphQL();
+ }
+
+ @TearDown(Level.Trial)
+ public void tearDown() {
+ shutDown = true;
+ queryExecutorService.shutdownNow();
+ fetchersExecutorService.shutdownNow();
+ }
+
+
+ @Benchmark
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ public Object benchMarkSimpleQueriesThroughput() {
+ return runManyQueriesToCompletion();
+ }
+
+
+ public static void main(String[] args) throws Exception {
+ // just to make sure it's all valid before testing
+ runAtStartup();
+
+ Options opt = new OptionsBuilder()
+ .include("benchmark.ComplexQueryBenchmark")
+ .addProfiler(GCProfiler.class)
+ .build();
+
+ new Runner(opt).run();
+ }
+
+ @SuppressWarnings({"ConstantValue", "LoopConditionNotUpdatedInsideLoop"})
+ private static void runAtStartup() {
+
+ ComplexQueryPerformance complexQueryBenchmark = new ComplexQueryPerformance();
+ complexQueryBenchmark.howManyQueries = 5;
+ complexQueryBenchmark.howManyItems = 10;
+
+ BenchmarkUtils.runInToolingForSomeTimeThenExit(
+ complexQueryBenchmark::setUp,
+ complexQueryBenchmark::runManyQueriesToCompletion,
+ complexQueryBenchmark::tearDown
+
+ );
+ }
+
+
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ private Void runManyQueriesToCompletion() {
+ CompletableFuture>[] cfs = new CompletableFuture[howManyQueries];
+ for (int i = 0; i < howManyQueries; i++) {
+ cfs[i] = CompletableFuture.supplyAsync(() -> executeQuery(howManyItems, howLongToSleep), queryExecutorService).thenCompose(cf -> cf);
+ }
+ Void result = CompletableFuture.allOf(cfs).join();
+ return result;
+ }
+
+ public CompletableFuture executeQuery(int howMany, int howLong) {
+ String fields = "id name f1 f2 f3 f4 f5 f6 f7 f8 f9 f10";
+ String query = "query q {"
+ + String.format("shops(howMany : %d) { %s departments( howMany : %d) { %s products(howMany : %d) { %s }}}\n"
+ , howMany, fields, 10, fields, 5, fields)
+ + String.format("expensiveShops(howMany : %d howLong : %d) { %s expensiveDepartments( howMany : %d howLong : %d) { %s expensiveProducts(howMany : %d howLong : %d) { %s }}}\n"
+ , howMany, howLong, fields, 10, howLong, fields, 5, howLong, fields)
+ + "}";
+ return graphQL.executeAsync(ExecutionInput.newExecutionInput(query).build());
+ }
+
+ private GraphQL buildGraphQL() {
+ TypeDefinitionRegistry definitionRegistry = new SchemaParser().parse(PerformanceTestingUtils.loadResource("storesanddepartments.graphqls"));
+
+ DataFetcher> shopsDF = env -> mkHowManyThings(env.getArgument("howMany"));
+ DataFetcher> expensiveShopsDF = env -> supplyAsync(() -> sleepAndReturnThings(env));
+ DataFetcher> departmentsDF = env -> mkHowManyThings(env.getArgument("howMany"));
+ DataFetcher> expensiveDepartmentsDF = env -> supplyAsyncListItems(env, () -> sleepAndReturnThings(env));
+ DataFetcher> productsDF = env -> mkHowManyThings(env.getArgument("howMany"));
+ DataFetcher> expensiveProductsDF = env -> supplyAsyncListItems(env, () -> sleepAndReturnThings(env));
+
+ RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
+ .type(newTypeWiring("Query")
+ .dataFetcher("shops", shopsDF)
+ .dataFetcher("expensiveShops", expensiveShopsDF))
+ .type(newTypeWiring("Shop")
+ .dataFetcher("departments", departmentsDF)
+ .dataFetcher("expensiveDepartments", expensiveDepartmentsDF))
+ .type(newTypeWiring("Department")
+ .dataFetcher("products", productsDF)
+ .dataFetcher("expensiveProducts", expensiveProductsDF))
+ .build();
+
+ GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(definitionRegistry, runtimeWiring);
+
+ return GraphQL.newGraphQL(graphQLSchema).build();
+ }
+
+ private CompletableFuture supplyAsyncListItems(DataFetchingEnvironment environment, Supplier codeToRun) {
+ return supplyAsync(codeToRun);
+ }
+
+ private CompletableFuture supplyAsync(Supplier codeToRun) {
+ if (!shutDown) {
+ //logEvery(100, "async fetcher");
+ return CompletableFuture.supplyAsync(codeToRun, fetchersExecutorService);
+ } else {
+ // if we have shutdown - get on with it, so we shut down quicker
+ return CompletableFuture.completedFuture(codeToRun.get());
+ }
+ }
+
+ private List sleepAndReturnThings(DataFetchingEnvironment env) {
+ // by sleeping, we hope to cause the objects to stay longer in GC land and hence have a longer lifecycle
+ // then a simple stack say or young gen gc. I don't know this will work, but I am trying it
+ // to represent work that takes some tie to complete
+ sleep(env.getArgument("howLong"));
+ return mkHowManyThings(env.getArgument("howMany"));
+ }
+
+ private void sleep(Integer howLong) {
+ if (howLong > 0) {
+ try {
+ Thread.sleep(howLong);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ AtomicInteger logCount = new AtomicInteger();
+
+ private void logEvery(int every, String s) {
+ int count = logCount.getAndIncrement();
+ if (count == 0 || count % every == 0) {
+ System.out.println("\t" + count + "\t" + s);
+ }
+ }
+
+ private List mkHowManyThings(Integer howMany) {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ for (int i = 0; i < howMany; i++) {
+ builder.add(new IdAndNamedThing(i));
+ }
+ return builder.build();
+ }
+
+ @SuppressWarnings("unused")
+ static class IdAndNamedThing {
+ private final int i;
+
+ public IdAndNamedThing(int i) {
+ this.i = i;
+ }
+
+ public String getId() {
+ return "id" + i;
+ }
+
+ public String getName() {
+ return "name" + i;
+ }
+
+ public String getF1() {
+ return "f1" + i;
+ }
+
+ public String getF2() {
+ return "f2" + i;
+ }
+
+ public String getF3() {
+ return "f3" + i;
+ }
+
+ public String getF4() {
+ return "f4" + i;
+ }
+
+ public String getF5() {
+ return "f5" + i;
+ }
+
+ public String getF6() {
+ return "f6" + i;
+ }
+
+ public String getF7() {
+ return "f7" + i;
+ }
+
+ public String getF8() {
+ return "f8" + i;
+ }
+
+ public String getF9() {
+ return "f9" + i;
+ }
+
+ public String getF10() {
+ return "f10" + i;
+ }
+ }
+}
diff --git a/src/test/java/performance/OverlappingFieldValidationPerformance.java b/src/test/java/performance/OverlappingFieldValidationPerformance.java
new file mode 100644
index 0000000000..77a2af6aec
--- /dev/null
+++ b/src/test/java/performance/OverlappingFieldValidationPerformance.java
@@ -0,0 +1,86 @@
+package performance;
+
+import graphql.ExecutionResult;
+import graphql.GraphQL;
+import graphql.i18n.I18n;
+import graphql.language.Document;
+import graphql.parser.Parser;
+import graphql.schema.GraphQLSchema;
+import graphql.schema.idl.SchemaGenerator;
+import graphql.validation.LanguageTraversal;
+import graphql.validation.RulesVisitor;
+import graphql.validation.ValidationContext;
+import graphql.validation.ValidationError;
+import graphql.validation.ValidationErrorCollector;
+import graphql.validation.rules.OverlappingFieldsCanBeMerged;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import static graphql.Assert.assertTrue;
+
+@State(Scope.Benchmark)
+@Warmup(iterations = 2, time = 5)
+@Measurement(iterations = 3)
+@Fork(3)
+public class OverlappingFieldValidationPerformance {
+
+ @State(Scope.Benchmark)
+ public static class MyState {
+
+ GraphQLSchema schema;
+ Document document;
+
+ @Setup
+ public void setup() {
+ try {
+ String schemaString = PerformanceTestingUtils.loadResource("large-schema-4.graphqls");
+ String query = PerformanceTestingUtils.loadResource("large-schema-4-query.graphql");
+ schema = SchemaGenerator.createdMockedSchema(schemaString);
+ document = Parser.parse(query);
+
+ // make sure this is a valid query overall
+ GraphQL graphQL = GraphQL.newGraphQL(schema).build();
+ ExecutionResult executionResult = graphQL.execute(query);
+ assertTrue(executionResult.getErrors().size() == 0);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ public void overlappingFieldValidationAbgTime(MyState myState, Blackhole blackhole) {
+ blackhole.consume(validateQuery(myState.schema, myState.document));
+ }
+
+ @Benchmark
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ public void overlappingFieldValidationThroughput(MyState myState, Blackhole blackhole) {
+ blackhole.consume(validateQuery(myState.schema, myState.document));
+ }
+
+ private List validateQuery(GraphQLSchema schema, Document document) {
+ ValidationErrorCollector errorCollector = new ValidationErrorCollector();
+ I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH);
+ ValidationContext validationContext = new ValidationContext(schema, document, i18n);
+ OverlappingFieldsCanBeMerged overlappingFieldsCanBeMerged = new OverlappingFieldsCanBeMerged(validationContext, errorCollector);
+ LanguageTraversal languageTraversal = new LanguageTraversal();
+ languageTraversal.traverse(document, new RulesVisitor(validationContext, Collections.singletonList(overlappingFieldsCanBeMerged)));
+ return errorCollector.getErrors();
+ }
+}
diff --git a/src/test/java/performance/PerformanceTestingUtils.java b/src/test/java/performance/PerformanceTestingUtils.java
new file mode 100644
index 0000000000..9e05fd661c
--- /dev/null
+++ b/src/test/java/performance/PerformanceTestingUtils.java
@@ -0,0 +1,84 @@
+package performance;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.Callable;
+
+public class PerformanceTestingUtils {
+
+ @SuppressWarnings("UnstableApiUsage")
+ static String loadResource(String name) {
+ return asRTE(() -> {
+ URL resource = PerformanceTestingUtils.class.getClassLoader().getResource(name);
+ if (resource == null) {
+ throw new IllegalArgumentException("missing resource: " + name);
+ }
+ byte[] bytes;
+ try (InputStream inputStream = resource.openStream()) {
+ bytes = inputStream.readAllBytes();
+ }
+ return new String(bytes, Charset.defaultCharset());
+ });
+ }
+
+ static T asRTE(Callable callable) {
+ try {
+ return callable.call();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void runInToolingForSomeTimeThenExit(Runnable setup, Runnable r, Runnable tearDown) {
+ int runForMillis = getRunForMillis();
+ if (runForMillis <= 0) {
+ System.out.print("'runForMillis' environment var is not set - continuing \n");
+ return;
+ }
+ System.out.printf("Running initial code in some tooling - runForMillis=%d \n", runForMillis);
+ System.out.print("Get your tooling in order and press enter...");
+ readLine();
+ System.out.print("Lets go...\n");
+ setup.run();
+
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("HH:mm:ss");
+ long now, then = System.currentTimeMillis();
+ do {
+ now = System.currentTimeMillis();
+ long msLeft = runForMillis - (now - then);
+ System.out.printf("\t%s Running in loop... %s ms left\n", dtf.format(LocalDateTime.now()), msLeft);
+ r.run();
+ now = System.currentTimeMillis();
+ } while ((now - then) < runForMillis);
+
+ tearDown.run();
+
+ System.out.printf("This ran for %d millis. Exiting...\n", System.currentTimeMillis() - then);
+ System.exit(0);
+ }
+
+ private static void readLine() {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ try {
+ br.readLine();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int getRunForMillis() {
+ String runFor = System.getenv("runForMillis");
+ try {
+ return Integer.parseInt(runFor);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
+}