sidekiq-heartbeat_monitor 1.0.1.4 → 1.0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sidekiq/heartbeat_monitor.rb +18 -9
- data/lib/sidekiq/heartbeat_monitor/config.rb +70 -47
- data/lib/sidekiq/heartbeat_monitor/scheduler.rb +34 -18
- data/lib/sidekiq/heartbeat_monitor/util.rb +20 -0
- data/lib/sidekiq/heartbeat_monitor/version.rb +1 -1
- data/lib/sidekiq/heartbeat_monitor/worker.rb +14 -25
- data/sidekiq-heartbeat_monitor.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7220abb41d5b0d785ef913cad349184e8e61e10
|
4
|
+
data.tar.gz: ef32dc8543669e7e375de343d6e69433d674d8d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45121b850630fa2c1df916c733014d26198506ccfc10315dfd36f9b16551427792a29a78c44d54bf36752c3d4d208bf4e60c332f62f667669a482bcb24f0a4cb
|
7
|
+
data.tar.gz: fd515bb0ab900404ad85e536e0b512a603f75cc5a88d941b0755d553dbfa1ce0bc5c47a6c91b8bc40615f0b83ff37896128c7d20f9858d04bba1419f84937425
|
@@ -1,29 +1,38 @@
|
|
1
1
|
require "sidekiq"
|
2
2
|
require 'sidekiq/heartbeat_monitor/version'
|
3
3
|
require 'sidekiq/heartbeat_monitor/config'
|
4
|
+
require 'sidekiq/heartbeat_monitor/util'
|
4
5
|
require 'sidekiq/heartbeat_monitor/scheduler'
|
5
6
|
require 'sidekiq/heartbeat_monitor/worker'
|
6
7
|
|
7
8
|
module Sidekiq
|
8
9
|
module HeartbeatMonitor
|
9
|
-
def self.configure(
|
10
|
-
|
11
|
-
|
10
|
+
def self.configure(options = {})
|
11
|
+
options = options.symbolize_keys
|
12
|
+
global_options = options.except(:queues)
|
13
|
+
|
14
|
+
@global_config = Config.new(**global_options)
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
@queue_config = {}
|
17
|
+
options[:queues].to_a.each do |queue_name, queue_options|
|
18
|
+
@queue_config[queue_name.to_s] = Config.new(**global_options.deep_merge(queue_options))
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
|
-
def self.
|
18
|
-
|
22
|
+
def self.config(queue = nil)
|
23
|
+
return @global_config if queue.blank?
|
24
|
+
|
25
|
+
queue_name = queue.is_a?(String) || queue.is_a?(Symbol) ? queue.to_s : queue.name.to_s
|
26
|
+
@queue_config[queue_name] || @global_config
|
19
27
|
end
|
20
28
|
|
21
|
-
def self.send_test!
|
22
|
-
test_queue = Sidekiq::Queue.new('test')
|
29
|
+
def self.send_test!(queue_name = nil)
|
30
|
+
test_queue = Sidekiq::Queue.new(queue_name || 'test')
|
23
31
|
|
24
32
|
send_backed_up_alert("Test backed up alert!", test_queue)
|
25
33
|
send_slowed_down_alert("Test slowed down alert!", test_queue)
|
26
34
|
end
|
35
|
+
|
27
36
|
end
|
28
37
|
end
|
29
38
|
|
@@ -2,73 +2,96 @@ require 'sidekiq/cron/job'
|
|
2
2
|
module Sidekiq
|
3
3
|
module HeartbeatMonitor
|
4
4
|
class Config
|
5
|
-
|
6
|
-
def configure(max_queue_size: nil, on_backed_up: nil, on_slowed_down: nil, dont_repeat_for: nil, slack_notifier_url: nil)
|
7
|
-
@max_queue_size = max_queue_size unless max_queue_size.nil?
|
5
|
+
attr_accessor :max_queue_size, :max_heartbeat_delay
|
8
6
|
|
9
|
-
|
7
|
+
##
|
8
|
+
# @param max_queue_size [Integer] The maximum queue size (default: 5000 or SIDEKIQ_MONITOR_MAX_QUEUE_SIZE environment value)
|
9
|
+
# @param on_backed_up [Proc|Array<Proc>] On backed up run this or these procs
|
10
|
+
# @param on_slowed_down [Proc|Array<Proc>] On slowed down run this or these procs
|
11
|
+
# @param dont_repeat_for [Integer] The don't repeat for (optional, default: 5.minutes)
|
12
|
+
# @param slack_notifier_url [String] The slack notifier url (optional)
|
13
|
+
# @param max_heartbeat_delay [Integer] The maximum heartbeat delay (default: 5 minute, max: 5 days)
|
14
|
+
def initialize(max_queue_size: nil, on_backed_up: nil, on_slowed_down: nil, dont_repeat_for: nil, slack_notifier_url: nil, max_heartbeat_delay: nil)
|
15
|
+
@max_queue_size = max_queue_size || ENV.fetch('SIDEKIQ_MONITOR_MAX_QUEUE_SIZE', 5000).to_i
|
10
16
|
|
11
|
-
|
12
|
-
@on_slowed_down = (on_slowed_down.is_a?(Enumerable) ? on_slowed_down : [on_slowed_down]) unless on_slowed_down.nil?
|
17
|
+
@dont_repeat_for = dont_repeat_for unless dont_repeat_for.nil?
|
13
18
|
|
14
|
-
|
15
|
-
@notifier = Slack::Notifier.new(slack_notifier_url)
|
19
|
+
@max_heartbeat_delay = max_heartbeat_delay || 5.minutes
|
16
20
|
|
17
|
-
|
21
|
+
@on_backed_up = (on_backed_up.is_a?(Enumerable) ? on_backed_up : [on_backed_up]) unless on_backed_up.nil?
|
22
|
+
@on_slowed_down = (on_slowed_down.is_a?(Enumerable) ? on_slowed_down : [on_slowed_down]) unless on_slowed_down.nil?
|
18
23
|
|
19
|
-
|
20
|
-
@on_slowed_down = @on_slowed_down.to_a + slack_notifier_callback
|
21
|
-
end
|
24
|
+
setup_slack_notifier!(slack_notifier_url) if slack_notifier_url.present?
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
name: 'sidekiq_monitor',
|
26
|
-
cron: '*/15 * * * * *',
|
27
|
-
klass: Sidekiq::HeartbeatMonitor::Scheduler
|
28
|
-
)
|
29
|
-
end
|
30
|
-
end
|
26
|
+
install_cron_job!
|
27
|
+
end
|
31
28
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
29
|
+
def send_test!
|
30
|
+
test_queue = Sidekiq::Queue.new('test')
|
31
|
+
send_backed_up_alert("Test backed up alert!", test_queue)
|
32
|
+
send_slowed_down_alert("Test slowed down alert!", test_queue)
|
33
|
+
end
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
35
|
+
def send_backed_up_alert!(message, q)
|
36
|
+
if @on_backed_up.blank?
|
37
|
+
puts ("WARNING: No 'on_backed_up' callback defined for sidekiq-heartbeat_monitor but one of the queues are backed up: #{message}")
|
38
|
+
return
|
39
|
+
end
|
43
40
|
|
44
|
-
|
41
|
+
if @dont_repeat_for.nil?
|
42
|
+
@on_backed_up.to_a.each { |alert| alert.call(message, q) }
|
43
|
+
else
|
44
|
+
DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_backed_up_alert") do
|
45
45
|
@on_backed_up.to_a.each { |alert| alert.call(message, q) }
|
46
|
-
else
|
47
|
-
DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_backed_up_alert") do
|
48
|
-
@on_backed_up.to_a.each { |alert| alert.call(message, q) }
|
49
|
-
end
|
50
46
|
end
|
51
47
|
end
|
48
|
+
end
|
52
49
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
50
|
+
def send_slowed_down_alert!(message, q)
|
51
|
+
if @on_slowed_down.blank?
|
52
|
+
puts ("WARNING: No 'on_slowed_down' callback defined for sidekiq-heartbeat_monitor but one of the queues are backed up: #{message}")
|
53
|
+
return
|
54
|
+
end
|
58
55
|
|
59
|
-
|
56
|
+
if @dont_repeat_for.nil?
|
57
|
+
@on_slowed_down.to_a.each { |alert| alert.call(message, q) }
|
58
|
+
else
|
59
|
+
DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_slowed_down_alert") do
|
60
60
|
@on_slowed_down.to_a.each { |alert| alert.call(message, q) }
|
61
|
-
else
|
62
|
-
DontRepeatFor.new(@dont_repeat_for, "Sidekiq/HeartbeatMonitor/#{q.name}/send_slowed_down_alert") do
|
63
|
-
@on_slowed_down.to_a.each { |alert| alert.call(message, q) }
|
64
|
-
end
|
65
61
|
end
|
66
62
|
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def setup_slack_notifier!(slack_notifier_url)
|
68
|
+
@notifier = Slack::Notifier.new(slack_notifier_url)
|
69
|
+
|
70
|
+
slack_notifier_callback = [-> (msg, queue) { @notifier.ping(msg) }]
|
71
|
+
|
72
|
+
@on_backed_up = @on_backed_up.to_a + slack_notifier_callback
|
73
|
+
@on_slowed_down = @on_slowed_down.to_a + slack_notifier_callback
|
74
|
+
end
|
75
|
+
|
76
|
+
def install_cron_job!
|
77
|
+
job = Sidekiq::Cron::Job.find("sidekiq_monitor")
|
78
|
+
|
79
|
+
target_job = Sidekiq::Cron::Job.new(
|
80
|
+
name: 'sidekiq_monitor',
|
81
|
+
cron: '* * * * *',
|
82
|
+
klass: Sidekiq::HeartbeatMonitor::Scheduler
|
83
|
+
)
|
67
84
|
|
68
|
-
|
69
|
-
|
85
|
+
if job.present?
|
86
|
+
if job.cron != target_job.cron && job.klass.to_s != target_job.klass
|
87
|
+
job.destroy
|
88
|
+
target_job.save
|
89
|
+
end
|
90
|
+
else
|
91
|
+
target_job.save
|
70
92
|
end
|
71
93
|
end
|
94
|
+
|
72
95
|
end
|
73
96
|
end
|
74
97
|
end
|
@@ -1,37 +1,53 @@
|
|
1
1
|
module Sidekiq
|
2
2
|
module HeartbeatMonitor
|
3
3
|
class Scheduler
|
4
|
+
TIME_BETWEEN_HEARTBEATS = 60
|
5
|
+
|
4
6
|
include Sidekiq::Worker
|
7
|
+
include Sidekiq::HeartbeatMonitor::Util
|
5
8
|
sidekiq_options retry: 0
|
6
9
|
|
7
10
|
##
|
8
11
|
# Checks to see if queues are backed up by 1000 or more jobs and also schedules the heartbeat job.
|
9
12
|
def perform
|
10
|
-
Sidekiq
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
Sidekiq.redis do |redis|
|
14
|
+
Sidekiq::Queue.all.each do |q|
|
15
|
+
queue_config = Sidekiq::HeartbeatMonitor.config(q)
|
16
|
+
check_queue_size!(q, queue_config)
|
17
|
+
|
18
|
+
key = "Sidekiq:HeartbeatMonitor:Worker-#{q.name}.enqueued_at"
|
19
|
+
|
20
|
+
last_enqueued_at = redis.get(key).to_i
|
21
|
+
|
22
|
+
if last_enqueued_at > 577997505 # Enqueued after Jan 2, 2020 when this code was written
|
23
|
+
time_since_enqueued = Time.now.to_i - last_enqueued_at
|
24
|
+
if (time_since_enqueued - TIME_BETWEEN_HEARTBEATS) > queue_config.max_heartbeat_delay
|
25
|
+
queue_config.send_slowed_down_alert!("⚠️ _#{q.name}_ queue is taking longer than #{format_time_str(time_since_enqueued)} to reach jobs.", q)
|
26
|
+
else
|
27
|
+
next
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
redis.set(key, Time.now.to_i, ex: 1.week)
|
32
|
+
|
33
|
+
Sidekiq::HeartbeatMonitor::Worker.client_push(
|
34
|
+
'class' => Sidekiq::HeartbeatMonitor::Worker,
|
35
|
+
'args' => [q.name],
|
36
|
+
'queue' => q.name
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
18
40
|
end
|
19
41
|
end
|
20
42
|
|
21
|
-
def check_queue_size(q,
|
43
|
+
def check_queue_size!(q, queue_config)
|
44
|
+
max_queue_size = queue_config.max_queue_size
|
45
|
+
|
22
46
|
if q.size > max_queue_size
|
23
|
-
|
47
|
+
queue_config.send_backed_up_alert!("⚠️ _#{q.name}_ queue has more than #{max_queue_size} jobs waiting to be processed. Current size is #{q.size}", q)
|
24
48
|
end
|
25
49
|
end
|
26
50
|
|
27
|
-
##
|
28
|
-
# @param msg [String] Message to post
|
29
|
-
# @param queue_name [Sidekiq::Queue] Queue we're concerned with
|
30
|
-
def send_server_alert(msg, q)
|
31
|
-
Sidekiq::HeartbeatMonitor::Config.send_backed_up_alert(msg, q)
|
32
|
-
|
33
|
-
true
|
34
|
-
end
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
module Sidekiq
|
3
|
+
module HeartbeatMonitor
|
4
|
+
module Util
|
5
|
+
|
6
|
+
def format_time_str(sec_backed_up)
|
7
|
+
hours = (sec_backed_up - (sec_backed_up % 3600)) / 3600
|
8
|
+
minutes = (sec_backed_up - (sec_backed_up % 60)) / 60
|
9
|
+
seconds = sec_backed_up % 60
|
10
|
+
|
11
|
+
nice_backed_up_str = "#{seconds} sec"
|
12
|
+
nice_backed_up_str = "#{minutes} min #{nice_backed_up_str}" if minutes > 0
|
13
|
+
nice_backed_up_str = "#{hours} hr #{nice_backed_up_str}" if hours > 0
|
14
|
+
|
15
|
+
nice_backed_up_str
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,43 +3,32 @@ module Sidekiq
|
|
3
3
|
class Worker
|
4
4
|
|
5
5
|
include Sidekiq::Worker
|
6
|
+
include Sidekiq::HeartbeatMonitor::Util
|
6
7
|
sidekiq_options retry: 0
|
7
8
|
|
8
9
|
##
|
9
10
|
# Runs every x seconds and ensures that the time between jobs is consistent and
|
10
11
|
# @param queue_name [String] Name of the queue that this heartbeat is running on.
|
11
|
-
def perform(queue_name
|
12
|
-
|
13
|
-
|
12
|
+
def perform(queue_name)
|
13
|
+
Sidekiq.redis do |redis|
|
14
|
+
q = Sidekiq::Queue.all.find{ |q| q.name.to_s == queue_name.to_s }
|
15
|
+
queue_config = Sidekiq::HeartbeatMonitor.config(q)
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
if time_since_last_run > (5.minutes + secs_between_beats)
|
18
|
-
sec_backed_up = time_since_last_run - secs_between_beats
|
17
|
+
key = "Sidekiq:HeartbeatMonitor:Worker-#{queue_name}.enqueued_at"
|
18
|
+
enqueued_at = redis.get(key).to_i
|
19
19
|
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
20
|
+
return if enqueued_at < 1577997505 # Enqueued before Jan 2, 2020 when this code was written
|
23
21
|
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
##
|
28
|
-
# @param msg [String] Message to post
|
29
|
-
# @param queue_name [String] Queue we're concerned with
|
30
|
-
def send_server_alert(msg, queue_name)
|
31
|
-
queue = Sidekiq::Queue.all.find{ |q| q.name.to_s == queue_name.to_s }
|
22
|
+
time_since_enqueued = Time.now.to_i - enqueued_at
|
32
23
|
|
33
|
-
|
24
|
+
if time_since_enqueued > queue_config.max_heartbeat_delay
|
25
|
+
queue_config.send_slowed_down_alert!("⚠️ _#{queue_name}_ queue took #{format_time_str(time_since_enqueued)} to reach job.", q)
|
26
|
+
end
|
34
27
|
|
35
|
-
|
28
|
+
redis.del(key)
|
29
|
+
end
|
36
30
|
end
|
37
31
|
|
38
|
-
private
|
39
|
-
|
40
|
-
def redis
|
41
|
-
@@redis ||= defined?($redis) ? $redis : Redis.new
|
42
|
-
end
|
43
32
|
|
44
33
|
end
|
45
34
|
end
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_dependency "sidekiq-cron", ">= 0.6"
|
32
32
|
spec.add_dependency "dont_repeat_for", ">= 1"
|
33
33
|
spec.add_dependency "slack-notifier", ">= 0.5"
|
34
|
+
spec.add_dependency "dotenv", ">= 1.0"
|
34
35
|
|
35
36
|
spec.add_development_dependency "rails", ">= 4"
|
36
37
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-heartbeat_monitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jay El-Kaake
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sidekiq
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: dotenv
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rails
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,6 +184,7 @@ files:
|
|
170
184
|
- lib/sidekiq/heartbeat_monitor.rb
|
171
185
|
- lib/sidekiq/heartbeat_monitor/config.rb
|
172
186
|
- lib/sidekiq/heartbeat_monitor/scheduler.rb
|
187
|
+
- lib/sidekiq/heartbeat_monitor/util.rb
|
173
188
|
- lib/sidekiq/heartbeat_monitor/version.rb
|
174
189
|
- lib/sidekiq/heartbeat_monitor/worker.rb
|
175
190
|
- sidekiq-heartbeat_monitor.gemspec
|