timers.js
3.18 KB
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
const EE = require('events')
const { resolve } = require('path')
const fs = require('@npmcli/fs')
const log = require('./log-shim')
const _timeListener = Symbol('timeListener')
const _timeEndListener = Symbol('timeEndListener')
const _init = Symbol('init')
// This is an event emiiter but on/off
// only listen on a single internal event that gets
// emitted whenever a timer ends
class Timers extends EE {
file = null
#unfinished = new Map()
#finished = {}
#onTimeEnd = Symbol('onTimeEnd')
#initialListener = null
#initialTimer = null
constructor ({ listener = null, start = 'npm' } = {}) {
super()
this.#initialListener = listener
this.#initialTimer = start
this[_init]()
}
get unfinished () {
return this.#unfinished
}
get finished () {
return this.#finished
}
[_init] () {
this.on()
if (this.#initialListener) {
this.on(this.#initialListener)
}
process.emit('time', this.#initialTimer)
this.started = this.#unfinished.get(this.#initialTimer)
}
on (listener) {
if (listener) {
super.on(this.#onTimeEnd, listener)
} else {
process.on('time', this[_timeListener])
process.on('timeEnd', this[_timeEndListener])
}
}
off (listener) {
if (listener) {
super.off(this.#onTimeEnd, listener)
} else {
this.removeAllListeners(this.#onTimeEnd)
process.off('time', this[_timeListener])
process.off('timeEnd', this[_timeEndListener])
}
}
time (name, fn) {
process.emit('time', name)
const end = () => process.emit('timeEnd', name)
if (typeof fn === 'function') {
const res = fn()
return res && res.finally ? res.finally(end) : (end(), res)
}
return end
}
load ({ dir } = {}) {
if (dir) {
this.file = resolve(dir, '_timing.json')
}
}
writeFile (fileData) {
if (!this.file) {
return
}
try {
const globalStart = this.started
const globalEnd = this.#finished.npm || Date.now()
const content = {
...fileData,
...this.#finished,
// add any unfinished timers with their relative start/end
unfinished: [...this.#unfinished.entries()].reduce((acc, [name, start]) => {
acc[name] = [start - globalStart, globalEnd - globalStart]
return acc
}, {}),
}
// we append line delimited json to this file...forever
// XXX: should we also write a process specific timing file?
// with similar rules to the debug log (max files, etc)
fs.withOwnerSync(
this.file,
() => fs.appendFileSync(this.file, JSON.stringify(content) + '\n'),
{ owner: 'inherit' }
)
} catch (e) {
this.file = null
log.warn('timing', `could not write timing file: ${e}`)
}
}
[_timeListener] = (name) => {
this.#unfinished.set(name, Date.now())
}
[_timeEndListener] = (name) => {
if (this.#unfinished.has(name)) {
const ms = Date.now() - this.#unfinished.get(name)
this.#finished[name] = ms
this.#unfinished.delete(name)
this.emit(this.#onTimeEnd, name, ms)
} else {
log.silly('timing', "Tried to end timer that doesn't exist:", name)
}
}
}
module.exports = Timers