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.
Files changed (228) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +992 -6
  3. data/LICENSE.txt +9 -0
  4. data/README.md +52 -43
  5. data/bin/sidekiq +22 -4
  6. data/bin/sidekiqload +209 -115
  7. data/bin/sidekiqmon +11 -0
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  11. data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  12. data/lib/sidekiq/api.rb +633 -295
  13. data/lib/sidekiq/capsule.rb +127 -0
  14. data/lib/sidekiq/cli.rb +270 -248
  15. data/lib/sidekiq/client.rb +139 -108
  16. data/lib/sidekiq/component.rb +68 -0
  17. data/lib/sidekiq/config.rb +287 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +53 -121
  21. data/lib/sidekiq/job.rb +374 -0
  22. data/lib/sidekiq/job_logger.rb +51 -0
  23. data/lib/sidekiq/job_retry.rb +301 -0
  24. data/lib/sidekiq/job_util.rb +107 -0
  25. data/lib/sidekiq/launcher.rb +241 -69
  26. data/lib/sidekiq/logger.rb +131 -0
  27. data/lib/sidekiq/manager.rb +88 -190
  28. data/lib/sidekiq/metrics/query.rb +155 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +136 -0
  31. data/lib/sidekiq/middleware/chain.rb +114 -56
  32. data/lib/sidekiq/middleware/current_attributes.rb +95 -0
  33. data/lib/sidekiq/middleware/i18n.rb +8 -7
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +146 -0
  36. data/lib/sidekiq/paginator.rb +29 -16
  37. data/lib/sidekiq/processor.rb +238 -118
  38. data/lib/sidekiq/rails.rb +57 -27
  39. data/lib/sidekiq/redis_client_adapter.rb +111 -0
  40. data/lib/sidekiq/redis_connection.rb +49 -50
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +173 -52
  43. data/lib/sidekiq/sd_notify.rb +149 -0
  44. data/lib/sidekiq/systemd.rb +24 -0
  45. data/lib/sidekiq/testing/inline.rb +7 -5
  46. data/lib/sidekiq/testing.rb +197 -65
  47. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  48. data/lib/sidekiq/version.rb +4 -1
  49. data/lib/sidekiq/web/action.rb +93 -0
  50. data/lib/sidekiq/web/application.rb +463 -0
  51. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  52. data/lib/sidekiq/web/helpers.rb +364 -0
  53. data/lib/sidekiq/web/router.rb +104 -0
  54. data/lib/sidekiq/web.rb +113 -216
  55. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  56. data/lib/sidekiq.rb +99 -142
  57. data/sidekiq.gemspec +26 -23
  58. data/web/assets/images/apple-touch-icon.png +0 -0
  59. data/web/assets/javascripts/application.js +163 -74
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/chart.min.js +13 -0
  62. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  63. data/web/assets/javascripts/dashboard-charts.js +182 -0
  64. data/web/assets/javascripts/dashboard.js +37 -280
  65. data/web/assets/javascripts/metrics.js +298 -0
  66. data/web/assets/stylesheets/application-dark.css +147 -0
  67. data/web/assets/stylesheets/application-rtl.css +153 -0
  68. data/web/assets/stylesheets/application.css +181 -198
  69. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  70. data/web/assets/stylesheets/bootstrap.css +4 -8
  71. data/web/locales/ar.yml +87 -0
  72. data/web/locales/cs.yml +62 -52
  73. data/web/locales/da.yml +60 -53
  74. data/web/locales/de.yml +65 -53
  75. data/web/locales/el.yml +43 -24
  76. data/web/locales/en.yml +86 -62
  77. data/web/locales/es.yml +70 -53
  78. data/web/locales/fa.yml +80 -0
  79. data/web/locales/fr.yml +86 -56
  80. data/web/locales/gd.yml +99 -0
  81. data/web/locales/he.yml +80 -0
  82. data/web/locales/hi.yml +59 -59
  83. data/web/locales/it.yml +53 -53
  84. data/web/locales/ja.yml +78 -56
  85. data/web/locales/ko.yml +52 -52
  86. data/web/locales/lt.yml +83 -0
  87. data/web/locales/nb.yml +61 -61
  88. data/web/locales/nl.yml +52 -52
  89. data/web/locales/pl.yml +45 -45
  90. data/web/locales/pt-br.yml +83 -55
  91. data/web/locales/pt.yml +51 -51
  92. data/web/locales/ru.yml +68 -60
  93. data/web/locales/sv.yml +53 -53
  94. data/web/locales/ta.yml +60 -60
  95. data/web/locales/uk.yml +62 -61
  96. data/web/locales/ur.yml +80 -0
  97. data/web/locales/vi.yml +83 -0
  98. data/web/locales/zh-cn.yml +43 -16
  99. data/web/locales/zh-tw.yml +42 -8
  100. data/web/views/_footer.erb +10 -9
  101. data/web/views/_job_info.erb +26 -5
  102. data/web/views/_metrics_period_select.erb +12 -0
  103. data/web/views/_nav.erb +6 -20
  104. data/web/views/_paging.erb +3 -1
  105. data/web/views/_poll_link.erb +3 -6
  106. data/web/views/_summary.erb +7 -7
  107. data/web/views/busy.erb +87 -28
  108. data/web/views/dashboard.erb +51 -21
  109. data/web/views/dead.erb +4 -4
  110. data/web/views/filtering.erb +7 -0
  111. data/web/views/layout.erb +15 -5
  112. data/web/views/metrics.erb +91 -0
  113. data/web/views/metrics_for_job.erb +59 -0
  114. data/web/views/morgue.erb +25 -22
  115. data/web/views/queue.erb +35 -25
  116. data/web/views/queues.erb +23 -7
  117. data/web/views/retries.erb +28 -23
  118. data/web/views/retry.erb +5 -5
  119. data/web/views/scheduled.erb +19 -17
  120. data/web/views/scheduled_job_info.erb +1 -1
  121. metadata +86 -268
  122. data/.gitignore +0 -12
  123. data/.travis.yml +0 -16
  124. data/3.0-Upgrade.md +0 -70
  125. data/COMM-LICENSE +0 -95
  126. data/Contributing.md +0 -32
  127. data/Ent-Changes.md +0 -39
  128. data/Gemfile +0 -27
  129. data/LICENSE +0 -9
  130. data/Pro-2.0-Upgrade.md +0 -138
  131. data/Pro-Changes.md +0 -454
  132. data/Rakefile +0 -9
  133. data/bin/sidekiqctl +0 -93
  134. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
  135. data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
  136. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  137. data/lib/sidekiq/actor.rb +0 -39
  138. data/lib/sidekiq/core_ext.rb +0 -105
  139. data/lib/sidekiq/exception_handler.rb +0 -30
  140. data/lib/sidekiq/extensions/action_mailer.rb +0 -56
  141. data/lib/sidekiq/extensions/active_record.rb +0 -39
  142. data/lib/sidekiq/extensions/class_methods.rb +0 -39
  143. data/lib/sidekiq/extensions/generic_proxy.rb +0 -24
  144. data/lib/sidekiq/logging.rb +0 -104
  145. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  146. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  147. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
  148. data/lib/sidekiq/util.rb +0 -68
  149. data/lib/sidekiq/web_helpers.rb +0 -249
  150. data/lib/sidekiq/worker.rb +0 -103
  151. data/test/config.yml +0 -9
  152. data/test/env_based_config.yml +0 -11
  153. data/test/fake_env.rb +0 -0
  154. data/test/fixtures/en.yml +0 -2
  155. data/test/helper.rb +0 -49
  156. data/test/test_api.rb +0 -493
  157. data/test/test_cli.rb +0 -335
  158. data/test/test_client.rb +0 -194
  159. data/test/test_exception_handler.rb +0 -55
  160. data/test/test_extensions.rb +0 -126
  161. data/test/test_fetch.rb +0 -104
  162. data/test/test_logging.rb +0 -34
  163. data/test/test_manager.rb +0 -168
  164. data/test/test_middleware.rb +0 -159
  165. data/test/test_processor.rb +0 -237
  166. data/test/test_rails.rb +0 -21
  167. data/test/test_redis_connection.rb +0 -126
  168. data/test/test_retry.rb +0 -325
  169. data/test/test_scheduled.rb +0 -114
  170. data/test/test_scheduling.rb +0 -49
  171. data/test/test_sidekiq.rb +0 -99
  172. data/test/test_testing.rb +0 -142
  173. data/test/test_testing_fake.rb +0 -268
  174. data/test/test_testing_inline.rb +0 -93
  175. data/test/test_util.rb +0 -16
  176. data/test/test_web.rb +0 -608
  177. data/test/test_web_helpers.rb +0 -53
  178. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  179. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  180. data/web/assets/images/status/active.png +0 -0
  181. data/web/assets/images/status/idle.png +0 -0
  182. data/web/assets/javascripts/locales/README.md +0 -27
  183. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  184. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  185. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  186. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  187. data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
  188. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  189. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  190. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  191. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  192. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  193. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  194. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  195. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  196. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  197. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  198. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  199. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  200. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  201. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  202. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  203. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  204. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  205. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  206. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  207. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  208. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  209. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  210. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  211. data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
  212. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  213. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  214. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  215. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  216. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  217. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  218. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  219. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  220. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  221. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  222. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  223. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  224. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  225. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
  226. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
  227. data/web/views/_poll_js.erb +0 -5
  228. /data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
