Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
tracking datafetcher result types
  • Loading branch information
andimarek committed May 20, 2025
commit 15cf6428f1a23ae2be7d5de418c754c85838b3aa
17 changes: 16 additions & 1 deletion src/main/java/graphql/ProfilerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import graphql.schema.SingletonPropertyDataFetcher;
import org.jspecify.annotations.NullMarked;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;

@Internal
Expand All @@ -33,15 +34,29 @@ public void setExecutionId(ExecutionId executionId) {

@Override
public void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {
String key = String.join("/", path.getKeysOnly());
String key = "/" + String.join("/", path.getKeysOnly());
profilerResult.addFieldFetched(key);
profilerResult.incrementDataFetcherInvocationCount(key);
ProfilerResult.DataFetcherType dataFetcherType;
if (dataFetcher instanceof PropertyDataFetcher || dataFetcher instanceof SingletonPropertyDataFetcher) {
dataFetcherType = ProfilerResult.DataFetcherType.PROPERTY_DATA_FETCHER;
} else {
dataFetcherType = ProfilerResult.DataFetcherType.CUSTOM;
// we only record the type of the result if it is not a PropertyDataFetcher
ProfilerResult.DataFetcherResultType dataFetcherResultType;
if (fetchedObject instanceof CompletableFuture) {
CompletableFuture<?> completableFuture = (CompletableFuture<?>) fetchedObject;
if (completableFuture.isDone()) {
dataFetcherResultType = ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED;
} else {
dataFetcherResultType = ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED;
}
} else {
dataFetcherResultType = ProfilerResult.DataFetcherResultType.MATERIALIZED;
}
profilerResult.setDataFetcherResultType(path.toString(), dataFetcherResultType);
}

profilerResult.setDataFetcherType(key, dataFetcherType);
}

Expand Down
14 changes: 13 additions & 1 deletion src/main/java/graphql/ProfilerResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ public class ProfilerResult {
private final Map<String, Integer> dataFetcherInvocationCount = new ConcurrentHashMap<>();
private final Map<String, DataFetcherType> dataFetcherTypeMap = new ConcurrentHashMap<>();

// the key is the whole result key, not just the query path
private final Map<String, DataFetcherResultType> dataFetcherResultType = new ConcurrentHashMap<>();


public enum DataFetcherType {
PROPERTY_DATA_FETCHER,
CUSTOM
}

public enum ResultType {
public enum DataFetcherResultType {
COMPLETABLE_FUTURE_COMPLETED,
COMPLETABLE_FUTURE_NOT_COMPLETED,
MATERIALIZED
Expand All @@ -50,6 +53,10 @@ void setDataFetcherType(String key, DataFetcherType dataFetcherType) {
}
}

void setDataFetcherResultType(String resultPath, DataFetcherResultType fetchedType) {
dataFetcherResultType.put(resultPath, fetchedType);
}

void incrementDataFetcherInvocationCount(String key) {
dataFetcherInvocationCount.compute(key, (k, v) -> v == null ? 1 : v + 1);
}
Expand Down Expand Up @@ -122,6 +129,10 @@ public long getTotalExecutionTime() {
return endTime - startTime;
}

public Map<String, DataFetcherResultType> getDataFetcherResultType() {
return dataFetcherResultType;
}

@Override
public String toString() {
return "ProfilerResult{" +
Expand All @@ -134,6 +145,7 @@ public String toString() {
", totalPropertyDataFetcherInvocations=" + totalPropertyDataFetcherInvocations +
", dataFetcherInvocationCount=" + dataFetcherInvocationCount +
", dataFetcherTypeMap=" + dataFetcherTypeMap +
", dataFetcherResultType=" + dataFetcherResultType +
'}';
}
}
49 changes: 47 additions & 2 deletions src/test/groovy/graphql/ProfilerTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import spock.lang.Specification
import java.time.Duration
import java.util.concurrent.CompletableFuture

import static graphql.ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_COMPLETED
import static graphql.ProfilerResult.DataFetcherResultType.COMPLETABLE_FUTURE_NOT_COMPLETED

class ProfilerTest extends Specification {


Expand Down Expand Up @@ -35,7 +38,7 @@ class ProfilerTest extends Specification {
result.getData() == [hello: "world"]

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

}

Expand Down Expand Up @@ -71,7 +74,7 @@ class ProfilerTest extends Specification {
result.getData() == [foo: [[id: "1", bar: "1"], [id: "2", bar: "2"], [id: "3", bar: "3"]]]

then:
profilerResult.getFieldsFetched() == ["foo", "foo/bar", "foo/id"] as Set
profilerResult.getFieldsFetched() == ["/foo", "/foo/bar", "/foo/id"] as Set
profilerResult.getTotalDataFetcherInvocations() == 7
profilerResult.getTotalCustomDataFetcherInvocations() == 4
profilerResult.getTotalPropertyDataFetcherInvocations() == 3
Expand Down Expand Up @@ -125,5 +128,47 @@ class ProfilerTest extends Specification {

}

def "data fetcher result types"() {
given:
def sdl = '''
type Query {
foo: Foo
}
type Foo {
id: String
name: String
}
'''
def schema = TestUtil.schema(sdl, [
Query: [
foo: { DataFetchingEnvironment dfe ->
return CompletableFuture.supplyAsync {
Thread.sleep(100)
return [id: "1", name: "foo"]
}
} as DataFetcher],
Foo : [
name: { DataFetchingEnvironment dfe ->
return CompletableFuture.completedFuture(dfe.source.name)
} as DataFetcher
]])
def graphql = GraphQL.newGraphQL(schema).build();

ExecutionInput ei = ExecutionInput.newExecutionInput()
.query("{ foo { id name } }")
.profileExecution(true)
.build()

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

then:
result.getData() == [foo: [id: "1", name: "foo"]]
profilerResult.getDataFetcherResultType() == ["/foo/name": COMPLETABLE_FUTURE_COMPLETED, "/foo": COMPLETABLE_FUTURE_NOT_COMPLETED]


}


}