Skip to content

Commit 0b7c15b

Browse files
committed
collect instrumentations
1 parent 4a47b9d commit 0b7c15b

6 files changed

Lines changed: 106 additions & 17 deletions

File tree

src/main/java/graphql/GraphQL.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@
2626
import graphql.language.Document;
2727
import graphql.schema.GraphQLSchema;
2828
import graphql.validation.ValidationError;
29+
import org.jspecify.annotations.NullMarked;
30+
import org.jspecify.annotations.NullUnmarked;
2931

3032
import java.util.List;
3133
import java.util.Locale;
32-
import java.util.Optional;
3334
import java.util.concurrent.CompletableFuture;
3435
import java.util.concurrent.CompletionException;
3536
import java.util.concurrent.atomic.AtomicReference;
@@ -82,6 +83,7 @@
8283
*/
8384
@SuppressWarnings("Duplicates")
8485
@PublicApi
86+
@NullMarked
8587
public class GraphQL {
8688

8789
/**
@@ -258,16 +260,17 @@ public GraphQL transform(Consumer<GraphQL.Builder> builderConsumer) {
258260
.queryExecutionStrategy(this.queryStrategy)
259261
.mutationExecutionStrategy(this.mutationStrategy)
260262
.subscriptionExecutionStrategy(this.subscriptionStrategy)
261-
.executionIdProvider(Optional.ofNullable(this.idProvider).orElse(builder.idProvider))
262-
.instrumentation(Optional.ofNullable(this.instrumentation).orElse(builder.instrumentation))
263-
.preparsedDocumentProvider(Optional.ofNullable(this.preparsedDocumentProvider).orElse(builder.preparsedDocumentProvider));
263+
.executionIdProvider(this.idProvider)
264+
.instrumentation(this.instrumentation)
265+
.preparsedDocumentProvider(this.preparsedDocumentProvider);
264266

265267
builderConsumer.accept(builder);
266268

267269
return builder.build();
268270
}
269271

270272
@PublicApi
273+
@NullUnmarked
271274
public static class Builder {
272275
private GraphQLSchema graphQLSchema;
273276
private ExecutionStrategy queryExecutionStrategy;
@@ -481,7 +484,7 @@ public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionI
481484
EngineRunningState engineRunningState = new EngineRunningState(executionInput, profiler);
482485
return engineRunningState.engineRun(() -> {
483486
ExecutionInput executionInputWithId = ensureInputHasId(executionInput);
484-
profiler.setExecutionInput(executionInputWithId);
487+
profiler.setExecutionInputAndInstrumentation(executionInputWithId, instrumentation);
485488
engineRunningState.updateExecutionInput(executionInputWithId);
486489

487490
CompletableFuture<InstrumentationState> instrumentationStateCF = instrumentation.createStateAsync(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInputWithId));
@@ -543,7 +546,7 @@ private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInpu
543546
return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors()));
544547
}
545548
try {
546-
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState, profiler);
549+
return execute(Assert.assertNotNull(executionInputRef.get()), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState, profiler);
547550
} catch (AbortExecutionException e) {
548551
return CompletableFuture.completedFuture(e.toExecutionResult());
549552
}
@@ -552,7 +555,7 @@ private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInpu
552555

553556
private PreparsedDocumentEntry parseAndValidate(AtomicReference<ExecutionInput> executionInputRef, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
554557

555-
ExecutionInput executionInput = executionInputRef.get();
558+
ExecutionInput executionInput = assertNotNull(executionInputRef.get());
556559

557560
ParseAndValidateResult parseResult = parse(executionInput, graphQLSchema, instrumentationState);
558561
if (parseResult.isFailure()) {

src/main/java/graphql/Profiler.java

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

33
import graphql.execution.EngineRunningObserver;
44
import graphql.execution.ResultPath;
5+
import graphql.execution.instrumentation.Instrumentation;
56
import graphql.language.OperationDefinition;
67
import graphql.schema.DataFetcher;
78
import org.jspecify.annotations.NullMarked;
@@ -16,8 +17,7 @@ public interface Profiler {
1617
};
1718

1819

19-
20-
default void setExecutionInput(ExecutionInput executionInput) {
20+
default void setExecutionInputAndInstrumentation(ExecutionInput executionInput, Instrumentation instrumentation) {
2121

2222
}
2323

src/main/java/graphql/ProfilerImpl.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import graphql.execution.EngineRunningObserver;
44
import graphql.execution.ExecutionId;
55
import graphql.execution.ResultPath;
6+
import graphql.execution.instrumentation.ChainedInstrumentation;
7+
import graphql.execution.instrumentation.Instrumentation;
68
import graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys;
79
import graphql.language.OperationDefinition;
810
import graphql.schema.DataFetcher;
@@ -11,6 +13,8 @@
1113
import org.jspecify.annotations.NullMarked;
1214
import org.jspecify.annotations.Nullable;
1315

16+
import java.util.ArrayList;
17+
import java.util.List;
1418
import java.util.concurrent.CompletableFuture;
1519
import java.util.concurrent.atomic.AtomicLong;
1620

@@ -23,18 +27,33 @@ public class ProfilerImpl implements Profiler {
2327
private volatile long lastStartTime;
2428
private final AtomicLong engineTotalRunningTime = new AtomicLong();
2529

26-
2730
final ProfilerResult profilerResult = new ProfilerResult();
2831

2932
public ProfilerImpl(GraphQLContext graphQLContext) {
33+
// No real work can happen here, since the engine didn't "officially" start yet.
3034
graphQLContext.put(ProfilerResult.PROFILER_CONTEXT_KEY, profilerResult);
3135
}
3236

3337
@Override
34-
public void setExecutionInput(ExecutionInput executionInput) {
38+
public void setExecutionInputAndInstrumentation(ExecutionInput executionInput, Instrumentation instrumentation) {
3539
profilerResult.setExecutionId(executionInput.getExecutionIdNonNull());
3640
boolean dataLoaderChainingEnabled = executionInput.getGraphQLContext().getBoolean(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING, false);
3741
profilerResult.setDataLoaderChainingEnabled(dataLoaderChainingEnabled);
42+
43+
List<String> instrumentationClasses = new ArrayList<>();
44+
collectInstrumentationClasses(instrumentationClasses, instrumentation);
45+
profilerResult.setInstrumentationClasses(instrumentationClasses);
46+
}
47+
48+
private void collectInstrumentationClasses(List<String> result, Instrumentation instrumentation) {
49+
if (instrumentation instanceof ChainedInstrumentation) {
50+
ChainedInstrumentation chainedInstrumentation = (ChainedInstrumentation) instrumentation;
51+
for (Instrumentation child : chainedInstrumentation.getInstrumentations()) {
52+
collectInstrumentationClasses(result, child);
53+
}
54+
} else {
55+
result.add(instrumentation.getClass().getName());
56+
}
3857
}
3958

4059
@Override

src/main/java/graphql/ProfilerResult.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,10 @@ public class ProfilerResult {
2929
private long engineTotalRunningTime;
3030
private final AtomicInteger totalDataFetcherInvocations = new AtomicInteger();
3131
private final AtomicInteger totalPropertyDataFetcherInvocations = new AtomicInteger();
32-
private final Set<String> fieldsFetched = ConcurrentHashMap.newKeySet();
33-
3432

35-
private final Map<String, Integer> dataFetcherInvocationCount = new ConcurrentHashMap<>();
33+
// this is the count of how many times a data loader was invoked per data loader name
3634
private final Map<String, Integer> dataLoaderLoadInvocations = new ConcurrentHashMap<>();
37-
private final Map<String, DataFetcherType> dataFetcherTypeMap = new ConcurrentHashMap<>();
3835

39-
private final Map<String, DataFetcherResultType> dataFetcherResultType = new ConcurrentHashMap<>();
4036
@Nullable
4137
private volatile String operationName;
4238
@Nullable
@@ -45,8 +41,27 @@ public class ProfilerResult {
4541
private final Set<Integer> oldStrategyDispatchingAll = ConcurrentHashMap.newKeySet();
4642
private final Set<Integer> chainedStrategyDispatching = ConcurrentHashMap.newKeySet();
4743

44+
private final List<String> instrumentationClasses = Collections.synchronizedList(new ArrayList<>());
45+
4846
private final List<DispatchEvent> dispatchEvents = Collections.synchronizedList(new ArrayList<>());
4947

48+
/**
49+
* the following fields can contain a lot of data for large requests
50+
*/
51+
// all fields fetched during the execution, key is the field path
52+
private final Set<String> fieldsFetched = ConcurrentHashMap.newKeySet();
53+
// this is the count of how many times a data fetcher was invoked per field
54+
private final Map<String, Integer> dataFetcherInvocationCount = new ConcurrentHashMap<>();
55+
// the type of the data fetcher per field, key is the field path
56+
private final Map<String, DataFetcherType> dataFetcherTypeMap = new ConcurrentHashMap<>();
57+
// the type of the data fetcher result field, key is the field path
58+
// in theory different DataFetcher invocations can return different types, but we only record the first one
59+
private final Map<String, DataFetcherResultType> dataFetcherResultType = new ConcurrentHashMap<>();
60+
61+
public void setInstrumentationClasses(List<String> instrumentationClasses) {
62+
this.instrumentationClasses.addAll(instrumentationClasses);
63+
}
64+
5065

5166
public static class DispatchEvent {
5267
final String dataLoaderName;
@@ -240,6 +255,10 @@ public List<DispatchEvent> getDispatchEvents() {
240255
return dispatchEvents;
241256
}
242257

258+
public List<String> getInstrumentationClasses() {
259+
return instrumentationClasses;
260+
}
261+
243262
public String fullSummary() {
244263
return "ProfilerResult{" +
245264
"executionId=" + executionId +
@@ -299,6 +318,7 @@ public Map<String, Object> shortSummaryMap() {
299318
result.put("oldStrategyDispatchingAll", oldStrategyDispatchingAll);
300319
result.put("chainedStrategyDispatching", chainedStrategyDispatching);
301320
result.put("dispatchEvents", getDispatchEventsAsMap());
321+
result.put("instrumentationClasses", instrumentationClasses);
302322
return result;
303323
}
304324

src/main/java/graphql/execution/instrumentation/InstrumentationContext.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package graphql.execution.instrumentation;
22

33
import graphql.PublicSpi;
4+
import org.jspecify.annotations.NullMarked;
5+
import org.jspecify.annotations.Nullable;
46

57
/**
68
* When a {@link Instrumentation}.'beginXXX()' method is called then it must return a non null InstrumentationContext
@@ -11,6 +13,7 @@
1113
* just happened or "loggers" to be called to record what has happened.
1214
*/
1315
@PublicSpi
16+
@NullMarked
1417
public interface InstrumentationContext<T> {
1518

1619
/**
@@ -24,6 +27,6 @@ public interface InstrumentationContext<T> {
2427
* @param result the result of the step (which may be null)
2528
* @param t this exception will be non null if an exception was thrown during the step
2629
*/
27-
void onCompleted(T result, Throwable t);
30+
void onCompleted(@Nullable T result, @Nullable Throwable t);
2831

2932
}

src/test/groovy/graphql/ProfilerTest.groovy

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package graphql
22

3+
import graphql.execution.instrumentation.ChainedInstrumentation
4+
import graphql.execution.instrumentation.Instrumentation
5+
import graphql.execution.instrumentation.SimplePerformantInstrumentation
36
import graphql.language.OperationDefinition
47
import graphql.schema.DataFetcher
58
import graphql.schema.DataFetchingEnvironment
@@ -51,6 +54,47 @@ class ProfilerTest extends Specification {
5154

5255
}
5356

57+
def "collects instrumentation list"() {
58+
given:
59+
def sdl = '''
60+
type Query {
61+
hello: String
62+
}
63+
'''
64+
def schema = TestUtil.schema(sdl, [Query: [
65+
hello: { DataFetchingEnvironment dfe -> return "world" } as DataFetcher
66+
]])
67+
Instrumentation fooInstrumentation = new Instrumentation() {};
68+
Instrumentation barInstrumentation = new Instrumentation() {};
69+
ChainedInstrumentation chainedInstrumentation = new ChainedInstrumentation(
70+
new ChainedInstrumentation(new SimplePerformantInstrumentation()),
71+
new ChainedInstrumentation(fooInstrumentation, barInstrumentation),
72+
new SimplePerformantInstrumentation())
73+
74+
75+
def graphql = GraphQL.newGraphQL(schema).instrumentation(chainedInstrumentation).build();
76+
77+
ExecutionInput ei = ExecutionInput.newExecutionInput()
78+
.query("{ hello }")
79+
.profileExecution(true)
80+
.build()
81+
82+
when:
83+
def result = graphql.execute(ei)
84+
def profilerResult = ei.getGraphQLContext().get(ProfilerResult.PROFILER_CONTEXT_KEY) as ProfilerResult
85+
86+
then:
87+
result.getData() == [hello: "world"]
88+
89+
then:
90+
profilerResult.getInstrumentationClasses() == ["graphql.execution.instrumentation.SimplePerformantInstrumentation",
91+
"graphql.ProfilerTest\$1",
92+
"graphql.ProfilerTest\$2",
93+
"graphql.execution.instrumentation.SimplePerformantInstrumentation"]
94+
95+
}
96+
97+
5498
def "two DF with list"() {
5599
given:
56100
def sdl = '''

0 commit comments

Comments
 (0)