Skip to content

Commit 0651b82

Browse files
committed
adding Profiler
1 parent 00057ef commit 0651b82

16 files changed

+221
-20
lines changed

src/main/java/graphql/EngineRunningState.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import graphql.execution.EngineRunningObserver;
44
import graphql.execution.ExecutionId;
5+
import org.jspecify.annotations.NullMarked;
56
import org.jspecify.annotations.Nullable;
67

78
import java.util.concurrent.CompletableFuture;
@@ -19,6 +20,7 @@
1920
import static graphql.execution.EngineRunningObserver.RunningState.RUNNING_START;
2021

2122
@Internal
23+
@NullMarked
2224
public class EngineRunningState {
2325

2426
@Nullable

src/main/java/graphql/ExecutionInput.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class ExecutionInput {
3131
private final Locale locale;
3232
// this is currently not used but we want it back soon after the v23 release
3333
private final AtomicBoolean cancelled;
34+
private final boolean profileExecution;
3435

3536

3637
@Internal
@@ -47,6 +48,7 @@ private ExecutionInput(Builder builder) {
4748
this.localContext = builder.localContext;
4849
this.extensions = builder.extensions;
4950
this.cancelled = builder.cancelled;
51+
this.profileExecution = builder.profileExecution;
5052
}
5153

5254
/**
@@ -142,6 +144,11 @@ public Map<String, Object> getExtensions() {
142144
return extensions;
143145
}
144146

147+
148+
public boolean isProfileExecution() {
149+
return profileExecution;
150+
}
151+
145152
/**
146153
* This helps you transform the current ExecutionInput object into another one by starting a builder with all
147154
* the current values and allows you to transform it how you want.
@@ -221,6 +228,7 @@ public static class Builder {
221228
private Locale locale = Locale.getDefault();
222229
private ExecutionId executionId;
223230
private AtomicBoolean cancelled = new AtomicBoolean(false);
231+
private boolean profileExecution;
224232

225233
public Builder query(String query) {
226234
this.query = assertNotNull(query, () -> "query can't be null");
@@ -283,7 +291,7 @@ public Builder context(Object context) {
283291
return this;
284292
}
285293

286-
/**
294+
/**
287295
* This will give you a builder of {@link GraphQLContext} and any values you set will be copied
288296
* into the underlying {@link GraphQLContext} of this execution input
289297
*
@@ -360,6 +368,11 @@ public Builder dataLoaderRegistry(DataLoaderRegistry dataLoaderRegistry) {
360368
return this;
361369
}
362370

371+
public Builder profileExecution(boolean profileExecution) {
372+
this.profileExecution = profileExecution;
373+
return this;
374+
}
375+
363376
public ExecutionInput build() {
364377
return new ExecutionInput(this);
365378
}

src/main/java/graphql/GraphQL.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ public CompletableFuture<ExecutionResult> executeAsync(UnaryOperator<ExecutionIn
420420
* @return a promise to an {@link ExecutionResult} which can include errors
421421
*/
422422
public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionInput) {
423+
Profiler profiler = executionInput.isProfileExecution() ? new ProfilerImpl(executionInput.getGraphQLContext()) : Profiler.NO_OP;
424+
profiler.start();
423425
EngineRunningState engineRunningState = new EngineRunningState(executionInput);
424426
return engineRunningState.engineRun(() -> {
425427
ExecutionInput executionInputWithId = ensureInputHasId(executionInput);
@@ -439,7 +441,7 @@ public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionI
439441

440442
GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters, instrumentationState);
441443

442-
CompletableFuture<ExecutionResult> executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState, engineRunningState);
444+
CompletableFuture<ExecutionResult> executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState, engineRunningState, profiler);
443445
//
444446
// finish up instrumentation
445447
executionResult = executionResult.whenComplete(completeInstrumentationCtxCF(executionInstrumentation));
@@ -471,7 +473,7 @@ private ExecutionInput ensureInputHasId(ExecutionInput executionInput) {
471473
}
472474

473475

474-
private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState, EngineRunningState engineRunningState) {
476+
private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState, EngineRunningState engineRunningState, Profiler profiler) {
475477
AtomicReference<ExecutionInput> executionInputRef = new AtomicReference<>(executionInput);
476478
Function<ExecutionInput, PreparsedDocumentEntry> computeFunction = transformedInput -> {
477479
// if they change the original query in the pre-parser, then we want to see it downstream from then on
@@ -484,7 +486,7 @@ private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInpu
484486
return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors()));
485487
}
486488
try {
487-
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState);
489+
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState, profiler);
488490
} catch (AbortExecutionException e) {
489491
return CompletableFuture.completedFuture(e.toExecutionResult());
490492
}
@@ -548,13 +550,14 @@ private CompletableFuture<ExecutionResult> execute(ExecutionInput executionInput
548550
Document document,
549551
GraphQLSchema graphQLSchema,
550552
InstrumentationState instrumentationState,
551-
EngineRunningState engineRunningState
553+
EngineRunningState engineRunningState,
554+
Profiler profiler
552555
) {
553556

554557
Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, valueUnboxer, responseMapFactory, doNotAutomaticallyDispatchDataLoader);
555558
ExecutionId executionId = executionInput.getExecutionId();
556559