@@ -1,99 +1,271 @@
1
- require 'sidekiq/actor'
2
- require 'sidekiq/manager'
3
- require 'sidekiq/fetch'
4
- require 'sidekiq/scheduled'
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 is a very simple Actor whose job is to
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 Actor
13
- include Util
11
+ include Sidekiq::Component
12
+
13
+ STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years
14
14
 
15
- trap_exit :actor_died
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
- attr_reader :manager, :poller, :fetcher
23
+ attr_accessor :managers, :poller
18
24
 
19
- def initialize(options)
20
- @condvar = Celluloid::Condition.new
21
- @manager = Sidekiq::Manager.new_link(@condvar, options)
22
- @poller = Sidekiq::Scheduled::Poller.new_link
23
- @fetcher = Sidekiq::Fetcher.new_link(@manager, options)
24
- @manager.fetcher = @fetcher
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
- def actor_died(actor, reason)
30
- # https://github.com/mperham/sidekiq/issues/2057#issuecomment-66485477
31
- return if @done || !reason
32
-
33
- Sidekiq.logger.warn("Sidekiq died due to the following error, cannot recover, process exiting")
34
- handle_exception(reason)
35
- exit(1)
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
- def run
39
- watchdog('Launcher#run') do
40
- manager.async.start
41
- poller.async.poll(true)
46
+ # Stops this instance from processing any more jobs,
47
+ def quiet
48
+ return if @done
42
49
 
43
- start_heartbeat
44
- end
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
- watchdog('Launcher#stop') do
49
- @done = true
50
- Sidekiq::Fetcher.done!
51
- fetcher.terminate if fetcher.alive?
52
- poller.terminate if poller.alive?
53
-
54
- manager.async.stop(:shutdown => true, :timeout => @options[:timeout])
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
- private
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
- key = identity
71
- data = {
72
- 'hostname' => hostname,
73
- 'started_at' => Time.now.to_f,
74
- 'pid' => $$,
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 stop_heartbeat
88
- Sidekiq.redis do |conn|
89
- conn.pipelined do
90
- conn.srem('processes', identity)
91
- conn.del("#{identity}:workers")
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