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
Next Next commit
adding Profiler
  • Loading branch information
andimarek committed May 19, 2025
commit 0651b82827c8aba586f361581275280f809f9447
2 changes: 2 additions & 0 deletions src/main/java/graphql/EngineRunningState.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import graphql.execution.EngineRunningObserver;
import graphql.execution.ExecutionId;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

import java.util.concurrent.CompletableFuture;
Expand All @@ -19,6 +20,7 @@
import static graphql.execution.EngineRunningObserver.RunningState.RUNNING_START;

@Internal
@NullMarked
public class EngineRunningState {

@Nullable
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/graphql/ExecutionInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class ExecutionInput {
private final Locale locale;
// this is currently not used but we want it back soon after the v23 release
private final AtomicBoolean cancelled;
private final boolean profileExecution;


@Internal
Expand All @@ -47,6 +48,7 @@ private ExecutionInput(Builder builder) {
this.localContext = builder.localContext;
this.extensions = builder.extensions;
this.cancelled = builder.cancelled;
this.profileExecution = builder.profileExecution;
}

/**
Expand Down Expand Up @@ -142,6 +144,11 @@ public Map<String, Object> getExtensions() {
return extensions;
}


public boolean isProfileExecution() {
return profileExecution;
}

/**
* This helps you transform the current ExecutionInput object into another one by starting a builder with all
* the current values and allows you to transform it how you want.
Expand Down Expand Up @@ -221,6 +228,7 @@ public static class Builder {
private Locale locale = Locale.getDefault();
private ExecutionId executionId;
private AtomicBoolean cancelled = new AtomicBoolean(false);
private boolean profileExecution;

public Builder query(String query) {
this.query = assertNotNull(query, () -> "query can't be null");
Expand Down Expand Up @@ -283,7 +291,7 @@ public Builder context(Object context) {
return this;
}

/**
/**
* This will give you a builder of {@link GraphQLContext} and any values you set will be copied
* into the underlying {@link GraphQLContext} of this execution input
*
Expand Down Expand Up @@ -360,6 +368,11 @@ public Builder dataLoaderRegistry(DataLoaderRegistry dataLoaderRegistry) {
return this;
}

public Builder profileExecution(boolean profileExecution) {
this.profileExecution = profileExecution;
return this;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For another PR, not this one

Can you add the toggle to GraphQLUnusualConfiguration instead?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be discussed!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be considered in another PR


public ExecutionInput build() {
return new ExecutionInput(this);
}
Expand Down
13 changes: 8 additions & 5 deletions src/main/java/graphql/GraphQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ public CompletableFuture<ExecutionResult> executeAsync(UnaryOperator<ExecutionIn
* @return a promise to an {@link ExecutionResult} which can include errors
*/
public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionInput) {
Profiler profiler = executionInput.isProfileExecution() ? new ProfilerImpl(executionInput.getGraphQLContext()) : Profiler.NO_OP;
profiler.start();
EngineRunningState engineRunningState = new EngineRunningState(executionInput);
return engineRunningState.engineRun(() -> {
ExecutionInput executionInputWithId = ensureInputHasId(executionInput);
Expand All @@ -439,7 +441,7 @@ public CompletableFuture<ExecutionResult> executeAsync(ExecutionInput executionI

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

CompletableFuture<ExecutionResult> executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState, engineRunningState);
CompletableFuture<ExecutionResult> executionResult = parseValidateAndExecute(instrumentedExecutionInput, graphQLSchema, instrumentationState, engineRunningState, profiler);
//
// finish up instrumentation
executionResult = executionResult.whenComplete(completeInstrumentationCtxCF(executionInstrumentation));
Expand Down Expand Up @@ -471,7 +473,7 @@ private ExecutionInput ensureInputHasId(ExecutionInput executionInput) {
}


private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState, EngineRunningState engineRunningState) {
private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState, EngineRunningState engineRunningState, Profiler profiler) {
AtomicReference<ExecutionInput> executionInputRef = new AtomicReference<>(executionInput);
Function<ExecutionInput, PreparsedDocumentEntry> computeFunction = transformedInput -> {
// if they change the original query in the pre-parser, then we want to see it downstream from then on
Expand All @@ -484,7 +486,7 @@ private CompletableFuture<ExecutionResult> parseValidateAndExecute(ExecutionInpu
return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors()));
}
try {
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState);
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState, engineRunningState, profiler);
} catch (AbortExecutionException e) {
return CompletableFuture.completedFuture(e.toExecutionResult());
}
Expand Down Expand Up @@ -548,13 +550,14 @@ private CompletableFuture<ExecutionResult> execute(ExecutionInput executionInput
Document document,
GraphQLSchema graphQLSchema,
InstrumentationState instrumentationState,
EngineRunningState engineRunningState
EngineRunningState engineRunningState,
Profiler profiler
) {

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

return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState);
return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState, profiler);
}

}
31 changes: 31 additions & 0 deletions src/main/java/graphql/Profiler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package graphql;

