Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0651b82
adding Profiler
andimarek May 19, 2025
cc1de5e
progress
andimarek May 19, 2025
eef80ec
track running time
andimarek May 20, 2025
15cf642
tracking datafetcher result types
andimarek May 20, 2025
b7379ef
operation details
andimarek May 20, 2025
275eaac
wip
andimarek May 21, 2025
cd002c0
dataloader tracking
andimarek May 21, 2025
b75a14d
Merge branch 'master' into profiler-2
andimarek May 26, 2025
450a9fc
track dataloader dispatch
andimarek May 26, 2025
5e9e713
track dataloader dispatch
andimarek May 27, 2025
b3515b7
Merge branch 'master' into profiler-2
andimarek Jun 3, 2025
1800f77
Merge branch 'master' into profiler-2
andimarek Jun 30, 2025
221b416
non nullable handling
andimarek Jul 1, 2025
2ba4123
Merge branch 'master' into profiler-2
andimarek Jul 8, 2025
4008c11
master merging
andimarek Jul 9, 2025
f7e31e6
wip
andimarek Jul 16, 2025
3e29da4
Merge branch 'master' into profiler-2
andimarek Jul 16, 2025
999bb2f
handle non null case
andimarek Jul 16, 2025
5dde0e3
handle non null case
andimarek Jul 16, 2025
4a47b9d
add Map specific summary methods
andimarek Jul 17, 2025
0b7c15b
collect instrumentations
andimarek Jul 17, 2025
e6faa18
manual dispatch collected
andimarek Jul 18, 2025
0ab88da
wip
andimarek Jul 18, 2025
8bcdef8
wip
andimarek Jul 18, 2025
62f1e84
improve naming
andimarek Jul 18, 2025
76cdbc1
stabilize test
andimarek Jul 18, 2025
ca618b4
wip
andimarek Jul 18, 2025
535eb8e
counting wrapped trivial data fetchers
andimarek Jul 18, 2025
a04f5cb
introspection fields
andimarek Jul 19, 2025
572090e
data fetcher type statistics
andimarek Jul 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
introspection fields
  • Loading branch information
andimarek committed Jul 19, 2025
commit a04f5cbf95b051c6d0084348dc6dba31485aecd6
4 changes: 3 additions & 1 deletion src/main/java/graphql/Profiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import graphql.execution.instrumentation.Instrumentation;
import graphql.language.OperationDefinition;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLOutputType;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

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

}

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

}

