sidekiq 5.2.7 → 8.0.5

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +845 -8
  3. data/LICENSE.txt +9 -0
  4. data/README.md +54 -54
  5. data/bin/multi_queue_bench +271 -0
  6. data/bin/sidekiq +22 -3
  7. data/bin/sidekiqload +219 -112
  8. data/bin/sidekiqmon +11 -0
  9. data/bin/webload +69 -0
  10. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +120 -0
  11. data/lib/generators/sidekiq/job_generator.rb +59 -0
  12. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  13. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  14. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  15. data/lib/sidekiq/api.rb +757 -373
  16. data/lib/sidekiq/capsule.rb +132 -0
  17. data/lib/sidekiq/cli.rb +210 -233
  18. data/lib/sidekiq/client.rb +145 -103
  19. data/lib/sidekiq/component.rb +128 -0
  20. data/lib/sidekiq/config.rb +315 -0
  21. data/lib/sidekiq/deploy.rb +64 -0
  22. data/lib/sidekiq/embedded.rb +64 -0
  23. data/lib/sidekiq/fetch.rb +49 -42
  24. data/lib/sidekiq/iterable_job.rb +56 -0
  25. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  26. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  27. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  28. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  29. data/lib/sidekiq/job/iterable.rb +306 -0
  30. data/lib/sidekiq/job.rb +385 -0
  31. data/lib/sidekiq/job_logger.rb +34 -7
  32. data/lib/sidekiq/job_retry.rb +164 -109
  33. data/lib/sidekiq/job_util.rb +113 -0
  34. data/lib/sidekiq/launcher.rb +208 -107
  35. data/lib/sidekiq/logger.rb +80 -0
  36. data/lib/sidekiq/manager.rb +42 -46
  37. data/lib/sidekiq/metrics/query.rb +184 -0
  38. data/lib/sidekiq/metrics/shared.rb +109 -0
  39. data/lib/sidekiq/metrics/tracking.rb +150 -0
  40. data/lib/sidekiq/middleware/chain.rb +113 -56
  41. data/lib/sidekiq/middleware/current_attributes.rb +119 -0
  42. data/lib/sidekiq/middleware/i18n.rb +7 -7
  43. data/lib/sidekiq/middleware/modules.rb +23 -0
  44. data/lib/sidekiq/monitor.rb +147 -0
  45. data/lib/sidekiq/paginator.rb +41 -16
  46. data/lib/sidekiq/processor.rb +146 -127
  47. data/lib/sidekiq/profiler.rb +72 -0
  48. data/lib/sidekiq/rails.rb +46 -43
  49. data/lib/sidekiq/redis_client_adapter.rb +113 -0
  50. data/lib/sidekiq/redis_connection.rb +79 -108
  51. data/lib/sidekiq/ring_buffer.rb +31 -0
  52. data/lib/sidekiq/scheduled.rb +112 -50
  53. data/lib/sidekiq/sd_notify.rb +149 -0
  54. data/lib/sidekiq/systemd.rb +26 -0
  55. data/lib/sidekiq/testing/inline.rb +6 -5
  56. data/lib/sidekiq/testing.rb +91 -90
  57. data/lib/sidekiq/transaction_aware_client.rb +51 -0
  58. data/lib/sidekiq/version.rb +7 -1
  59. data/lib/sidekiq/web/action.rb +125 -60
  60. data/lib/sidekiq/web/application.rb +363 -259
  61. data/lib/sidekiq/web/config.rb +120 -0
  62. data/lib/sidekiq/web/csrf_protection.rb +183 -0
  63. data/lib/sidekiq/web/helpers.rb +241 -120
  64. data/lib/sidekiq/web/router.rb +62 -71
  65. data/lib/sidekiq/web.rb +69 -161
  66. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  67. data/lib/sidekiq.rb +94 -182
  68. data/sidekiq.gemspec +26 -16
  69. data/web/assets/images/apple-touch-icon.png +0 -0
  70. data/web/assets/javascripts/application.js +150 -61
  71. data/web/assets/javascripts/base-charts.js +120 -0
  72. data/web/assets/javascripts/chart.min.js +13 -0
  73. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  74. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  75. data/web/assets/javascripts/dashboard-charts.js +194 -0
  76. data/web/assets/javascripts/dashboard.js +41 -293
  77. data/web/assets/javascripts/metrics.js +280 -0
  78. data/web/assets/stylesheets/style.css +766 -0
  79. data/web/locales/ar.yml +72 -65
  80. data/web/locales/cs.yml +63 -62
  81. data/web/locales/da.yml +61 -53
  82. data/web/locales/de.yml +66 -53
  83. data/web/locales/el.yml +44 -24
  84. data/web/locales/en.yml +94 -66
  85. data/web/locales/es.yml +92 -54
  86. data/web/locales/fa.yml +66 -65
  87. data/web/locales/fr.yml +83 -62
  88. data/web/locales/gd.yml +99 -0
  89. data/web/locales/he.yml +66 -64
  90. data/web/locales/hi.yml +60 -59
  91. data/web/locales/it.yml +93 -54
  92. data/web/locales/ja.yml +75 -64
  93. data/web/locales/ko.yml +53 -52
  94. data/web/locales/lt.yml +84 -0
  95. data/web/locales/nb.yml +62 -61
  96. data/web/locales/nl.yml +53 -52
  97. data/web/locales/pl.yml +46 -45
  98. data/web/locales/{pt-br.yml → pt-BR.yml} +84 -56
  99. data/web/locales/pt.yml +52 -51
  100. data/web/locales/ru.yml +69 -63
  101. data/web/locales/sv.yml +54 -53
  102. data/web/locales/ta.yml +61 -60
  103. data/web/locales/tr.yml +101 -0
  104. data/web/locales/uk.yml +86 -61
  105. data/web/locales/ur.yml +65 -64
  106. data/web/locales/vi.yml +84 -0
  107. data/web/locales/zh-CN.yml +106 -0
  108. data/web/locales/{zh-tw.yml → zh-TW.yml} +43 -9
  109. data/web/views/_footer.erb +31 -19
  110. data/web/views/_job_info.erb +94 -75
  111. data/web/views/_metrics_period_select.erb +15 -0
  112. data/web/views/_nav.erb +14 -21
  113. data/web/views/_paging.erb +23 -19
  114. data/web/views/_poll_link.erb +3 -6
  115. data/web/views/_summary.erb +23 -23
  116. data/web/views/busy.erb +139 -87
  117. data/web/views/dashboard.erb +82 -53
  118. data/web/views/dead.erb +31 -27
  119. data/web/views/filtering.erb +6 -0
  120. data/web/views/layout.erb +15 -29
  121. data/web/views/metrics.erb +84 -0
  122. data/web/views/metrics_for_job.erb +58 -0
  123. data/web/views/morgue.erb +60 -70
  124. data/web/views/profiles.erb +43 -0
  125. data/web/views/queue.erb +50 -39
  126. data/web/views/queues.erb +45 -29
  127. data/web/views/retries.erb +65 -75
  128. data/web/views/retry.erb +32 -27
  129. data/web/views/scheduled.erb +58 -52
  130. data/web/views/scheduled_job_info.erb +1 -1
  131. metadata +96 -76
  132. data/.circleci/config.yml +0 -61
  133. data/.github/contributing.md +0 -32
  134. data/.github/issue_template.md +0 -11
  135. data/.gitignore +0 -15
  136. data/.travis.yml +0 -11
  137. data/3.0-Upgrade.md +0 -70
  138. data/4.0-Upgrade.md +0 -53
  139. data/5.0-Upgrade.md +0 -56
  140. data/COMM-LICENSE +0 -97
  141. data/Ent-Changes.md +0 -238
  142. data/Gemfile +0 -23
  143. data/LICENSE +0 -9
  144. data/Pro-2.0-Upgrade.md +0 -138
  145. data/Pro-3.0-Upgrade.md +0 -44
  146. data/Pro-4.0-Upgrade.md +0 -35
  147. data/Pro-Changes.md +0 -759
  148. data/Rakefile +0 -9
  149. data/bin/sidekiqctl +0 -20
  150. data/code_of_conduct.md +0 -50
  151. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  152. data/lib/sidekiq/core_ext.rb +0 -1
  153. data/lib/sidekiq/ctl.rb +0 -221
  154. data/lib/sidekiq/delay.rb +0 -42
  155. data/lib/sidekiq/exception_handler.rb +0 -29
  156. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  157. data/lib/sidekiq/extensions/active_record.rb +0 -40
  158. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  159. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  160. data/lib/sidekiq/logging.rb +0 -122
  161. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  162. data/lib/sidekiq/util.rb +0 -66
  163. data/lib/sidekiq/worker.rb +0 -220
  164. data/web/assets/stylesheets/application-rtl.css +0 -246
  165. data/web/assets/stylesheets/application.css +0 -1144
  166. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  167. data/web/assets/stylesheets/bootstrap.css +0 -5
  168. data/web/locales/zh-cn.yml +0 -68
  169. data/web/views/_status.erb +0 -4
