forked from jskorepa/apollo-tracing-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
172 lines (154 loc) · 5.06 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import {
ResponsePath,
responsePathAsArray,
GraphQLResolveInfo,
GraphQLType
} from "graphql";
import { GraphQLExtension } from "graphql-extensions";
export interface TracingFormat {
version: 1;
startTime: string;
endTime: string;
duration: number;
calculation: {
startOffset: number;
duration: number;
};
execution: {
duration: number;
resolvers: {
path: (string | number)[];
parentType: string;
fieldName: string;
returnType: string;
startOffset: number;
duration: number;
}[];
};
}
interface ResolverCall {
path: ResponsePath;
fieldName: string;
parentType: GraphQLType;
returnType: GraphQLType;
startOffset: HighResolutionTime;
endOffset?: HighResolutionTime;
}
export class TracingExtension<TContext = any>
implements GraphQLExtension<TContext> {
private startWallTime?: Date;
private endWallTime?: Date;
private startHrTime?: HighResolutionTime;
private duration?: HighResolutionTime;
private calcStartOffset?: HighResolutionTime;
private calcEndOffset?: HighResolutionTime;
private executionStartOffset?: HighResolutionTime;
private executionEndOffset?: HighResolutionTime;
private resolverCalls: ResolverCall[] = [];
requestDidStart() {
this.startWallTime = new Date();
this.startHrTime = process.hrtime();
}
calculationDidStart() {
this.calcStartOffset = process.hrtime(this.startHrTime);
}
calculationDidEnd() {
this.calcEndOffset = process.hrtime(this.startHrTime);
}
executionDidStart() {
this.executionStartOffset = process.hrtime(this.startHrTime);
}
executionDidEnd() {
this.executionEndOffset = process.hrtime(this.startHrTime);
}
willResolveField(
_source: any,
_args: { [argName: string]: any },
_context: TContext,
info: GraphQLResolveInfo
) {
const resolverCall: ResolverCall = {
path: info.path,
fieldName: info.fieldName,
parentType: info.parentType,
returnType: info.returnType,
startOffset: process.hrtime(this.startHrTime)
};
this.resolverCalls.push(resolverCall);
return () => {
resolverCall.endOffset = process.hrtime(this.startHrTime);
};
}
didResolveField(
_source: any,
_args: { [argName: string]: any },
_context: TContext,
info: GraphQLResolveInfo
) {}
requestDidEnd() {
this.duration = process.hrtime(this.startHrTime);
this.endWallTime = new Date();
}
format(): [string, TracingFormat] | undefined {
// 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 this.startWallTime === "undefined" ||
typeof this.endWallTime === "undefined" ||
typeof this.duration === "undefined" ||
typeof this.calcEndOffset === "undefined" ||
typeof this.calcStartOffset === "undefined" ||
typeof this.executionStartOffset === "undefined" ||
typeof this.executionEndOffset === "undefined"
) {
return;
}
return [
"tracing",
{
version: 1,
startTime: this.startWallTime.toISOString(),
endTime: this.endWallTime.toISOString(),
duration: durationHrTimeToNanos(this.duration),
calculation: {
startOffset: durationHrTimeToNanos(this.calcStartOffset),
duration: (durationHrTimeToNanos(this.calcEndOffset) - durationHrTimeToNanos(this.calcStartOffset))
},
execution: {
duration: (durationHrTimeToNanos(this.executionEndOffset) - durationHrTimeToNanos(this.executionStartOffset)),
resolvers: this.resolverCalls.map(resolverCall => {
const startOffset = durationHrTimeToNanos(resolverCall.startOffset);
const duration = resolverCall.endOffset
? durationHrTimeToNanos(resolverCall.endOffset) - startOffset
: 0;
return {
path: responsePathAsArray(resolverCall.path),
parentType: resolverCall.parentType.toString(),
fieldName: resolverCall.fieldName,
returnType: resolverCall.returnType.toString(),
startOffset,
duration
};
})
}
}
];
}
}
type HighResolutionTime = [number, number];
// Converts an hrtime array (as returned from process.hrtime) to nanoseconds.
//
// ONLY CALL THIS ON VALUES REPRESENTING DELTAS, NOT ON THE RAW RETURN VALUE
// FROM process.hrtime() WITH NO ARGUMENTS.
//
// The entire point of the hrtime data structure is that the JavaScript Number
// type can't represent all int64 values without loss of precision:
// Number.MAX_SAFE_INTEGER nanoseconds is about 104 days. Calling this function
// on a duration that represents a value less than 104 days is fine. Calling
// this function on an absolute time (which is generally roughly time since
// system boot) is not a good idea.
function durationHrTimeToNanos(hrtime: HighResolutionTime) {
return hrtime[0] * 1e9 + hrtime[1];
}