Skip to content
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

refactor: Introduce "tracing" plugin and switch request pipeline to use it. #3991

Merged
merged 15 commits into from
May 12, 2020
Merged
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
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
import { Headers } from 'apollo-server-env';
import { buildServiceDefinition } from '@apollographql/apollo-tools';
import { Logger } from "apollo-server-types";
import { plugin as pluginTracing } from "apollo-tracing";

const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldDefinitionNode) {
Expand Down Expand Up @@ -783,11 +784,13 @@ export class ApolloServerBase {
}

private ensurePluginInstantiation(plugins?: PluginDefinition[]): void {
if (!plugins || !plugins.length) {
return;
const pluginsToInit = [...plugins || []];
abernix marked this conversation as resolved.
Show resolved Hide resolved

if (this.config.tracing) {
pluginsToInit.push(pluginTracing())
}

this.plugins = plugins.map(plugin => {
this.plugins = pluginsToInit.map(plugin => {
if (typeof plugin === 'function') {
return plugin();
}
Expand Down
6 changes: 0 additions & 6 deletions packages/apollo-server-core/src/requestPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
CacheControlExtension,
CacheControlExtensionOptions,
} from 'apollo-cache-control';
import { TracingExtension } from 'apollo-tracing';
import {
ApolloError,
fromGraphQLError,
Expand Down Expand Up @@ -95,7 +94,6 @@ export interface GraphQLRequestPipelineConfig<TContext> {
dataSources?: () => DataSources<TContext>;

extensions?: Array<() => GraphQLExtension>;
tracing?: boolean;
persistedQueries?: PersistedQueryOptions;
cacheControl?: CacheControlExtensionOptions;

Expand Down Expand Up @@ -600,10 +598,6 @@ export async function processGraphQLRequest<TContext>(
// objects.
const extensions = config.extensions ? config.extensions.map(f => f()) : [];

if (config.tracing) {
extensions.push(new TracingExtension());
}

if (config.cacheControl) {
cacheControlExtension = new CacheControlExtension(config.cacheControl);
extensions.push(cacheControlExtension);
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-tracing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"apollo-server-env": "file:../apollo-server-env",
"apollo-server-plugin-base": "file:../apollo-server-plugin-base",
"graphql-extensions": "file:../graphql-extensions"
},
"peerDependencies": {
Expand Down
93 changes: 92 additions & 1 deletion packages/apollo-tracing/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
GraphQLResolveInfo,
GraphQLType,
} from 'graphql';

import { ApolloServerPlugin } from "apollo-server-plugin-base";
import { GraphQLExtension } from 'graphql-extensions';

export interface TracingFormat {
Expand Down Expand Up @@ -33,6 +33,97 @@ interface ResolverCall {
endOffset?: HighResolutionTime;
}

export const plugin = (_futureOptions = {}) => (): ApolloServerPlugin => ({
requestDidStart() {
let startWallTime: Date | undefined;
let endWallTime: Date | undefined;
let startHrTime: HighResolutionTime | undefined;
let duration: HighResolutionTime | undefined;
const resolverCalls: ResolverCall[] = [];

startWallTime = new Date();
startHrTime = process.hrtime();
abernix marked this conversation as resolved.
Show resolved Hide resolved

return {
executionDidStart() {
// It's a little odd that we record the end time after execution rather
// than at the end of the whole request, but because we need to include
// our formatted trace in the request itself, we have to record it
// before the request is over! It's also odd that we don't do traces for
// parse or validation errors, but runQuery doesn't currently support
// that, as format() is only invoked after execution.
return () => {
duration = process.hrtime(startHrTime);
endWallTime = new Date();
};
},
willResolveField(...args) {
const [, , , info] = args;

const resolverCall: ResolverCall = {
path: info.path,
fieldName: info.fieldName,
parentType: info.parentType,
returnType: info.returnType,
startOffset: process.hrtime(startHrTime),
};

resolverCalls.push(resolverCall);

return () => {
resolverCall.endOffset = process.hrtime(startHrTime);
};
},
willSendResponse({ response }) {
// In the event that we are called prior to the initialization of
// critical date metrics, we'll return undefined to signal that the
// extension did not format properly. Any undefined extension
// results are simply purged by the graphql-extensions module.
if (
typeof startWallTime === 'undefined' ||
typeof endWallTime === 'undefined' ||
typeof duration === 'undefined'
) {
return;
}

const extensions =
response.extensions || (response.extensions = Object.create(null));

if (typeof extensions.tracing !== 'undefined') {
throw new Error("The tracing information already existed.");
abernix marked this conversation as resolved.
Show resolved Hide resolved
}

// Set the extensions.
extensions.tracing = {
version: 1,
startTime: startWallTime.toISOString(),
endTime: endWallTime.toISOString(),
duration: durationHrTimeToNanos(duration),
execution: {
resolvers: resolverCalls.map(resolverCall => {
const startOffset = durationHrTimeToNanos(
resolverCall.startOffset,
);
const duration = resolverCall.endOffset
? durationHrTimeToNanos(resolverCall.endOffset) - startOffset
: 0;
return {
path: [...responsePathAsArray(resolverCall.path)],
abernix marked this conversation as resolved.
Show resolved Hide resolved
parentType: resolverCall.parentType.toString(),
fieldName: resolverCall.fieldName,
returnType: resolverCall.returnType.toString(),
startOffset,
duration,
};
}),
},
};
},
};
},
})

export class TracingExtension<TContext = any>
implements GraphQLExtension<TContext> {
private startWallTime?: Date;
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-tracing/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"exclude": ["**/__tests__", "**/__mocks__"],
"references": [
{ "path": "../graphql-extensions" },
{ "path": "../apollo-server-plugin-base" },
]
}