@@ -1,26 +1,96 @@
1
1
  # frozen_string_literal: true
2
- require 'uri'
3
- require 'set'
4
- require 'yaml'
5
- require 'cgi'
2
+
3
+ require "uri"
4
+ require "yaml"
5
+ require "cgi/escape"
6
6
 
7
7
  module Sidekiq
8
- # This is not a public API
8
+ # These methods are available to pages within the Web UI and UI extensions.
9
+ # They are not public APIs for applications to use.
9
10
  module WebHelpers
11
+ def store_name
12
+ hash = redis_info
13
+ return "Dragonfly" if hash.has_key?("dragonfly_version")
14
+ return "Valkey" if hash.has_key?("valkey_version")
15
+ "Redis"
16
+ end
17
+
18
+ def store_version
19
+ hash = redis_info
20
+ return hash["dragonfly_version"] if hash.has_key?("dragonfly_version")
21
+ return hash["valkey_version"] if hash.has_key?("valkey_version")
22
+ hash["redis_version"]
23
+ end
24
+
25
+ def style_tag(location, **kwargs)
26
+ global = location.match?(/:\/\//)
27
+ location = root_path + location if !global && !location.start_with?(root_path)
28
+ attrs = {
29
+ type: "text/css",
30
+ media: "screen",
31
+ rel: "stylesheet",
32
+ nonce: csp_nonce,
33
+ href: location
34
+ }
35
+ add_to_head do
36
+ html_tag(:link, attrs.merge(kwargs))
37
+ end
38
+ end
39
+
40
+ def script_tag(location, **kwargs)
41
+ global = location.match?(/:\/\//)
42
+ location = root_path + location if !global && !location.start_with?(root_path)
43
+ attrs = {
44
+ type: "text/javascript",
45
+ nonce: csp_nonce,
46
+ src: location
47
+ }
48
+ html_tag(:script, attrs.merge(kwargs)) {}
49
+ end
50
+
51
+ # NB: keys and values are not escaped; do not allow user input
52
+ # in the attributes
53
+ private def html_tag(tagname, attrs)
54
+ s = "<#{tagname}"
55
+ attrs.each_pair do |k, v|
56
+ next unless v
57
+ s << " #{k}=\"#{v}\""
58
+ end
59
+ if block_given?
60
+ s << ">"
61
+ yield s
62
+ s << "</#{tagname}>"
63
+ else
64
+ s << " />"
65
+ end
66
+ s
67
+ end
68
+
10
69
  def strings(lang)
11
70
  @@strings ||= {}
12
- @@strings[lang] ||= begin
13
- # Allow sidekiq-web extensions to add locale paths
14
- # so extensions can be localized
15
- settings.locales.each_with_object({}) do |path, global|
16
- find_locale_files(lang).each do |file|
17
- strs = YAML.load(File.open(file))
18
- global.merge!(strs[lang])
19
- end
71
+
72
+ # Allow sidekiq-web extensions to add locale paths
73
+ # so extensions can be localized
74
+ @@strings[lang] ||= config.locales.each_with_object({}) do |path, global|
75
+ find_locale_files(lang).each do |file|
76
+ strs = YAML.safe_load_file(file)
77
+ global.merge!(strs[lang])
20
78
  end
21
79
  end
22
80
  end
23
81
 
82
+ def to_json(x)
83
+ Sidekiq.dump_json(x)
84
+ end
85
+
86
+ def singularize(str, count)
87
+ if count == 1 && str.respond_to?(:singularize) # rails
88
+ str.singularize
89
+ else
90
+ str
91
+ end
92
+ end
93
+
24
94
  def clear_caches
25
95
  @@strings = nil
26
96
  @@locale_files = nil
@@ -28,21 +98,46 @@ module Sidekiq
28
98
  end
29
99
 
30
100
  def locale_files
31
- @@locale_files ||= settings.locales.flat_map do |path|
101
+ @@locale_files ||= config.locales.flat_map { |path|
32
102
  Dir["#{path}/*.yml"]
33
- end
103
+ }
34
104
  end
35
105
 
36
106
  def available_locales
37
- @@available_locales ||= locale_files.map { |path| File.basename(path, '.yml') }.uniq
107
+ @@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
38
108
  end
39
109
 
40
110
  def find_locale_files(lang)
41
111
  locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
42
112
  end
43
113
 
44
- # This is a hook for a Sidekiq Pro feature. Please don't touch.
45
- def filtering(*)
114
+ def language_name(locale)
115
+ strings(locale).fetch("LanguageName", locale)
116
+ end
117
+
118
+ def search(jobset, substr)
119
+ resultset = jobset.scan(substr).to_a
120
+ @current_page = 1
121
+ @count = @total_size = resultset.size
122
+ resultset
123
+ end
124
+
125
+ def filtering(which)
126
+ erb(:filtering, locals: {which: which})
127
+ end
128
+
129
+ def filter_link(jid, within = "retries")
130
+ if within.nil?
131
+ ::Rack::Utils.escape_html(jid)
132
+ else
133
+ "<a href='#{root_path}#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
134
+ end
135
+ end
136
+
137
+ def display_tags(job, within = "retries")
138
+ job.tags.map { |tag|
139
+ "<span class='label label-info jobtag jobtag-#{Rack::Utils.escape_html(tag)}'>#{filter_link(tag, within)}</span>"
140
+ }.join(" ")
46
141
  end
47
142
 
48
143
  # This view helper provide ability display you html code in
@@ -62,66 +157,73 @@ module Sidekiq
62
157
  @head_html.join if defined?(@head_html)
63
158
  end
64
159
 
65
- def poll_path
66
- if current_path != '' && params['poll']
67
- root_path + current_path
68
- else
69
- ""
70
- end
71
- end
72
-
73
160
  def text_direction
74
- get_locale['TextDirection'] || 'ltr'
161
+ get_locale["TextDirection"] || "ltr"
75
162
  end
76
163
 
77
164
  def rtl?
78
- text_direction == 'rtl'
165
+ text_direction == "rtl"
79
166
  end
80
167
 
81
- # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
168
+ # See https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.4
169
+ # Returns an array of language tags ordered by their quality value
170
+ #
171
+ # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
82
172
  def user_preferred_languages
83
- languages = env['HTTP_ACCEPT_LANGUAGE']
84
- languages.to_s.downcase.gsub(/\s+/, '').split(',').map do |language|
85
- locale, quality = language.split(';q=', 2)
86
- locale = nil if locale == '*' # Ignore wildcards
173
+ languages = env["HTTP_ACCEPT_LANGUAGE"]
174
+ languages.to_s.gsub(/\s+/, "").split(",").map { |language|
175
+ locale, quality = language.split(";q=", 2)
176
+ locale = nil if locale == "*" # Ignore wildcards
87
177
  quality = quality ? quality.to_f : 1.0
88
178
  [locale, quality]
89
- end.sort do |(_, left), (_, right)|
179
+ }.sort { |(_, left), (_, right)|
90
180
  right <=> left
91
- end.map(&:first).compact
181
+ }.map(&:first).compact
92
182
  end
93
183
 
94
184
  # Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2"
95
185
  # this method will try to best match the available locales to the user's preferred languages.
96
- #
97
- # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
98
186
  def locale
99
- @locale ||= begin
100
- matched_locale = user_preferred_languages.map do |preferred|
101
- preferred_language = preferred.split('-', 2).first
187
+ # session[:locale] is set via the locale selector from the footer
188
+ @locale ||= if (l = session&.fetch(:locale, nil)) && available_locales.include?(l)
189
+ l
190
+ else
191
+ matched_locale = nil
192
+ # Attempt to find a case-insensitive exact match first
193
+ user_preferred_languages.each do |preferred|
194
+ # We only care about the language and primary subtag
195
+ # "en-GB-oxendict" becomes "en-GB"
196
+ language_tag = preferred.split("-")[0..1].join("-")
197
+ matched_locale = available_locales.find { |available_locale| available_locale.casecmp?(language_tag) }
198
+ break if matched_locale
199
+ end
102
200
 
103
- lang_group = available_locales.select do |available|
104
- preferred_language == available.split('-', 2).first
105
- end
201
+ return matched_locale if matched_locale
106
202
 
107
- lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
108
- end.compact.first
203
+ # Find the first base language match
204
+ # "en-US,es-MX;q=0.9" matches "en"
205
+ user_preferred_languages.each do |preferred|
206
+ base_language = preferred.split("-", 2).first
207
+ matched_locale = available_locales.find { |available_locale| available_locale.casecmp?(base_language) }
208
+ break if matched_locale
209
+ end
109
210
 
110
- matched_locale || 'en'
211
+ matched_locale || "en"
111
212
  end
112
213
  end
113
214
 
114
- # mperham/sidekiq#3243
215
+ # sidekiq/sidekiq#3243
115
216
  def unfiltered?
116
- yield unless env['PATH_INFO'].start_with?("/filter/")
217
+ s = url_params("substr")
218
+ yield unless s && s.size > 0
117
219
  end
118
220
 
119
221
  def get_locale
120
222
  strings(locale)
121
223
  end
122
224
 
123
- def t(msg, options={})
124
- string = get_locale[msg] || strings('en')[msg] || msg
225
+ def t(msg, options = {})
226
+ string = get_locale[msg] || strings("en")[msg] || msg
125
227
  if options.empty?
126
228
  string
127
229
  else
@@ -129,118 +231,135 @@ module Sidekiq
129
231
  end
130
232
  end
131
233
 
132
- def workers
133
- @workers ||= Sidekiq::Workers.new
234
+ def sort_direction_label
235
+ (url_params("direction") == "asc") ? "&uarr;" : "&darr;"
236
+ end
237
+
238
+ def workset
239
+ @work ||= Sidekiq::WorkSet.new
134
240
  end
135
241
 
136
242
  def processes
137
243
  @processes ||= Sidekiq::ProcessSet.new
138
244
  end
139
245
 
140
- def stats
141
- @stats ||= Sidekiq::Stats.new
246
+ # Sorts processes by hostname following the natural sort order
247
+ def sorted_processes
248
+ @sorted_processes ||= begin
249
+ return processes unless processes.all? { |p| p["hostname"] }
250
+
251
+ processes.to_a.sort_by do |process|
252
+ # Kudos to `shurikk` on StackOverflow
253
+ # https://stackoverflow.com/a/15170063/575547
254
+ process["hostname"].split(/(\d+)/).map { |a| /\d+/.match?(a) ? a.to_i : a }
255
+ end
256
+ end
142
257
  end
143
258
 
144
- def retries_with_score(score)
145
- Sidekiq.redis do |conn|
146
- conn.zrangebyscore('retry', score, score)
147
- end.map { |msg| Sidekiq.load_json(msg) }
259
+ def busy_weights(capsule_weights)
260
+ # backwards compat with 7.0.0, remove in 7.1
261
+ cw = [capsule_weights].flatten
262
+ cw.map { |hash|
263
+ hash.map { |name, weight| (weight > 0) ? +name << ": " << weight.to_s : name }.join(", ")
264
+ }.join("; ")
148
265
  end
149
266
 
150
- def redis_connection
151
- Sidekiq.redis do |conn|
152
- c = conn.connection
153
- "redis://#{c[:location]}/#{c[:db]}"
154
- end
267
+ def stats
268
+ @stats ||= Sidekiq::Stats.new
155
269
  end
156
270
 
157
- def namespace
158
- @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
271
+ def redis_url
272
+ Sidekiq.redis do |conn|
273
+ conn.config.server_url
274
+ end
159
275
  end
160
276
 
161
277
  def redis_info
162
- Sidekiq.redis_info
278
+ @info ||= Sidekiq.default_configuration.redis_info
163
279
  end
164
280
 
165
281
  def root_path
166
- "#{env['SCRIPT_NAME']}/"
282
+ "#{env["SCRIPT_NAME"]}/"
167
283
  end
168
284
 
169
285
  def current_path
170
- @current_path ||= request.path_info.gsub(/^\//,'')
286
+ @current_path ||= request.path_info.gsub(/^\//, "")
171
287
  end
172
288
 
173
289
  def current_status
174
- workers.size == 0 ? 'idle' : 'active'
290
+ (workset.size == 0) ? "idle" : "active"
175
291
  end
176
292
 
177
293
  def relative_time(time)
178
294
  stamp = time.getutc.iso8601
179
- %{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
295
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
180
296
  end
181
297
 
182
298
  def job_params(job, score)
183
- "#{score}-#{job['jid']}"
299
+ "#{score}-#{job["jid"]}"
184
300
  end
185
301
 
186
- def parse_params(params)
187
- score, jid = params.split("-", 2)
302
+ def parse_key(key)
303
+ score, jid = key.split("-", 2)
188
304
  [score.to_f, jid]
189
305
  end
190
306
 
191
- SAFE_QPARAMS = %w(page poll)
307
+ SAFE_QPARAMS = %w[page direction]
192
308
 
193
309
  # Merge options with current params, filter safe params, and stringify to query string
194
310
  def qparams(options)
195
- # stringify
196
- options.keys.each do |key|
197
- options[key.to_s] = options.delete(key)
198
- end
311
+ stringified_options = options.transform_keys(&:to_s)
312
+
313
+ to_query_string(request.params.merge(stringified_options))
314
+ end
199
315
 
200
- params.merge(options).map do |key, value|
316
+ def to_query_string(hash)
317
+ hash.map { |key, value|
201
318
  SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
202
- end.compact.join("&")
319
+ }.compact.join("&")
203
320
  end
204
321
 
205
322
  def truncate(text, truncate_after_chars = 2000)
206
- truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
323
+ (truncate_after_chars && text.size > truncate_after_chars) ? "#{text[0..truncate_after_chars]}..." : text
207
324
  end
208
325
 
209
326
  def display_args(args, truncate_after_chars = 2000)
210
- return "Invalid job payload, args is nil" if args == nil
211
- return "Invalid job payload, args must be an Array, not #{args.class.name}" if !args.is_a?(Array)
327
+ return "Invalid job payload, args is nil" if args.nil?
328
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
212
329
 
213
330
  begin
214
- args.map do |arg|
331
+ args.map { |arg|
215
332
  h(truncate(to_display(arg), truncate_after_chars))
216
- end.join(", ")
333
+ }.join(", ")
217
334
  rescue
218
335
  "Illegal job arguments: #{h args.inspect}"
219
336
  end
220
337
  end
221
338
 
222
339
  def csrf_tag
223
- "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
340
+ "<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
341
+ end
342
+
343
+ def csp_nonce
344
+ env[:csp_nonce]
224
345
  end
225
346
 
226
347
  def to_display(arg)
348
+ arg.inspect
349
+ rescue
227
350
  begin
228
- arg.inspect
229
- rescue
230
- begin
231
- arg.to_s
232
- rescue => ex
233
- "Cannot display argument: [#{ex.class.name}] #{ex.message}"
234
- end
351
+ arg.to_s
352
+ rescue => ex
353
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
235
354
  end
236
355
  end
237
356
 
238
- RETRY_JOB_KEYS = Set.new(%w(
357
+ RETRY_JOB_KEYS = Set.new(%w[
239
358
  queue class args retry_count retried_at failed_at
240
359
  jid error_message error_class backtrace
241
360
  error_backtrace enqueued_at retry wrapped
242
- created_at
243
- ))
361
+ created_at tags display_class
362
+ ])
244
363
 
245
364
  def retry_extra_items(retry_job)
246
365
  @retry_extra_items ||= {}.tap do |extra|
@@ -250,24 +369,28 @@ module Sidekiq
250
369
  end
251
370
  end
252
371
 
253
- def number_with_delimiter(number)
254
- begin
255
- Float(number)
256
- rescue ArgumentError, TypeError
257
- return number
372
+ def format_memory(rss_kb)
373
+ return "0" if rss_kb.nil? || rss_kb == 0
374
+
375
+ if rss_kb < 100_000
376
+ "#{number_with_delimiter(rss_kb)} KB"
377
+ elsif rss_kb < 10_000_000
378
+ "#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
379
+ else
380
+ "#{number_with_delimiter(rss_kb / (1024.0 * 1024.0), precision: 1)} GB"
258
381
  end
382
+ end
259
383
 
260
- options = {delimiter: ',', separator: '.'}
261
- parts = number.to_s.to_str.split('.')
262
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
263
- parts.join(options[:separator])
384
+ def number_with_delimiter(number, options = {})
385
+ precision = options[:precision] || 0
386
+ %(<span data-nwp="#{precision}">#{number.round(precision)}</span>)
264
387
  end
265
388
 
266
389
  def h(text)
267
- ::Rack::Utils.escape_html(text)
390
+ ::Rack::Utils.escape_html(text.to_s)
268
391
  rescue ArgumentError => e
269
- raise unless e.message.eql?('invalid byte sequence in UTF-8')
270
- text.encode!('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode!('UTF-8', 'UTF-16')
392
+ raise unless e.message.eql?("invalid byte sequence in UTF-8")
393
+ text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
271
394
  retry
272
395
  end
273
396
 
@@ -284,7 +407,7 @@ module Sidekiq
284
407
  end
285
408
 
286
409
  def environment_title_prefix
287
- environment = Sidekiq.options[:environment] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
410
+ environment = Sidekiq.default_configuration[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
288
411
 
289
412
  "[#{environment.upcase}] " unless environment == "production"
290
413
  end
@@ -294,30 +417,28 @@ module Sidekiq
294
417
  end
295
418
 
296
419
  def server_utc_time
297
- Time.now.utc.strftime('%H:%M:%S UTC')
420
+ Time.now.utc.strftime("%H:%M:%S UTC")
298
421
  end
299
422
 
300
- def redis_connection_and_namespace
301
- @redis_connection_and_namespace ||= begin
302
- namespace_suffix = namespace == nil ? '' : "##{namespace}"
303
- "#{redis_connection}#{namespace_suffix}"
304
- end
423
+ def pollable?
424
+ # there's no point to refreshing the metrics pages every N seconds
425
+ !(current_path == "" || current_path.index("metrics"))
305
426
  end
306
427
 
307
428
  def retry_or_delete_or_kill(job, params)
308
- if params['retry']
429
+ if params["retry"]
309
430
  job.retry
310
- elsif params['delete']
431
+ elsif params["delete"]
311
432
  job.delete
312
- elsif params['kill']
433
+ elsif params["kill"]
313
434
  job.kill
314
435
  end
315
436
  end
316
437
 
317
438
  def delete_or_add_queue(job, params)
318
- if params['delete']
439
+ if params["delete"]
319
440
  job.delete
320
- elsif params['add_to_queue']
441
+ elsif params["add_to_queue"]
321
442
  job.add_to_queue
322
443
  end
323
444
  end