Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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