Skip to content

Commit a04f5cb

Browse files
committed
introspection fields
1 parent 535eb8e commit a04f5cb

File tree

5 files changed

+109
-3
lines changed

5 files changed

+109
-3
lines changed

src/main/java/graphql/Profiler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import graphql.execution.instrumentation.Instrumentation;
66
import graphql.language.OperationDefinition;
77
import graphql.schema.DataFetcher;
8+
import graphql.schema.GraphQLFieldDefinition;
9+
import graphql.schema.GraphQLOutputType;
810
import org.jspecify.annotations.NullMarked;
911
import org.jspecify.annotations.Nullable;
1012

@@ -26,7 +28,7 @@ default void dataLoaderUsed(String dataLoaderName) {
2628

2729
}
2830

29-
default void fieldFetched(Object fetchedObject, DataFetcher<?> originalDataFetcher, DataFetcher<?> dataFetcher, ResultPath path) {
31+
default void fieldFetched(Object fetchedObject, DataFetcher<?> originalDataFetcher, DataFetcher<?> dataFetcher, ResultPath path, GraphQLFieldDefinition fieldDef, GraphQLOutputType parentType) {
3032

3133
}
3234

src/main/java/graphql/ProfilerImpl.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
import graphql.execution.instrumentation.ChainedInstrumentation;
77
import graphql.execution.instrumentation.Instrumentation;
88
import graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys;
9+
import graphql.introspection.Introspection;
910
import graphql.language.OperationDefinition;
1011
import graphql.schema.DataFetcher;
12+
import graphql.schema.GraphQLFieldDefinition;
13+
import graphql.schema.GraphQLOutputType;
14+
import graphql.schema.GraphQLTypeUtil;
1115
import graphql.schema.PropertyDataFetcher;
1216
import graphql.schema.SingletonPropertyDataFetcher;
1317
import org.jspecify.annotations.NullMarked;
@@ -58,8 +62,15 @@ private void collectInstrumentationClasses(List<String> result, Instrumentation
5862

5963

6064
@Override
61-
public void fieldFetched(Object fetchedObject, DataFetcher<?> originalDataFetcher, DataFetcher<?> dataFetcher, ResultPath path) {
65+
public void fieldFetched(Object fetchedObject, DataFetcher<?> originalDataFetcher, DataFetcher<?> dataFetcher, ResultPath path, GraphQLFieldDefinition fieldDef, GraphQLOutputType parentType) {
6266
String key = "/" + String.join("/", path.getKeysOnly());
67+
if (Introspection.isIntrospectionTypes(GraphQLTypeUtil.unwrapAll(fieldDef.getType()))
68+
|| Introspection.isIntrospectionTypes(GraphQLTypeUtil.unwrapAll(parentType))
69+
|| fieldDef.getName().equals(Introspection.SchemaMetaFieldDef.getName())
70+
|| fieldDef.getName().equals(Introspection.TypeMetaFieldDef.getName())
71+
|| fieldDef.getName().equals(Introspection.TypeNameMetaFieldDef.getName())) {
72+
return;
73+
}
6374
profilerResult.addFieldFetched(key);
6475
profilerResult.incrementDataFetcherInvocationCount(key);
6576
ProfilerResult.DataFetcherType dataFetcherType;

src/main/java/graphql/ProfilerResult.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ public List<String> getInstrumentationClasses() {
267267
return instrumentationClasses;
268268
}
269269

270+
270271
public Map<String, Object> shortSummaryMap() {
271272
Map<String, Object> result = new LinkedHashMap<>();
272273
result.put("executionId", Assert.assertNotNull(executionId));
@@ -276,6 +277,7 @@ public Map<String, Object> shortSummaryMap() {
276277
result.put("totalRunTime", (endTime - startTime) + "(" + (endTime - startTime) / 1_000_000 + "ms)");
277278
result.put("engineTotalRunningTime", engineTotalRunningTime + "(" + engineTotalRunningTime / 1_000_000 + "ms)");
278279
result.put("totalDataFetcherInvocations", totalDataFetcherInvocations);
280+
result.put("totalCustomDataFetcherInvocations", getTotalCustomDataFetcherInvocations());
279281
result.put("totalTrivialDataFetcherInvocations", totalTrivialDataFetcherInvocations);
280282
result.put("totalWrappedTrivialDataFetcherInvocations", totalWrappedTrivialDataFetcherInvocations);
281283
result.put("fieldsFetchedCount", fieldsFetched.size());
@@ -284,6 +286,34 @@ public Map<String, Object> shortSummaryMap() {
284286
result.put("oldStrategyDispatchingAll", oldStrategyDispatchingAll);
285287
result.put("dispatchEvents", getDispatchEventsAsMap());
286288
result.put("instrumentationClasses", instrumentationClasses);
289+
int completedCount = 0;
290+
int notCompletedCount = 0;
291+
int materializedCount = 0;
292+
// we want to minimize the overall size because it is intended to be logged
293+
// and logging can be expensive and is limited in size very often
294+
Map<String, String> resultTypes = new LinkedHashMap<>();
295+
for (String field : dataFetcherResultType.keySet()) {
296+
DataFetcherResultType dataFetcherResultType1 = dataFetcherResultType.get(field);
297+
String shortType = null;
298+
if (dataFetcherResultType1 == DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED) {
299+
completedCount++;
300+
shortType = "C";
301+
} else if (dataFetcherResultType1 == DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED) {
302+
notCompletedCount++;
303+
shortType = "N";
304+
} else if (dataFetcherResultType1 == DataFetcherResultType.MATERIALIZED) {
305+
materializedCount++;
306+
shortType = "M";
307+
} else {
308+
Assert.assertShouldNeverHappen();
309+
}
310+
resultTypes.put(field, Assert.assertNotNull(shortType));
311+
}
312+
result.put("dataFetcherResultTypesCount", Map.of(
313+
DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED, completedCount,
314+
DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED, notCompletedCount,
315+
DataFetcherResultType.MATERIALIZED, materializedCount));
316+
result.put("dataFetcherResultType", resultTypes);
287317
return result;
288318
}
289319

src/main/java/graphql/execution/ExecutionStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ private Object invokeDataFetcher(ExecutionContext executionContext, ExecutionStr
505505
} else {
506506
fetchedValueRaw = dataFetcher.get(dataFetchingEnvironment.get());
507507
}
508-
executionContext.getProfiler().fieldFetched(fetchedValueRaw, originalDataFetcher, dataFetcher, parameters.getPath());
508+
executionContext.getProfiler().fieldFetched(fetchedValueRaw, originalDataFetcher, dataFetcher, parameters.getPath(), fieldDef, parameters.getExecutionStepInfo().getType());
509509
fetchedValue = Async.toCompletableFutureOrMaterializedObject(fetchedValueRaw);
510510
} catch (Exception e) {
511511
fetchedValue = Async.exceptionallyCompletedFuture(e);

src/test/groovy/graphql/ProfilerTest.groovy

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import java.util.concurrent.atomic.AtomicInteger
2222
import static graphql.ExecutionInput.newExecutionInput
2323
import static graphql.ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED
2424
import static graphql.ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED
25+
import static graphql.ProfilerResult.DataFetcherResultType.MATERIALIZED
2526
import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.setEnableDataLoaderChaining
2627
import static java.util.concurrent.CompletableFuture.supplyAsync
2728

@@ -57,6 +58,67 @@ class ProfilerTest extends Specification {
5758

5859
}
5960

61+
def "introspection fields are ignored"() {
62+
given:
63+
def sdl = '''
64+
type Query {
65+
hello: String
66+
}
67+
'''
68+
def schema = TestUtil.schema(sdl, [Query: [
69+
hello: { DataFetchingEnvironment dfe -> return "world" } as DataFetcher
70+
]])
71+
def graphql = GraphQL.newGraphQL(schema).build();
72+
73+
ExecutionInput ei = ExecutionInput.newExecutionInput()
74+
.query("{ hello __typename alias:__typename __schema {types{name}} __type(name: \"Query\") {name} }")
75+
.profileExecution(true)
76+
.build()
77+
78+
when:
79+
def result = graphql.execute(ei)
80+
def profilerResult = ei.getGraphQLContext().get(ProfilerResult.PROFILER_CONTEXT_KEY) as ProfilerResult
81+
82+
then:
83+
result.getData()["hello"] == "world"
84+
85+
then:
86+
profilerResult.getFieldsFetched() == ["/hello",] as Set
87+
profilerResult.getTotalDataFetcherInvocations() == 1
88+
89+
}
90+
91+
def "pure introspection "() {
92+
given:
93+
def sdl = '''
94+
type Query {
95+
hello: String
96+
}
97+
'''
98+
def schema = TestUtil.schema(sdl, [Query: [
99+
hello: { DataFetchingEnvironment dfe -> return "world" } as DataFetcher
100+
]])
101+
def graphql = GraphQL.newGraphQL(schema).build();
102+
103+
ExecutionInput ei = ExecutionInput.newExecutionInput()
104+
.query("{ __schema {types{name}} __type(name: \"Query\") {name} }")
105+
.profileExecution(true)
106+
.build()
107+
108+
when:
109+
def result = graphql.execute(ei)
110+
def profilerResult = ei.getGraphQLContext().get(ProfilerResult.PROFILER_CONTEXT_KEY) as ProfilerResult
111+
112+
then:
113+
result.getData()["__schema"] != null
114+
115+
then:
116+
profilerResult.getFieldsFetched() == [] as Set
117+
profilerResult.getTotalDataFetcherInvocations() == 0
118+
119+
}
120+
121+
60122
def "instrumented data fetcher"() {
61123
given:
62124
def sdl = '''
@@ -109,6 +171,7 @@ class ProfilerTest extends Specification {
109171
profilerResult.getTotalTrivialDataFetcherInvocations() == 1
110172
profilerResult.getTotalTrivialDataFetcherInvocations() == 1
111173
profilerResult.getTotalCustomDataFetcherInvocations() == 1
174+
profilerResult.getDataFetcherResultType() == ["/dog": MATERIALIZED]
112175
}
113176

114177

0 commit comments

Comments
 (0)