sidekiq-amigo 1.3.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56470f3e16978212a6e8a23d970c7709c0f24854b80c15d522989389e0cc829e
4
- data.tar.gz: 2bed22f2a190e056aa715814f460ba009fd2c2d3884e6d34eb866084659af962
3
+ metadata.gz: defd2060d1c4a56780815bbaff9cfc54169ed12857fc5c1e20a075a5ea60ee8d
4
+ data.tar.gz: 1cefc43ca64867f333d5706c5f2df3998a3fac294d957ccdf95f05763589d1aa
5
5
  SHA512:
6
- metadata.gz: 18ac44e7cffa487844980f937193ac4601585e380aa08c85599591181271e3ded429271be3407d120e0bf280c541c0f9c28ca23c2c5e9a528deda26c16ca8735
7
- data.tar.gz: 243229bf9a1f661eb6ef4b9fe847a979634fa729df34e756016f6f2c403ae1e8f73d444e437e640b86d300e38169a391b72f1676a49e985aeac11d41684c4974
6
+ metadata.gz: fde4becc89be41126f447ce282a9c6ccbe5adb05b979cc6dbea455d7bd7735043001115722450b412434253a23dc0b80bab6766b43804ad58698d0eddfd6713b
7
+ data.tar.gz: 43209e4289474df8d62bc1d8052869df1cd7c375df921ead70f5aeb69f03be79dbaba53f33b1f04d66153681fd0f6e92a598176a3e7261718cda972306b571af
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/api"
4
+
5
+ require "amigo"
6
+
7
+ # When queues achieve a latency that is too high,
8
+ # take some action.
9
+ # You should start this up at Sidekiq application startup:
10
+ #
11
+ # # sidekiq.rb
12
+ # Amigo::Autoscaler.new.start
13
+ #
14
+ # Right now, this is pretty simple- we alert any time
15
+ # there is a latency over a threshold.
16
+ #
17
+ # In the future, we can:
18
+ #
19
+ # 1) actually autoscale rather than just alert
20
+ # (this may take the form of a POST to a configurable endpoint),
21
+ # 2) become more sophisticated with how we detect latency growth.
22
+ #
23
+ module Amigo
24
+ class Autoscaler
25
+ class InvalidHandler < StandardError; end
26
+
27
+ # How often should Autoscaler check for latency?
28
+ # @return [Integer]
29
+ attr_reader :poll_interval
30
+ # What latency should we alert on?
31
+ # @return [Integer]
32
+ attr_reader :latency_threshold
33
+ # What hosts/processes should this run on?
34
+ # Look at ENV['DYNO'] and Socket.gethostname.
35
+ # Default to only run on 'web.1', which is the first Heroku web dyno.
36
+ # We run on the web, not worker, dyno, so we report backed up queues
37
+ # in case we, say, turn off all workers (broken web processes
38
+ # are generally easier to find).
39
+ # @return [Regexp]
40
+ attr_reader :hostname_regex
41
+ # Methods to call when alerting.
42
+ # Valid values are 'log' and 'sentry' (requires Sentry to be required already).
43
+ # Anything that responds to +call+ will be invoked with a hash of
44
+ # `{queue name => latency in seconds}`.
45
+ # @return [Array<String,Proc>]
46
+ attr_reader :handlers
47
+ # Only alert this often.
48
+ # For example, with poll_interval of 10 seconds
49
+ # and alert_interval of 200 seconds,
50
+ # we'd alert once and then 210 seconds later.
51
+ # @return [Integer]
52
+ attr_reader :alert_interval
53
+
54
+ def initialize(
55
+ poll_interval: 20,
56
+ latency_threshold: 5,
57
+ hostname_regex: /^web\.1$/,
58
+ handlers: ["log"],
59
+ alert_interval: 120
60
+ )
61
+
62
+ @poll_interval = poll_interval
63
+ @latency_threshold = latency_threshold
64
+ @hostname_regex = hostname_regex
65
+ @handlers = handlers
66
+ @alert_interval = alert_interval
67
+ end
68
+
69
+ def polling_thread
70
+ return @polling_thread
71
+ end
72
+
73
+ def setup
74
+ # Store these as strings OR procs, rather than grabbing self.method here.
75
+ # It gets extremely hard ot test if we capture the method here.
76
+ @alert_methods = self.handlers.map do |a|
77
+ if a.respond_to?(:call)
78
+ a
79
+ else
80
+ method_name = meth = "alert_#{a.strip}".to_sym
81
+ raise InvalidHandler, a.inspect unless self.method(method_name)
82
+ meth
83
+ end
84
+ end
85
+ @last_alerted = Time.at(0)
86
+ @stop = false
87
+ end
88
+
89
+ def start
90
+ raise "already started" unless @polling_thread.nil?
91
+
92
+ hostname = ENV.fetch("DYNO") { Socket.gethostname }
93
+ return false unless self.hostname_regex.match?(hostname)
94
+
95
+ self.log(:info, "async_autoscaler_starting")
96
+ self.setup
97
+ @polling_thread = Thread.new do
98
+ until @stop
99
+ Kernel.sleep(self.poll_interval)
100
+ self.check unless @stop
101
+ end
102
+ end
103
+ return true
104
+ end
105
+
106
+ def stop
107
+ @stop = true
108
+ end
109
+
110
+ def check
111
+ now = Time.now
112
+ skip_check = now < (@last_alerted + self.poll_interval)
113
+ if skip_check
114
+ self.log(:debug, "async_autoscaler_skip_check")
115
+ return
116
+ end
117
+ self.log(:info, "async_autoscaler_check")
118
+ high_latency_queues = Sidekiq::Queue.all.
119
+ map { |q| [q.name, q.latency] }.
120
+ select { |(_, latency)| latency > self.latency_threshold }.
121
+ to_h
122
+ return if high_latency_queues.empty?
123
+ @alert_methods.each do |m|
124
+ m.respond_to?(:call) ? m.call(high_latency_queues) : self.send(m, high_latency_queues)
125
+ end
126
+ @last_alerted = now
127
+ end
128
+
129
+ def alert_sentry(names_and_latencies)
130
+ Sentry.with_scope do |scope|
131
+ scope.set_extras(high_latency_queues: names_and_latencies)
132
+ names = names_and_latencies.map(&:first).sort.join(", ")
133
+ Sentry.capture_message("Some queues have a high latency: #{names}")
134
+ end
135
+ end
136
+
137
+ def alert_log(names_and_latencies)
138
+ self.log(:warn, "high_latency_queues", queues: names_and_latencies)
139
+ end
140
+
141
+ def alert_test(_names_and_latencies); end
142
+
143
+ protected def log(level, msg, **kw)
144
+ Amigo.log(nil, level, msg, kw)
145
+ end
146
+ end
147
+ end
data/lib/amigo/retry.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq"
4
+ require "sidekiq/api"
4
5
 
