hirefire-resource 0.10.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +175 -0
- data/LICENSE +21 -66
- data/README.md +34 -78
- data/hirefire-resource.gemspec +21 -15
- data/lib/hirefire/configuration.rb +31 -0
- data/lib/hirefire/errors/job_queue_latency_unsupported.rb +12 -0
- data/lib/hirefire/errors.rb +9 -0
- data/lib/hirefire/hirefire.rb +15 -0
- data/lib/hirefire/macro/bunny.rb +55 -55
- data/lib/hirefire/macro/delayed_job.rb +80 -60
- data/lib/hirefire/macro/deprecated/bunny.rb +85 -0
- data/lib/hirefire/macro/deprecated/delayed_job.rb +62 -0
- data/lib/hirefire/macro/deprecated/good_job.rb +32 -0
- data/lib/hirefire/macro/deprecated/que.rb +76 -0
- data/lib/hirefire/macro/deprecated/queue_classic.rb +28 -0
- data/lib/hirefire/macro/deprecated/resque.rb +47 -0
- data/lib/hirefire/macro/deprecated/sidekiq.rb +138 -0
- data/lib/hirefire/macro/good_job.rb +48 -13
- data/lib/hirefire/macro/que.rb +56 -36
- data/lib/hirefire/macro/queue_classic.rb +86 -0
- data/lib/hirefire/macro/resque.rb +112 -24
- data/lib/hirefire/macro/sidekiq.rb +324 -74
- data/lib/hirefire/macro/solid_queue.rb +166 -0
- data/lib/hirefire/middleware.rb +50 -103
- data/lib/hirefire/railtie.rb +1 -1
- data/lib/hirefire/resource.rb +1 -47
- data/lib/hirefire/utility.rb +22 -0
- data/lib/hirefire/version.rb +5 -0
- data/lib/hirefire/web.rb +151 -0
- data/lib/hirefire/worker.rb +39 -0
- data/lib/hirefire-resource.rb +4 -9
- metadata +50 -24
- data/.github/workflows/main.yml +0 -16
- data/.gitignore +0 -5
- data/Gemfile +0 -10
- data/Gemfile.lock +0 -49
- data/Rakefile +0 -4
- data/lib/hirefire/macro/qc.rb +0 -23
- data/lib/hirefire/macro/qu.rb +0 -27
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HireFire
|
|
4
|
+
module Macro
|
|
5
|
+
module Deprecated
|
|
6
|
+
# Provides backward compatibility with the deprecated Sidekiq macro.
|
|
7
|
+
# For new implementations, refer to {HireFire::Macro::Sidekiq}.
|
|
8
|
+
module Sidekiq
|
|
9
|
+
# Calculates the latency (in seconds) for the specified Sidekiq queue.
|
|
10
|
+
#
|
|
11
|
+
# The method uses the Sidekiq::Queue class to obtain the latency of a queue, which
|
|
12
|
+
# is the duration since the oldest job in the queue was enqueued.
|
|
13
|
+
#
|
|
14
|
+
# @param queue [String, Symbol] The name of the queue to measure latency.
|
|
15
|
+
# Defaults to "default" if no queue name is provided.
|
|
16
|
+
# @return [Float] The latency of the queue in seconds.
|
|
17
|
+
# @example Calculating latency for the default queue
|
|
18
|
+
# HireFire::Macro::Sidekiq.latency
|
|
19
|
+
# @example Calculating latency for the "critical" queue
|
|
20
|
+
# HireFire::Macro::Sidekiq.latency("critical")
|
|
21
|
+
def latency(queue = "default")
|
|
22
|
+
::Sidekiq::Queue.new(queue.to_s).latency
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Counts the number of jobs in the specified Sidekiq queue(s).
|
|
26
|
+
#
|
|
27
|
+
# The method supports various options to include or exclude jobs from
|
|
28
|
+
# specific sets like scheduled, retries, or in-progress jobs.
|
|
29
|
+
#
|
|
30
|
+
# @param args [Array<String, Symbol, Hash>] Queue names to count jobs in and an optional hash of options.
|
|
31
|
+
# Pass an empty array or no arguments to count jobs in all queues.
|
|
32
|
+
# The last argument can be a Hash of options to modify the count behavior.
|
|
33
|
+
# Possible keys are :skip_scheduled, :skip_retries, :skip_working.
|
|
34
|
+
# These keys are booleans which default to false.
|
|
35
|
+
# @return [Integer] Total number of jobs in the specified queues.
|
|
36
|
+
# @example Counting jobs in all queues
|
|
37
|
+
# HireFire::Macro::Sidekiq.queue
|
|
38
|
+
# @example Counting jobs in the "default" and "critical" queues
|
|
39
|
+
# HireFire::Macro::Sidekiq.queue("default", "critical")
|
|
40
|
+
# @example Counting jobs in the "default" queue, excluding scheduled jobs
|
|
41
|
+
# HireFire::Macro::Sidekiq.queue("default", skip_scheduled: true)
|
|
42
|
+
# @example Counting jobs in the "default" queue, excluding retryable jobs
|
|
43
|
+
# HireFire::Macro::Sidekiq.queue("default", skip_retries: true)
|
|
44
|
+
# @example Counting jobs in the "default" queue, excluding in-progress jobs
|
|
45
|
+
# HireFire::Macro::Sidekiq.queue("default", skip_working: true)
|
|
46
|
+
def queue(*args)
|
|
47
|
+
require "sidekiq/api"
|
|
48
|
+
|
|
49
|
+
args.flatten!
|
|
50
|
+
|
|
51
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
52
|
+
queues = args.map(&:to_s)
|
|
53
|
+
all_queues = ::Sidekiq::Queue.all.map(&:name)
|
|
54
|
+
queues = all_queues if queues.empty?
|
|
55
|
+
|
|
56
|
+
if fast_lookup_capable?(queues, all_queues)
|
|
57
|
+
fast_lookup(options)
|
|
58
|
+
else
|
|
59
|
+
dynamic_lookup(queues, options)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def fast_lookup_capable?(queues, all_queues)
|
|
66
|
+
# When no queue names are provided (or all of them are), we know we
|
|
67
|
+
# can perform much faster counts using Sidekiq::Stats and Redis
|
|
68
|
+
queues.sort == all_queues.sort
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def fast_lookup(options)
|
|
72
|
+
stats = ::Sidekiq::Stats.new
|
|
73
|
+
|
|
74
|
+
in_queues = stats.enqueued
|
|
75
|
+
|
|
76
|
+
if !options[:skip_scheduled]
|
|
77
|
+
in_schedule = ::Sidekiq.redis { |c| c.zcount("schedule", "-inf", Time.now.to_f) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if !options[:skip_retries]
|
|
81
|
+
in_retry = ::Sidekiq.redis { |c| c.zcount("retry", "-inf", Time.now.to_f) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if !options[:skip_working]
|
|
85
|
+
in_progress = stats.workers_size
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
[in_queues, in_schedule, in_retry, in_progress].compact.inject(&:+)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def dynamic_lookup(queues, options)
|
|
92
|
+
in_queues = queues.inject(0) do |memo, name|
|
|
93
|
+
memo += ::Sidekiq::Queue.new(name).size
|
|
94
|
+
memo
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if !options[:skip_scheduled]
|
|
98
|
+
max = options[:max_scheduled]
|
|
99
|
+
|
|
100
|
+
# For potentially long-running loops, compare all jobs against
|
|
101
|
+
# time when the set snapshot was taken to avoid incorrect counts.
|
|
102
|
+
now = Time.now
|
|
103
|
+
|
|
104
|
+
in_schedule = ::Sidekiq::ScheduledSet.new.inject(0) do |memo, job|
|
|
105
|
+
memo += 1 if queues.include?(job["queue"]) && job.at <= now
|
|
106
|
+
break memo if max && memo >= max
|
|
107
|
+
memo
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if !options[:skip_retries]
|
|
112
|
+
now = Time.now
|
|
113
|
+
|
|
114
|
+
in_retry = ::Sidekiq::RetrySet.new.inject(0) do |memo, job|
|
|
115
|
+
memo += 1 if queues.include?(job["queue"]) && job.at <= now
|
|
116
|
+
memo
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
now = Time.now
|
|
121
|
+
now_as_i = now.to_i
|
|
122
|
+
|
|
123
|
+
if !options[:skip_working]
|
|
124
|
+
in_progress = ::Sidekiq::Workers.new.count do |key, tid, job|
|
|
125
|
+
if Gem::Version.new(::Sidekiq::VERSION) >= Gem::Version.new("7.0.0")
|
|
126
|
+
queues.include?(job.queue) && job.run_at <= now
|
|
127
|
+
else
|
|
128
|
+
queues.include?(job["queue"]) && job["run_at"] <= now_as_i
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
[in_queues, in_schedule, in_retry, in_progress].compact.inject(&:+)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -1,25 +1,60 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "deprecated/good_job"
|
|
4
|
+
|
|
3
5
|
module HireFire
|
|
4
6
|
module Macro
|
|
5
7
|
module GoodJob
|
|
8
|
+
extend HireFire::Macro::Deprecated::GoodJob
|
|
9
|
+
extend HireFire::Utility
|
|
6
10
|
extend self
|
|
7
11
|
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# @example Queue Macro Usage
|
|
12
|
-
# HireFire::Macro::GoodJob.queue # counts all queues.
|
|
13
|
-
# HireFire::Macro::GoodJob.queue("email") # counts the `email` queue.
|
|
12
|
+
# Calculates the maximum job queue latency using GoodJob. If no queues are specified, it
|
|
13
|
+
# measures latency across all available queues.
|
|
14
14
|
#
|
|
15
|
-
# @param [String]
|
|
16
|
-
#
|
|
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
|
+
# @return [Float] Maximum job queue latency in seconds.
|
|
18
|
+
# @example Calculate latency across all queues
|
|
19
|
+
# HireFire::Macro::GoodJob.job_queue_latency
|
|
20
|
+
# @example Calculate latency for the "default" queue
|
|
21
|
+
# HireFire::Macro::GoodJob.job_queue_latency(:default)
|
|
22
|
+
# @example Calculate latency across "default" and "mailer" queues
|
|
23
|
+
# HireFire::Macro::GoodJob.job_queue_latency(:default, :mailer)
|
|
24
|
+
def job_queue_latency(*queues)
|
|
25
|
+
queues = normalize_queues(queues, allow_empty: true)
|
|
26
|
+
query = ::GoodJob::Execution
|
|
27
|
+
query = query.where(queue_name: queues) if queues.any?
|
|
28
|
+
query = query.where(finished_at: nil)
|
|
29
|
+
query = query.where(scheduled_at: ..Time.now).or(query.where(scheduled_at: nil))
|
|
30
|
+
query = query.order(scheduled_at: :asc, created_at: :asc)
|
|
31
|
+
|
|
32
|
+
if (job = query.first)
|
|
33
|
+
Time.now - (job.scheduled_at || job.created_at)
|
|
34
|
+
else
|
|
35
|
+
0.0
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Calculates the total job queue size using GoodJob. If no queues are specified, it
|
|
40
|
+
# measures size across all available queues.
|
|
17
41
|
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
42
|
+
# @param queues [Array<String, Symbol>] (optional) Names of the queues for size measurement.
|
|
43
|
+
# If not provided, size is measured across all queues.
|
|
44
|
+
# @return [Integer] Total job queue size.
|
|
45
|
+
# @example Calculate size across all queues
|
|
46
|
+
# HireFire::Macro::GoodJob.job_queue_size
|
|
47
|
+
# @example Calculate size for the "default" queue
|
|
48
|
+
# HireFire::Macro::GoodJob.job_queue_size(:default)
|
|
49
|
+
# @example Calculate size across "default" and "mailer" queues
|
|
50
|
+
# HireFire::Macro::GoodJob.job_queue_size(:default, :mailer)
|
|
51
|
+
def job_queue_size(*queues)
|
|
52
|
+
queues = normalize_queues(queues, allow_empty: true)
|
|
53
|
+
query = ::GoodJob::Execution
|
|
54
|
+
query = query.where(queue_name: queues) if queues.any?
|
|
55
|
+
query = query.where(finished_at: nil)
|
|
56
|
+
query = query.where(scheduled_at: ..Time.now).or(query.where(scheduled_at: nil))
|
|
57
|
+
query.count
|
|
23
58
|
end
|
|
24
59
|
end
|
|
25
60
|
end
|
data/lib/hirefire/macro/que.rb
CHANGED
|
@@ -1,55 +1,75 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "deprecated/que"
|
|
4
|
+
|
|
3
5
|
module HireFire
|
|
4
6
|
module Macro
|
|
5
7
|
module Que
|
|
8
|
+
extend HireFire::Macro::Deprecated::Que
|
|
9
|
+
extend HireFire::Utility
|
|
6
10
|
extend self
|
|
7
11
|
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# @example Queue Macro Usage
|
|
12
|
-
# HireFire::Macro::Que.queue # counts all queues.
|
|
13
|
-
# HireFire::Macro::Que.queue("email") # counts the `email` queue.
|
|
14
|
-
#
|
|
15
|
-
# @param [String] queue the queue name to count. (default: nil # gets all queues)
|
|
16
|
-
# @return [Integer] the number of jobs in the queue(s).
|
|
12
|
+
# Calculates the maximum job queue latency using Que. If no queues are specified, it
|
|
13
|
+
# measures latency across all available queues.
|
|
17
14
|
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
# @return [Float] Maximum job queue latency in seconds.
|
|
18
|
+
# @example Calculate latency across all queues
|
|
19
|
+
# HireFire::Macro::Que.job_queue_latency
|
|
20
|
+
# @example Calculate latency for the "default" queue
|
|
21
|
+
# HireFire::Macro::Que.job_queue_latency(:default)
|
|
22
|
+
# @example Calculate latency across "default" and "mailer" queues
|
|
23
|
+
# HireFire::Macro::Que.job_queue_latency(:default, :mailer)
|
|
24
|
+
def job_queue_latency(*queues)
|
|
25
|
+
query = <<~SQL
|
|
26
|
+
SELECT run_at FROM que_jobs
|
|
27
|
+
WHERE run_at <= NOW()
|
|
28
|
+
AND finished_at IS NULL
|
|
29
|
+
AND expired_at IS NULL
|
|
30
|
+
#{filter_by_queues_if_any(queues)}
|
|
31
|
+
ORDER BY run_at ASC LIMIT 1
|
|
32
|
+
SQL
|
|
33
|
+
|
|
34
|
+
result = ::Que.execute(query).first
|
|
35
|
+
result ? (Time.now - result[:run_at].to_time) : 0.0
|
|
22
36
|
end
|
|
23
37
|
|
|
24
|
-
|
|
38
|
+
# Calculates the total job queue size using Que. If no queues are specified, it
|
|
39
|
+
# measures size across all available queues.
|
|
40
|
+
#
|
|
41
|
+
# @param queues [Array<String, Symbol>] (optional) Names of the queues for size measurement.
|
|
42
|
+
# If not provided, size is measured across all queues.
|
|
43
|
+
# @return [Integer] Total job queue size.
|
|
44
|
+
# @example Calculate size across all queues
|
|
45
|
+
# HireFire::Macro::Que.job_queue_size
|
|
46
|
+
# @example Calculate size for the "default" queue
|
|
47
|
+
# HireFire::Macro::Que.job_queue_size(:default)
|
|
48
|
+
# @example Calculate size across "default" and "mailer" queues
|
|
49
|
+
# HireFire::Macro::Que.job_queue_size(:default, :mailer)
|
|
50
|
+
def job_queue_size(*queues)
|
|
51
|
+
query = <<~SQL
|
|
52
|
+
SELECT COUNT(*) AS total FROM que_jobs
|
|
53
|
+
WHERE run_at <= NOW()
|
|
54
|
+
AND finished_at IS NULL
|
|
55
|
+
AND expired_at IS NULL
|
|
56
|
+
#{filter_by_queues_if_any(queues)}
|
|
57
|
+
SQL
|
|
25
58
|
|
|
26
|
-
|
|
27
|
-
return QUE_V0_QUERY if defined?(::Que::Version)
|
|
28
|
-
return QUE_V1_QUERY if defined?(::Que::VERSION)
|
|
29
|
-
raise "Couldn't find Que version"
|
|
59
|
+
::Que.execute(query).first.fetch(:total).to_i
|
|
30
60
|
end
|
|
31
61
|
|
|
32
|
-
|
|
33
|
-
queues.map { |queue| "'#{queue}'" }.join(",")
|
|
34
|
-
end
|
|
62
|
+
private
|
|
35
63
|
|
|
36
|
-
def
|
|
37
|
-
|
|
64
|
+
def filter_by_queues_if_any(queues)
|
|
65
|
+
queues = normalize_queues(queues, allow_empty: true)
|
|
66
|
+
queues = queues.map(&method(:sanitize_sql)).join(", ")
|
|
67
|
+
queues.empty? ? "" : "AND queue IN (#{queues})"
|
|
38
68
|
end
|
|
39
69
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
WHERE run_at < NOW()
|
|
44
|
-
QUERY
|
|
45
|
-
|
|
46
|
-
QUE_V1_QUERY = query_const(<<-QUERY)
|
|
47
|
-
SELECT COUNT(*) AS total
|
|
48
|
-
FROM que_jobs
|
|
49
|
-
WHERE finished_at IS NULL
|
|
50
|
-
AND expired_at IS NULL
|
|
51
|
-
AND run_at <= NOW()
|
|
52
|
-
QUERY
|
|
70
|
+
def sanitize_sql(value)
|
|
71
|
+
"'" + value.to_s.gsub(/['"\\]/, '\\\\\\&') + "'"
|
|
72
|
+
end
|
|
53
73
|
end
|
|
54
74
|
end
|
|
55
75
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "deprecated/queue_classic"
|
|
4
|
+
|
|
5
|
+
module HireFire
|
|
6
|
+
module Macro
|
|
7
|
+
module QC
|
|
8
|
+
extend HireFire::Macro::Deprecated::QC
|
|
9
|
+
extend HireFire::Utility
|
|
10
|
+
extend self
|
|
11
|
+
|
|
12
|
+
# Calculates the maximum job queue latency using Queue Classic. If no queues are specified, it
|
|
13
|
+
# measures latency across all available queues.
|
|
14
|
+
#
|
|
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
|
+
# @return [Float] Maximum job queue latency in seconds.
|
|
18
|
+
# @example Calculate latency across all queues
|
|
19
|
+
# HireFire::Macro::QC.job_queue_latency
|
|
20
|
+
# @example Calculate latency for the "default" queue
|
|
21
|
+
# HireFire::Macro::QC.job_queue_latency(:default)
|
|
22
|
+
# @example Calculate latency across "default" and "mailer" queues
|
|
23
|
+
# HireFire::Macro::QC.job_queue_latency(:default, :mailer)
|
|
24
|
+
def job_queue_latency(*queues)
|
|
25
|
+
queues = normalize_queues(queues, allow_empty: true)
|
|
26
|
+
|
|
27
|
+
query = <<~SQL
|
|
28
|
+
SELECT EXTRACT(EPOCH FROM (now() - scheduled_at)) AS latency
|
|
29
|
+
FROM #{::QC.table_name}
|
|
30
|
+
WHERE scheduled_at <= now()
|
|
31
|
+
#{filter_by_queues_if_any(queues)}
|
|
32
|
+
ORDER BY scheduled_at ASC
|
|
33
|
+
LIMIT 1
|
|
34
|
+
SQL
|
|
35
|
+
|
|
36
|
+
connection = ::QC.default_conn_adapter
|
|
37
|
+
|
|
38
|
+
result = if queues.any?
|
|
39
|
+
connection.execute(query, "{#{queues.to_a.join(",")}}")
|
|
40
|
+
else
|
|
41
|
+
connection.execute(query)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
(result && result["latency"]) ? result["latency"].to_f : 0.0
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Calculates the total job queue size using Queue Classic. If no queues are specified, it
|
|
48
|
+
# measures size across all available queues.
|
|
49
|
+
#
|
|
50
|
+
# @param queues [Array<String, Symbol>] (optional) Names of the queues for size measurement.
|
|
51
|
+
# If not provided, size is measured across all queues.
|
|
52
|
+
# @return [Integer] Total job queue size.
|
|
53
|
+
# @example Calculate size across all queues
|
|
54
|
+
# HireFire::Macro::QC.job_queue_size
|
|
55
|
+
# @example Calculate size for the "default" queue
|
|
56
|
+
# HireFire::Macro::QC.job_queue_size(:default)
|
|
57
|
+
# @example Calculate size across "default" and "mailer" queues
|
|
58
|
+
# HireFire::Macro::QC.job_queue_size(:default, :mailer)
|
|
59
|
+
def job_queue_size(*queues)
|
|
60
|
+
queues = normalize_queues(queues, allow_empty: true)
|
|
61
|
+
|
|
62
|
+
query = <<~SQL
|
|
63
|
+
SELECT COUNT(*) FROM #{::QC.table_name}
|
|
64
|
+
WHERE scheduled_at <= now()
|
|
65
|
+
#{filter_by_queues_if_any(queues)}
|
|
66
|
+
SQL
|
|
67
|
+
|
|
68
|
+
connection = ::QC.default_conn_adapter
|
|
69
|
+
|
|
70
|
+
result = if queues.any?
|
|
71
|
+
connection.execute(query, "{#{queues.to_a.join(",")}}")
|
|
72
|
+
else
|
|
73
|
+
connection.execute(query)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
result["count"].to_i
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def filter_by_queues_if_any(queues)
|
|
82
|
+
queues.any? ? "AND q_name = ANY($1::text[])" : ""
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -1,41 +1,129 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "deprecated/resque"
|
|
4
|
+
|
|
3
5
|
module HireFire
|
|
4
6
|
module Macro
|
|
5
7
|
module Resque
|
|
8
|
+
extend HireFire::Errors::JobQueueLatencyUnsupported
|
|
9
|
+
extend HireFire::Macro::Deprecated::Resque
|
|
10
|
+
extend HireFire::Utility
|
|
6
11
|
extend self
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# @return [Integer] the number of jobs in the queue(s).
|
|
13
|
+
SIZE_METHODS = [
|
|
14
|
+
:enqueued_size,
|
|
15
|
+
:working_size,
|
|
16
|
+
:scheduled_size
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
# Calculates the maximum job queue size using Resque. If no queues are specified, it
|
|
20
|
+
# measures size across all available queues.
|
|
17
21
|
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
# @param queues [Array<String, Symbol>] (optional) Names of the queues for size measurement.
|
|
23
|
+
# If not provided, size is measured across all queues.
|
|
24
|
+
# @return [Integer] Total job queue size.
|
|
25
|
+
# @example Calculate size across all queues
|
|
26
|
+
# HireFire::Macro::Resque.job_queue_size
|
|
27
|
+
# @example Calculate size for the "default" queue
|
|
28
|
+
# HireFire::Macro::Resque.job_queue_size(:default)
|
|
29
|
+
# @example Calculate size across the "default" and "mailer" queues
|
|
30
|
+
# HireFire::Macro::Resque.job_queue_size(:default, :mailer)
|
|
31
|
+
def job_queue_size(*queues)
|
|
32
|
+
queues = normalize_queues(queues, allow_empty: true)
|
|
33
|
+
|
|
34
|
+
SIZE_METHODS.sum do |size_method|
|
|
35
|
+
method(size_method).call(queues)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def enqueued_size(queues)
|
|
42
|
+
queues = registered_queues if queues.empty?
|
|
43
|
+
|
|
44
|
+
::Resque.redis.pipelined do |pipeline|
|
|
45
|
+
queues.each do |queue|
|
|
46
|
+
pipeline.llen("queue:#{queue}")
|
|
47
|
+
end
|
|
48
|
+
end.sum
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def working_size(queues)
|
|
52
|
+
ids = ::Resque.redis.smembers(:workers).compact
|
|
53
|
+
|
|
54
|
+
workers = ::Resque.redis.pipelined do |pipeline|
|
|
55
|
+
ids.each do |id|
|
|
56
|
+
pipeline.get("worker:#{id}")
|
|
57
|
+
end
|
|
58
|
+
end.compact
|
|
21
59
|
|
|
22
|
-
|
|
60
|
+
if queues.empty?
|
|
61
|
+
workers.count
|
|
62
|
+
else
|
|
63
|
+
workers.count do |worker|
|
|
64
|
+
queues.include?(::Resque.decode(worker)["queue"])
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def scheduled_size(queues)
|
|
70
|
+
cursor = 0
|
|
71
|
+
batch = 1000
|
|
72
|
+
total_size = 0
|
|
73
|
+
current_time = Time.now.to_i
|
|
74
|
+
|
|
75
|
+
loop do
|
|
76
|
+
timestamps = ::Resque.redis.zrangebyscore(
|
|
77
|
+
"delayed_queue_schedule",
|
|
78
|
+
"-inf",
|
|
79
|
+
current_time,
|
|
80
|
+
limit: [cursor, batch]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
break if timestamps.empty?
|
|
84
|
+
|
|
85
|
+
if queues.empty?
|
|
86
|
+
total_size += ::Resque.redis.pipelined do |pipeline|
|
|
87
|
+
timestamps.each do |timestamp|
|
|
88
|
+
pipeline.llen("delayed:#{timestamp}")
|
|
89
|
+
end
|
|
90
|
+
end.sum
|
|
91
|
+
else
|
|
92
|
+
timestamps.each do |timestamp|
|
|
93
|
+
job_cursor = 0
|
|
94
|
+
|
|
95
|
+
loop do
|
|
96
|
+
encoded_jobs = ::Resque.redis.lrange(
|
|
97
|
+
"delayed:#{timestamp}",
|
|
98
|
+
job_cursor,
|
|
99
|
+
job_cursor + batch - 1
|
|
100
|
+
)
|
|
23
101
|
|
|
24
|
-
|
|
25
|
-
ids = Array(redis.smembers(:workers)).compact
|
|
26
|
-
raw_jobs = redis.pipelined { ids.map { |id| redis.get("worker:#{id}") } }
|
|
27
|
-
jobs = raw_jobs.map { |raw_job| ::Resque.decode(raw_job) || {} }
|
|
102
|
+
break if encoded_jobs.empty?
|
|
28
103
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
104
|
+
total_size += encoded_jobs.count do |encoded_job|
|
|
105
|
+
queues.include?(::Resque.decode(encoded_job)["queue"])
|
|
106
|
+
end
|
|
32
107
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
108
|
+
break if encoded_jobs.size < batch
|
|
109
|
+
|
|
110
|
+
job_cursor += batch
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
break if timestamps.size < batch
|
|
116
|
+
|
|
117
|
+
cursor += batch
|
|
36
118
|
end
|
|
37
119
|
|
|
38
|
-
|
|
120
|
+
total_size
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def registered_queues
|
|
126
|
+
::Resque.redis.keys("queue:*").map { |key| key[6..] }.to_set
|
|
39
127
|
end
|
|
40
128
|
end
|
|
41
129
|
end
|