hirefire-resource 0.10.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +171 -0
  3. data/LICENSE +21 -66
  4. data/README.md +34 -78
  5. data/hirefire-resource.gemspec +21 -15
  6. data/lib/hirefire/configuration.rb +31 -0
  7. data/lib/hirefire/errors/job_queue_latency_unsupported.rb +12 -0
  8. data/lib/hirefire/errors.rb +9 -0
  9. data/lib/hirefire/hirefire.rb +15 -0
  10. data/lib/hirefire/macro/bunny.rb +55 -55
  11. data/lib/hirefire/macro/delayed_job.rb +80 -60
  12. data/lib/hirefire/macro/deprecated/bunny.rb +85 -0
  13. data/lib/hirefire/macro/deprecated/delayed_job.rb +62 -0
  14. data/lib/hirefire/macro/deprecated/good_job.rb +32 -0
  15. data/lib/hirefire/macro/deprecated/que.rb +76 -0
  16. data/lib/hirefire/macro/deprecated/queue_classic.rb +28 -0
  17. data/lib/hirefire/macro/deprecated/resque.rb +47 -0
  18. data/lib/hirefire/macro/deprecated/sidekiq.rb +138 -0
  19. data/lib/hirefire/macro/good_job.rb +48 -13
  20. data/lib/hirefire/macro/que.rb +56 -36
  21. data/lib/hirefire/macro/queue_classic.rb +86 -0
  22. data/lib/hirefire/macro/resque.rb +111 -25
  23. data/lib/hirefire/macro/sidekiq.rb +324 -74
  24. data/lib/hirefire/macro/solid_queue.rb +166 -0
  25. data/lib/hirefire/middleware.rb +50 -103
  26. data/lib/hirefire/railtie.rb +1 -1
  27. data/lib/hirefire/resource.rb +1 -47
  28. data/lib/hirefire/utility.rb +22 -0
  29. data/lib/hirefire/version.rb +5 -0
  30. data/lib/hirefire/web.rb +151 -0
  31. data/lib/hirefire/worker.rb +39 -0
  32. data/lib/hirefire-resource.rb +4 -9
  33. metadata +50 -25
  34. data/.github/workflows/main.yml +0 -16
  35. data/.gitignore +0 -5
  36. data/Gemfile +0 -10
  37. data/Gemfile.lock +0 -52
  38. data/Rakefile +0 -4
  39. data/bin/rake +0 -29
  40. data/lib/hirefire/macro/qc.rb +0 -23
  41. data/lib/hirefire/macro/qu.rb +0 -27
@@ -1,123 +1,373 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest/sha1"
4
+ require_relative "deprecated/sidekiq"
5
+
3
6
  module HireFire
4
7
  module Macro
5
8
  module Sidekiq
9
+ extend HireFire::Macro::Deprecated::Sidekiq
6
10
  extend self
7
11
 
8
- # The latency in seconds for the provided queue.
9
- #
10
- # @example Sidekiq Queue Latency Macro Usage
11
- # HireFire::Macro::Sidekiq.queue # default queue
12
- # HireFire::Macro::Sidekiq.queue("email") # email queue
12
+ # Calculates the maximum job queue latency using Sidekiq. If no queues are specified, it
13
+ # measures latency across all available queues.
13
14
  #
