qdone 2.0.13-alpha → 2.0.14-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commonjs/src/consumer.js +90 -27
- package/commonjs/src/defaults.js +18 -1
- package/commonjs/src/idleQueues.js +2 -3
- package/commonjs/src/scheduler/jobExecutor.js +109 -60
- package/commonjs/src/scheduler/queueManager.js +116 -35
- package/commonjs/src/scheduler/systemMonitor.js +21 -37
- package/package.json +1 -1
- package/src/consumer.js +49 -20
- package/src/defaults.js +20 -1
- package/src/idleQueues.js +1 -1
- package/src/scheduler/jobExecutor.js +75 -30
- package/src/scheduler/queueManager.js +86 -23
- package/src/scheduler/systemMonitor.js +18 -32
|
@@ -20,6 +20,7 @@ export class JobExecutor {
|
|
|
20
20
|
constructor (opt) {
|
|
21
21
|
this.opt = opt
|
|
22
22
|
this.jobs = []
|
|
23
|
+
this.jobsByMessageId = {}
|
|
23
24
|
this.stats = {
|
|
24
25
|
activeJobs: 0,
|
|
25
26
|
sqsCalls: 0,
|
|
@@ -28,15 +29,16 @@ export class JobExecutor {
|
|
|
28
29
|
jobsFailed: 0,
|
|
29
30
|
jobsDeleted: 0
|
|
30
31
|
}
|
|
31
|
-
this.maintainVisibility()
|
|
32
|
+
this.maintainPromise = this.maintainVisibility()
|
|
32
33
|
debug({ this: this })
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
shutdown () {
|
|
36
|
+
async shutdown () {
|
|
36
37
|
this.shutdownRequested = true
|
|
37
38
|
// Trigger a maintenance run right away in case it speeds us up
|
|
38
39
|
clearTimeout(this.maintainVisibilityTimeout)
|
|
39
|
-
this.
|
|
40
|
+
await this.maintainPromise
|
|
41
|
+
await this.maintainVisibility()
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
activeJobCount () {
|
|
@@ -47,8 +49,9 @@ export class JobExecutor {
|
|
|
47
49
|
* Changes message visibility on all running jobs using as few calls as possible.
|
|
48
50
|
*/
|
|
49
51
|
async maintainVisibility () {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
clearTimeout(this.maintainVisibilityTimeout)
|
|
53
|
+
// debug('maintainVisibility', this.jobs)
|
|
54
|
+
const start = new Date()
|
|
52
55
|
const jobsToExtendByQrl = {}
|
|
53
56
|
const jobsToDeleteByQrl = {}
|
|
54
57
|
const jobsToCleanup = new Set()
|
|
@@ -59,29 +62,36 @@ export class JobExecutor {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
// Build list of jobs we need to deal with
|
|
62
|
-
for (
|
|
63
|
-
const
|
|
65
|
+
for (let i = 0; i < this.jobs.length; i++) {
|
|
66
|
+
const job = this.jobs[i]
|
|
67
|
+
const jobRunTime = Math.round((start - job.start) / 1000)
|
|
68
|
+
// debug('considering job', job)
|
|
64
69
|
if (job.status === 'complete') {
|
|
65
70
|
const jobsToDelete = jobsToDeleteByQrl[job.qrl] || []
|
|
71
|
+
job.status = 'deleting'
|
|
66
72
|
jobsToDelete.push(job)
|
|
67
73
|
jobsToDeleteByQrl[job.qrl] = jobsToDelete
|
|
68
74
|
} else if (job.status === 'failed') {
|
|
69
75
|
jobsToCleanup.add(job)
|
|
70
|
-
} else if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
} else if (job.status === 'processing') {
|
|
77
|
+
debug('processing', { job, jobRunTime })
|
|
78
|
+
if (jobRunTime >= job.extendAtSecond) {
|
|
79
|
+
// Add it to our organized list of jobs
|
|
80
|
+
const jobsToExtend = jobsToExtendByQrl[job.qrl] || []
|
|
81
|
+
jobsToExtend.push(job)
|
|
82
|
+
jobsToExtendByQrl[job.qrl] = jobsToExtend
|
|
83
|
+
|
|
84
|
+
// Update the visibility timeout, double every time, up to max
|
|
85
|
+
const doubled = job.visibilityTimeout * 2
|
|
86
|
+
const secondsUntilMax = Math.max(1, maxJobSeconds - jobRunTime)
|
|
87
|
+
// const secondsUntilKill = Math.max(1, this.opt.killAfter - jobRunTime)
|
|
88
|
+
job.visibilityTimeout = Math.min(doubled, secondsUntilMax) //, secondsUntilKill)
|
|
89
|
+
job.extendAtSecond = Math.round(jobRunTime + job.visibilityTimeout) // this is what we use next time
|
|
90
|
+
debug({ doubled, secondsUntilMax, job })
|
|
91
|
+
}
|
|
82
92
|
}
|
|
83
93
|
}
|
|
84
|
-
debug('maintainVisibility', { jobsToDeleteByQrl, jobsToExtendByQrl })
|
|
94
|
+
// debug('maintainVisibility', { jobsToDeleteByQrl, jobsToExtendByQrl })
|
|
85
95
|
|
|
86
96
|
// Extend in batches for each queue
|
|
87
97
|
for (const qrl in jobsToExtendByQrl) {
|
|
@@ -100,6 +110,7 @@ export class JobExecutor {
|
|
|
100
110
|
}
|
|
101
111
|
entries.push(entry)
|
|
102
112
|
}
|
|
113
|
+
debug({ entries })
|
|
103
114
|
|
|
104
115
|
// Change batch
|
|
105
116
|
const input = { QueueUrl: qrl, Entries: entries }
|
|
@@ -113,7 +124,7 @@ export class JobExecutor {
|
|
|
113
124
|
if (this.opt.verbose) {
|
|
114
125
|
console.error(chalk.blue('Extended'), count, chalk.blue('jobs'))
|
|
115
126
|
} else if (!this.opt.disableLog) {
|
|
116
|
-
console.log(JSON.stringify({ event: 'EXTEND_VISIBILITY_TIMEOUTS', timestamp:
|
|
127
|
+
console.log(JSON.stringify({ event: 'EXTEND_VISIBILITY_TIMEOUTS', timestamp: start, count, qrl }))
|
|
117
128
|
}
|
|
118
129
|
}
|
|
119
130
|
// TODO Sentry
|
|
@@ -136,6 +147,7 @@ export class JobExecutor {
|
|
|
136
147
|
}
|
|
137
148
|
entries.push(entry)
|
|
138
149
|
}
|
|
150
|
+
debug({ entries })
|
|
139
151
|
|
|
140
152
|
// Delete batch
|
|
141
153
|
const input = { QueueUrl: qrl, Entries: entries }
|
|
@@ -148,7 +160,7 @@ export class JobExecutor {
|
|
|
148
160
|
if (this.opt.verbose) {
|
|
149
161
|
console.error(chalk.blue('Deleted'), count, chalk.blue('jobs'))
|
|
150
162
|
} else if (!this.opt.disableLog) {
|
|
151
|
-
console.log(JSON.stringify({ event: 'DELETE_MESSAGES', timestamp:
|
|
163
|
+
console.log(JSON.stringify({ event: 'DELETE_MESSAGES', timestamp: start, count, qrl }))
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
166
|
debug('DeleteMessageBatch returned', result)
|
|
@@ -157,22 +169,40 @@ export class JobExecutor {
|
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
// Get rid of deleted and failed jobs
|
|
160
|
-
this.jobs = this.jobs.filter(
|
|
172
|
+
this.jobs = this.jobs.filter(job => {
|
|
173
|
+
if (job.status === 'deleting' || job.status === 'failed') {
|
|
174
|
+
debug('removed', job.message.MessageId)
|
|
175
|
+
delete this.jobsByMessageId[job.message.MessageId]
|
|
176
|
+
return false
|
|
177
|
+
} else {
|
|
178
|
+
return true
|
|
179
|
+
}
|
|
180
|
+
})
|
|
161
181
|
|
|
162
|
-
//
|
|
182
|
+
// Bail if we are shutting down
|
|
163
183
|
if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0) return
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
|
|
185
|
+
// Check later, but count the time we spent. Make sure we check at least
|
|
186
|
+
// every period seconds.
|
|
187
|
+
const msElapsed = new Date() - start
|
|
188
|
+
const msPeriod = this.shutdownRequested ? 1 * 1000 : 10 * 1000
|
|
189
|
+
const msLeft = Math.max(0, msPeriod - msElapsed)
|
|
190
|
+
const msMin = this.shutdownRequested ? 1000 : 0
|
|
191
|
+
const nextCheckInMs = Math.max(msMin, msLeft)
|
|
192
|
+
debug({ msElapsed, msPeriod, msLeft, msMin, nextCheckInMs })
|
|
193
|
+
this.maintainVisibilityTimeout = setTimeout(() => {
|
|
194
|
+
this.maintainPromise = this.maintainVisibility()
|
|
195
|
+
}, nextCheckInMs)
|
|
166
196
|
}
|
|
167
197
|
|
|
168
198
|
async executeJob (message, callback, qname, qrl) {
|
|
169
199
|
// Create job entry and track it
|
|
170
200
|
const payload = this.opt.json ? JSON.parse(message.Body) : message.Body
|
|
171
|
-
const visibilityTimeout =
|
|
201
|
+
const visibilityTimeout = 60
|
|
172
202
|
const job = {
|
|
173
203
|
status: 'processing',
|
|
174
204
|
start: new Date(),
|
|
175
|
-
visibilityTimeout
|
|
205
|
+
visibilityTimeout,
|
|
176
206
|
extendAtSecond: visibilityTimeout / 2,
|
|
177
207
|
payload: this.opt.json ? JSON.parse(message.Body) : message.Body,
|
|
178
208
|
message,
|
|
@@ -180,8 +210,23 @@ export class JobExecutor {
|
|
|
180
210
|
qname,
|
|
181
211
|
qrl
|
|
182
212
|
}
|
|
183
|
-
|
|
213
|
+
|
|
214
|
+
// See if we are already executing this job
|
|
215
|
+
const oldJob = this.jobsByMessageId[job.message.MessageId]
|
|
216
|
+
if (oldJob) {
|
|
217
|
+
// If we actually see the same job again, we fucked up, probably due to
|
|
218
|
+
// the system being overloaded and us missing our extension call. So
|
|
219
|
+
// we'll celebrate this occasion by throwing a big fat error.
|
|
220
|
+
debug({ oldJob })
|
|
221
|
+
const e = new Error(`Saw job ${oldJob.message.MessageId} twice`)
|
|
222
|
+
e.job = oldJob
|
|
223
|
+
// TODO: sentry breadcrumb
|
|
224
|
+
throw e
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// debug('executeJob', job)
|
|
184
228
|
this.jobs.push(job)
|
|
229
|
+
this.jobsByMessageId[job.message.MessageId] = job
|
|
185
230
|
this.stats.activeJobs++
|
|
186
231
|
if (this.opt.verbose) {
|
|
187
232
|
console.error(chalk.blue('Executing:'), qname, chalk.blue('-->'), job.payload)
|
|
@@ -218,7 +263,7 @@ export class JobExecutor {
|
|
|
218
263
|
}
|
|
219
264
|
this.stats.jobsSucceeded++
|
|
220
265
|
} catch (err) {
|
|
221
|
-
debug('exec.catch')
|
|
266
|
+
// debug('exec.catch', err)
|
|
222
267
|
// Fail path for job execution
|
|
223
268
|
if (this.opt.verbose) {
|
|
224
269
|
console.error(chalk.red('FAILED'), message.Body)
|
|
@@ -7,7 +7,6 @@ import Debug from 'debug'
|
|
|
7
7
|
|
|
8
8
|
import { normalizeQueueName, getQnameUrlPairs } from '../qrlCache.js'
|
|
9
9
|
import { cheapIdleCheck } from '../idleQueues.js'
|
|
10
|
-
import { getSQSClient } from '../sqs.js'
|
|
11
10
|
|
|
12
11
|
const debug = Debug('qdone:queueManager')
|
|
13
12
|
|
|
@@ -17,57 +16,120 @@ export class QueueManager {
|
|
|
17
16
|
this.queues = queues
|
|
18
17
|
this.resolveSeconds = resolveSeconds
|
|
19
18
|
this.selectedPairs = []
|
|
19
|
+
this.icehouse = {}
|
|
20
20
|
this.resolveTimeout = undefined
|
|
21
21
|
this.shutdownRequested = false
|
|
22
|
-
this.resolveQueues()
|
|
22
|
+
this.resolvePromise = this.resolveQueues()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Sends a queue to the icehouse, where it waits for a while before being
|
|
26
|
+
// checked again
|
|
27
|
+
updateIcehouse (qrl, emptyReceive) {
|
|
28
|
+
const foundEntry = !!this.icehouse[qrl]
|
|
29
|
+
const { lastCheck, secondsToWait, numEmptyReceives } = this.icehouse[qrl] || {
|
|
30
|
+
lastCheck: new Date(),
|
|
31
|
+
secondsToWait: Math.round(20 + 10 * Math.random()),
|
|
32
|
+
numEmptyReceives: 0 + emptyReceive
|
|
33
|
+
}
|
|
34
|
+
if (emptyReceive) {
|
|
35
|
+
const now = new Date()
|
|
36
|
+
const secondsElapsed = lastCheck - now
|
|
37
|
+
const minWait = 10
|
|
38
|
+
const maxWait = 600
|
|
39
|
+
const baseSeconds = numEmptyReceives ** 2 * 20
|
|
40
|
+
const jitterSeconds = Math.round((Math.random() - 0.5) * baseSeconds)
|
|
41
|
+
const newSecondsToWait = Math.max(minWait, Math.min(maxWait, baseSeconds + jitterSeconds))
|
|
42
|
+
const newEntry = { lastCheck: now, secondsToWait: newSecondsToWait, numEmptyReceives: numEmptyReceives + 1 }
|
|
43
|
+
this.icehouse[qrl] = newEntry
|
|
44
|
+
if (this.opt.verbose) {
|
|
45
|
+
console.error(chalk.blue('Sending queue to icehouse'), qrl, chalk.blue('for'), newSecondsToWait, chalk.blue('seconds'))
|
|
46
|
+
}
|
|
47
|
+
debug({ foundEntry, newEntry, lastCheck, secondsToWait, now, secondsElapsed, maxWait, minWait, baseSeconds, jitterSeconds })
|
|
48
|
+
} else {
|
|
49
|
+
delete this.icehouse[qrl]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Returns true if the queue should be kept in the icehouse
|
|
54
|
+
keepInIcehouse (qrl, now) {
|
|
55
|
+
if (this.icehouse[qrl]) {
|
|
56
|
+
const { lastCheck, secondsToWait } = this.icehouse[qrl]
|
|
57
|
+
const secondsElapsed = Math.round((now - lastCheck) / 1000)
|
|
58
|
+
debug({ icehouseCheck: { qrl, lastCheck, secondsToWait, secondsElapsed } })
|
|
59
|
+
const letOut = secondsElapsed > secondsToWait
|
|
60
|
+
if (letOut && this.opt.verbose) {
|
|
61
|
+
console.error(chalk.blue('Coming out of icehouse:'), qrl)
|
|
62
|
+
}
|
|
63
|
+
return !letOut
|
|
64
|
+
} else {
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
23
67
|
}
|
|
24
68
|
|
|
25
69
|
async resolveQueues () {
|
|
70
|
+
clearTimeout(this.resolveTimeout)
|
|
26
71
|
if (this.shutdownRequested) return
|
|
27
72
|
|
|
28
73
|
// Start processing
|
|
29
|
-
if (this.opt.verbose) console.error(chalk.blue('Resolving queues: ') + this.queues.join(' '))
|
|
30
74
|
const qnames = this.queues.map(queue => normalizeQueueName(queue, this.opt))
|
|
31
75
|
const pairs = await getQnameUrlPairs(qnames, this.opt)
|
|
76
|
+
if (this.opt.verbose) console.error(chalk.blue('Resolving queues:'))
|
|
32
77
|
|
|
33
78
|
if (this.shutdownRequested) return
|
|
34
79
|
|
|
80
|
+
// Filter out queues
|
|
81
|
+
const now = new Date()
|
|
82
|
+
const filteredPairs = pairs
|
|
83
|
+
// first failed
|
|
84
|
+
.filter(({ qname, qrl }) => {
|
|
85
|
+
const suf = this.opt.failSuffix + (this.opt.fifo ? '.fifo' : '')
|
|
86
|
+
const isFailQueue = qname.slice(-suf.length) === suf
|
|
87
|
+
return this.opt.includeFailed ? true : !isFailQueue
|
|
88
|
+
})
|
|
89
|
+
// first fifo
|
|
90
|
+
.filter(({ qname, qrl }) => {
|
|
91
|
+
const isFifo = qname.endsWith('.fifo')
|
|
92
|
+
return this.opt.fifo ? isFifo : !isFifo
|
|
93
|
+
})
|
|
94
|
+
// then icehouse
|
|
95
|
+
.filter(({ qname, qrl }) => !this.keepInIcehouse(qrl, now))
|
|
96
|
+
|
|
35
97
|
// Figure out which pairs are active
|
|
36
98
|
const activePairs = []
|
|
37
99
|
if (this.opt.activeOnly) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
100
|
+
if (this.opt.verbose) {
|
|
101
|
+
console.error(chalk.blue(' checking active only'))
|
|
102
|
+
}
|
|
103
|
+
await Promise.all(filteredPairs.map(async ({ qname, qrl }) => {
|
|
104
|
+
const { result: { idle } } = await cheapIdleCheck(qname, qrl, this.opt)
|
|
105
|
+
debug({ idle, qname })
|
|
106
|
+
if (!idle) activePairs.push({ qname, qrl })
|
|
42
107
|
}))
|
|
43
108
|
}
|
|
44
109
|
|
|
45
110
|
if (this.shutdownRequested) return
|
|
46
111
|
|
|
47
|
-
// Finished resolving
|
|
48
|
-
if (this.opt.verbose) {
|
|
49
|
-
console.error(chalk.blue(' done'))
|
|
50
|
-
console.error()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
112
|
// Figure out which queues we want to listen on, choosing between active and
|
|
54
113
|
// all, filtering out failed queues if the user wants that
|
|
55
|
-
this.selectedPairs = (this.opt.activeOnly ? activePairs :
|
|
56
|
-
.filter(({ qname }) => {
|
|
57
|
-
const suf = this.opt.failSuffix + (this.opt.fifo ? '.fifo' : '')
|
|
58
|
-
const isFailQueue = qname.slice(-suf.length) === suf
|
|
59
|
-
const shouldInclude = this.opt.includeFailed ? true : !isFailQueue
|
|
60
|
-
return shouldInclude
|
|
61
|
-
})
|
|
114
|
+
this.selectedPairs = (this.opt.activeOnly ? activePairs : filteredPairs)
|
|
62
115
|
|
|
63
116
|
// Randomize order
|
|
64
117
|
this.selectedPairs.sort(() => 0.5 - Math.random())
|
|
118
|
+
if (this.opt.verbose) console.error(chalk.blue(' selected:\n ') + this.selectedPairs.map(({ qname }) => qname).join('\n '))
|
|
65
119
|
debug('selectedPairs', this.selectedPairs)
|
|
66
120
|
|
|
121
|
+
// Finished resolving
|
|
122
|
+
if (this.opt.verbose) {
|
|
123
|
+
console.error(chalk.blue(' done'))
|
|
124
|
+
console.error()
|
|
125
|
+
}
|
|
126
|
+
|
|
67
127
|
if (this.opt.verbose) {
|
|
68
128
|
console.error(chalk.blue('Will resolve queues again in ' + this.resolveSeconds + ' seconds'))
|
|
69
129
|
}
|
|
70
|
-
this.resolveTimeout = setTimeout(() =>
|
|
130
|
+
this.resolveTimeout = setTimeout(() => {
|
|
131
|
+
this.resolvePromise = this.resolveQueues()
|
|
132
|
+
}, this.resolveSeconds * 1000)
|
|
71
133
|
}
|
|
72
134
|
|
|
73
135
|
// Return the next queue in the lineup
|
|
@@ -81,8 +143,9 @@ export class QueueManager {
|
|
|
81
143
|
return this.selectedPairs
|
|
82
144
|
}
|
|
83
145
|
|
|
84
|
-
shutdown () {
|
|
85
|
-
clearTimeout(this.resolveTimeout)
|
|
146
|
+
async shutdown () {
|
|
86
147
|
this.shutdownRequested = true
|
|
148
|
+
clearTimeout(this.resolveTimeout)
|
|
149
|
+
await this.resolvePromise
|
|
87
150
|
}
|
|
88
151
|
}
|
|
@@ -4,55 +4,41 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export class SystemMonitor {
|
|
7
|
-
constructor (
|
|
8
|
-
this.
|
|
9
|
-
this.smoothingFactor = smoothingFactor
|
|
7
|
+
constructor (reportCallback, reportSeconds = 1) {
|
|
8
|
+
this.reportCallback = reportCallback || console.log
|
|
10
9
|
this.reportSeconds = reportSeconds
|
|
11
|
-
this.measurements =
|
|
12
|
-
|
|
13
|
-
setImmediate: []
|
|
14
|
-
}
|
|
15
|
-
this.timeouts = {
|
|
16
|
-
setTimeout: undefined,
|
|
17
|
-
setImmediate: undefined,
|
|
18
|
-
reportLatency: undefined
|
|
19
|
-
}
|
|
20
|
-
this.measureLatencySetTimeout()
|
|
10
|
+
this.measurements = []
|
|
11
|
+
this.measure()
|
|
21
12
|
this.reportLatency()
|
|
22
13
|
}
|
|
23
14
|
|
|
24
|
-
|
|
15
|
+
measure () {
|
|
16
|
+
clearTimeout(this.measureTimeout)
|
|
25
17
|
const start = new Date()
|
|
26
|
-
this.
|
|
18
|
+
this.measureTimeout = setTimeout(() => {
|
|
27
19
|
const latency = new Date() - start
|
|
28
|
-
this.measurements.
|
|
29
|
-
if (this.measurements.
|
|
30
|
-
this.
|
|
20
|
+
this.measurements.push(latency)
|
|
21
|
+
if (this.measurements.length > 1000) this.measurements.shift()
|
|
22
|
+
this.measure()
|
|
31
23
|
})
|
|
32
24
|
}
|
|
33
25
|
|
|
34
26
|
getLatency () {
|
|
35
|
-
|
|
36
|
-
for (const k in this.measurements) {
|
|
37
|
-
const values = this.measurements[k]
|
|
38
|
-
results[k] = values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0
|
|
39
|
-
}
|
|
40
|
-
return results
|
|
27
|
+
return this.measurements.length ? this.measurements.reduce((a, b) => a + b, 0) / this.measurements.length : 0
|
|
41
28
|
}
|
|
42
29
|
|
|
43
30
|
reportLatency () {
|
|
44
|
-
this.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
31
|
+
clearTimeout(this.reportTimeout)
|
|
32
|
+
this.reportTimeout = setTimeout(() => {
|
|
33
|
+
const latency = this.getLatency()
|
|
34
|
+
// console.log({ latency })
|
|
35
|
+
if (this.reportCallback) this.reportCallback(latency)
|
|
50
36
|
this.reportLatency()
|
|
51
37
|
}, this.reportSeconds * 1000)
|
|
52
38
|
}
|
|
53
39
|
|
|
54
40
|
shutdown () {
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
clearTimeout(this.measureTimeout)
|
|
42
|
+
clearTimeout(this.reportTimeout)
|
|
57
43
|
}
|
|
58
44
|
}
|