/
timing.server.ts
122 lines (111 loc) · 2.75 KB
/
timing.server.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
import { type CreateReporter } from '@epic-web/cachified'
export type Timings = Record<
string,
Array<
{ desc?: string } & (
| { time: number; start?: never }
| { time?: never; start: number }
)
>
>
export function makeTimings(type: string, desc?: string) {
const timings: Timings = {
[type]: [{ desc, start: performance.now() }],
}
Object.defineProperty(timings, 'toString', {
value: function () {
return getServerTimeHeader(timings)
},
enumerable: false,
})
return timings
}
function createTimer(type: string, desc?: string) {
const start = performance.now()
return {
end(timings: Timings) {
let timingType = timings[type]
if (!timingType) {
// eslint-disable-next-line no-multi-assign
timingType = timings[type] = []
}
timingType.push({ desc, time: performance.now() - start })
},
}
}
export async function time<ReturnType>(
fn: Promise<ReturnType> | (() => ReturnType | Promise<ReturnType>),
{
type,
desc,
timings,
}: {
type: string
desc?: string
timings?: Timings
},
): Promise<ReturnType> {
const timer = createTimer(type, desc)
const promise = typeof fn === 'function' ? fn() : fn
if (!timings) return promise
const result = await promise
timer.end(timings)
return result
}
export function getServerTimeHeader(timings?: Timings) {
if (!timings) return ''
return Object.entries(timings)
.map(([key, timingInfos]) => {
const dur = timingInfos
.reduce((acc, timingInfo) => {
const time = timingInfo.time ?? performance.now() - timingInfo.start
return acc + time
}, 0)
.toFixed(1)
const desc = timingInfos
.map(t => t.desc)
.filter(Boolean)
.join(' & ')
return [
key.replaceAll(/(:| |@|=|;|,|\/|\\)/g, '_'),
desc ? `desc=${JSON.stringify(desc)}` : null,
`dur=${dur}`,
]
.filter(Boolean)
.join(';')
})
.join(',')
}
export function combineServerTimings(headers1: Headers, headers2: Headers) {
const newHeaders = new Headers(headers1)
newHeaders.append('Server-Timing', headers2.get('Server-Timing') ?? '')
return newHeaders.get('Server-Timing') ?? ''
}
export function cachifiedTimingReporter<Value>(
timings?: Timings,
): undefined | CreateReporter<Value> {
if (!timings) return
return ({ key }) => {
const cacheRetrievalTimer = createTimer(
`cache:${key}`,
`${key} cache retrieval`,
)
let getFreshValueTimer: ReturnType<typeof createTimer> | undefined
return event => {
switch (event.name) {
case 'getFreshValueStart':
getFreshValueTimer = createTimer(
`getFreshValue:${key}`,
`request forced to wait for a fresh ${key} value`,
)
break
case 'getFreshValueSuccess':
getFreshValueTimer?.end(timings)
break
case 'done':
cacheRetrievalTimer.end(timings)
break
}
}
}
}