sidekiq 3.5.4 → 7.2.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 +5 -5
- data/Changes.md +992 -6
- data/LICENSE.txt +9 -0
- data/README.md +52 -43
- data/bin/sidekiq +22 -4
- data/bin/sidekiqload +209 -115
- data/bin/sidekiqmon +11 -0
- data/lib/generators/sidekiq/job_generator.rb +57 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
- data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
- data/lib/sidekiq/api.rb +633 -295
- data/lib/sidekiq/capsule.rb +127 -0
- data/lib/sidekiq/cli.rb +270 -248
- data/lib/sidekiq/client.rb +139 -108
- data/lib/sidekiq/component.rb +68 -0
- data/lib/sidekiq/config.rb +287 -0
- data/lib/sidekiq/deploy.rb +62 -0
- data/lib/sidekiq/embedded.rb +61 -0
- data/lib/sidekiq/fetch.rb +53 -121
- data/lib/sidekiq/job.rb +374 -0
- data/lib/sidekiq/job_logger.rb +51 -0
- data/lib/sidekiq/job_retry.rb +301 -0
- data/lib/sidekiq/job_util.rb +107 -0
- data/lib/sidekiq/launcher.rb +241 -69
- data/lib/sidekiq/logger.rb +131 -0
- data/lib/sidekiq/manager.rb +88 -190
- data/lib/sidekiq/metrics/query.rb +155 -0
- data/lib/sidekiq/metrics/shared.rb +95 -0
- data/lib/sidekiq/metrics/tracking.rb +136 -0
- data/lib/sidekiq/middleware/chain.rb +114 -56
- data/lib/sidekiq/middleware/current_attributes.rb +95 -0
- data/lib/sidekiq/middleware/i18n.rb +8 -7
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +146 -0
- data/lib/sidekiq/paginator.rb +29 -16
- data/lib/sidekiq/processor.rb +238 -118
- data/lib/sidekiq/rails.rb +57 -27
- data/lib/sidekiq/redis_client_adapter.rb +111 -0
- data/lib/sidekiq/redis_connection.rb +49 -50
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +173 -52
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing/inline.rb +7 -5
- data/lib/sidekiq/testing.rb +197 -65
- data/lib/sidekiq/transaction_aware_client.rb +44 -0
- data/lib/sidekiq/version.rb +4 -1
- data/lib/sidekiq/web/action.rb +93 -0
- data/lib/sidekiq/web/application.rb +463 -0
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +364 -0
- data/lib/sidekiq/web/router.rb +104 -0
- data/lib/sidekiq/web.rb +113 -216
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +99 -142
- data/sidekiq.gemspec +26 -23
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +163 -74
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +182 -0
- data/web/assets/javascripts/dashboard.js +37 -280
- data/web/assets/javascripts/metrics.js +298 -0
- data/web/assets/stylesheets/application-dark.css +147 -0
- data/web/assets/stylesheets/application-rtl.css +153 -0
- data/web/assets/stylesheets/application.css +181 -198
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +4 -8
- data/web/locales/ar.yml +87 -0
- data/web/locales/cs.yml +62 -52
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -53
- data/web/locales/el.yml +43 -24
- data/web/locales/en.yml +86 -62
- data/web/locales/es.yml +70 -53
- data/web/locales/fa.yml +80 -0
- data/web/locales/fr.yml +86 -56
- data/web/locales/gd.yml +99 -0
- data/web/locales/he.yml +80 -0
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +53 -53
- data/web/locales/ja.yml +78 -56
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +83 -0
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +83 -55
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +68 -60
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/uk.yml +62 -61
- data/web/locales/ur.yml +80 -0
- data/web/locales/vi.yml +83 -0
- data/web/locales/zh-cn.yml +43 -16
- data/web/locales/zh-tw.yml +42 -8
- data/web/views/_footer.erb +10 -9
- data/web/views/_job_info.erb +26 -5
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_nav.erb +6 -20
- data/web/views/_paging.erb +3 -1
- data/web/views/_poll_link.erb +3 -6
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +87 -28
- data/web/views/dashboard.erb +51 -21
- data/web/views/dead.erb +4 -4
- data/web/views/filtering.erb +7 -0
- data/web/views/layout.erb +15 -5
- data/web/views/metrics.erb +91 -0
- data/web/views/metrics_for_job.erb +59 -0
- data/web/views/morgue.erb +25 -22
- data/web/views/queue.erb +35 -25
- data/web/views/queues.erb +23 -7
- data/web/views/retries.erb +28 -23
- data/web/views/retry.erb +5 -5
- data/web/views/scheduled.erb +19 -17
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +86 -268
- data/.gitignore +0 -12
- data/.travis.yml +0 -16
- data/3.0-Upgrade.md +0 -70
- data/COMM-LICENSE +0 -95
- data/Contributing.md +0 -32
- data/Ent-Changes.md +0 -39
- data/Gemfile +0 -27
- data/LICENSE +0 -9
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-Changes.md +0 -454
- data/Rakefile +0 -9
- data/bin/sidekiqctl +0 -93
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
- data/lib/generators/sidekiq/worker_generator.rb +0 -49
- data/lib/sidekiq/actor.rb +0 -39
- data/lib/sidekiq/core_ext.rb +0 -105
- data/lib/sidekiq/exception_handler.rb +0 -30
- data/lib/sidekiq/extensions/action_mailer.rb +0 -56
- data/lib/sidekiq/extensions/active_record.rb +0 -39
- data/lib/sidekiq/extensions/class_methods.rb +0 -39
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -24
- data/lib/sidekiq/logging.rb +0 -104
- data/lib/sidekiq/middleware/server/active_record.rb +0 -13
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
- data/lib/sidekiq/util.rb +0 -68
- data/lib/sidekiq/web_helpers.rb +0 -249
- data/lib/sidekiq/worker.rb +0 -103
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -0
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -49
- data/test/test_api.rb +0 -493
- data/test/test_cli.rb +0 -335
- data/test/test_client.rb +0 -194
- data/test/test_exception_handler.rb +0 -55
- data/test/test_extensions.rb +0 -126
- data/test/test_fetch.rb +0 -104
- data/test/test_logging.rb +0 -34
- data/test/test_manager.rb +0 -168
- data/test/test_middleware.rb +0 -159
- data/test/test_processor.rb +0 -237
- data/test/test_rails.rb +0 -21
- data/test/test_redis_connection.rb +0 -126
- data/test/test_retry.rb +0 -325
- data/test/test_scheduled.rb +0 -114
- data/test/test_scheduling.rb +0 -49
- data/test/test_sidekiq.rb +0 -99
- data/test/test_testing.rb +0 -142
- data/test/test_testing_fake.rb +0 -268
- data/test/test_testing_inline.rb +0 -93
- data/test/test_util.rb +0 -16
- data/test/test_web.rb +0 -608
- data/test/test_web_helpers.rb +0 -53
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/javascripts/locales/README.md +0 -27
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
- data/web/views/_poll_js.erb +0 -5
- /data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
data/lib/sidekiq/launcher.rb
CHANGED
|
@@ -1,99 +1,271 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sidekiq/manager"
|
|
4
|
+
require "sidekiq/capsule"
|
|
5
|
+
require "sidekiq/scheduled"
|
|
6
|
+
require "sidekiq/ring_buffer"
|
|
5
7
|
|
|
6
8
|
module Sidekiq
|
|
7
|
-
# The Launcher
|
|
8
|
-
# start, monitor and stop the core Actors in Sidekiq.
|
|
9
|
-
# If any of these actors die, the Sidekiq process exits
|
|
10
|
-
# immediately.
|
|
9
|
+
# The Launcher starts the Capsule Managers, the Poller thread and provides the process heartbeat.
|
|
11
10
|
class Launcher
|
|
12
|
-
include
|
|
13
|
-
|
|
11
|
+
include Sidekiq::Component
|
|
12
|
+
|
|
13
|
+
STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
PROCTITLES = [
|
|
16
|
+
proc { "sidekiq" },
|
|
17
|
+
proc { Sidekiq::VERSION },
|
|
18
|
+
proc { |me, data| data["tag"] },
|
|
19
|
+
proc { |me, data| "[#{Processor::WORK_STATE.size} of #{me.config.total_concurrency} busy]" },
|
|
20
|
+
proc { |me, data| "stopping" if me.stopping? }
|
|
21
|
+
]
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
attr_accessor :managers, :poller
|
|
18
24
|
|
|
19
|
-
def initialize(
|
|
20
|
-
@
|
|
21
|
-
@
|
|
22
|
-
@
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
def initialize(config, embedded: false)
|
|
26
|
+
@config = config
|
|
27
|
+
@embedded = embedded
|
|
28
|
+
@managers = config.capsules.values.map do |cap|
|
|
29
|
+
Sidekiq::Manager.new(cap)
|
|
30
|
+
end
|
|
31
|
+
@poller = Sidekiq::Scheduled::Poller.new(@config)
|
|
25
32
|
@done = false
|
|
26
|
-
@options = options
|
|
27
33
|
end
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Sidekiq.
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
# Start this Sidekiq instance. If an embedding process already
|
|
36
|
+
# has a heartbeat thread, caller can use `async_beat: false`
|
|
37
|
+
# and instead have thread call Launcher#heartbeat every N seconds.
|
|
38
|
+
def run(async_beat: true)
|
|
39
|
+
Sidekiq.freeze!
|
|
40
|
+
logger.debug { @config.merge!({}) }
|
|
41
|
+
@thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
|
|
42
|
+
@poller.start
|
|
43
|
+
@managers.each(&:start)
|
|
36
44
|
end
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
poller.async.poll(true)
|
|
46
|
+
# Stops this instance from processing any more jobs,
|
|
47
|
+
def quiet
|
|
48
|
+
return if @done
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
@done = true
|
|
51
|
+
@managers.each(&:quiet)
|
|
52
|
+
@poller.terminate
|
|
53
|
+
fire_event(:quiet, reverse: true)
|
|
45
54
|
end
|
|
46
55
|
|
|
56
|
+
# Shuts down this Sidekiq instance. Waits up to the deadline for all jobs to complete.
|
|
47
57
|
def stop
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
fire_event(:shutdown, true)
|
|
56
|
-
@condvar.wait
|
|
57
|
-
manager.terminate
|
|
58
|
-
|
|
59
|
-
# Requeue everything in case there was a worker who grabbed work while stopped
|
|
60
|
-
# This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
|
|
61
|
-
Sidekiq::Fetcher.strategy.bulk_requeue([], @options)
|
|
62
|
-
|
|
63
|
-
stop_heartbeat
|
|
58
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @config[:timeout]
|
|
59
|
+
|
|
60
|
+
quiet
|
|
61
|
+
stoppers = @managers.map do |mgr|
|
|
62
|
+
Thread.new do
|
|
63
|
+
mgr.stop(deadline)
|
|
64
|
+
end
|
|
64
65
|
end
|
|
66
|
+
|
|
67
|
+
fire_event(:shutdown, reverse: true)
|
|
68
|
+
stoppers.each(&:join)
|
|
69
|
+
|
|
70
|
+
clear_heartbeat
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stopping?
|
|
74
|
+
@done
|
|
65
75
|
end
|
|
66
76
|
|
|
67
|
-
|
|
77
|
+
# If embedding Sidekiq, you can have the process heartbeat
|
|
78
|
+
# call this method to regularly heartbeat rather than creating
|
|
79
|
+
# a separate thread.
|
|
80
|
+
def heartbeat
|
|
81
|
+
❤
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private unless $TESTING
|
|
85
|
+
|
|
86
|
+
BEAT_PAUSE = 10
|
|
68
87
|
|
|
69
88
|
def start_heartbeat
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
'tag' => @options[:tag] || '',
|
|
76
|
-
'concurrency' => @options[:concurrency],
|
|
77
|
-
'queues' => @options[:queues].uniq,
|
|
78
|
-
'labels' => Sidekiq.options[:labels],
|
|
79
|
-
'identity' => identity,
|
|
80
|
-
}
|
|
81
|
-
# this data doesn't change so dump it to a string
|
|
82
|
-
# now so we don't need to dump it every heartbeat.
|
|
83
|
-
json = Sidekiq.dump_json(data)
|
|
84
|
-
manager.heartbeat(key, data, json)
|
|
89
|
+
loop do
|
|
90
|
+
beat
|
|
91
|
+
sleep BEAT_PAUSE
|
|
92
|
+
end
|
|
93
|
+
logger.info("Heartbeat stopping...")
|
|
85
94
|
end
|
|
86
95
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
def beat
|
|
97
|
+
$0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") unless @embedded
|
|
98
|
+
❤
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def clear_heartbeat
|
|
102
|
+
flush_stats
|
|
103
|
+
|
|
104
|
+
# Remove record from Redis since we are shutting down.
|
|
105
|
+
# Note we don't stop the heartbeat thread; if the process
|
|
106
|
+
# doesn't actually exit, it'll reappear in the Web UI.
|
|
107
|
+
redis do |conn|
|
|
108
|
+
conn.pipelined do |pipeline|
|
|
109
|
+
pipeline.srem("processes", [identity])
|
|
110
|
+
pipeline.unlink("#{identity}:work")
|
|
92
111
|
end
|
|
93
112
|
end
|
|
94
113
|
rescue
|
|
95
114
|
# best effort, ignore network errors
|
|
96
115
|
end
|
|
97
116
|
|
|
117
|
+
def flush_stats
|
|
118
|
+
fails = Processor::FAILURE.reset
|
|
119
|
+
procd = Processor::PROCESSED.reset
|
|
120
|
+
return if fails + procd == 0
|
|
121
|
+
|
|
122
|
+
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
|
123
|
+
begin
|
|
124
|
+
redis do |conn|
|
|
125
|
+
conn.pipelined do |pipeline|
|
|
126
|
+
pipeline.incrby("stat:processed", procd)
|
|
127
|
+
pipeline.incrby("stat:processed:#{nowdate}", procd)
|
|
128
|
+
pipeline.expire("stat:processed:#{nowdate}", STATS_TTL)
|
|
129
|
+
|
|
130
|
+
pipeline.incrby("stat:failed", fails)
|
|
131
|
+
pipeline.incrby("stat:failed:#{nowdate}", fails)
|
|
132
|
+
pipeline.expire("stat:failed:#{nowdate}", STATS_TTL)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
rescue => ex
|
|
136
|
+
logger.warn("Unable to flush stats: #{ex}")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def ❤
|
|
141
|
+
key = identity
|
|
142
|
+
fails = procd = 0
|
|
143
|
+
|
|
144
|
+
begin
|
|
145
|
+
flush_stats
|
|
146
|
+
|
|
147
|
+
curstate = Processor::WORK_STATE.dup
|
|
148
|
+
redis do |conn|
|
|
149
|
+
# work is the current set of executing jobs
|
|
150
|
+
work_key = "#{key}:work"
|
|
151
|
+
conn.pipelined do |transaction|
|
|
152
|
+
transaction.unlink(work_key)
|
|
153
|
+
curstate.each_pair do |tid, hash|
|
|
154
|
+
transaction.hset(work_key, tid, Sidekiq.dump_json(hash))
|
|
155
|
+
end
|
|
156
|
+
transaction.expire(work_key, 60)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
rtt = check_rtt
|
|
161
|
+
|
|
162
|
+
fails = procd = 0
|
|
163
|
+
kb = memory_usage(::Process.pid)
|
|
164
|
+
|
|
165
|
+
_, exists, _, _, signal = redis { |conn|
|
|
166
|
+
conn.multi { |transaction|
|
|
167
|
+
transaction.sadd("processes", [key])
|
|
168
|
+
transaction.exists(key)
|
|
169
|
+
transaction.hset(key, "info", to_json,
|
|
170
|
+
"busy", curstate.size,
|
|
171
|
+
"beat", Time.now.to_f,
|
|
172
|
+
"rtt_us", rtt,
|
|
173
|
+
"quiet", @done.to_s,
|
|
174
|
+
"rss", kb)
|
|
175
|
+
transaction.expire(key, 60)
|
|
176
|
+
transaction.rpop("#{key}-signals")
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# first heartbeat or recovering from an outage and need to reestablish our heartbeat
|
|
181
|
+
fire_event(:heartbeat) unless exists > 0
|
|
182
|
+
fire_event(:beat, oneshot: false)
|
|
183
|
+
|
|
184
|
+
::Process.kill(signal, ::Process.pid) if signal && !@embedded
|
|
185
|
+
rescue => e
|
|
186
|
+
# ignore all redis/network issues
|
|
187
|
+
logger.error("heartbeat: #{e}")
|
|
188
|
+
# don't lose the counts if there was a network issue
|
|
189
|
+
Processor::PROCESSED.incr(procd)
|
|
190
|
+
Processor::FAILURE.incr(fails)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# We run the heartbeat every five seconds.
|
|
195
|
+
# Capture five samples of RTT, log a warning if each sample
|
|
196
|
+
# is above our warning threshold.
|
|
197
|
+
RTT_READINGS = RingBuffer.new(5)
|
|
198
|
+
RTT_WARNING_LEVEL = 50_000
|
|
199
|
+
|
|
200
|
+
def check_rtt
|
|
201
|
+
a = b = 0
|
|
202
|
+
redis do |x|
|
|
203
|
+
a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
|
204
|
+
x.ping
|
|
205
|
+
b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
|
206
|
+
end
|
|
207
|
+
rtt = b - a
|
|
208
|
+
RTT_READINGS << rtt
|
|
209
|
+
# Ideal RTT for Redis is < 1000µs
|
|
210
|
+
# Workable is < 10,000µs
|
|
211
|
+
# Log a warning if it's a disaster.
|
|
212
|
+
if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
|
|
213
|
+
logger.warn <<~EOM
|
|
214
|
+
Your Redis network connection is performing extremely poorly.
|
|
215
|
+
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
|
216
|
+
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
|
217
|
+
If these values are close to 100,000, that means your Sidekiq process may be
|
|
218
|
+
CPU-saturated; reduce your concurrency and/or see https://github.com/sidekiq/sidekiq/discussions/5039
|
|
219
|
+
EOM
|
|
220
|
+
RTT_READINGS.reset
|
|
221
|
+
end
|
|
222
|
+
rtt
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
MEMORY_GRABBER = case RUBY_PLATFORM
|
|
226
|
+
when /linux/
|
|
227
|
+
->(pid) {
|
|
228
|
+
IO.readlines("/proc/#{$$}/status").each do |line|
|
|
229
|
+
next unless line.start_with?("VmRSS:")
|
|
230
|
+
break line.split[1].to_i
|
|
231
|
+
end
|
|
232
|
+
}
|
|
233
|
+
when /darwin|bsd/
|
|
234
|
+
->(pid) {
|
|
235
|
+
`ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
|
|
236
|
+
}
|
|
237
|
+
else
|
|
238
|
+
->(pid) { 0 }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def memory_usage(pid)
|
|
242
|
+
MEMORY_GRABBER.call(pid)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def to_data
|
|
246
|
+
@data ||= {
|
|
247
|
+
"hostname" => hostname,
|
|
248
|
+
"started_at" => Time.now.to_f,
|
|
249
|
+
"pid" => ::Process.pid,
|
|
250
|
+
"tag" => @config[:tag] || "",
|
|
251
|
+
"concurrency" => @config.total_concurrency,
|
|
252
|
+
"queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
|
|
253
|
+
"weights" => to_weights,
|
|
254
|
+
"labels" => @config[:labels].to_a,
|
|
255
|
+
"identity" => identity,
|
|
256
|
+
"version" => Sidekiq::VERSION,
|
|
257
|
+
"embedded" => @embedded
|
|
258
|
+
}
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def to_weights
|
|
262
|
+
@config.capsules.values.map(&:weights)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def to_json
|
|
266
|
+
# this data changes infrequently so dump it to a string
|
|
267
|
+
# now so we don't need to dump it every heartbeat.
|
|
268
|
+
@json ||= Sidekiq.dump_json(to_data)
|
|
269
|
+
end
|
|
98
270
|
end
|
|
99
271
|
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module Sidekiq
|
|
7
|
+
module Context
|
|
8
|
+
def self.with(hash)
|
|
9
|
+
orig_context = current.dup
|
|
10
|
+
current.merge!(hash)
|
|
11
|
+
yield
|
|
12
|
+
ensure
|
|
13
|
+
Thread.current[:sidekiq_context] = orig_context
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.current
|
|
17
|
+
Thread.current[:sidekiq_context] ||= {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.add(k, v)
|
|
21
|
+
current[k] = v
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module LoggingUtils
|
|
26
|
+
LEVELS = {
|
|
27
|
+
"debug" => 0,
|
|
28
|
+
"info" => 1,
|
|
29
|
+
"warn" => 2,
|
|
30
|
+
"error" => 3,
|
|
31
|
+
"fatal" => 4
|
|
32
|
+
}
|
|
33
|
+
LEVELS.default_proc = proc do |_, level|
|
|
34
|
+
puts("Invalid log level: #{level.inspect}")
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
LEVELS.each do |level, numeric_level|
|
|
39
|
+
define_method("#{level}?") do
|
|
40
|
+
local_level.nil? ? super() : local_level <= numeric_level
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def local_level
|
|
45
|
+
Thread.current[:sidekiq_log_level]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def local_level=(level)
|
|
49
|
+
case level
|
|
50
|
+
when Integer
|
|
51
|
+
Thread.current[:sidekiq_log_level] = level
|
|
52
|
+
when Symbol, String
|
|
53
|
+
Thread.current[:sidekiq_log_level] = LEVELS[level.to_s]
|
|
54
|
+
when nil
|
|
55
|
+
Thread.current[:sidekiq_log_level] = nil
|
|
56
|
+
else
|
|
57
|
+
raise ArgumentError, "Invalid log level: #{level.inspect}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def level
|
|
62
|
+
local_level || super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Change the thread-local level for the duration of the given block.
|
|
66
|
+
def log_at(level)
|
|
67
|
+
old_local_level = local_level
|
|
68
|
+
self.local_level = level
|
|
69
|
+
yield
|
|
70
|
+
ensure
|
|
71
|
+
self.local_level = old_local_level
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class Logger < ::Logger
|
|
76
|
+
include LoggingUtils
|
|
77
|
+
|
|
78
|
+
module Formatters
|
|
79
|
+
class Base < ::Logger::Formatter
|
|
80
|
+
def tid
|
|
81
|
+
Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def ctx
|
|
85
|
+
Sidekiq::Context.current
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def format_context
|
|
89
|
+
if ctx.any?
|
|
90
|
+
" " + ctx.compact.map { |k, v|
|
|
91
|
+
case v
|
|
92
|
+
when Array
|
|
93
|
+
"#{k}=#{v.join(",")}"
|
|
94
|
+
else
|
|
95
|
+
"#{k}=#{v}"
|
|
96
|
+
end
|
|
97
|
+
}.join(" ")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class Pretty < Base
|
|
103
|
+
def call(severity, time, program_name, message)
|
|
104
|
+
"#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class WithoutTimestamp < Pretty
|
|
109
|
+
def call(severity, time, program_name, message)
|
|
110
|
+
"pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class JSON < Base
|
|
115
|
+
def call(severity, time, program_name, message)
|
|
116
|
+
hash = {
|
|
117
|
+
ts: time.utc.iso8601(3),
|
|
118
|
+
pid: ::Process.pid,
|
|
119
|
+
tid: tid,
|
|
120
|
+
lvl: severity,
|
|
121
|
+
msg: message
|
|
122
|
+
}
|
|
123
|
+
c = ctx
|
|
124
|
+
hash["ctx"] = c unless c.empty?
|
|
125
|
+
|
|
126
|
+
Sidekiq.dump_json(hash) << "\n"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|