Skip to content

Commit d336fbe

Browse files
committed
Merge branch 'profiler-2' into defer-dataloader-with-profiler
2 parents aa206d7 + cd002c0 commit d336fbe

19 files changed

+777
-25
lines changed

src/main/java/graphql/EngineRunningState.java

Lines changed: 6 additions & 3 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
@@ -40,10 +42,11 @@ public EngineRunningState() {
4042
this.executionId = null;
4143
}
4244

43-
public EngineRunningState(ExecutionInput executionInput) {
45+
public EngineRunningState(ExecutionInput executionInput, Profiler profiler) {
4446
EngineRunningObserver engineRunningObserver = executionInput.getGraphQLContext().get(EngineRunningObserver.ENGINE_RUNNING_OBSERVER_KEY);
45-
if (engineRunningObserver != null) {
46-
this.engineRunningObserver = engineRunningObserver;
47+
EngineRunningObserver wrappedObserver = profiler.wrapEngineRunningObserver(engineRunningObserver);
48+
if (wrappedObserver != null) {
49+
this.engineRunningObserver = wrappedObserver;
4750
this.graphQLContext = executionInput.getGraphQLContext();
4851
this.executionId = executionInput.getExecutionId();
4952
} else {

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
/**
226234
* Package level access to the graphql context
@@ -291,7 +299,7 @@ public Builder context(Object context) {
291299
return this;
292300
}
293301

294-
/**
302+
/**
295303
* This will give you a builder of {@link GraphQLContext} and any values you set will be copied
296304
* into the underlying {@link GraphQLContext} of this execution input
297305
*
@@ -368,6 +376,11 @@ public Builder dataLoaderRegistry(DataLoaderRegistry dataLoaderRegistry) {
368376
return this;
369377
}
370378

379+
public Builder profileExecution(boolean profileExecution) {
380+
this.profileExecution = profileExecution;
381+
return this;
382+
}
383+
371384
public ExecutionInput build() {
372385
return new ExecutionInput(this);
373386
}

src/main/java/graphql/GraphQL.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -486,9 +486,11 @@ public CompletableFuture<ExecutionResult> executeAsync(UnaryOperator<ExecutionIn
486486
* @return a promise to an {@link ExecutionResult} which can include errors
487487
*/
488488
public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionInput) {
489-
EngineRunningState engineRunningState = new EngineRunningState(executionInput);
489+
Profiler profiler = executionInput.isProfileExecution() ? new ProfilerImpl(executionInput.getGraphQLContext()) : Profiler.NO_OP;
490+
EngineRunningState engineRunningState = new EngineRunningState(executionInput, profiler);
490491
return engineRunningState.engineRun(() -> {
491492
ExecutionInput executionInputWithId = ensureInputHasId(executionInput);
493+
profiler.executionInput(executionInputWithId);
492494
engineRunningState.updateExecutionId(executionInputWithId.getExecutionId());
493495

494496
CompletableFuture<InstrumentationState> instrumentationStateCF = instrumentation.createStateAsync(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInputWithId));
@@ -505,7 +507,7 @@ public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionI
505507

506508
GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters, instrumentationState);
507509

508-
CompletableFuture<ExecutionResult> executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState, engineRunningState);
510+
CompletableFuture<ExecutionResult> executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState, engineRunningState, profiler);
509511
//
510512
// finish up instrumentation
511513
executionResult = executionResult.whenComplete(completeInstrumentationCtxCF(executionInstrumentation));
@@ -537,7 +539,7 @@ private ExecutionInput ensureInputHasId(ExecutionInput executionInput) {
537539
}
538540

539541

540-
private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState, EngineRunningState engineRunningState) {
542+
private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState, EngineRunningState engineRunningState, Profiler profiler) {
541543
AtomicReference<ExecutionInput> executionInputRef = new AtomicReference<>(executionInput);
542544
Function<ExecutionInput, PreparsedDocumentEntry> computeFunction = transformedInput -> {
543545
// if they change the original query in the pre-parser, then we want to see it downstream from then on
@@ -550,7 +552,7 @@ private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInpu
550552
return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors()));
551553
}
552554
try {
553-
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState);
555+
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState, profiler);
554556
} catch (AbortExecutionException e) {
555557
return CompletableFuture.completedFuture(e.toExecutionResult());
556558
}
@@ -614,13 +616,14 @@ private CompletableFuture<ExecutionResult> execute(ExecutionInput executionInput
614616
Document document,
615617
GraphQLSchema graphQLSchema,
616618
InstrumentationState instrumentationState,
617-
EngineRunningState engineRunningState
619+
EngineRunningState engineRunningState,
620+
Profiler profiler
618621
) {
619622

620623
Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, valueUnboxer, responseMapFactory, doNotAutomaticallyDispatchDataLoader);
621624
ExecutionId executionId = executionInput.getExecutionId();
622625

