qdone 2.0.12-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 +91 -29
- package/commonjs/src/defaults.js +20 -1
- package/commonjs/src/idleQueues.js +2 -3
- package/commonjs/src/scheduler/jobExecutor.js +136 -100
- 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 +50 -22
- package/src/defaults.js +22 -1
- package/src/idleQueues.js +1 -1
- package/src/scheduler/jobExecutor.js +95 -51
- 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
|
-
|
|
39
|
-
|
|
38
|
+
// Trigger a maintenance run right away in case it speeds us up
|
|
39
|
+
clearTimeout(this.maintainVisibilityTimeout)
|
|
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 }
|
|
@@ -107,18 +118,17 @@ export class JobExecutor {
|
|
|
107
118
|
const result = await getSQSClient().send(new ChangeMessageVisibilityBatchCommand(input))
|
|
108
119
|
debug('ChangeMessageVisibilityBatch returned', result)
|
|
109
120
|
this.stats.sqsCalls++
|
|
110
|
-
if (result.Successful)
|
|
121
|
+
if (result.Successful) {
|
|
122
|
+
const count = result.Successful.length || 0
|
|
123
|
+
this.stats.timeoutsExtended += count
|
|
124
|
+
if (this.opt.verbose) {
|
|
125
|
+
console.error(chalk.blue('Extended'), count, chalk.blue('jobs'))
|
|
126
|
+
} else if (!this.opt.disableLog) {
|
|
127
|
+
console.log(JSON.stringify({ event: 'EXTEND_VISIBILITY_TIMEOUTS', timestamp: start, count, qrl }))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
111
130
|
// TODO Sentry
|
|
112
131
|
}
|
|
113
|
-
if (this.opt.verbose) {
|
|
114
|
-
console.error(chalk.blue('Extended these jobs: '), jobsToExtend)
|
|
115
|
-
} else if (!this.opt.disableLog) {
|
|
116
|
-
console.log(JSON.stringify({
|
|
117
|
-
event: 'EXTEND_VISIBILITY_TIMEOUTS',
|
|
118
|
-
timestamp: now,
|
|
119
|
-
messageIds: jobsToExtend.map(({ message }) => message.MessageId)
|
|
120
|
-
}))
|
|
121
|
-
}
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
// Delete in batches for each queue
|
|
@@ -137,43 +147,62 @@ export class JobExecutor {
|
|
|
137
147
|
}
|
|
138
148
|
entries.push(entry)
|
|
139
149
|
}
|
|
150
|
+
debug({ entries })
|
|
140
151
|
|
|
141
152
|
// Delete batch
|
|
142
153
|
const input = { QueueUrl: qrl, Entries: entries }
|
|
143
154
|
debug({ DeleteMessageBatch: input })
|
|
144
155
|
const result = await getSQSClient().send(new DeleteMessageBatchCommand(input))
|
|
145
156
|
this.stats.sqsCalls++
|
|
146
|
-
if (result.Successful)
|
|
157
|
+
if (result.Successful) {
|
|
158
|
+
const count = result.Successful.length || 0
|
|
159
|
+
this.stats.jobsDeleted += count
|
|
160
|
+
if (this.opt.verbose) {
|
|
161
|
+
console.error(chalk.blue('Deleted'), count, chalk.blue('jobs'))
|
|
162
|
+
} else if (!this.opt.disableLog) {
|
|
163
|
+
console.log(JSON.stringify({ event: 'DELETE_MESSAGES', timestamp: start, count, qrl }))
|
|
164
|
+
}
|
|
165
|
+
}
|
|
147
166
|
debug('DeleteMessageBatch returned', result)
|
|
148
167
|
// TODO Sentry
|
|
149
168
|
}
|
|
150
|
-
if (this.opt.verbose) {
|
|
151
|
-
console.error(chalk.blue('Deleted these finished jobs: '), jobsToDelete)
|
|
152
|
-
} else if (!this.opt.disableLog) {
|
|
153
|
-
console.log(JSON.stringify({
|
|
154
|
-
event: 'DELETE_MESSAGES',
|
|
155
|
-
timestamp: now,
|
|
156
|
-
messageIds: jobsToDelete.map(({ message }) => message.MessageId)
|
|
157
|
-
}))
|
|
158
|
-
}
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
// Get rid of deleted and failed jobs
|
|
162
|
-
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
|
+
})
|
|
163
181
|
|
|
164
|
-
//
|
|
182
|
+
// Bail if we are shutting down
|
|
165
183
|
if (this.shutdownRequested && this.stats.activeJobs === 0 && this.jobs.length === 0) return
|
|
166
|
-
|
|
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)
|
|
167
196
|
}
|
|
168
197
|
|
|
169
198
|
async executeJob (message, callback, qname, qrl) {
|
|
170
199
|
// Create job entry and track it
|
|
171
200
|
const payload = this.opt.json ? JSON.parse(message.Body) : message.Body
|
|
172
|
-
const visibilityTimeout =
|
|
201
|
+
const visibilityTimeout = 60
|
|
173
202
|
const job = {
|
|
174
203
|
status: 'processing',
|
|
175
204
|
start: new Date(),
|
|
176
|
-
visibilityTimeout
|
|
205
|
+
visibilityTimeout,
|
|
177
206
|
extendAtSecond: visibilityTimeout / 2,
|
|
178
207
|
payload: this.opt.json ? JSON.parse(message.Body) : message.Body,
|
|
179
208
|
message,
|
|
@@ -181,8 +210,23 @@ export class JobExecutor {
|
|
|
181
210
|
qname,
|
|
182
211
|
qrl
|
|
183
212
|
}
|
|
184
|
-
|
|
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)
|
|
185
228
|
this.jobs.push(job)
|
|
229
|
+
this.jobsByMessageId[job.message.MessageId] = job
|
|
186
230
|
this.stats.activeJobs++
|
|
187
231
|
if (this.opt.verbose) {
|
|
188
232
|
console.error(chalk.blue('Executing:'), qname, chalk.blue('-->'), job.payload)
|
|
@@ -190,7 +234,7 @@ export class JobExecutor {
|
|
|
190
234
|
console.log(JSON.stringify({
|
|
191
235
|
event: 'MESSAGE_PROCESSING_START',
|
|
192
236
|
timestamp: new Date(),
|
|
193
|
-
|
|
237
|
+
qrl,
|
|
194
238
|
messageId: message.MessageId,
|
|
195
239
|
payload: job.payload
|
|
196
240
|
}))
|
|
@@ -219,7 +263,7 @@ export class JobExecutor {
|
|
|
219
263
|
}
|
|
220
264
|
this.stats.jobsSucceeded++
|
|
221
265
|
} catch (err) {
|
|
222
|
-
debug('exec.catch')
|
|
266
|
+
// debug('exec.catch', err)
|
|
223
267
|
// Fail path for job execution
|
|
224
268
|
if (this.opt.verbose) {
|
|
225
269
|
console.error(chalk.red('FAILED'), message.Body)
|
|
@@ -229,7 +273,7 @@ export class JobExecutor {
|
|
|
229
273
|
console.log(JSON.stringify({
|
|
230
274
|
event: 'MESSAGE_PROCESSING_FAILED',
|
|
231
275
|
reason: 'exception thrown',
|
|
232
|
-
|
|
276
|
+
qrl,
|
|
233
277
|
timestamp: new Date(),
|
|
234
278
|
messageId: message.MessageId,
|
|
235
279
|
payload,
|
|
@@ -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
|
}
|