import graphql.execution.ResultPath;
import graphql.schema.DataFetcher;
import org.jspecify.annotations.NullMarked;

@Internal
@NullMarked
public interface Profiler {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a quick JavaDoc explaining what this does

A Profiler monitors important execution timings and counters. Enable this via ExecutionInput/new Unusual Configuration method

Of course 1 sentence isn't enough, let's add a documentation link here when it's written

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an internal class btw



Profiler NO_OP = new Profiler() {
};

default void start() {

}


default void rootFieldCount(int size) {

}

default void subSelectionCount(int size) {

}

default void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {

}
}
51 changes: 51 additions & 0 deletions src/main/java/graphql/ProfilerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package graphql;

import graphql.execution.ResultPath;
import graphql.schema.DataFetcher;
import org.jspecify.annotations.NullMarked;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, but in the future, with more Profiler features you might want to introduce options to toggle sets of tracking on and off

e.g. toggle DataLoader related tracking on and off

@Internal
@NullMarked
public class ProfilerImpl implements Profiler {

volatile long startTime;
volatile int rootFieldCount;

AtomicInteger propertyDataFetcherCount;

final Map<String, Integer> dataFetcherInvocationCount = new ConcurrentHashMap<>();


final ProfilerResult profilerResult = new ProfilerResult();

public ProfilerImpl(GraphQLContext graphQLContext) {
graphQLContext.put(ProfilerResult.PROFILER_CONTEXT_KEY, profilerResult);
}

@Override
public void start() {
startTime = System.nanoTime();
}


@Override
public void rootFieldCount(int count) {
this.rootFieldCount = count;
}

@Override
public void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {
String key = String.join("/", path.getKeysOnly());
profilerResult.addFieldFetched(key);

// dataFetcherInvocationCount.compute(key, (k, v) -> v == null ? 1 : v + 1);
//
// if (dataFetcher instanceof PropertyDataFetcher) {
// propertyDataFetcherCount.incrementAndGet();
// }
}
}
37 changes: 37 additions & 0 deletions src/main/java/graphql/ProfilerResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package graphql;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@ExperimentalApi
public class ProfilerResult {

public static String PROFILER_CONTEXT_KEY = "__GJ_PROFILER";

private int fieldCount;

private int propertyDataFetcherCount;

private final Set<String> fieldsFetched = ConcurrentHashMap.newKeySet();

public static enum ResultType {
COMPLETABLE_FUTURE_COMPLETED,
COMPLETABLE_FUTURE_NOT_COMPLETED,
MATERIALIZED

}

private Map<String, ResultType> queryPathToResultType;


public void addFieldFetched(String fieldPath) {
fieldsFetched.add(fieldPath);
}

public Set<String> getFieldsFetched() {
return fieldsFetched;
}


}
6 changes: 3 additions & 3 deletions src/main/java/graphql/execution/Execution.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.ExperimentalApi;
import graphql.GraphQLContext;
import graphql.GraphQLError;
import graphql.Internal;
import graphql.Profiler;
import graphql.execution.incremental.IncrementalCallState;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.InstrumentationContext;
Expand Down Expand Up @@ -37,7 +37,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

Expand Down Expand Up @@ -77,7 +76,7 @@ public Execution(ExecutionStrategy queryStrategy,
this.doNotAutomaticallyDispatchDataLoader = doNotAutomaticallyDispatchDataLoader;
}