Expand Down
13 changes: 12 additions & 1 deletion src/main/java/graphql/ProfilerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
import graphql.execution.instrumentation.ChainedInstrumentation;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys;
import graphql.introspection.Introspection;
import graphql.language.OperationDefinition;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLTypeUtil;
import graphql.schema.PropertyDataFetcher;
import graphql.schema.SingletonPropertyDataFetcher;
import org.jspecify.annotations.NullMarked;
Expand Down Expand Up @@ -58,8 +62,15 @@ private void collectInstrumentationClasses(List<String> result, Instrumentation


@Override
public void fieldFetched(Object fetchedObject, DataFetcher<?> originalDataFetcher, DataFetcher<?> dataFetcher, ResultPath path) {
public void fieldFetched(Object fetchedObject, DataFetcher<?> originalDataFetcher, DataFetcher<?> dataFetcher, ResultPath path, GraphQLFieldDefinition fieldDef, GraphQLOutputType parentType) {
String key = "/" + String.join("/", path.getKeysOnly());
if (Introspection.isIntrospectionTypes(GraphQLTypeUtil.unwrapAll(fieldDef.getType()))
|| Introspection.isIntrospectionTypes(GraphQLTypeUtil.unwrapAll(parentType))
|| fieldDef.getName().equals(Introspection.SchemaMetaFieldDef.getName())
|| fieldDef.getName().equals(Introspection.TypeMetaFieldDef.getName())
|| fieldDef.getName().equals(Introspection.TypeNameMetaFieldDef.getName())) {
return;
}
profilerResult.addFieldFetched(key);
profilerResult.incrementDataFetcherInvocationCount(key);
ProfilerResult.DataFetcherType dataFetcherType;
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/graphql/ProfilerResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ public List<String> getInstrumentationClasses() {
return instrumentationClasses;
}


public Map<String, Object> shortSummaryMap() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("executionId", Assert.assertNotNull(executionId));
Expand All @@ -276,6 +277,7 @@ public Map<String, Object> shortSummaryMap() {
result.put("totalRunTime", (endTime - startTime) + "(" + (endTime - startTime) / 1_000_000 + "ms)");
result.put("engineTotalRunningTime", engineTotalRunningTime + "(" + engineTotalRunningTime / 1_000_000 + "ms)");
result.put("totalDataFetcherInvocations", totalDataFetcherInvocations);
result.put("totalCustomDataFetcherInvocations", getTotalCustomDataFetcherInvocations());
result.put("totalTrivialDataFetcherInvocations", totalTrivialDataFetcherInvocations);
result.put("totalWrappedTrivialDataFetcherInvocations", totalWrappedTrivialDataFetcherInvocations);
result.put("fieldsFetchedCount", fieldsFetched.size());
Expand All @@ -284,6 +286,34 @@ public Map<String, Object> shortSummaryMap() {
result.put("oldStrategyDispatchingAll", oldStrategyDispatchingAll);
result.put("dispatchEvents", getDispatchEventsAsMap());
result.put("instrumentationClasses", instrumentationClasses);
int completedCount = 0;
int notCompletedCount = 0;
int materializedCount = 0;
// we want to minimize the overall size because it is intended to be logged
// and logging can be expensive and is limited in size very often
Map<String, String> resultTypes = new LinkedHashMap<>();
for (String field : dataFetcherResultType.keySet()) {
DataFetcherResultType dataFetcherResultType1 = dataFetcherResultType.get(field);
String shortType = null;
if (dataFetcherResultType1 == DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED) {
completedCount++;
shortType = "C";
} else if (dataFetcherResultType1 == DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED) {
notCompletedCount++;
shortType = "N";
} else if (dataFetcherResultType1 == DataFetcherResultType.MATERIALIZED) {
materializedCount++;
shortType = "M";
} else {
Assert.assertShouldNeverHappen();
}
resultTypes.put(field, Assert.assertNotNull(shortType));
}
result.put("dataFetcherResultTypesCount", Map.of(
DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED, completedCount,
DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED, notCompletedCount,
DataFetcherResultType.MATERIALIZED, materializedCount));
result.put("dataFetcherResultType", resultTypes);
return result;
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ private Object invokeDataFetcher(ExecutionContext executionContext, ExecutionStr
} else {
fetchedValueRaw = dataFetcher.get(dataFetchingEnvironment.get());
}
executionContext.getProfiler().fieldFetched(fetchedValueRaw, originalDataFetcher, dataFetcher, parameters.getPath());
executionContext.getProfiler().fieldFetched(fetchedValueRaw, originalDataFetcher, dataFetcher, parameters.getPath(), fieldDef, parameters.getExecutionStepInfo().getType());
fetchedValue = Async.toCompletableFutureOrMaterializedObject(fetchedValueRaw);
} catch (Exception e) {
fetchedValue = Async.exceptionallyCompletedFuture(e);
Expand Down
63 changes: 63 additions & 0 deletions src/test/groovy/graphql/ProfilerTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import java.util.concurrent.atomic.AtomicInteger
import static graphql.ExecutionInput.newExecutionInput
import static graphql.ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED
import static graphql.ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED
import static graphql.ProfilerResult.DataFetcherResultType.MATERIALIZED
import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.setEnableDataLoaderChaining
import static java.util.concurrent.CompletableFuture.supplyAsync

Expand Down Expand Up @@ -57,6 +58,67 @@ class ProfilerTest extends Specification {

}

def "introspection fields are ignored"() {
given:
def sdl = '''
type Query {
hello: String
}
'''
def schema = TestUtil.schema(sdl, [Query: [
hello: { DataFetchingEnvironment dfe -> return "world" } as DataFetcher
]])
def graphql = GraphQL.newGraphQL(schema).build();

ExecutionInput ei = ExecutionInput.newExecutionInput()
.query("{ hello __typename alias:__typename __schema {types{name}} __type(name: \"Query\") {name} }")
.profileExecution(true)
.build()

when:
def result = graphql.execute(ei)
def profilerResult = ei.getGraphQLContext().get(ProfilerResult.PROFILER_CONTEXT_KEY) as ProfilerResult

then:
result.getData()["hello"] == "world"

then:
profilerResult.getFieldsFetched() == ["/hello",] as Set
profilerResult.getTotalDataFetcherInvocations() == 1

}

def "pure introspection "() {
given:
def sdl = '''
type Query {
hello: String
}
'''
def schema = TestUtil.schema(sdl, [Query: [
hello: { DataFetchingEnvironment dfe -> return "world" } as DataFetcher
]])
def graphql = GraphQL.newGraphQL(schema).build();

ExecutionInput ei = ExecutionInput.newExecutionInput()
.query("{ __schema {types{name}} __type(name: \"Query\") {name} }")
.profileExecution(true)
.build()

when:
def result = graphql.execute(ei)
def profilerResult = ei.getGraphQLContext().get(ProfilerResult.PROFILER_CONTEXT_KEY) as ProfilerResult

then:
result.getData()["__schema"] != null

then:
profilerResult.getFieldsFetched() == [] as Set
profilerResult.getTotalDataFetcherInvocations() == 0

}


def "instrumented data fetcher"() {
given:
def sdl = '''
Expand Down Expand Up @@ -109,6 +171,7 @@ class ProfilerTest extends Specification {
profilerResult.getTotalTrivialDataFetcherInvocations() == 1
profilerResult.getTotalTrivialDataFetcherInvocations() == 1
profilerResult.getTotalCustomDataFetcherInvocations() == 1
profilerResult.getDataFetcherResultType() == ["/dog": MATERIALIZED]
}


Expand Down