sidekiq 6.2.2 → 8.1.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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +726 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +70 -39
  5. data/bin/kiq +17 -0
  6. data/bin/lint-herb +13 -0
  7. data/bin/multi_queue_bench +271 -0
  8. data/bin/sidekiq +4 -9
  9. data/bin/sidekiqload +214 -115
  10. data/bin/sidekiqmon +4 -1
  11. data/bin/webload +69 -0
  12. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  13. data/lib/generators/sidekiq/job_generator.rb +71 -0
  14. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +3 -3
  15. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  16. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  17. data/lib/sidekiq/api.rb +729 -264
  18. data/lib/sidekiq/capsule.rb +135 -0
  19. data/lib/sidekiq/cli.rb +124 -100
  20. data/lib/sidekiq/client.rb +153 -106
  21. data/lib/sidekiq/component.rb +132 -0
  22. data/lib/sidekiq/config.rb +320 -0
  23. data/lib/sidekiq/deploy.rb +64 -0
  24. data/lib/sidekiq/embedded.rb +64 -0
  25. data/lib/sidekiq/fetch.rb +27 -26
  26. data/lib/sidekiq/iterable_job.rb +56 -0
  27. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  28. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  29. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  30. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  31. data/lib/sidekiq/job/iterable.rb +322 -0
  32. data/lib/sidekiq/job.rb +397 -5
  33. data/lib/sidekiq/job_logger.rb +23 -32
  34. data/lib/sidekiq/job_retry.rb +141 -68
  35. data/lib/sidekiq/job_util.rb +113 -0
  36. data/lib/sidekiq/launcher.rb +122 -98
  37. data/lib/sidekiq/loader.rb +57 -0
  38. data/lib/sidekiq/logger.rb +27 -106
  39. data/lib/sidekiq/manager.rb +41 -43
  40. data/lib/sidekiq/metrics/query.rb +184 -0
  41. data/lib/sidekiq/metrics/shared.rb +109 -0
  42. data/lib/sidekiq/metrics/tracking.rb +153 -0
  43. data/lib/sidekiq/middleware/chain.rb +96 -51
  44. data/lib/sidekiq/middleware/current_attributes.rb +120 -0
  45. data/lib/sidekiq/middleware/i18n.rb +8 -4
  46. data/lib/sidekiq/middleware/modules.rb +23 -0
  47. data/lib/sidekiq/monitor.rb +16 -6
  48. data/lib/sidekiq/paginator.rb +37 -10
  49. data/lib/sidekiq/processor.rb +105 -87
  50. data/lib/sidekiq/profiler.rb +73 -0
  51. data/lib/sidekiq/rails.rb +49 -36
  52. data/lib/sidekiq/redis_client_adapter.rb +117 -0
  53. data/lib/sidekiq/redis_connection.rb +55 -86
  54. data/lib/sidekiq/ring_buffer.rb +32 -0
  55. data/lib/sidekiq/scheduled.rb +106 -50
  56. data/lib/sidekiq/systemd.rb +2 -0
  57. data/lib/sidekiq/test_api.rb +331 -0
  58. data/lib/sidekiq/testing/inline.rb +2 -30
  59. data/lib/sidekiq/testing.rb +2 -342
  60. data/lib/sidekiq/transaction_aware_client.rb +59 -0
  61. data/lib/sidekiq/tui/controls.rb +53 -0
  62. data/lib/sidekiq/tui/filtering.rb +53 -0
  63. data/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  64. data/lib/sidekiq/tui/tabs/busy.rb +118 -0
  65. data/lib/sidekiq/tui/tabs/dead.rb +19 -0
  66. data/lib/sidekiq/tui/tabs/home.rb +144 -0
  67. data/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  68. data/lib/sidekiq/tui/tabs/queues.rb +95 -0
  69. data/lib/sidekiq/tui/tabs/retries.rb +19 -0
  70. data/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  71. data/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  72. data/lib/sidekiq/tui/tabs.rb +15 -0
  73. data/lib/sidekiq/tui.rb +382 -0
  74. data/lib/sidekiq/version.rb +6 -1
  75. data/lib/sidekiq/web/action.rb +149 -64
  76. data/lib/sidekiq/web/application.rb +376 -268
  77. data/lib/sidekiq/web/config.rb +117 -0
  78. data/lib/sidekiq/web/helpers.rb +213 -87
  79. data/lib/sidekiq/web/router.rb +61 -74
  80. data/lib/sidekiq/web.rb +71 -100
  81. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  82. data/lib/sidekiq.rb +95 -196
  83. data/sidekiq.gemspec +14 -11
  84. data/web/assets/images/logo.png +0 -0
  85. data/web/assets/images/status.png +0 -0
  86. data/web/assets/javascripts/application.js +171 -57
  87. data/web/assets/javascripts/base-charts.js +120 -0
  88. data/web/assets/javascripts/chart.min.js +13 -0
  89. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  90. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  91. data/web/assets/javascripts/dashboard-charts.js +194 -0
  92. data/web/assets/javascripts/dashboard.js +41 -274
  93. data/web/assets/javascripts/metrics.js +280 -0
  94. data/web/assets/stylesheets/style.css +776 -0
  95. data/web/locales/ar.yml +72 -70
  96. data/web/locales/cs.yml +64 -62
  97. data/web/locales/da.yml +62 -53
  98. data/web/locales/de.yml +67 -65
  99. data/web/locales/el.yml +45 -24
  100. data/web/locales/en.yml +93 -69
  101. data/web/locales/es.yml +91 -68
  102. data/web/locales/fa.yml +67 -65
  103. data/web/locales/fr.yml +82 -67
  104. data/web/locales/gd.yml +110 -0
  105. data/web/locales/he.yml +67 -64
  106. data/web/locales/hi.yml +61 -59
  107. data/web/locales/it.yml +94 -54
  108. data/web/locales/ja.yml +74 -68
  109. data/web/locales/ko.yml +54 -52
  110. data/web/locales/lt.yml +68 -66
  111. data/web/locales/nb.yml +63 -61
  112. data/web/locales/nl.yml +54 -52
  113. data/web/locales/pl.yml +47 -45
  114. data/web/locales/{pt-br.yml → pt-BR.yml} +85 -56
  115. data/web/locales/pt.yml +53 -51
  116. data/web/locales/ru.yml +69 -66
  117. data/web/locales/sv.yml +55 -53
  118. data/web/locales/ta.yml +62 -60
  119. data/web/locales/tr.yml +102 -0
  120. data/web/locales/uk.yml +87 -61
  121. data/web/locales/ur.yml +66 -64
  122. data/web/locales/vi.yml +69 -67
  123. data/web/locales/zh-CN.yml +107 -0
  124. data/web/locales/{zh-tw.yml → zh-TW.yml} +44 -9
  125. data/web/views/_footer.html.erb +32 -0
  126. data/web/views/_job_info.html.erb +115 -0
  127. data/web/views/_metrics_period_select.html.erb +15 -0
  128. data/web/views/_nav.html.erb +45 -0
  129. data/web/views/_paging.html.erb +26 -0
  130. data/web/views/_poll_link.html.erb +4 -0
  131. data/web/views/_summary.html.erb +40 -0
  132. data/web/views/busy.html.erb +151 -0
  133. data/web/views/dashboard.html.erb +104 -0
  134. data/web/views/dead.html.erb +38 -0
  135. data/web/views/filtering.html.erb +6 -0
  136. data/web/views/layout.html.erb +26 -0
  137. data/web/views/metrics.html.erb +85 -0
  138. data/web/views/metrics_for_job.html.erb +58 -0
  139. data/web/views/morgue.html.erb +69 -0
  140. data/web/views/profiles.html.erb +43 -0
  141. data/web/views/queue.html.erb +57 -0
  142. data/web/views/queues.html.erb +46 -0
  143. data/web/views/retries.html.erb +77 -0
  144. data/web/views/retry.html.erb +39 -0
  145. data/web/views/scheduled.html.erb +64 -0
  146. data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
  147. metadata +130 -61
  148. data/LICENSE +0 -9
  149. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  150. data/lib/sidekiq/delay.rb +0 -41
  151. data/lib/sidekiq/exception_handler.rb +0 -27
  152. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  153. data/lib/sidekiq/extensions/active_record.rb +0 -43
  154. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  155. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  156. data/lib/sidekiq/util.rb +0 -95
  157. data/lib/sidekiq/web/csrf_protection.rb +0 -180
  158. data/lib/sidekiq/worker.rb +0 -244
  159. data/web/assets/stylesheets/application-dark.css +0 -147
  160. data/web/assets/stylesheets/application-rtl.css +0 -246
  161. data/web/assets/stylesheets/application.css +0 -1053
  162. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  163. data/web/assets/stylesheets/bootstrap.css +0 -5
  164. data/web/locales/zh-cn.yml +0 -68
  165. data/web/views/_footer.erb +0 -20
  166. data/web/views/_job_info.erb +0 -89
  167. data/web/views/_nav.erb +0 -52
  168. data/web/views/_paging.erb +0 -23
  169. data/web/views/_poll_link.erb +0 -7
  170. data/web/views/_status.erb +0 -4
  171. data/web/views/_summary.erb +0 -40
  172. data/web/views/busy.erb +0 -132
  173. data/web/views/dashboard.erb +0 -83
  174. data/web/views/dead.erb +0 -34
  175. data/web/views/layout.erb +0 -42
  176. data/web/views/morgue.erb +0 -78
  177. data/web/views/queue.erb +0 -55
  178. data/web/views/queues.erb +0 -38
  179. data/web/views/retries.erb +0 -83
  180. data/web/views/retry.erb +0 -34
  181. data/web/views/scheduled.erb +0 -57