14
- def latency(queue = "default")
15
- ::Sidekiq::Queue.new(queue).latency
15
+ # @param queues [Array<String, Symbol>] (optional) Names of the queues for latency
16
+ # measurement. If not provided, latency is measured across all queues.
17
+ # @param options [Hash] Options to control and filter the latency calculation.
18
+ # @option options [Boolean] :skip_retries (false) If true, skips the RetrySet in latency calculation.
19
+ # @option options [Boolean] :skip_scheduled (false) If true, skips the ScheduledSet in latency calculation.
20
+ # @return [Float] Maximum job queue latency in seconds.
21
+ # @example Calculate latency across all queues
22
+ # HireFire::Macro::Sidekiq.job_queue_latency
23
+ # @example Calculate latency for the "default" queue
24
+ # HireFire::Macro::Sidekiq.job_queue_latency(:default)
25
+ # @example Calculate maximum latency across "default" and "mailer" queues
26
+ # HireFire::Macro::Sidekiq.job_queue_latency(:default, :mailer)
27
+ # @example Calculate latency for the "default" queue, excluding scheduled jobs
28
+ # HireFire::Macro::Sidekiq.job_queue_latency(:default, skip_scheduled: true)
29
+ # @example Calculate latency for the "default" queue, excluding retries
30
+ # HireFire::Macro::Sidekiq.job_queue_latency(:default, skip_retries: true)
31
+ def job_queue_latency(*queues, **options)
32
+ JobQueueLatency.call(*queues, **options)
16
33
  end
17
34
 
18
- # Counts the amount of jobs in the (provided) Sidekiq queue(s).
19
- #
20
- # @example Sidekiq Queue Size Macro Usage
21
- # HireFire::Macro::Sidekiq.queue # all queues
22
- # HireFire::Macro::Sidekiq.queue("email") # only email queue
23
- # HireFire::Macro::Sidekiq.queue("audio", "video") # audio and video queues
24
- # HireFire::Macro::Sidekiq.queue("email", skip_scheduled: true) # only email, will not count scheduled queue
25
- # HireFire::Macro::Sidekiq.queue("audio", skip_retries: true) # only audio, will not count the retries queue
26
- # HireFire::Macro::Sidekiq.queue("audio", skip_working: true) # only audio, will not count already queued
35
+ # Calculates the total job queue size using Sidekiq. If no queues are specified, it measures
36
+ # size across all available queues.
27
37
  #
28
- # @param [Array] queues provide one or more queue names, or none for "all".
29
- # @return [Integer] the number of jobs in the queue(s).
30
- #
31
- def queue(*queues)
32
- require "sidekiq/api"
38
+ # @param queues [Array<String, Symbol>] (optional) Names of the queues for size measurement.
39
+ # If not provided, size is measured across all queues.
40
+ # @param options [Hash] Options to control and filter the count.
41
+ # @option options [Boolean] :server (false) If true, counts jobs server-side using Lua scripting.
42
+ # @option options [Boolean] :skip_retries (false) If true, skips counting jobs in retry queues.
43
+ # @option options [Boolean] :skip_scheduled (false) If true, skips counting jobs in scheduled queues.
44
+ # @option options [Boolean] :skip_working (false) If true, skips counting running jobs.
45
+ # @option options [Integer, nil] :max_scheduled (nil) Max number of scheduled jobs to consider; nil for no limit.
46
+ # @return [Integer] Total job queue size.
47
+ # @example Calculate size across all queues
48
+ # HireFire::Macro::Sidekiq.job_queue_size
49
+ # @example Calculate size for the "default" queue
50
+ # HireFire::Macro::Sidekiq.job_queue_size(:default)
51
+ # @example Calculate size across "default" and "mailer" queues
52
+ # HireFire::Macro::Sidekiq.job_queue_size(:default, :mailer)
53
+ # @example Calculate size for the "default" queue, excluding scheduled jobs
54
+ # HireFire::Macro::Sidekiq.job_queue_size(:default, skip_scheduled: true)
55
+ # @example Calculate size for the "default" queue, excluding retries
56
+ # HireFire::Macro::Sidekiq.job_queue_size(:default, skip_retries: true)
57
+ # @example Calculate size for the "default" queue, excluding running jobs
58
+ # HireFire::Macro::Sidekiq.job_queue_size(:default, skip_working: true)
59
+ # @example Calculate size for the "default" queue using server-side aggregation
60
+ # HireFire::Macro::Sidekiq.job_queue_size(:default, server: true)
61
+ # @example Calculate size for the "default" queue, limiting counting of scheduled jobs to 100_000
62
+ # HireFire::Macro::Sidekiq.job_queue_size(:default, max_scheduled: 100_000)
63
+ def job_queue_size(*queues, **options)
64
+ JobQueueSize.call(*queues, **options)
65
+ end
33
66
 
