-
Notifications
You must be signed in to change notification settings - Fork 5
/
profile.ts
112 lines (101 loc) · 2.37 KB
/
profile.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
/*
* Copyright (c) 2020, J2 Innovations. All Rights Reserved
*/
/* eslint @typescript-eslint/no-explicit-any: "off", @typescript-eslint/explicit-module-boundary-types: "off" */
function out(text: string): void {
console.log(text)
}
function profileFunc(
targetName: string,
propertyKey: string,
description: string,
func: () => any
): unknown {
// Date.now() has it's issues but it's good enough for the use case and works in a
// browser, node and deno.
const t0 = Date.now()
function profileEnd(): void {
const t1 = Date.now()
out(
`Profiled ${targetName ? `${targetName}.` : ''}${propertyKey}${
description ? ` - ${description} ` : ''
}: ${t1 - t0}ms`
)
}
let isAsync = false
try {
const retVal = func()
// Attempt to detect whether a promise is being returned or not.
if (
retVal &&
typeof retVal === 'object' &&
typeof retVal.then === 'function' &&
typeof retVal.finally === 'function'
) {
isAsync = true
// If a promise is returned then latch onto it so we can measure the time.
return retVal.finally(profileEnd)
}
return retVal
} finally {
if (!isAsync) {
profileEnd()
}
}
}
/**
* A property accessor decorator used for profiling method call, getters and setters.
*
* This API is considered experimental.
*/
export function profile(
description = ''
): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
): PropertyDescriptor {
// The original getter.
const get = descriptor?.get
const set = descriptor?.set
const value = descriptor?.value
const targetName = target?.constructor?.name || ''
if (typeof get === 'function') {
return {
get(): any {
return profileFunc(
targetName,
propertyKey,
description,
() => get.call(this)
)
},
}
} else if (typeof set === 'function') {
return {
set(arg: any): any {
return profileFunc(
targetName,
propertyKey,
description,
() => set.call(this, arg)
)
},
}
} else if (typeof value === 'function') {
return {
value(...args: any[]): any {
return profileFunc(
targetName,
propertyKey,
description,
() => value.apply(this, args)
)
},
}
} else {
throw new Error(`Unsupported profile call for ${propertyKey}`)
}
}
}