qdone 1.7.0 → 2.0.0-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/README.md +9 -1
- package/commonjs/index.js +10 -0
- package/commonjs/package.json +3 -0
- package/commonjs/src/cache.js +142 -0
- package/commonjs/src/cloudWatch.js +148 -0
- package/commonjs/src/consumer.js +483 -0
- package/commonjs/src/defaults.js +107 -0
- package/commonjs/src/enqueue.js +498 -0
- package/commonjs/src/idleQueues.js +466 -0
- package/commonjs/src/qrlCache.js +250 -0
- package/commonjs/src/sqs.js +160 -0
- package/npm-shrinkwrap.json +17240 -3367
- package/package.json +41 -29
- package/src/bin.js +3 -0
- package/src/cache.js +18 -22
- package/src/cli.js +268 -182
- package/src/cloudWatch.js +97 -0
- package/src/consumer.js +346 -0
- package/src/defaults.js +114 -0
- package/src/enqueue.js +239 -196
- package/src/idleQueues.js +242 -223
- package/src/monitor.js +53 -0
- package/src/qrlCache.js +110 -83
- package/src/sentry.js +30 -0
- package/src/sqs.js +73 -0
- package/src/worker.js +197 -202
- package/.coveralls.yml +0 -1
- package/.travis.yml +0 -19
- package/CHANGELOG.md +0 -121
- package/index.js +0 -6
- package/qdone +0 -2
- package/test/fixtures/test-child-kill-linux.sh +0 -9
- package/test/fixtures/test-fifo01-x24.batch +0 -24
- package/test/fixtures/test-too-big-1.batch +0 -10
- package/test/fixtures/test-unique01-x24.batch +0 -24
- package/test/fixtures/test-unique02-x24.batch +0 -24
- package/test/fixtures/test-unique24-x24.batch +0 -24
- package/test/fixtures/test-unique24-x240.batch +0 -240
- package/test/test.cache.js +0 -61
- package/test/test.cli.js +0 -1609
package/src/idleQueues.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementation of checks and caching of checks to determine if queues are idle.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import { getSQSClient } from './sqs.js'
|
|
6
|
+
import { getCloudWatchClient } from './cloudWatch.js'
|
|
7
|
+
import { getOptionsWithDefaults } from './defaults.js'
|
|
8
|
+
import { GetQueueAttributesCommand, DeleteQueueCommand, QueueDoesNotExist } from '@aws-sdk/client-sqs'
|
|
9
|
+
import { GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch'
|
|
10
|
+
import { normalizeFailQueueName, getQnameUrlPairs, fifoSuffix } from './qrlCache.js'
|
|
11
|
+
import { getCache, setCache } from './cache.js'
|
|
12
|
+
// const AWS = require('aws-sdk')
|
|
1
13
|
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const qrlCache = require('./qrlCache')
|
|
5
|
-
const cache = require('./cache')
|
|
6
|
-
const AWS = require('aws-sdk')
|
|
14
|
+
import Debug from 'debug'
|
|
15
|
+
const debug = Debug('qdone:idleQueues')
|
|
7
16
|
|
|
8
17
|
// Queue attributes we check to determine idle
|
|
9
|
-
const attributeNames = [
|
|
18
|
+
export const attributeNames = [
|
|
10
19
|
'ApproximateNumberOfMessages',
|
|
11
20
|
'ApproximateNumberOfMessagesNotVisible',
|
|
12
21
|
'ApproximateNumberOfMessagesDelayed'
|
|
@@ -24,58 +33,55 @@ const metricNames = [
|
|
|
24
33
|
'ApproximateAgeOfOldestMessage'
|
|
25
34
|
]
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Actual SQS call, used in conjunction with cache.
|
|
38
|
+
*/
|
|
39
|
+
export async function _cheapIdleCheck (qname, qrl, opt) {
|
|
40
|
+
const client = getSQSClient()
|
|
41
|
+
const cmd = new GetQueueAttributesCommand({ AttributeNames: attributeNames, QueueUrl: qrl })
|
|
42
|
+
const data = await client.send(cmd)
|
|
43
|
+
// debug('data', data)
|
|
44
|
+
const result = data.Attributes
|
|
45
|
+
result.queue = qname.slice(opt.prefix.length)
|
|
46
|
+
// We are idle if all the messages attributes are zero
|
|
47
|
+
result.idle = attributeNames.filter(k => result[k] === '0').length === attributeNames.length
|
|
48
|
+
return { result, SQS: 1 }
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
/**
|
|
42
52
|
* Gets queue attributes from the SQS api and assesses whether queue is idle
|
|
43
53
|
* at this immediate moment.
|
|
44
54
|
*/
|
|
45
|
-
function cheapIdleCheck (qname, qrl,
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return _cheapIdleCheck(qname, qrl, options).then(({ result, SQS }) => {
|
|
57
|
-
debug({ action: 'setCache', key, result })
|
|
58
|
-
return cache.setCache(key, result, options).then(ok => {
|
|
59
|
-
debug({ action: 'return result of set cache', result })
|
|
60
|
-
return Promise.resolve({ result, SQS })
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
})
|
|
55
|
+
export async function cheapIdleCheck (qname, qrl, opt) {
|
|
56
|
+
// Just call the API if we don't have a cache
|
|
57
|
+
if (!opt.cacheUri) return _cheapIdleCheck(qname, qrl, opt)
|
|
58
|
+
|
|
59
|
+
// Otherwise check cache
|
|
60
|
+
const key = 'cheap-idle-check:' + qrl
|
|
61
|
+
const cacheResult = await getCache(key, opt)
|
|
62
|
+
debug({ cacheResult })
|
|
63
|
+
if (cacheResult) {
|
|
64
|
+
debug({ action: 'return resolved' })
|
|
65
|
+
return { result: cacheResult, SQS: 0 }
|
|
65
66
|
} else {
|
|
66
|
-
|
|
67
|
+
// Cache miss, make call
|
|
68
|
+
debug({ action: 'do real check' })
|
|
69
|
+
const { result, SQS } = await _cheapIdleCheck(qname, qrl, opt)
|
|
70
|
+
debug({ action: 'setCache', key, result })
|
|
71
|
+
const ok = await setCache(key, result, opt)
|
|
72
|
+
debug({ action: 'return result of set cache', ok })
|
|
73
|
+
return { result, SQS }
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
|
-
exports.cheapIdleCheck = cheapIdleCheck
|
|
70
76
|
|
|
71
77
|
/**
|
|
72
78
|
* Gets a single metric from the CloudWatch api.
|
|
73
79
|
*/
|
|
74
|
-
function getMetric (qname, qrl, metricName,
|
|
80
|
+
export async function getMetric (qname, qrl, metricName, opt) {
|
|
75
81
|
debug('getMetric', qname, qrl, metricName)
|
|
76
82
|
const now = new Date()
|
|
77
83
|
const params = {
|
|
78
|
-
StartTime: new Date(now.getTime() - 1000 * 60 *
|
|
84
|
+
StartTime: new Date(now.getTime() - 1000 * 60 * opt.idleFor),
|
|
79
85
|
EndTime: now,
|
|
80
86
|
MetricName: metricName,
|
|
81
87
|
Namespace: 'AWS/SQS',
|
|
@@ -84,16 +90,13 @@ function getMetric (qname, qrl, metricName, options) {
|
|
|
84
90
|
Statistics: ['Sum']
|
|
85
91
|
// Unit: ['']
|
|
86
92
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
[metricName]: data.Datapoints.map(d => d.Sum).reduce((a, b) => a + b, 0)
|
|
95
|
-
})
|
|
96
|
-
})
|
|
93
|
+
const client = getCloudWatchClient()
|
|
94
|
+
const cmd = new GetMetricStatisticsCommand(params)
|
|
95
|
+
const data = await client.send(cmd)
|
|
96
|
+
debug('getMetric data', data)
|
|
97
|
+
return {
|
|
98
|
+
[metricName]: data.Datapoints.map(d => d.Sum).reduce((a, b) => a + b, 0)
|
|
99
|
+
}
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
/**
|
|
@@ -107,208 +110,224 @@ function getMetric (qname, qrl, metricName, options) {
|
|
|
107
110
|
* We could randomize the order, but for my test use case, it's always cheaper
|
|
108
111
|
* to check NumberOfMessagesSent first, and is the primary indicator of use.
|
|
109
112
|
*/
|
|
110
|
-
function checkIdle (qname, qrl,
|
|
113
|
+
export async function checkIdle (qname, qrl, opt) {
|
|
111
114
|
// Do the cheap check first to make sure there is no data in flight at the moment
|
|
112
115
|
debug('checkIdle', qname, qrl)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
116
|
+
const { result: cheapResult, SQS } = await cheapIdleCheck(qname, qrl, opt)
|
|
117
|
+
debug('cheapResult', cheapResult)
|
|
118
|
+
|
|
119
|
+
// Short circuit further calls if cheap result shows data
|
|
120
|
+
if (cheapResult.idle === false) {
|
|
121
|
+
return {
|
|
122
|
+
queue: qname.slice(opt.prefix.length),
|
|
123
|
+
cheap: cheapResult,
|
|
124
|
+
idle: false,
|
|
125
|
+
apiCalls: { SQS, CloudWatch: 0 }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If we get here, there's nothing in the queue at the moment,
|
|
130
|
+
// so we have to check metrics one at a time
|
|
131
|
+
const apiCalls = { SQS: 1, CloudWatch: 0 }
|
|
132
|
+
const results = []
|
|
133
|
+
let idle = true
|
|
134
|
+
for (const metricName of metricNames) {
|
|
135
|
+
// Check metrics in order
|
|
136
|
+
const result = await getMetric(qname, qrl, metricName, opt)
|
|
137
|
+
results.push(result)
|
|
138
|
+
debug('getMetric result', result)
|
|
139
|
+
apiCalls.CloudWatch++
|
|
140
|
+
|
|
141
|
+
// Recalculate idle
|
|
142
|
+
idle = result[metricName] === 0
|
|
143
|
+
if (!idle) break // and stop checking metrics if we find evidence of activity
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Calculate stats
|
|
147
|
+
const stats = Object.assign(
|
|
148
|
+
{
|
|
149
|
+
queue: qname.slice(opt.prefix.length),
|
|
150
|
+
cheap: cheapResult,
|
|
151
|
+
apiCalls,
|
|
152
|
+
idle
|
|
153
|
+
},
|
|
154
|
+
...results // merge in results from CloudWatch
|
|
155
|
+
)
|
|
156
|
+
debug('checkIdle stats', stats)
|
|
157
|
+
return stats
|
|
153
158
|
}
|
|
154
159
|
|
|
155
160
|
/**
|
|
156
161
|
* Just deletes a queue.
|
|
157
162
|
*/
|
|
158
|
-
function deleteQueue (qname, qrl,
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
apiCalls: { SQS: 1, CloudWatch: 0 }
|
|
168
|
-
})
|
|
169
|
-
})
|
|
163
|
+
export async function deleteQueue (qname, qrl, opt) {
|
|
164
|
+
const cmd = new DeleteQueueCommand({ QueueUrl: qrl })
|
|
165
|
+
const result = await getSQSClient().send(cmd)
|
|
166
|
+
debug(result)
|
|
167
|
+
if (opt.verbose) console.error(chalk.blue('Deleted ') + qname.slice(opt.prefix.length))
|
|
168
|
+
return {
|
|
169
|
+
deleted: true,
|
|
170
|
+
apiCalls: { SQS: 1, CloudWatch: 0 }
|
|
171
|
+
}
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
/**
|
|
173
175
|
* Processes a single queue, checking for idle, deleting if applicable.
|
|
174
176
|
*/
|
|
175
|
-
function processQueue (qname, qrl,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
177
|
+
export async function processQueue (qname, qrl, opt) {
|
|
178
|
+
const result = await checkIdle(qname, qrl, opt)
|
|
179
|
+
debug(qname, result)
|
|
180
|
+
|
|
181
|
+
// Queue is active
|
|
182
|
+
if (!result.idle) {
|
|
183
|
+
// Notify and return
|
|
184
|
+
if (opt.verbose) console.error(chalk.blue('Queue ') + qname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'active' + chalk.blue(' in the last ') + opt.idleFor + chalk.blue(' minutes.'))
|
|
185
|
+
return result
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Queue is idle
|
|
189
|
+
if (opt.verbose) console.error(chalk.blue('Queue ') + qname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
|
|
190
|
+
if (opt.delete) {
|
|
191
|
+
const deleteResult = await deleteQueue(qname, qrl, opt)
|
|
192
|
+
const resultIncludingDelete = Object.assign(result, {
|
|
193
|
+
deleted: deleteResult.deleted,
|
|
194
|
+
apiCalls: {
|
|
195
|
+
SQS: result.apiCalls.SQS + deleteResult.apiCalls.SQS,
|
|
196
|
+
CloudWatch: result.apiCalls.CloudWatch + deleteResult.apiCalls.CloudWatch
|
|
195
197
|
}
|
|
196
|
-
// End this branch of the tree
|
|
197
|
-
return Promise.resolve(result)
|
|
198
198
|
})
|
|
199
|
+
return resultIncludingDelete
|
|
200
|
+
}
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
204
|
* Processes a queue and its fail queue, treating them as a unit.
|
|
203
205
|
*/
|
|
204
|
-
function processQueuePair (qname, qrl,
|
|
206
|
+
export async function processQueuePair (qname, qrl, opt) {
|
|
205
207
|
const isFifo = qname.endsWith('.fifo')
|
|
206
|
-
const normalizeOptions = Object.assign({},
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
} else {
|
|
235
|
-
if (options.verbose) console.error(chalk.blue('Queue ') + fqname.slice(options.prefix.length) + chalk.blue(' has been ') + 'active' + chalk.blue(' in the last ') + options['idle-for'] + chalk.blue(' minutes.'))
|
|
236
|
-
}
|
|
237
|
-
return Promise.resolve(Object.assign(result, { idle: result.idle && fresult.idle, failq: fresult }, { apiCalls: {
|
|
208
|
+
const normalizeOptions = Object.assign({}, opt, { fifo: isFifo })
|
|
209
|
+
// Generate fail queue name/url
|
|
210
|
+
const fqname = normalizeFailQueueName(qname, normalizeOptions)
|
|
211
|
+
const fqrl = normalizeFailQueueName(qrl, normalizeOptions)
|
|
212
|
+
|
|
213
|
+
// Idle check
|
|
214
|
+
const result = await checkIdle(qname, qrl, opt)
|
|
215
|
+
debug('result', result)
|
|
216
|
+
|
|
217
|
+
// Queue is active
|
|
218
|
+
const active = !result.idle
|
|
219
|
+
if (active) {
|
|
220
|
+
if (opt.verbose) console.error(chalk.blue('Queue ') + qname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'active' + chalk.blue(' in the last ') + opt.idleFor + chalk.blue(' minutes.'))
|
|
221
|
+
return result
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Queue is idle
|
|
225
|
+
if (opt.verbose) console.error(chalk.blue('Queue ') + qname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
|
|
226
|
+
|
|
227
|
+
// Check fail queue
|
|
228
|
+
try {
|
|
229
|
+
const fresult = await checkIdle(fqname, fqrl, opt)
|
|
230
|
+
debug('fresult', fresult)
|
|
231
|
+
const idleCheckResult = Object.assign(
|
|
232
|
+
result,
|
|
233
|
+
{ idle: result.idle && fresult.idle, failq: fresult },
|
|
234
|
+
{
|
|
235
|
+
apiCalls: {
|
|
238
236
|
SQS: result.apiCalls.SQS + fresult.apiCalls.SQS,
|
|
239
237
|
CloudWatch: result.apiCalls.CloudWatch + fresult.apiCalls.CloudWatch
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
deleted: deleteResult.deleted,
|
|
250
|
-
apiCalls: {
|
|
251
|
-
SQS: result.apiCalls.SQS + deleteResult.apiCalls.SQS,
|
|
252
|
-
CloudWatch: result.apiCalls.CloudWatch + deleteResult.apiCalls.CloudWatch
|
|
253
|
-
}
|
|
254
|
-
}))
|
|
255
|
-
} else {
|
|
256
|
-
return result
|
|
257
|
-
}
|
|
258
|
-
} else {
|
|
259
|
-
throw e
|
|
260
|
-
}
|
|
261
|
-
})
|
|
262
|
-
} else {
|
|
263
|
-
if (options.verbose) console.error(chalk.blue('Queue ') + qname.slice(options.prefix.length) + chalk.blue(' has been ') + 'active' + chalk.blue(' in the last ') + options['idle-for'] + chalk.blue(' minutes.'))
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
// Queue is active
|
|
243
|
+
const factive = !fresult.idle
|
|
244
|
+
if (factive) {
|
|
245
|
+
if (opt.verbose) console.error(chalk.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'active' + chalk.blue(' in the last ') + opt.idleFor + chalk.blue(' minutes.'))
|
|
246
|
+
return idleCheckResult
|
|
264
247
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
248
|
+
|
|
249
|
+
// Queue is idle
|
|
250
|
+
if (opt.verbose) console.error(chalk.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk.blue(' has been ') + 'idle' + chalk.blue(' for the last ') + opt.idleFor + chalk.blue(' minutes.'))
|
|
251
|
+
|
|
252
|
+
// Trigger a delete if the user wants it
|
|
253
|
+
if (!opt.delete) return idleCheckResult
|
|
254
|
+
const [dresult, dfresult] = await Promise.all([
|
|
255
|
+
deleteQueue(qname, qrl, opt),
|
|
256
|
+
deleteQueue(fqname, fqrl, opt)
|
|
257
|
+
])
|
|
258
|
+
return Object.assign(idleCheckResult, {
|
|
259
|
+
apiCalls: {
|
|
260
|
+
// Sum the SQS calls across all four
|
|
261
|
+
SQS: [result, fresult, dresult, dfresult]
|
|
262
|
+
.map(r => r.apiCalls.SQS)
|
|
263
|
+
.reduce((a, b) => a + b, 0),
|
|
264
|
+
// Sum the CloudWatch calls across all four
|
|
265
|
+
CloudWatch: [result, fresult, dresult, dfresult]
|
|
266
|
+
.map(r => r.apiCalls.CloudWatch)
|
|
267
|
+
.reduce((a, b) => a + b, 0)
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
} catch (e) {
|
|
271
|
+
// Handle the case where the fail queue has been deleted or was never
|
|
272
|
+
// created for some reason
|
|
273
|
+
if (!(e instanceof QueueDoesNotExist)) throw e
|
|
274
|
+
|
|
275
|
+
// Fail queue doesn't exist if we get here
|
|
276
|
+
if (opt.verbose) console.error(chalk.blue('Queue ') + fqname.slice(opt.prefix.length) + chalk.blue(' does not exist.'))
|
|
277
|
+
|
|
278
|
+
// Handle delete
|
|
279
|
+
if (!opt.delete) return result
|
|
280
|
+
const deleteResult = await deleteQueue(qname, qrl, opt)
|
|
281
|
+
const resultIncludingDelete = Object.assign(result, {
|
|
282
|
+
deleted: deleteResult.deleted,
|
|
283
|
+
apiCalls: {
|
|
284
|
+
SQS: result.apiCalls.SQS + deleteResult.apiCalls.SQS,
|
|
285
|
+
CloudWatch: result.apiCalls.CloudWatch + deleteResult.apiCalls.CloudWatch
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
return resultIncludingDelete
|
|
289
|
+
}
|
|
268
290
|
}
|
|
269
291
|
|
|
270
292
|
//
|
|
271
293
|
// Resolve queues for listening loop listen
|
|
272
294
|
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
295
|
+
export async function idleQueues (queues, options) {
|
|
296
|
+
const opt = getOptionsWithDefaults(options)
|
|
297
|
+
if (opt.verbose) console.error(chalk.blue('Resolving queues: ') + queues.join(' '))
|
|
298
|
+
const qnames = queues.map(queue => opt.prefix + queue)
|
|
299
|
+
const entries = await getQnameUrlPairs(qnames, opt)
|
|
300
|
+
debug('getQnameUrlPairs.then')
|
|
301
|
+
if (opt.verbose) {
|
|
302
|
+
console.error(chalk.blue(' done'))
|
|
303
|
+
console.error()
|
|
304
|
+
}
|
|
284
305
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
})
|
|
306
|
+
// Filter out any queue ending in suffix unless --include-failed is set
|
|
307
|
+
const filteredEntries = entries.filter(entry => {
|
|
308
|
+
const suf = opt.failSuffix
|
|
309
|
+
const sufFifo = opt.failSuffix + fifoSuffix
|
|
310
|
+
const isFail = entry.qname.endsWith(suf)
|
|
311
|
+
const isFifoFail = entry.qname.endsWith(sufFifo)
|
|
312
|
+
return opt.includeFailed ? true : (!isFail && !isFifoFail)
|
|
313
|
+
})
|
|
294
314
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
315
|
+
// But only if we have queues to remove
|
|
316
|
+
if (filteredEntries.length) {
|
|
317
|
+
if (opt.verbose) {
|
|
318
|
+
console.error(chalk.blue('Checking queues (in this order):'))
|
|
319
|
+
console.error(filteredEntries.map(e =>
|
|
320
|
+
' ' + e.qname.slice(opt.prefix.length) + chalk.blue(' - ' + e.qrl)
|
|
321
|
+
).join('\n'))
|
|
322
|
+
console.error()
|
|
323
|
+
}
|
|
324
|
+
// Check each queue in parallel
|
|
325
|
+
if (opt.unpair) return Promise.all(filteredEntries.map(e => processQueue(e.qname, e.qrl, opt)))
|
|
326
|
+
return Promise.all(filteredEntries.map(e => processQueuePair(e.qname, e.qrl, opt)))
|
|
327
|
+
}
|
|
308
328
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
})
|
|
329
|
+
// Otherwise, let caller know
|
|
330
|
+
return 'noQueues'
|
|
312
331
|
}
|
|
313
332
|
|
|
314
333
|
debug('loaded')
|
package/src/monitor.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-queue monitoring functionaliry
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getMatchingQueues, getQueueAttributes } from './sqs.js'
|
|
6
|
+
import Debug from 'debug'
|
|
7
|
+
const debug = Debug('sd:utils:qmonitor:index')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Splits a queue name with a single wildcard into prefix and suffix regex.
|
|
11
|
+
*/
|
|
12
|
+
export function interpretWildcard (queueName) {
|
|
13
|
+
const [prefix, suffix] = queueName.split('*')
|
|
14
|
+
// Strip anything that could cause backreferences
|
|
15
|
+
const safeSuffix = (suffix || '').replace(/[^a-zA-Z0-9_.]+/g, '').replace(/\./g, '\\.')
|
|
16
|
+
const suffixRegex = new RegExp(`${safeSuffix}$`)
|
|
17
|
+
// debug({ prefix, suffix, safeSuffix, suffixRegex })
|
|
18
|
+
return { prefix, suffix, safeSuffix, suffixRegex }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Aggregates inmportant attributes across queues and reports a summary.
|
|
23
|
+
* Attributes:
|
|
24
|
+
* - ApproximateNumberOfMessages: Sum
|
|
25
|
+
* - ApproximateNumberOfMessagesDelayed: Sum
|
|
26
|
+
* - ApproximateNumberOfMessagesNotVisible: Sum
|
|
27
|
+
*/
|
|
28
|
+
export async function getAggregateData (queueName) {
|
|
29
|
+
const { prefix, suffixRegex } = interpretWildcard(queueName)
|
|
30
|
+
const qrls = await getMatchingQueues(prefix, suffixRegex)
|
|
31
|
+
// debug({ qrls })
|
|
32
|
+
const data = await getQueueAttributes(qrls)
|
|
33
|
+
// debug({ data })
|
|
34
|
+
const total = { totalQueues: 0, contributingQueueNames: new Set() }
|
|
35
|
+
for (const { queue, result } of data) {
|
|
36
|
+
// debug({ row })
|
|
37
|
+
total.totalQueues++
|
|
38
|
+
for (const key in result.Attributes) {
|
|
39
|
+
const newAtrribute = parseInt(result.Attributes[key], 10)
|
|
40
|
+
if (newAtrribute > 0) {
|
|
41
|
+
total.contributingQueueNames.add(queue)
|
|
42
|
+
total[key] = (total[key] || 0) + newAtrribute
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// debug({ total })
|
|
47
|
+
// convert set to array
|
|
48
|
+
total.contributingQueueNames = [...total.contributingQueueNames.values()]
|
|
49
|
+
total.queueName = queueName
|
|
50
|
+
return total
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
debug('loaded')
|