Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 6 additions & 3 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 All @@ -40,10 +42,11 @@ public EngineRunningState() {
this.executionId = null;
}

public EngineRunningState(ExecutionInput executionInput) {
public EngineRunningState(ExecutionInput executionInput, Profiler profiler) {
EngineRunningObserver engineRunningObserver = executionInput.getGraphQLContext().get(EngineRunningObserver.ENGINE_RUNNING_OBSERVER_KEY);
if (engineRunningObserver != null) {
this.engineRunningObserver = engineRunningObserver;
EngineRunningObserver wrappedObserver = profiler.wrapEngineRunningObserver(engineRunningObserver);
if (wrappedObserver != null) {
this.engineRunningObserver = wrappedObserver;
this.graphQLContext = executionInput.getGraphQLContext();
this.executionId = executionInput.getExecutionId();
} else {
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;

/**
* Package level access to the graphql context
Expand Down Expand Up @@ -291,7 +299,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 @@ -368,6 +376,11 @@ public Builder dataLoaderRegistry(DataLoaderRegistry dataLoaderRegistry) {
return this;
}

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

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

CompletableFuture<InstrumentationState> instrumentationStateCF = instrumentation.createStateAsync(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInputWithId));
Expand All @@ -505,7 +507,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 @@ -537,7 +539,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 @@ -550,7 +552,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 @@ -614,13 +616,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);
}

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

import graphql.execution.EngineRunningObserver;
import graphql.execution.ResultPath;
import graphql.language.OperationDefinition;
import graphql.schema.DataFetcher;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@Internal
@NullMarked
public interface Profiler {


Profiler NO_OP = new Profiler() {
};


default void rootFieldCount(int size) {

}

default void subSelectionCount(int size) {

}

default void executionInput(ExecutionInput executionInput) {

}

default void dataLoaderUsed(String dataLoaderName) {


}

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

}

default @Nullable EngineRunningObserver wrapEngineRunningObserver(EngineRunningObserver engineRunningObserver) {
return engineRunningObserver;
}

default void operationDefinition(OperationDefinition operationDefinition) {

}

default void oldStrategyDispatchingAll(int level) {

}

default void chainedStrategyDispatching(int level) {

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

import graphql.execution.EngineRunningObserver;
import graphql.execution.ExecutionId;
import graphql.execution.ResultPath;
import graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys;
import graphql.language.OperationDefinition;
import graphql.schema.DataFetcher;
import graphql.schema.PropertyDataFetcher;
import graphql.schema.SingletonPropertyDataFetcher;
import org.jspecify.annotations.NullMarked;

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

@Internal
@NullMarked
public class ProfilerImpl implements Profiler {

private volatile long startTime;
private volatile long endTime;
private volatile long lastStartTime;
private final AtomicLong engineTotalRunningTime = new AtomicLong();


final ProfilerResult profilerResult = new ProfilerResult();

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

@Override
public void executionInput(ExecutionInput executionInput) {
profilerResult.setExecutionId(executionInput.getExecutionId());
boolean dataLoaderChainingEnabled = executionInput.getGraphQLContext().getBoolean(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING, false);
profilerResult.setDataLoaderChainingEnabled(dataLoaderChainingEnabled);
}

@Override
public void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) {
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(key, dataFetcherResultType);
}

profilerResult.setDataFetcherType(key, dataFetcherType);
}

@Override
public EngineRunningObserver wrapEngineRunningObserver(EngineRunningObserver engineRunningObserver) {
// nothing to wrap here
return new EngineRunningObserver() {
@Override
public void runningStateChanged(ExecutionId executionId, GraphQLContext graphQLContext, RunningState runningState) {
runningStateChangedImpl(executionId, graphQLContext, runningState);
if (engineRunningObserver != null) {
engineRunningObserver.runningStateChanged(executionId, graphQLContext, runningState);
}
}
};
}

private void runningStateChangedImpl(ExecutionId executionId, GraphQLContext graphQLContext, EngineRunningObserver.RunningState runningState) {
long now = System.nanoTime();
if (runningState == EngineRunningObserver.RunningState.RUNNING_START) {
startTime = now;
lastStartTime = startTime;
} else if (runningState == EngineRunningObserver.RunningState.NOT_RUNNING_FINISH) {
endTime = now;
engineTotalRunningTime.set(engineTotalRunningTime.get() + (endTime - lastStartTime));
profilerResult.setTimes(startTime, endTime, engineTotalRunningTime.get());
} else if (runningState == EngineRunningObserver.RunningState.RUNNING) {
lastStartTime = now;
} else if (runningState == EngineRunningObserver.RunningState.NOT_RUNNING) {
engineTotalRunningTime.set(engineTotalRunningTime.get() + (now - lastStartTime));
} else {
Assert.assertShouldNeverHappen();
}
}

@Override
public void operationDefinition(OperationDefinition operationDefinition) {
profilerResult.setOperation(operationDefinition);
}

@Override
public void dataLoaderUsed(String dataLoaderName) {
profilerResult.addDataLoaderUsed(dataLoaderName);
}

@Override
public void chainedStrategyDispatching(int level) {
profilerResult.chainedStrategyDispatching(level);
}

@Override
public void oldStrategyDispatchingAll(int level) {
profilerResult.oldStrategyDispatchingAll(level);
}
}
Loading