1 | import {
|
2 | responsePathAsArray,
|
3 | GraphQLResolveInfo,
|
4 | GraphQLFieldResolver
|
5 | } from "graphql";
|
6 |
|
7 | type HighResolutionTime = [number, number];
|
8 |
|
9 | const traceHRStartTime = Symbol("HR Start Time");
|
10 | const traceWallStartTime = Symbol("Wall Start Time");
|
11 | export const apolloTracingContext = Symbol("Apollo Tracing Context");
|
12 |
|
13 | export interface ApolloTracingContext {
|
14 | [traceHRStartTime]: HighResolutionTime;
|
15 | [traceWallStartTime]: Date;
|
16 | version: 1;
|
17 | startTime: string;
|
18 | endTime: string;
|
19 | duration: number;
|
20 | execution: {
|
21 | parsing?: {
|
22 | startOffset: number;
|
23 | duration: number;
|
24 | };
|
25 | validation?: {
|
26 | startOffset: number;
|
27 | duration: number;
|
28 | };
|
29 | resolvers: ApolloTracingResolverStats[];
|
30 | };
|
31 | }
|
32 |
|
33 | export interface ApolloTracingResolverStats {
|
34 | path: (string | number)[];
|
35 | parentType: string;
|
36 | fieldName: string;
|
37 | returnType: string;
|
38 | startOffset: number;
|
39 | duration: number;
|
40 | }
|
41 |
|
42 | function durationHrTimeToNanos(hrtime: HighResolutionTime): number {
|
43 | return hrtime[0] * 1e9 + hrtime[1];
|
44 | }
|
45 |
|
46 | export async function apolloTracingGraphQLMiddleware(
|
47 | resolve: GraphQLFieldResolver<any, any, any>,
|
48 | parent: unknown,
|
49 | args: unknown,
|
50 | context: { [apolloTracingContext]: ApolloTracingContext },
|
51 | info: GraphQLResolveInfo
|
52 | ): Promise<any> {
|
53 | const startOffset = durationHrTimeToNanos(
|
54 | process.hrtime(context[apolloTracingContext][traceHRStartTime])
|
55 | );
|
56 | try {
|
57 | return await resolve(parent, args, context, info);
|
58 | } finally {
|
59 | context[apolloTracingContext].execution.resolvers.push({
|
60 | path: [...responsePathAsArray(info.path)],
|
61 | parentType: info.parentType.toString(),
|
62 | fieldName: info.fieldName,
|
63 | returnType: info.returnType.toString(),
|
64 | startOffset,
|
65 | duration:
|
66 | durationHrTimeToNanos(
|
67 | process.hrtime(context[apolloTracingContext][traceHRStartTime])
|
68 | ) - startOffset
|
69 | });
|
70 | }
|
71 | }
|
72 |
|
73 | export function startTracingContext(): ApolloTracingContext {
|
74 | const wallStartTime = new Date();
|
75 | return {
|
76 | [traceHRStartTime]: process.hrtime(),
|
77 | [traceWallStartTime]: wallStartTime,
|
78 | version: 1,
|
79 | startTime: wallStartTime.toISOString(),
|
80 | endTime: "",
|
81 | duration: 0,
|
82 | execution: {
|
83 | resolvers: []
|
84 | }
|
85 | };
|
86 | }
|
87 |
|
88 | export function endTracingContext(context: ApolloTracingContext): void {
|
89 | context.endTime = new Date().toISOString();
|
90 | context.duration = durationHrTimeToNanos(
|
91 | process.hrtime(context[traceHRStartTime])
|
92 | );
|
93 | }
|