623-
return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState);
626+
return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState, profiler);
624627
}
625628

626629
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package graphql;
2+
3+
import graphql.execution.EngineRunningObserver;
4+
import graphql.execution.ResultPath;
5+
import graphql.language.OperationDefinition;
6+
import graphql.schema.DataFetcher;
7+
import org.jspecify.annotations.NullMarked;
8+
import org.jspecify.annotations.Nullable;
9+
10+
@Internal
11+
@NullMarked
12+
public interface Profiler {
13+
14+
15+
Profiler NO_OP = new Profiler() {
16+
};
17+
18+
19+
default void rootFieldCount(int size) {
20+
21+
}
22+
23+
default void subSelectionCount(int size) {
24+
25+
}
26+
27+
default void executionInput(ExecutionInput executionInput) {
28+
29+
}
30+
31+
default void dataLoaderUsed(String dataLoaderName) {
32+
33+
34+
}
35+
36+
default void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {
37+
38+
}
39+
40+
default @Nullable EngineRunningObserver wrapEngineRunningObserver(EngineRunningObserver engineRunningObserver) {
41+
return engineRunningObserver;
42+
}
43+
44+
default void operationDefinition(OperationDefinition operationDefinition) {
45+
46+
}
47+
48+
default void oldStrategyDispatchingAll(int level) {
49+
50+
}
51+
52+
default void chainedStrategyDispatching(int level) {
53+
54+
}
55+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package graphql;
2+
3+
import graphql.execution.EngineRunningObserver;
4+
import graphql.execution.ExecutionId;
5+
import graphql.execution.ResultPath;
6+
import graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys;
7+
import graphql.language.OperationDefinition;
8+
import graphql.schema.DataFetcher;
9+
import graphql.schema.PropertyDataFetcher;
10+
import graphql.schema.SingletonPropertyDataFetcher;
11+
import org.jspecify.annotations.NullMarked;
12+
13+
import java.util.concurrent.CompletableFuture;
14+
import java.util.concurrent.atomic.AtomicLong;
15+
16+
@Internal
17+
@NullMarked
18+
public class ProfilerImpl implements Profiler {
19+
20+
private volatile long startTime;
21+
private volatile long endTime;
22+
private volatile long lastStartTime;
23+
private final AtomicLong engineTotalRunningTime = new AtomicLong();
24+
25+
26+
final ProfilerResult profilerResult = new ProfilerResult();
27+
28+
public ProfilerImpl(GraphQLContext graphQLContext) {
29+
graphQLContext.put(ProfilerResult.PROFILER_CONTEXT_KEY, profilerResult);
30+
}
31+
32+
@Override
33+
public void executionInput(ExecutionInput executionInput) {
34+
profilerResult.setExecutionId(executionInput.getExecutionId());
35+
boolean dataLoaderChainingEnabled = executionInput.getGraphQLContext().getBoolean(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING, false);
36+
profilerResult.setDataLoaderChainingEnabled(dataLoaderChainingEnabled);
37+
}
38+
39+
@Override
40+
public void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {
41+
String key = "/" + String.join("/", path.getKeysOnly());
42+
profilerResult.addFieldFetched(key);
43+
profilerResult.incrementDataFetcherInvocationCount(key);
44+
ProfilerResult.DataFetcherType dataFetcherType;
45+
if (dataFetcher instanceof PropertyDataFetcher || dataFetcher instanceof SingletonPropertyDataFetcher) {
46+
dataFetcherType = ProfilerResult.DataFetcherType.PROPERTY_DATA_FETCHER;
47+
} else {
48+
dataFetcherType = ProfilerResult.DataFetcherType.CUSTOM;
49+
// we only record the type of the result if it is not a PropertyDataFetcher
50+
ProfilerResult.DataFetcherResultType dataFetcherResultType;
51+
if (fetchedObject instanceof CompletableFuture) {
52+
CompletableFuture<?> completableFuture = (CompletableFuture<?>) fetchedObject;
53+
if (completableFuture.isDone()) {
54+
dataFetcherResultType = ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED;
55+
} else {
56+
dataFetcherResultType = ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED;
57+
}
58+
} else {
59+
dataFetcherResultType = ProfilerResult.DataFetcherResultType.MATERIALIZED;
60+
}
61+
profilerResult.setDataFetcherResultType(key, dataFetcherResultType);
62+
}
63+
64+
profilerResult.setDataFetcherType(key, dataFetcherType);
65+
}
66+
67+
@Override
68+
public EngineRunningObserver wrapEngineRunningObserver(EngineRunningObserver engineRunningObserver) {
69+
// nothing to wrap here
70+
return new EngineRunningObserver() {
71+
@Override
72+
public void runningStateChanged(ExecutionId executionId, GraphQLContext graphQLContext, RunningState runningState) {
73+
runningStateChangedImpl(executionId, graphQLContext, runningState);
74+
if (engineRunningObserver != null) {
75+
engineRunningObserver.runningStateChanged(executionId, graphQLContext, runningState);
76+
}
77+
}
78+
};
79+
}
80+
81+
private void runningStateChangedImpl(ExecutionId executionId, GraphQLContext graphQLContext, EngineRunningObserver.RunningState runningState) {
82+
long now = System.nanoTime();
83+
if (runningState == EngineRunningObserver.RunningState.RUNNING_START) {
84+
startTime = now;
85+
lastStartTime = startTime;
86+
} else if (runningState == EngineRunningObserver.RunningState.NOT_RUNNING_FINISH) {
87+
endTime = now;
88+
engineTotalRunningTime.set(engineTotalRunningTime.get() + (endTime - lastStartTime));
89+
profilerResult.setTimes(startTime, endTime, engineTotalRunningTime.get());
90+
} else if (runningState == EngineRunningObserver.RunningState.RUNNING) {
91+
lastStartTime = now;
92+
} else if (runningState == EngineRunningObserver.RunningState.NOT_RUNNING) {
93+
engineTotalRunningTime.set(engineTotalRunningTime.get() + (now - lastStartTime));
94+
} else {
95+
Assert.assertShouldNeverHappen();
96+
}
97+
}
98+
99+
@Override
100+
public void operationDefinition(OperationDefinition operationDefinition) {
101+
profilerResult.setOperation(operationDefinition);
102+
}
103+
104+
@Override
105+
public void dataLoaderUsed(String dataLoaderName) {
106+
profilerResult.addDataLoaderUsed(dataLoaderName);
107+
}
108+
109+
@Override
110+
public void chainedStrategyDispatching(int level) {
111+
profilerResult.chainedStrategyDispatching(level);
112+
}
113+
114+
@Override
115+
public void oldStrategyDispatchingAll(int level) {
116+
profilerResult.oldStrategyDispatchingAll(level);
117+
}
118+
}

0 commit comments

Comments
 (0)