34
- queues.flatten!
67
+ # @!visibility private
68
+ module Common
69
+ private
35
70
 
36
- options = if queues.last.is_a?(Hash)
37
- queues.pop
38
- else
39
- {}
40
- end
71
+ def find_each_in_set(set)
72
+ cursor = 0
73
+ batch = 1000
74
+
75
+ loop do
76
+ entries = ::Sidekiq.redis do |connection|
77
+ if Gem::Version.new(::Sidekiq::VERSION) >= Gem::Version.new("7.0.0")
78
+ connection.zrange set.name, cursor, cursor + batch - 1, "WITHSCORES"
79
+ else
80
+ connection.zrange set.name, cursor, cursor + batch - 1, withscores: true
81
+ end
82
+ end
83
+
84
+ break if entries.empty?
41
85
 
42
- queues.map!(&:to_s)
43
- all_queues = ::Sidekiq::Queue.all.map(&:name)
44
- queues = all_queues if queues.empty?
86
+ entries.each do |entry, score|
87
+ yield ::Sidekiq::SortedEntry.new(self, score, entry)
88
+ end
45
89
 
46
- if fast_lookup_capable?(queues, all_queues)
47
- fast_lookup(options)
48
- else
49
- dynamic_lookup(queues, options)
90
+ cursor += batch
91
+ end
92
+ end
93
+
94
+ def registered_queues
95
+ ::Sidekiq::Queue.all.map(&:name).to_set
50
96
  end
51
97
  end
52
98
 
53
- private
99
+ # @!visibility private
100
+ module JobQueueLatency
101
+ extend Common
102
+ extend HireFire::Utility
103
+ extend self
54
104
 
55
- def fast_lookup_capable?(queues, all_queues)
56
- # When no queue names are provided (or all of them are), we know we
57
- # can peform much faster counts using Sidekiq::Stats and Redis
58
- queues.sort == all_queues.sort
59
- end
105
+ def call(*queues, skip_retries: false, skip_scheduled: false)
106
+ require "sidekiq/api"
60
107
 
61
- def fast_lookup(options)
62
- stats = ::Sidekiq::Stats.new
108
+ queues = normalize_queues(queues, allow_empty: true)
109
+ latencies = []
110
+ latencies << enqueued_latency(queues)
111
+ latencies << set_latency(::Sidekiq::RetrySet.new, queues) unless skip_retries
112
+ latencies << set_latency(::Sidekiq::ScheduledSet.new, queues) unless skip_scheduled
113
+ latencies.max
114
+ end
63
115
 
64
- in_queues = stats.enqueued
116
+ private
65
117
 
66
- if !options[:skip_scheduled]
67
- in_schedule = ::Sidekiq.redis { |c| c.zcount("schedule", "-inf", Time.now.to_f) }
118
+ def enqueued_latency(queues)
119
+ queues = registered_queues if queues.empty?
120
+
121
+ oldest_jobs = ::Sidekiq.redis do |conn|
122
+ conn.pipelined do |pipeline|
123
+ queues.each do |queue|
124
+ pipeline.lindex("queue:#{queue}", -1)
125
+ end
126
+ end
127
+ end
128
+
129
+ max_latencies = oldest_jobs.map do |job_payload|
130
+ job = job_payload ? JSON.parse(job_payload) : {}
131
+ job["enqueued_at"] ? Time.now.to_f - job["enqueued_at"] : 0.0
132
+ end
133
+
134
+ max_latencies.max || 0.0
68
135
  end
69
136
 
