forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
trace.ts
121 lines (108 loc) · 2.93 KB
/
trace.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
import { SpanId } from './shared'
import { reporter } from './report'
const NUM_OF_MICROSEC_IN_SEC = BigInt('1000')
let count = 0
const getId = () => {
count++
return count
}
// eslint typescript has a bug with TS enums
/* eslint-disable no-shadow */
export enum SpanStatus {
Started,
Stopped,
}
export class Span {
name: string
id: SpanId
parentId?: SpanId
duration: number | null
attrs: { [key: string]: any }
status: SpanStatus
now: number
_start: bigint
constructor({
name,
parentId,
attrs,
startTime,
}: {
name: string
parentId?: SpanId
startTime?: bigint
attrs?: Object
}) {
this.name = name
this.parentId = parentId
this.duration = null
this.attrs = attrs ? { ...attrs } : {}
this.status = SpanStatus.Started
this.id = getId()
this._start = startTime || process.hrtime.bigint()
// hrtime cannot be used to reconstruct tracing span's actual start time
// since it does not have relation to clock time:
// `These times are relative to an arbitrary time in the past, and not related to the time of day and therefore not subject to clock drift`
// https://nodejs.org/api/process.html#processhrtimetime
// Capturing current datetime as additional metadata for external reconstruction.
this.now = Date.now()
}
// Durations are reported as microseconds. This gives 1000x the precision
// of something like Date.now(), which reports in milliseconds.
// Additionally, ~285 years can be safely represented as microseconds as
// a float64 in both JSON and JavaScript.
stop(stopTime?: bigint) {
const end: bigint = stopTime || process.hrtime.bigint()
const duration = (end - this._start) / NUM_OF_MICROSEC_IN_SEC
this.status = SpanStatus.Stopped
if (duration > Number.MAX_SAFE_INTEGER) {
throw new Error(`Duration is too long to express as float64: ${duration}`)
}
const timestamp = this._start / NUM_OF_MICROSEC_IN_SEC
reporter.report(
this.name,
Number(duration),
Number(timestamp),
this.id,
this.parentId,
this.attrs,
this.now
)
}
traceChild(name: string, attrs?: Object) {
return new Span({ name, parentId: this.id, attrs })
}
manualTraceChild(
name: string,
startTime: bigint,
stopTime: bigint,
attrs?: Object
) {
const span = new Span({ name, parentId: this.id, attrs, startTime })
span.stop(stopTime)
}
setAttribute(key: string, value: any) {
this.attrs[key] = String(value)
}
traceFn<T>(fn: () => T): T {
try {
return fn()
} finally {
this.stop()
}
}
async traceAsyncFn<T>(fn: () => T | Promise<T>): Promise<T> {
try {
return await fn()
} finally {
this.stop()
}
}
}
export const trace = (
name: string,
parentId?: SpanId,
attrs?: { [key: string]: string }
) => {
return new Span({ name, parentId, attrs })
}
export const flushAllTraces = () => reporter.flushAll()