Repository navigation
adding Profiler #3976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
adding Profiler #3976
Changes from 20 commits
0651b82
cc1de5e
eef80ec
15cf642
b7379ef
275eaac
cd002c0
b75a14d
450a9fc
5e9e713
b3515b7
1800f77
221b416
2ba4123
4008c11
f7e31e6
3e29da4
999bb2f
5dde0e3
4a47b9d
0b7c15b
e6faa18
0ab88da
8bcdef8
62f1e84
76cdbc1
ca618b4
535eb8e
a04f5cb
572090e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| 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 { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a quick JavaDoc explaining what this does
Of course 1 sentence isn't enough, let's add a documentation link here when it's written
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 setExecutionInput(ExecutionInput executionInput) { | ||
|
|
||
| } | ||
|
|
||
| default void dataLoaderUsed(String dataLoaderName) { | ||
|
|
||
|
|
||
| } | ||
|
|
||
| default void fieldFetched(Object fetchedObject, DataFetcher<?> dataFetcher, ResultPath path) { | ||
|
|
||
| } | ||
|
|
||
| default @Nullable EngineRunningObserver wrapEngineRunningObserver(@Nullable EngineRunningObserver engineRunningObserver) { | ||
| return engineRunningObserver; | ||
| } | ||
|
|
||
| default void operationDefinition(OperationDefinition operationDefinition) { | ||
|
|
||
| } | ||
|
|
||
| default void oldStrategyDispatchingAll(int level) { | ||
|
|
||
| } | ||
|
|
||
| default void chainedStrategyDispatching(int level) { | ||
|
|
||
| } | ||
|
|
||
| default void batchLoadedOldStrategy(String name, int level, int count) { | ||
|
|
||
|
|
||
| } | ||
|
|
||
| default void batchLoadedNewStrategy(String name, @Nullable Integer level, int count) { | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| 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 org.jspecify.annotations.Nullable; | ||
|
|
||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.atomic.AtomicLong; | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
|
||
| 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 setExecutionInput(ExecutionInput executionInput) { | ||
| profilerResult.setExecutionId(executionInput.getExecutionIdNonNull()); | ||
| 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(@Nullable EngineRunningObserver engineRunningObserver) { | ||
| // nothing to wrap here | ||
| return new EngineRunningObserver() { | ||
| @Override | ||
| public void runningStateChanged(@Nullable ExecutionId executionId, GraphQLContext graphQLContext, RunningState runningState) { | ||
| runningStateChangedImpl(executionId, graphQLContext, runningState); | ||
| if (engineRunningObserver != null) { | ||
| engineRunningObserver.runningStateChanged(executionId, graphQLContext, runningState); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| private void runningStateChangedImpl(@Nullable 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make it more obvious to a user, can comment on this method (or interface method) to indicate that this refers to the option I think to us, we know what Chained DataLoaders are but this is not a well known phrase I'll also make a note to mention in the documentation, this is a way to measure the impact of Chained DataLoaders |
||
| } | ||
|
|
||
| @Override | ||
| public void oldStrategyDispatchingAll(int level) { | ||
| profilerResult.oldStrategyDispatchingAll(level); | ||
| } | ||
|
|
||
| @Override | ||
| public void batchLoadedOldStrategy(String name, int level, int count) { | ||
| profilerResult.addDispatchEvent(name, level, count); | ||
| } | ||
|
|
||
| @Override | ||
| public void batchLoadedNewStrategy(String name, @Nullable Integer level, int count) { | ||
| profilerResult.addDispatchEvent(name, level, count); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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
GraphQLUnusualConfigurationinstead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be discussed!
There was a problem hiding this comment.
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