70
- if !options[:skip_retries]
71
- in_retry = ::Sidekiq.redis { |c| c.zcount("retry", "-inf", Time.now.to_f) }
137
+ def set_latency(set, queues)
138
+ max_latency = 0.0
139
+ now = Time.now
140
+
141
+ find_each_in_set(set) do |job|
142
+ if job.at > now
143
+ break
144
+ elsif queues.empty? || queues.include?(job.queue)
145
+ max_latency = now - job.at
146
+ break
147
+ end
148
+ end
149
+
150
+ max_latency
72
151
  end
152
+ end
153
+
154
+ # @!visibility private
155
+ module JobQueueSize
156
+ extend Common
157
+ extend HireFire::Utility
158
+ extend self
73
159
 
74
- if !options[:skip_working]
75
- in_progress = stats.workers_size
160
+ def call(*queues, server: false, **options)
161
+ require "sidekiq/api"
162
+
163
+ queues = normalize_queues(queues, allow_empty: true)
164
+
165
+ if server
166
+ server_lookup(queues, **options)
167
+ else
168
+ client_lookup(queues, **options)
169
+ end
76
170
  end
77
171
 
78
- [in_queues, in_schedule, in_retry, in_progress].compact.inject(&:+)
79
- end
172
+ private
80
173
 
81
- def dynamic_lookup(queues, options)
82
- in_queues = queues.inject(0) do |memo, name|
83
- memo += ::Sidekiq::Queue.new(name).size
84
- memo
174
+ def client_lookup(queues, skip_retries: false, skip_scheduled: false, skip_working: false, max_scheduled: nil)
175
+ size = enqueued_size(queues)
176
+ size += scheduled_size(queues, max_scheduled) unless skip_scheduled
177
+ size += retry_size(queues) unless skip_retries
178
+ size += working_size(queues) unless skip_working
179
+ size
85
180
  end
86
181
 
87
- if !options[:skip_scheduled]
88
- max = options[:max_scheduled]
182
+ def enqueued_size(queues)
183
+ queues = registered_queues if queues.empty?
89
184
 
90
- # For potentially long-running loops, compare all jobs against
91
- # time when the set snapshot was taken to avoid incorrect counts.
185
+ ::Sidekiq.redis do |conn|
186
+ conn.pipelined do |pipeline|
187
+ queues.each { |name| pipeline.llen("queue:#{name}") }
188
+ end
189
+ end.sum
190
+ end
191
+
192
+ def scheduled_size(queues, max = nil)
193
+ size, now = 0, Time.now
194
+
195
+ find_each_in_set(::Sidekiq::ScheduledSet.new) do |job|
196
+ if job.at > now || max && size >= max
197
+ break
198
+ elsif queues.empty? || queues.include?(job["queue"])
199
+ size += 1
200
+ end
201
+ end
202
+
203
+ size
204
+ end
205
+
206
+ def retry_size(queues)
207
+ size = 0
92
208
  now = Time.now
93
209
 
94
- in_schedule = ::Sidekiq::ScheduledSet.new.inject(0) do |memo, job|
95
- memo += 1 if queues.include?(job["queue"]) && job.at <= now
96
- break memo if max && memo >= max
97
- memo
210
+ find_each_in_set(::Sidekiq::RetrySet.new) do |job|
211
+ if job.at > now
212
+ break
213
+ elsif queues.empty? || queues.include?(job["queue"])
214
+ size += 1
215
+ end
98
216
  end
217
+
218
+ size
99
219
  end
100
220
 
101
- if !options[:skip_retries]
221
+ def working_size(queues)
102
222
  now = Time.now
223
+ now_as_i = now.to_i
103
224
 
104
- in_retry = ::Sidekiq::RetrySet.new.inject(0) do |memo, job|
105
- memo += 1 if queues.include?(job["queue"]) && job.at <= now
106
- memo
225
+ ::Sidekiq::Workers.new.count do |key, tid, job|
226
+ if Gem::Version.new(::Sidekiq::VERSION) >= Gem::Version.new("7.0.0")
227
+ (queues.empty? || queues.include?(job.queue)) && job.run_at <= now
228
+ else
229
+ (queues.empty? || queues.include?(job["queue"])) && job["run_at"] <= now_as_i
230
+ end
107
231
  end