@@ -0,0 +1,131 @@
1
+ require_relative "base_tab"
2
+
3
+ module Sidekiq
4
+ class TUI
5
+ module Tabs
6
+ class Metrics < BaseTab
7
+ include Filtering
8
+
9
+ COLORS = %i[light_blue light_cyan light_yellow light_red light_green white gray]
10
+
11
+ def features
12
+ %i[filterable]
13
+ end
14
+
15
+ def on_filter_change
16
+ @data[:metrics_refresh] = nil
17
+ end
18
+
19
+ def regexp
20
+ filtering? ? Regexp.new(Regexp.escape(current_filter), Regexp::IGNORECASE) : nil
21
+ end
22
+
23
+ def refresh_data
24
+ refresh_data_for_stats
25
+
26
+ # only need to refresh every 60 seconds
27
+ if !@data[:metrics_refresh] || @data[:metrics_refresh] < Time.now
28
+ q = Sidekiq::Metrics::Query.new
29
+ query_result = q.top_jobs(class_filter: regexp, minutes: 60)
30
+ @data[:metrics] = query_result
31
+ @data[:metrics_refresh] = Time.now + 60
32
+ end
33
+ end
34
+
35
+ def render(tui, frame, area)
36
+ chunks = tui.layout_split(
37
+ area,
38
+ direction: :vertical,
39
+ constraints: [
40
+ tui.constraint_length(4), # Stats
41
+ tui.constraint_fill(1) # Chart
42
+ # TOOD Table
43
+ ]
44
+ )
45
+
46
+ render_stats_section(tui, frame, chunks[0])
47
+ render_metrics_chart(tui, frame, chunks[1])
48
+ end
49
+
50
+ # Run to generate metrics data:
51
+ # cd myapp && bundle install
52
+ # bundle exec rake seed_jobs
53
+ # bundle exec sidekiq
54
+ def render_metrics_chart(tui, frame, area)
55
+ y_max = 5
56
+ csize = COLORS.size
57
+ q = @data[:metrics]
58
+ job_results = q.job_results.sort_by { |(kls, jr)| jr.totals["s"] }.reverse.first(COLORS.size)
59
+ # visible_kls = job_results.first(5).map(&:first)
60
+ # chart_data = {
61
+ # series: job_results.map { |(kls, jr)| [kls, jr.dig("series", "s")] }.to_h,
62
+ # marks: query_result.marks.map { |m| [m.bucket, m.label] },
63
+ # starts_at: query_result.starts_at.iso8601,
64
+ # ends_at: query_result.ends_at.iso8601,
65
+ # visibleKls: visible_kls,
66
+ # yLabel: 'TotalExecutionTime',
67
+ # units: 'seconds',
68
+ # markLabel: '*',
69
+ # }
70
+
71
+ datasets = job_results.map.with_index do |(kls, data), idx|
72
+ # log kls, data, idx
73
+ hrdata = data.dig("series", "s")
74
+ tm = Time.now
75
+ tmi = tm.to_i
76
+ tm = Time.at(tmi - (tmi % 60)).utc
77
+ data = Array.new(60) { |idx| idx }.map do |bucket_idx|
78
+ jumpback = bucket_idx * 60
79
+ value = hrdata[(tm - jumpback).iso8601] || 0
80
+ y_max = value if value > y_max
81
+ # we have 60 data points, newest data should be
82
+ # at highest indexes so we have to rejigger the index
83
+ # here
84
+ [59 - bucket_idx, value]
85
+ end
86
+ # log data
87
+
88
+ # log(data)
89
+ tui.dataset(name: kls,
90
+ data: data,
91
+ style: tui.style(fg: COLORS[idx % csize]),
92
+ marker: :dot,
93
+ graph_type: :line)
94
+ end
95
+
96
+ num_labels = 5
97
+ y_labels = (0...num_labels).map do |i|
98
+ value = ((y_max * i) / (num_labels - 1)).round
99
+ value.to_s
100
+ end
101
+ xlabels = [
102
+ q.starts_at.iso8601[11..15],
103
+ q.ends_at.iso8601[11..15]
104
+ ]
105
+
106
+ # beacon_pulse = (Time.now.to_i % 2 == 0) ? "●" : " "
107
+
108
+ chart = tui.chart(
109
+ datasets: datasets,
110
+ x_axis: tui.axis(
111
+ bounds: [0.0, 60.0],
112
+ labels: xlabels,
113
+ style: tui.style(fg: :white)
114
+ ),
115
+ y_axis: tui.axis(
116
+ bounds: [0.0, y_max.to_f],
117
+ labels: y_labels,
118
+ style: tui.style(fg: :white)
119
+ ),
120
+ block: tui.block(
121
+ title: t(name),
122
+ borders: [:all]
123
+ )
124
+ )
125
+
126
+ frame.render_widget(chart, area)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,95 @@
1
+ require_relative "base_tab"
2
+
3
+ module Sidekiq
4
+ class TUI
5
+ module Tabs
6
+ class Queues < BaseTab
7
+ def features
8
+ %i[selectable]
9
+ end
10
+
11
+ def controls
12
+ @controls ||= super + [
13
+ {code: "D", modifiers: ["shift"], display: "D", description: "Delete",
14
+ action: ->(tui, tab) { tab.delete_queue! }, refresh: true},
15
+ {code: "p", description: "Pause/Unpause Queue",
16
+ action: ->(tui, tab) { tab.toggle_pause_queue! }}
17
+ ]
18
+ end
19
+
20
+ def delete_queue!
21
+ each_selection do |qname|
22
+ Sidekiq::Queue.new(qname).clear
23
+ end
24
+ end
25
+
26
+ def toggle_pause_queue!
27
+ return unless Sidekiq.pro?
28
+
29
+ each_selection do |qname|
30
+ queue = Sidekiq::Queue.new(qname)
31
+ if queue.paused?
32
+ queue.unpause!
33
+ else
34
+ queue.pause!
35
+ end
36
+ end
37
+ end
38
+
39
+ def refresh_data
40
+ refresh_data_for_stats
41
+
42
+ queue_summaries = Sidekiq::Stats.new.queue_summaries.sort_by(&:name)
43
+
44
+ selected = Array(@data[:selected])
45
+ queues = queue_summaries.map { |queue_summary|
46
+ row_cells = [
47
+ selected.index(queue_summary.name) ? "✅" : "",
48
+ queue_summary.name,
49
+ queue_summary.size.to_s,
50
+ number_with_delimiter(queue_summary.latency, {precision: 2})
51
+ ]
52
+ row_cells << (queue_summary.paused? ? "✅" : "") if Sidekiq.pro?
53
+ row_cells
54
+ }
55
+
56
+ table_row_ids = queue_summaries.map(&:name)
57
+
58
+ @data[:queues] = queues
59
+ @data[:table] = {row_ids: table_row_ids}
60
+ end
61
+
62
+ def render(tui, frame, area)
63
+ header = ["☑️", "Queue", "Size", "Latency"].map { |x| t(x) }
64
+ header << "Paused?" if Sidekiq.pro?
65
+
66
+ chunks = tui.layout_split(
67
+ area,
68
+ direction: :vertical,
69
+ constraints: [
70
+ tui.constraint_length(4), # Stats
71
+ tui.constraint_fill(1) # Table
72
+ ]
73
+ )
74
+
75
+ render_stats_section(tui, frame, chunks[0])
76
+ render_table(tui, frame, chunks[1]) do
77
+ {
78
+ title: t(name),
79
+ header:,
80
+ widths: header.map.with_index { |_, idx|
81
+ tui.constraint_length((idx == 1) ? 60 : 10)
82
+ },
83
+ rows: @data[:queues].map.with_index { |cells, idx|
84
+ tui.table_row(
85
+ cells:,
86
+ style: idx.even? ? nil : tui.style(bg: :dark_gray)
87
+ )
88
+ }
89
+ }
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "base_tab"
2
+ require_relative "set_tab"
3
+
4
+ module Sidekiq
5
+ class TUI
6
+ module Tabs
7
+ class Retries < BaseTab
8
+ include SetTab
9
+
10
+ def set_class = Sidekiq::RetrySet
11
+
12
+ def refresh_data
13
+ refresh_data_for_stats
14
+ refresh_data_for_set
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "base_tab"
2
+ require_relative "set_tab"
3
+
4
+ module Sidekiq
5
+ class TUI
6
+ module Tabs
7
+ class Scheduled < BaseTab
8
+ include SetTab
9
+
10
+ def set_class = Sidekiq::ScheduledSet
11
+
12
+ def refresh_data
13
+ refresh_data_for_stats
14
+ refresh_data_for_set
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ module Sidekiq
2
+ class TUI
3
+ module Tabs
4
+ module SetTab
5
+ include Sidekiq::Paginator
6
+ include Filtering
7
+
8
+ def features
9
+ %i[selectable pageable filterable]
10
+ end
11
+
12
+ def controls
13
+ @controls ||= super + [{code: "D", modifiers: ["shift"], display: "D", description: "Delete",
14
+ action: ->(tui, tab) { tab.alter_rows!(:delete) }, refresh: true},
15
+ {code: "R", modifiers: ["shift"], display: "R", description: "Retry",
16
+ action: ->(tui, tab) { tab.alter_rows!(:retry) }, refresh: true},
17
+ {code: "E", modifiers: ["shift"], display: "E", description: "Enqueue",
18
+ action: ->(tui, tab) { tab.alter_rows!(:add_to_queue) }, refresh: true},
19
+ {code: "K", modifiers: ["shift"], display: "K", description: "Kill",
20
+ action: ->(tui, tab) { tab.alter_rows!(:kill) }, refresh: true}]
21
+ end
22
+
23
+ def alter_rows!(action)
24
+ # log(to_s, @data[:selected])
25
+ set = set_class.new
26
+ each_selection do |id|
27
+ score, jid = id.split("|")
28
+ item = set.fetch(score, jid)&.first
29
+ item&.send(action)
30
+ end
31
+ end
32
+
33
+ def refresh_data_for_set
34
+ set = set_class.new
35
+ f = current_filter
36
+ pager, rows, current, total = if f && f.size > 2
37
+ rows = set.scan(f).to_a
38
+ sz = rows.size
39
+ [Sidekiq::TUI::PageOptions.new(1, sz), rows, 1, sz]
40
+ else
41
+ pager = @data.dig(:table, :pager) || Sidekiq::TUI::PageOptions.new(1, 25)
42
+ current, total, items = page(set.name, pager.page, pager.size)
43
+ rows = items.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
44
+ [pager, rows, current, total]
45
+ end
46
+
47
+ @data.merge!(
48
+ table: {pager:, rows:, current_page: current, total:,
49
+ next_page: (current * pager.size < total) ? pager.page + 1 : nil,
50
+ row_ids: rows.map { |job| [job.score, job["jid"]].join("|") }}
51
+ )
52
+ end
53
+
54
+ def render(tui, frame, area)
55
+ chunks = tui.layout_split(
56
+ area,
57
+ direction: :vertical,
58
+ constraints: [
59
+ tui.constraint_length(4), # Stats
60
+ tui.constraint_fill(1) # Table
61
+ ]
62
+ )
63
+
64
+ render_stats_section(tui, frame, chunks[0])
65
+ render_table(tui, frame, chunks[1]) do
66
+ {
67
+ title: t(name),
68
+ header: ["☑️", "When", "Queue", "Job", "Arguments"].map { |x| t(x) },
69
+ widths: [
70
+ tui.constraint_length(5),
71
+ tui.constraint_length(24),
72
+ tui.constraint_length(20),
73
+ tui.constraint_length(30),
74
+ tui.constraint_fill(1)
75
+ ]
76
+ }.tap do |h|
77
+ rows = @data[:table][:rows].map.with_index { |entry, idx|
78
+ tui.table_row(
79
+ cells: [
80
+ selected?(entry) ? "✅" : "",
81
+ entry.at,
82
+ entry.queue,
83
+ entry.display_class,
84
+ entry.display_args
85
+ ],
86
+ style: idx.even? ? nil : tui.style(bg: :dark_gray)
87
+ )
88
+ }
89
+ h[:rows] = rows
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "tabs/home"
2
+ require_relative "tabs/busy"
3
+ require_relative "tabs/queues"
4
+ require_relative "tabs/scheduled"
5
+ require_relative "tabs/retries"
6
+ require_relative "tabs/dead"
7
+ require_relative "tabs/metrics"
8
+
9
+ module Sidekiq
10
+ class TUI
11
+ module Tabs
12
+ All = Set.new([Home, Busy, Queues, Scheduled, Retries, Dead, Metrics])
13
+ end
14
+ end
15
+ end