557-
return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState);
560+
return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState, profiler);
558561
}
559562

560563
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package graphql;
2+
3+
import graphql.execution.ResultPath;
4+
import graphql.schema.DataFetcher;
5+
import org.jspecify.annotations.NullMarked;
6+
7+
@Internal
8+
@NullMarked
9+
public interface Profiler {
10+
11+
12+
Profiler NO_OP = new Profiler() {
13+
};
14+
15+
default void start() {
16+
17+
}
18+
19+
20+
default void rootFieldCount(int size) {
21+
22+
}
23+
24+
default void subSelectionCount(int size) {
25+
26+
}
27+
28+
default void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {
29+
30+
}
31+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package graphql;
2+
3+
import graphql.execution.ResultPath;
4+
import graphql.schema.DataFetcher;
5+
import org.jspecify.annotations.NullMarked;
6+
7+
import java.util.Map;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.concurrent.atomic.AtomicInteger;
10+
11+
@Internal
12+
@NullMarked
13+
public class ProfilerImpl implements Profiler {
14+
15+
volatile long startTime;
16+
volatile int rootFieldCount;
17+
18+
AtomicInteger propertyDataFetcherCount;
19+
20+
final Map<String, Integer> dataFetcherInvocationCount = new ConcurrentHashMap<>();
21+
22+
23+
final ProfilerResult profilerResult = new ProfilerResult();
24+
25+
public ProfilerImpl(GraphQLContext graphQLContext) {
26+
graphQLContext.put(ProfilerResult.PROFILER_CONTEXT_KEY, profilerResult);
27+
}
28+
29+
@Override
30+
public void start() {
31+
startTime = System.nanoTime();
32+
}
33+
34+
35+
@Override
36+
public void rootFieldCount(int count) {
37+
this.rootFieldCount = count;
38+
}
39+
40+
@Override
41+
public void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {
42+
String key = String.join("/", path.getKeysOnly());
43+
profilerResult.addFieldFetched(key);
44+
45+
// dataFetcherInvocationCount.compute(key, (k, v) -> v == null ? 1 : v + 1);
46+
//
47+
// if (dataFetcher instanceof PropertyDataFetcher) {
48+
// propertyDataFetcherCount.incrementAndGet();
49+
// }
50+
}
51+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package graphql;
2+
3+
import java.util.Map;
4+
import java.util.Set;
5+
import java.util.concurrent.ConcurrentHashMap;
6+
7+
@ExperimentalApi
8+
public class ProfilerResult {
9+
10+
public static String PROFILER_CONTEXT_KEY = "__GJ_PROFILER";
11+
12+
private int fieldCount;
13+
14+
private int propertyDataFetcherCount;
15+
16+
private final Set<String> fieldsFetched = ConcurrentHashMap.newKeySet();
17+
18+
public static enum ResultType {
19+
COMPLETABLE_FUTURE_COMPLETED,
20+
COMPLETABLE_FUTURE_NOT_COMPLETED,
21+
MATERIALIZED
22+
23+
}
24+
25+
private Map<String, ResultType> queryPathToResultType;
26+
27+
28+
public void addFieldFetched(String fieldPath) {
29+
fieldsFetched.add(fieldPath);
30+
}
31+
32+
public Set<String> getFieldsFetched() {
33+
return fieldsFetched;
34+
}
35+
36+
37+
}

src/main/java/graphql/execution/Execution.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import graphql.ExecutionInput;
77
import graphql.ExecutionResult;
88
import graphql.ExecutionResultImpl;
9-
import graphql.ExperimentalApi;
109
import graphql.GraphQLContext;
1110
import graphql.GraphQLError;
1211
import graphql.Internal;
12+
import graphql.Profiler;
1313
import graphql.execution.incremental.IncrementalCallState;
1414
import graphql.execution.instrumentation.Instrumentation;
1515
import graphql.execution.instrumentation.InstrumentationContext;
@@ -37,7 +37,6 @@
3737
import java.util.Collections;
3838
import java.util.List;
3939
import java.util.Map;
40-
import java.util.Optional;
4140
import java.util.concurrent.CompletableFuture;
4241
import java.util.function.Supplier;
4342

@@ -77,7 +76,7 @@ public Execution(ExecutionStrategy queryStrategy,
7776
this.doNotAutomaticallyDispatchDataLoader = doNotAutomaticallyDispatchDataLoader;
7877
}
7978

80-
public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState, EngineRunningState engineRunningState) {
79+
public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState, EngineRunningState engineRunningState, Profiler profiler) {
8180
NodeUtil.GetOperationResult getOperationResult;
8281
CoercedVariables coercedVariables;
8382
Supplier<NormalizedVariables> normalizedVariableValues;
@@ -118,6 +117,7 @@ public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSche
118117
.executionInput(executionInput)
119118
.propagapropagateErrorsOnNonNullContractFailureeErrors(propagateErrorsOnNonNullContractFailure)
120119
.engineRunningState(engineRunningState)
120+
.profiler(profiler)
121121
.build();
122122

123123
executionContext.getGraphQLContext().put(ResultNodesInfo.RESULT_NODES_INFO, executionContext.getResultNodesInfo());

src/main/java/graphql/execution/ExecutionContext.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import graphql.GraphQLContext;
1010
import graphql.GraphQLError;
1111
import graphql.Internal;
12+
import graphql.Profiler;
1213
import graphql.PublicApi;
1314
import graphql.collect.ImmutableKit;
1415
import graphql.execution.incremental.IncrementalCallState;
@@ -29,7 +30,6 @@
2930
import java.util.Locale;
3031
import java.util.Map;
3132
import java.util.Set;
32-
import java.util.concurrent.atomic.AtomicInteger;
3333
import java.util.concurrent.atomic.AtomicReference;
3434
import java.util.function.Consumer;
3535
import java.util.function.Supplier;
@@ -67,14 +67,14 @@ public class ExecutionContext {
6767
private final Supplier<ExecutableNormalizedOperation> queryTree;
6868
private final boolean propagateErrorsOnNonNullContractFailure;
6969

70-
private final AtomicInteger isRunning = new AtomicInteger(0);
71-
7270
// this is modified after creation so it needs to be volatile to ensure visibility across Threads
7371
private volatile DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = DataLoaderDispatchStrategy.NO_OP;
7472

7573
private final ResultNodesInfo resultNodesInfo = new ResultNodesInfo();
7674
private final EngineRunningState engineRunningState;
7775

76+
private final Profiler profiler;
77+
7878
ExecutionContext(ExecutionContextBuilder builder) {
7979
this.graphQLSchema = builder.graphQLSchema;
8080
this.executionId = builder.executionId;
@@ -102,6 +102,7 @@ public class ExecutionContext {
102102
this.queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables));
103103
this.propagateErrorsOnNonNullContractFailure = builder.propagateErrorsOnNonNullContractFailure;
104104
this.engineRunningState = builder.engineRunningState;
105+
this.profiler = builder.profiler;
105106
}
106107

107108
public ExecutionId getExecutionId() {
@@ -376,4 +377,10 @@ public boolean hasIncrementalSupport() {
376377
public EngineRunningState getEngineRunningState() {
377378
return engineRunningState;
378379
}
380+
381+
382+
@Internal
383+
public Profiler getProfiler() {
384+
return profiler;
385+
}
379386
}

src/main/java/graphql/execution/ExecutionContextBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import graphql.GraphQLContext;
99
import graphql.GraphQLError;
1010
import graphql.Internal;
11+
import graphql.Profiler;
1112
import graphql.collect.ImmutableKit;
1213
import graphql.execution.instrumentation.Instrumentation;
1314
import graphql.execution.instrumentation.InstrumentationState;
@@ -53,6 +54,7 @@ public class ExecutionContextBuilder {
5354
boolean propagateErrorsOnNonNullContractFailure = true;
5455
EngineRunningState engineRunningState;
5556
ResponseMapFactory responseMapFactory = ResponseMapFactory.DEFAULT;
57+
Profiler profiler;
5658

5759
/**
5860
* @return a new builder of {@link graphql.execution.ExecutionContext}s
@@ -102,6 +104,7 @@ public ExecutionContextBuilder() {
102104
propagateErrorsOnNonNullContractFailure = other.propagateErrorsOnNonNullContractFailure();
103105
engineRunningState = other.getEngineRunningState();
104106
responseMapFactory = other.getResponseMapFactory();
107+
profiler = other.getProfiler();
105108
}
106109

107110
public ExecutionContextBuilder instrumentation(Instrumentation instrumentation) {
@@ -253,4 +256,9 @@ public ExecutionContextBuilder engineRunningState(EngineRunningState engineRunni
253256
this.engineRunningState = engineRunningState;
254257
return this;
255258
}
259+
260+
public ExecutionContextBuilder profiler(Profiler profiler) {
261+
this.profiler = profiler;
262+
return this;
263+
}
256264
}

0 commit comments

Comments
 (0)