108
232
  end
109
233
 
110
- now = Time.now.to_i
234
+ SERVER_SIDE_SCRIPT = <<~LUA
235
+ local tonumber = tonumber
236
+ local cjson_decode = cjson.decode
237
+
238
+ local function enqueued_size(queues)
239
+ local size = 0
111
240
 
112
- if !options[:skip_working]
113
- # Objects yielded to Workers#each:
114
- # https://github.com/mperham/sidekiq/blob/305ab8eedc362325da2e218b2a0e20e510668a42/lib/sidekiq/api.rb#L912
115
- in_progress = ::Sidekiq::Workers.new.count do |key, tid, job|
116
- queues.include?(job["queue"]) && job["run_at"] <= now
241
+ if next(queues) == nil then
242
+ queues = redis.call("keys", "queue:*")
243
+
244
+ for _, name in ipairs(queues) do
245
+ queues[string.sub(name, 7)] = true
246
+ end
247
+ end
248
+
249
+ for queue, _ in pairs(queues) do
250
+ size = size + redis.call("llen", "queue:" .. queue)
251
+ end
252
+
253
+ return size
254
+ end
255
+
256
+ local function set_size(queues, set, now, max)
257
+ local size = 0
258
+ local limit = 100
259
+ local offset = 0
260
+ local jobs
261
+
262
+ repeat
263
+ jobs = redis.call("zrangebyscore", set, "-inf", now, "LIMIT", offset, limit)
264
+ offset = offset + limit
265
+
266
+ for i = 1, #jobs do
267
+ local job = cjson_decode(jobs[i])
268
+
269
+ if job and (next(queues) == nil or queues[job.queue]) then
270
+ size = size + 1
271
+ end
272
+ end
273
+ until #jobs == 0 or (max > 0 and size >= max)
274
+
275
+ return size
276
+ end
277
+
278
+ local function working_size(queues)
279
+ local size = 0
280
+ local cursor = "0"
281
+
282
+ repeat
283
+ local process_sets = redis.call("SSCAN", "processes", cursor)
284
+ cursor = process_sets[1]
285
+
286
+ for _, process_key in ipairs(process_sets[2]) do
287
+ local worker_key = process_key .. ":work"
288
+ local worker_data = redis.call("HGETALL", worker_key)
289
+
290
+ for i = 2, #worker_data, 2 do
291
+ local worker = cjson_decode(worker_data[i])
292
+
293
+ if next(queues) == nil or queues[worker.queue] then
294
+ size = size + 1
295
+ end
296
+ end
297
+ end
298
+ until cursor == "0"
299
+
300
+ return size
301
+ end
302
+
303
+ local now = tonumber(ARGV[1])
304
+ local max_scheduled = tonumber(ARGV[2])
305
+ local skip_scheduled = tonumber(ARGV[3]) == 1
306
+ local skip_retries = tonumber(ARGV[4]) == 1
307
+ local skip_working = tonumber(ARGV[5]) == 1
308
+
309
+ local queues = {}
310
+ for i = 6, #ARGV do
311
+ queues[ARGV[i]] = true
312
+ end
313
+
314
+ local size = enqueued_size(queues)
315
+
316
+ if not skip_scheduled then
317
+ size = size + set_size(queues, "schedule", now, max_scheduled)
318
+ end
319
+
320
+ if not skip_retries then
321
+ size = size + set_size(queues, "retry", now, 0)
322
+ end
323
+
324
+ if not skip_working then
325
+ size = size + working_size(queues)
326
+ end
327
+
328
+ return size
329
+ LUA
330
+
331
+ SERVER_SIDE_SCRIPT_SHA = Digest::SHA1.hexdigest(SERVER_SIDE_SCRIPT).freeze
332
+
333
+ def server_lookup(queues, skip_scheduled: false, skip_retries: false, skip_working: false, max_scheduled: 0)
334
+ ::Sidekiq.redis do |connection|
335
+ now = Time.now.to_i
336
+ skip_scheduled = skip_scheduled ? 1 : 0
337
+ skip_retries = skip_retries ? 1 : 0
338
+ skip_working = skip_working ? 1 : 0
339
+
340
+ if defined?(::Sidekiq::RedisClientAdapter::CompatClient) && connection.is_a?(::Sidekiq::RedisClientAdapter::CompatClient)
341
+ count_with_redis_client(connection, now, max_scheduled, skip_scheduled, skip_retries, skip_working, *queues)
342
+ elsif defined?(::Redis) && connection.is_a?(::Redis)
343
+ count_with_redis(connection, now, max_scheduled, skip_scheduled, skip_retries, skip_working, *queues)
344
+ else
345
+ raise "Unsupported Redis connection type: #{connection.class}"
346
+ end
117
347
  end