5
6
  # Middleware so Sidekiq workers can use a custom retry logic.
6
7
  # See +Amigo::Retry::Retry+, +Amigo::Retry::Die+,
@@ -64,11 +65,15 @@ module Amigo
64
65
 
65
66
  def handle_retry(worker, job, e)
66
67
  Sidekiq.logger.info("scheduling_retry")
68
+ job["error_class"] = e.class.to_s
69
+ job["error_message"] = e.to_s
67
70
  self.amigo_retry_in(worker.class, job, e.interval_or_timestamp)
68
71
  end
69
72
 
70
- def handle_die(_worker, job, _e)
73
+ def handle_die(_worker, job, e)
71
74
  Sidekiq.logger.warn("sending_to_deadset")
75
+ job["error_class"] = e.class.to_s
76
+ job["error_message"] = e.to_s
72
77
  payload = Sidekiq.dump_json(job)
73
78
  Sidekiq::DeadSet.new.kill(payload, notify_failure: false)
74
79
  end
data/lib/amigo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Amigo
4
- VERSION = "1.3.0"
4
+ VERSION = "1.4.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-amigo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lithic Technology
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-29 00:00:00.000000000 Z
11
+ date: 2023-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.10'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sentry-ruby
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '5'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: timecop
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -133,6 +147,7 @@ extra_rdoc_files: []
133
147
  files:
134
148
  - lib/amigo.rb
135
149
  - lib/amigo/audit_logger.rb
150
+ - lib/amigo/autoscaler.rb
136
151
  - lib/amigo/deprecated_jobs.rb
137
152
  - lib/amigo/job.rb
138
153
  - lib/amigo/queue_backoff_job.rb