public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState, EngineRunningState engineRunningState) {
public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionInput executionInput, InstrumentationState instrumentationState, EngineRunningState engineRunningState, Profiler profiler) {
NodeUtil.GetOperationResult getOperationResult;
CoercedVariables coercedVariables;
Supplier<NormalizedVariables> normalizedVariableValues;
Expand Down Expand Up @@ -118,6 +117,7 @@ public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSche
.executionInput(executionInput)
.propagapropagateErrorsOnNonNullContractFailureeErrors(propagateErrorsOnNonNullContractFailure)
.engineRunningState(engineRunningState)
.profiler(profiler)
.build();

executionContext.getGraphQLContext().put(ResultNodesInfo.RESULT_NODES_INFO, executionContext.getResultNodesInfo());
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/graphql/execution/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import graphql.GraphQLContext;
import graphql.GraphQLError;
import graphql.Internal;
import graphql.Profiler;
import graphql.PublicApi;
import graphql.collect.ImmutableKit;
import graphql.execution.incremental.IncrementalCallState;
Expand All @@ -29,7 +30,6 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;
Expand Down Expand Up @@ -67,14 +67,14 @@ public class ExecutionContext {
private final Supplier<ExecutableNormalizedOperation> queryTree;
private final boolean propagateErrorsOnNonNullContractFailure;

private final AtomicInteger isRunning = new AtomicInteger(0);

// this is modified after creation so it needs to be volatile to ensure visibility across Threads
private volatile DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = DataLoaderDispatchStrategy.NO_OP;

private final ResultNodesInfo resultNodesInfo = new ResultNodesInfo();
private final EngineRunningState engineRunningState;

private final Profiler profiler;

ExecutionContext(ExecutionContextBuilder builder) {
this.graphQLSchema = builder.graphQLSchema;
this.executionId = builder.executionId;
Expand Down Expand Up @@ -102,6 +102,7 @@ public class ExecutionContext {
this.queryTree = FpKit.interThreadMemoize(() -> ExecutableNormalizedOperationFactory.createExecutableNormalizedOperation(graphQLSchema, operationDefinition, fragmentsByName, coercedVariables));
this.propagateErrorsOnNonNullContractFailure = builder.propagateErrorsOnNonNullContractFailure;
this.engineRunningState = builder.engineRunningState;
this.profiler = builder.profiler;
}

public ExecutionId getExecutionId() {
Expand Down Expand Up @@ -376,4 +377,10 @@ public boolean hasIncrementalSupport() {
public EngineRunningState getEngineRunningState() {
return engineRunningState;
}


@Internal
public Profiler getProfiler() {
return profiler;
}
}
8 changes: 8 additions & 0 deletions src/main/java/graphql/execution/ExecutionContextBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import graphql.GraphQLContext;
import graphql.GraphQLError;
import graphql.Internal;
import graphql.Profiler;
import graphql.collect.ImmutableKit;
import graphql.execution.instrumentation.Instrumentation;
import graphql.execution.instrumentation.InstrumentationState;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class ExecutionContextBuilder {
boolean propagateErrorsOnNonNullContractFailure = true;
EngineRunningState engineRunningState;
ResponseMapFactory responseMapFactory = ResponseMapFactory.DEFAULT;
Profiler profiler;

/**
* @return a new builder of {@link graphql.execution.ExecutionContext}s
Expand Down Expand Up @@ -102,6 +104,7 @@ public ExecutionContextBuilder() {
propagateErrorsOnNonNullContractFailure = other.propagateErrorsOnNonNullContractFailure();
engineRunningState = other.getEngineRunningState();
responseMapFactory = other.getResponseMapFactory();
profiler = other.getProfiler();
}

public ExecutionContextBuilder instrumentation(Instrumentation instrumentation) {
Expand Down Expand Up @@ -253,4 +256,9 @@ public ExecutionContextBuilder engineRunningState(EngineRunningState engineRunni
this.engineRunningState = engineRunningState;
return this;
}

public ExecutionContextBuilder profiler(Profiler profiler) {
this.profiler = profiler;
return this;
}
}
Loading