118
348
  end
119
349
 
120
- [in_queues, in_schedule, in_retry, in_progress].compact.inject(&:+)
350
+ def count_with_redis(connection, *args)
351
+ connection.evalsha(SERVER_SIDE_SCRIPT_SHA, argv: args)
352
+ rescue Redis::CommandError => e
353
+ if e.message.include?("NOSCRIPT")
354
+ connection.script(:load, SERVER_SIDE_SCRIPT)
355
+ retry
356
+ else
357
+ raise
358
+ end
359
+ end
360
+
361
+ def count_with_redis_client(connection, *args)
362
+ connection.call("evalsha", SERVER_SIDE_SCRIPT_SHA, 0, *args)
363
+ rescue RedisClient::CommandError => e
364
+ if e.message.include?("NOSCRIPT")
365
+ connection.call("script", "load", SERVER_SIDE_SCRIPT)
366
+ retry
367
+ else
368
+ raise
369
+ end
370
+ end
121
371
  end
122
372
  end
123
373
  end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HireFire
4
+ module Macro
5
+ module SolidQueue
6
+ extend HireFire::Utility
7
+ extend self
8
+
9
+ LATENCY_METHODS = [
10
+ :ready_latency,
11
+ :scheduled_latency,
12
+ :blocked_latency
13
+ ].freeze
14
+
15
+ # Calculates the maximum job queue latency using SolidQueue. If no queues are specified, it
16
+ # measures latency across all available queues.
17
+ #
18
+ # This function measures the job queue latency across the Ready, Scheduled, and Blocked
19
+ # queues, based on the enqueue, schedule, and expiration times of their executions. Executions
20
+ # in the Claimed queue, as well as in paused queues, are excluded from the calculation.
21
+ #
22
+ # @param queues [Array<String, Symbol>] (optional) Names of the queues for latency
23
+ # measurement. If not provided, latency is measured across all queues.
24
+ # @return [Float] Maximum job queue latency in seconds.
25
+ # @example Calculate latency across all queues
26
+ # HireFire::Macro::SolidQueue.job_queue_latency
27
+ # @example Calculate latency for the "default" queue
28
+ # HireFire::Macro::SolidQueue.job_queue_latency(:default)
29
+ # @example Calculate latency across "default" and "mailer" queues
30
+ # HireFire::Macro::SolidQueue.job_queue_latency(:default, :mailer)
31
+ # @example Calculate latency across "mailer_*" queues (i.e. mailer_notification, mailer_newsletter)
32
+ # HireFire::Macro::SolidQueue.job_queue_latency(:"mailer_*")
33
+ def job_queue_latency(*queues)
34
+ queues, now = determine_queues(queues), Time.now
35
+
36
+ LATENCY_METHODS.map do |latency_method|
37
+ method(latency_method).call(queues, now: now)
38
+ end.max
39
+ end
40
+
41
+ SIZE_METHODS = [
42
+ :ready_size,
43
+ :scheduled_size,
44
+ :claimed_size,
45
+ :blocked_size
46
+ ].freeze
47
+
48
+ # Calculates the total job queue size using SolidQueue. If no queues are specified, it
49
+ # measures size across all available queues.
50
+ #
51
+ # This function measures the job queue latency across the Ready, Scheduled, Blocked, and
52
+ # Claimed queues, based on the schedule and expiration times of their executions. Executions
53
+ # in paused queues are excluded from the calculation.
54
+ #
55
+ # @param queues [Array<String, Symbol>] (optional) Names of the queues for size measurement.
56
+ # If not provided, size is measured across all queues.
57
+ # @return [Integer] Total job queue size.
58
+ # @example Calculate size across all queues
59
+ # HireFire::Macro::SolidQueue.job_queue_size
60
+ # @example Calculate size for the "default" queue
61
+ # HireFire::Macro::SolidQueue.job_queue_size(:default)
62
+ # @example Calculate size across "default" and "mailer" queues
63
+ # HireFire::Macro::SolidQueue.job_queue_size(:default, :mailer)
64
+ # @example Calculate size across "mailer_*" queues (i.e. mailer_notification, mailer_newsletter)
65
+ # HireFire::Macro::SolidQueue.job_queue_size(:"mailer_*")
66
+ def job_queue_size(*queues)
67
+ queues = determine_queues(queues)
68
+
69
+ SIZE_METHODS.sum do |count_method|
70
+ method(count_method).call(queues)
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def determine_queues(queues)
77
+ queues = normalize_queues(queues, allow_empty: true)
78
+
79
+ Set.new(
80
+ if queues.empty?
81
+ registered_queues
82
+ elsif queues.any? { |queue| queue.end_with?("*") }
83
+ expand_wildcards(queues)
84
+ else
85
+ queues
86
+ end
87
+ ) - paused_queues
88
+ end
89
+
90
+ def registered_queues
91
+ ::SolidQueue::Queue.all.map(&:name)
92
+ end
93
+
94
+ def paused_queues
95
+ ::SolidQueue::Pause.pluck(:queue_name)
96
+ end
97
+
98
+ def expand_wildcards(queues)
99
+ cached_registered_queues = registered_queues
100
+
101
+ queues.flat_map do |queue|
102
+ if queue.end_with?("*")
103
+ cached_registered_queues.select do |registered_queue|
104
+ registered_queue.start_with?(queue[0..-2])
105
+ end
106
+ else
107
+ queue
108
+ end
109
+ end
110
+ end
111
+
112
+ def ready_latency(queues, now:)
113
+ now - (
114
+ ::SolidQueue::ReadyExecution
115
+ .where(queue_name: queues)
116
+ .minimum(:created_at) || now
117
+ )
118
+ end
119
+
120
+ def ready_size(queues)
121
+ ::SolidQueue::ReadyExecution
122
+ .where(queue_name: queues)
123
+ .count
124
+ end
125
+
126
+ def scheduled_latency(queues, now:)
127
+ now - (
128
+ ::SolidQueue::ScheduledExecution
129
+ .due
130
+ .where(queue_name: queues)
131
+ .minimum(:scheduled_at) || now
132
+ )
133
+ end
134
+
135
+ def scheduled_size(queues)
136
+ ::SolidQueue::ScheduledExecution
137
+ .due
138
+ .where(queue_name: queues)
139
+ .count
140
+ end
141
+
142
+ def blocked_latency(queues, now:)
143
+ now - (
144
+ ::SolidQueue::BlockedExecution
145
+ .expired
146
+ .where(queue_name: queues)
147
+ .minimum(:expires_at) || now
148
+ )
149
+ end
150
+
151
+ def blocked_size(queues)
152
+ ::SolidQueue::BlockedExecution
153
+ .expired
154
+ .where(queue_name: queues)
155
+ .count
156
+ end
157
+
158
+ def claimed_size(queues)
159
+ ::SolidQueue::Job
160
+ .joins(:claimed_execution)
161
+ .where(queue_name: queues)
162
+ .count
163
+ end
164
+ end
165
+ end
166
+ end