Skip to content

Commit 1491db6

Browse files
authored
Merge pull request graphql-java#3746 from graphql-java/performance-testing-suit
initial tests for regular performance testing
2 parents 5978ff1 + 702ae2e commit 1491db6

File tree

3 files changed

+442
-0
lines changed

3 files changed

+442
-0
lines changed
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package performance;
2+
3+
import benchmark.BenchmarkUtils;
4+
import com.google.common.collect.ImmutableList;
5+
import graphql.ExecutionInput;
6+
import graphql.ExecutionResult;
7+
import graphql.GraphQL;
8+
import graphql.schema.DataFetcher;
9+
import graphql.schema.DataFetchingEnvironment;
10+
import graphql.schema.GraphQLSchema;
11+
import graphql.schema.idl.RuntimeWiring;
12+
import graphql.schema.idl.SchemaGenerator;
13+
import graphql.schema.idl.SchemaParser;
14+
import graphql.schema.idl.TypeDefinitionRegistry;
15+
import org.openjdk.jmh.annotations.Benchmark;
16+
import org.openjdk.jmh.annotations.BenchmarkMode;
17+
import org.openjdk.jmh.annotations.Fork;
18+
import org.openjdk.jmh.annotations.Level;
19+
import org.openjdk.jmh.annotations.Measurement;
20+
import org.openjdk.jmh.annotations.Mode;
21+
import org.openjdk.jmh.annotations.OutputTimeUnit;
22+
import org.openjdk.jmh.annotations.Param;
23+
import org.openjdk.jmh.annotations.Scope;
24+
import org.openjdk.jmh.annotations.Setup;
25+
import org.openjdk.jmh.annotations.State;
26+
import org.openjdk.jmh.annotations.TearDown;
27+
import org.openjdk.jmh.annotations.Warmup;
28+
import org.openjdk.jmh.profile.GCProfiler;
29+
import org.openjdk.jmh.runner.Runner;
30+
import org.openjdk.jmh.runner.options.Options;
31+
import org.openjdk.jmh.runner.options.OptionsBuilder;
32+
33+
import java.util.List;
34+
import java.util.concurrent.CompletableFuture;
35+
import java.util.concurrent.ExecutorService;
36+
import java.util.concurrent.Executors;
37+
import java.util.concurrent.TimeUnit;
38+
import java.util.concurrent.atomic.AtomicInteger;
39+
import java.util.function.Supplier;
40+
41+
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
42+
43+
/**
44+
* This benchmark is an attempt to have a more complex query that involves async and sync work together
45+
* along with multiple threads happening.
46+
* <p>
47+
* It can also be run in a forever mode say if you want to connect a profiler to it say
48+
*/
49+
@State(Scope.Benchmark)
50+
@Warmup(iterations = 2, time = 5)
51+
@Measurement(iterations = 2)
52+
@Fork(2)
53+
public class ComplexQueryPerformance {
54+
55+
@Param({"5", "10", "20"})
56+
int howManyItems = 5;
57+
int howLongToSleep = 5;
58+
int howManyQueries = 10;
59+
int howManyQueryThreads = 10;
60+
int howManyFetcherThreads = 10;
61+
62+
ExecutorService queryExecutorService;
63+
ExecutorService fetchersExecutorService;
64+
GraphQL graphQL;
65+
volatile boolean shutDown;
66+
67+
@Setup(Level.Trial)
68+
public void setUp() {
69+
shutDown = false;
70+
queryExecutorService = Executors.newFixedThreadPool(howManyQueryThreads);
71+
fetchersExecutorService = Executors.newFixedThreadPool(howManyFetcherThreads);
72+
graphQL = buildGraphQL();
73+
}
74+
75+
@TearDown(Level.Trial)
76+
public void tearDown() {
77+
shutDown = true;
78+
queryExecutorService.shutdownNow();
79+
fetchersExecutorService.shutdownNow();
80+
}
81+
82+
83+
@Benchmark
84+
@BenchmarkMode(Mode.Throughput)
85+
@OutputTimeUnit(TimeUnit.SECONDS)
86+
public Object benchMarkSimpleQueriesThroughput() {
87+
return runManyQueriesToCompletion();
88+
}
89+
90+
91+
public static void main(String[] args) throws Exception {
92+
// just to make sure it's all valid before testing
93+
runAtStartup();
94+
95+
Options opt = new OptionsBuilder()
96+
.include("benchmark.ComplexQueryBenchmark")
97+
.addProfiler(GCProfiler.class)
98+
.build();
99+
100+
new Runner(opt).run();
101+
}
102+
103+
@SuppressWarnings({"ConstantValue", "LoopConditionNotUpdatedInsideLoop"})
104+
private static void runAtStartup() {
105+
106+
ComplexQueryPerformance complexQueryBenchmark = new ComplexQueryPerformance();
107+
complexQueryBenchmark.howManyQueries = 5;
108+
complexQueryBenchmark.howManyItems = 10;
109+
110+
BenchmarkUtils.runInToolingForSomeTimeThenExit(
111+
complexQueryBenchmark::setUp,
112+
complexQueryBenchmark::runManyQueriesToCompletion,
113+
complexQueryBenchmark::tearDown
114+
115+
);
116+
}
117+
118+
119+
@SuppressWarnings("UnnecessaryLocalVariable")
120+
private Void runManyQueriesToCompletion() {
121+
CompletableFuture<?>[] cfs = new CompletableFuture[howManyQueries];
122+
for (int i = 0; i < howManyQueries; i++) {
123+
cfs[i] = CompletableFuture.supplyAsync(() -> executeQuery(howManyItems, howLongToSleep), queryExecutorService).thenCompose(cf -> cf);
124+
}
125+
Void result = CompletableFuture.allOf(cfs).join();
126+
return result;
127+
}
128+
129+
public CompletableFuture<ExecutionResult> executeQuery(int howMany, int howLong) {
130+
String fields = "id name f1 f2 f3 f4 f5 f6 f7 f8 f9 f10";
131+
String query = "query q {"
132+
+ String.format("shops(howMany : %d) { %s departments( howMany : %d) { %s products(howMany : %d) { %s }}}\n"
133+
, howMany, fields, 10, fields, 5, fields)
134+
+ String.format("expensiveShops(howMany : %d howLong : %d) { %s expensiveDepartments( howMany : %d howLong : %d) { %s expensiveProducts(howMany : %d howLong : %d) { %s }}}\n"
135+
, howMany, howLong, fields, 10, howLong, fields, 5, howLong, fields)
136+
+ "}";
137+
return graphQL.executeAsync(ExecutionInput.newExecutionInput(query).build());
138+
}
139+
140+
private GraphQL buildGraphQL() {
141+
TypeDefinitionRegistry definitionRegistry = new SchemaParser().parse(PerformanceTestingUtils.loadResource("storesanddepartments.graphqls"));
142+
143+
DataFetcher<?> shopsDF = env -> mkHowManyThings(env.getArgument("howMany"));
144+
DataFetcher<?> expensiveShopsDF = env -> supplyAsync(() -> sleepAndReturnThings(env));
145+
DataFetcher<?> departmentsDF = env -> mkHowManyThings(env.getArgument("howMany"));
146+
DataFetcher<?> expensiveDepartmentsDF = env -> supplyAsyncListItems(env, () -> sleepAndReturnThings(env));
147+
DataFetcher<?> productsDF = env -> mkHowManyThings(env.getArgument("howMany"));
148+
DataFetcher<?> expensiveProductsDF = env -> supplyAsyncListItems(env, () -> sleepAndReturnThings(env));
149+
150+
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
151+
.type(newTypeWiring("Query")
152+
.dataFetcher("shops", shopsDF)
153+
.dataFetcher("expensiveShops", expensiveShopsDF))
154+
.type(newTypeWiring("Shop")
155+
.dataFetcher("departments", departmentsDF)
156+
.dataFetcher("expensiveDepartments", expensiveDepartmentsDF))
157+
.type(newTypeWiring("Department")
158+
.dataFetcher("products", productsDF)
159+
.dataFetcher("expensiveProducts", expensiveProductsDF))
160+
.build();
161+
162+
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(definitionRegistry, runtimeWiring);
163+
164+
return GraphQL.newGraphQL(graphQLSchema).build();
165+
}
166+
167+
private <T> CompletableFuture<T> supplyAsyncListItems(DataFetchingEnvironment environment, Supplier<T> codeToRun) {
168+
return supplyAsync(codeToRun);
169+
}
170+
171+
private <T> CompletableFuture<T> supplyAsync(Supplier<T> codeToRun) {
172+
if (!shutDown) {
173+
//logEvery(100, "async fetcher");
174+
return CompletableFuture.supplyAsync(codeToRun, fetchersExecutorService);
175+
} else {
176+
// if we have shutdown - get on with it, so we shut down quicker
177+
return CompletableFuture.completedFuture(codeToRun.get());
178+
}
179+
}
180+
181+
private List<IdAndNamedThing> sleepAndReturnThings(DataFetchingEnvironment env) {
182+
// by sleeping, we hope to cause the objects to stay longer in GC land and hence have a longer lifecycle
183+
// then a simple stack say or young gen gc. I don't know this will work, but I am trying it
184+
// to represent work that takes some tie to complete
185+
sleep(env.getArgument("howLong"));
186+
return mkHowManyThings(env.getArgument("howMany"));
187+
}
188+
189+
private void sleep(Integer howLong) {
190+
if (howLong > 0) {
191+
try {
192+
Thread.sleep(howLong);
193+
} catch (InterruptedException e) {
194+
throw new RuntimeException(e);
195+
}
196+
}
197+
}
198+
199+
AtomicInteger logCount = new AtomicInteger();
200+
201+
private void logEvery(int every, String s) {
202+
int count = logCount.getAndIncrement();
203+
if (count == 0 || count % every == 0) {
204+
System.out.println("\t" + count + "\t" + s);
205+
}
206+
}
207+
208+
private List<IdAndNamedThing> mkHowManyThings(Integer howMany) {
209+
ImmutableList.Builder<IdAndNamedThing> builder = ImmutableList.builder();
210+
for (int i = 0; i < howMany; i++) {
211+
builder.add(new IdAndNamedThing(i));
212+
}
213+
return builder.build();
214+
}
215+
216+
@SuppressWarnings("unused")
217+
static class IdAndNamedThing {
218+
private final int i;
219+
220+
public IdAndNamedThing(int i) {
221+
this.i = i;
222+
}
223+
224+
public String getId() {
225+
return "id" + i;
226+
}
227+
228+
public String getName() {
229+
return "name" + i;
230+
}
231+
232+
public String getF1() {
233+
return "f1" + i;
234+
}
235+
236+
public String getF2() {
237+
return "f2" + i;
238+
}
239+
240+
public String getF3() {
241+
return "f3" + i;
242+
}
243+
244+
public String getF4() {
245+
return "f4" + i;
246+
}
247+
248+
public String getF5() {
249+
return "f5" + i;
250+
}
251+
252+
public String getF6() {
253+
return "f6" + i;
254+
}
255+
256+
public String getF7() {
257+
return "f7" + i;
258+
}
259+
260+
public String getF8() {
261+
return "f8" + i;
262+
}
263+
264+
public String getF9() {
265+
return "f9" + i;
266+
}
267+
268+
public String getF10() {
269+
return "f10" + i;
270+
}
271+
}
272+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package performance;
2+
3+
import graphql.ExecutionResult;
4+
import graphql.GraphQL;
5+
import graphql.i18n.I18n;
6+
import graphql.language.Document;
7+
import graphql.parser.Parser;
8+
import graphql.schema.GraphQLSchema;
9+
import graphql.schema.idl.SchemaGenerator;
10+
import graphql.validation.LanguageTraversal;
11+
import graphql.validation.RulesVisitor;
12+
import graphql.validation.ValidationContext;
13+
import graphql.validation.ValidationError;
14+
import graphql.validation.ValidationErrorCollector;
15+
import graphql.validation.rules.OverlappingFieldsCanBeMerged;
16+
import org.openjdk.jmh.annotations.Benchmark;
17+
import org.openjdk.jmh.annotations.BenchmarkMode;
18+
import org.openjdk.jmh.annotations.Fork;
19+
import org.openjdk.jmh.annotations.Measurement;
20+
import org.openjdk.jmh.annotations.Mode;
21+
import org.openjdk.jmh.annotations.OutputTimeUnit;
22+
import org.openjdk.jmh.annotations.Scope;
23+
import org.openjdk.jmh.annotations.Setup;
24+
import org.openjdk.jmh.annotations.State;
25+
import org.openjdk.jmh.annotations.Warmup;
26+
import org.openjdk.jmh.infra.Blackhole;
27+
28+
import java.util.Collections;
29+
import java.util.List;
30+
import java.util.Locale;
31+
import java.util.concurrent.TimeUnit;
32+
33+
import static graphql.Assert.assertTrue;
34+
35+
@State(Scope.Benchmark)
36+
@Warmup(iterations = 2, time = 5)
37+
@Measurement(iterations = 3)
38+
@Fork(3)
39+
public class OverlappingFieldValidationPerformance {
40+
41+
@State(Scope.Benchmark)
42+
public static class MyState {
43+
44+
GraphQLSchema schema;
45+
Document document;
46+
47+
@Setup
48+
public void setup() {
49+
try {
50+
String schemaString = PerformanceTestingUtils.loadResource("large-schema-4.graphqls");
51+
String query = PerformanceTestingUtils.loadResource("large-schema-4-query.graphql");
52+
schema = SchemaGenerator.createdMockedSchema(schemaString);
53+
document = Parser.parse(query);
54+
55+
// make sure this is a valid query overall
56+
GraphQL graphQL = GraphQL.newGraphQL(schema).build();
57+
ExecutionResult executionResult = graphQL.execute(query);
58+
assertTrue(executionResult.getErrors().size() == 0);
59+
} catch (Exception e) {
60+
throw new RuntimeException(e);
61+
}
62+
}
63+
}
64+
65+
@Benchmark
66+
@BenchmarkMode(Mode.AverageTime)
67+
public void overlappingFieldValidationAbgTime(MyState myState, Blackhole blackhole) {
68+
blackhole.consume(validateQuery(myState.schema, myState.document));
69+
}
70+
71+
@Benchmark
72+
@OutputTimeUnit(TimeUnit.SECONDS)
73+
public void overlappingFieldValidationThroughput(MyState myState, Blackhole blackhole) {
74+
blackhole.consume(validateQuery(myState.schema, myState.document));
75+
}
76+
77+
private List<ValidationError> validateQuery(GraphQLSchema schema, Document document) {
78+
ValidationErrorCollector errorCollector = new ValidationErrorCollector();
79+
I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH);
80+
ValidationContext validationContext = new ValidationContext(schema, document, i18n);
81+
OverlappingFieldsCanBeMerged overlappingFieldsCanBeMerged = new OverlappingFieldsCanBeMerged(validationContext, errorCollector);
82+
LanguageTraversal languageTraversal = new LanguageTraversal();
83+
languageTraversal.traverse(document, new RulesVisitor(validationContext, Collections.singletonList(overlappingFieldsCanBeMerged)));
84+
return errorCollector.getErrors();
85+
}
86+
}

0 commit comments

Comments
 (0)