ultra-fast-tool 0.0.1

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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/sidekiq-8.1.6/Changes.md +2515 -0
  3. data/sidekiq-8.1.6/LICENSE.txt +9 -0
  4. data/sidekiq-8.1.6/README.md +124 -0
  5. data/sidekiq-8.1.6/bin/kiq +17 -0
  6. data/sidekiq-8.1.6/bin/lint-herb +13 -0
  7. data/sidekiq-8.1.6/bin/multi_queue_bench +271 -0
  8. data/sidekiq-8.1.6/bin/sidekiq +37 -0
  9. data/sidekiq-8.1.6/bin/sidekiqload +256 -0
  10. data/sidekiq-8.1.6/bin/sidekiqmon +11 -0
  11. data/sidekiq-8.1.6/bin/webload +69 -0
  12. data/sidekiq-8.1.6/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  13. data/sidekiq-8.1.6/lib/generators/sidekiq/job_generator.rb +71 -0
  14. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job.rb.erb +9 -0
  15. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  16. data/sidekiq-8.1.6/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  17. data/sidekiq-8.1.6/lib/sidekiq/api.rb +1466 -0
  18. data/sidekiq-8.1.6/lib/sidekiq/capsule.rb +135 -0
  19. data/sidekiq-8.1.6/lib/sidekiq/cli.rb +433 -0
  20. data/sidekiq-8.1.6/lib/sidekiq/client.rb +306 -0
  21. data/sidekiq-8.1.6/lib/sidekiq/component.rb +132 -0
  22. data/sidekiq-8.1.6/lib/sidekiq/config.rb +316 -0
  23. data/sidekiq-8.1.6/lib/sidekiq/deploy.rb +64 -0
  24. data/sidekiq-8.1.6/lib/sidekiq/embedded.rb +64 -0
  25. data/sidekiq-8.1.6/lib/sidekiq/fetch.rb +89 -0
  26. data/sidekiq-8.1.6/lib/sidekiq/iterable_job.rb +56 -0
  27. data/sidekiq-8.1.6/lib/sidekiq/job/interrupt_handler.rb +24 -0
  28. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  29. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  30. data/sidekiq-8.1.6/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  31. data/sidekiq-8.1.6/lib/sidekiq/job/iterable.rb +322 -0
  32. data/sidekiq-8.1.6/lib/sidekiq/job.rb +400 -0
  33. data/sidekiq-8.1.6/lib/sidekiq/job_logger.rb +54 -0
  34. data/sidekiq-8.1.6/lib/sidekiq/job_retry.rb +332 -0
  35. data/sidekiq-8.1.6/lib/sidekiq/job_util.rb +113 -0
  36. data/sidekiq-8.1.6/lib/sidekiq/launcher.rb +283 -0
  37. data/sidekiq-8.1.6/lib/sidekiq/loader.rb +57 -0
  38. data/sidekiq-8.1.6/lib/sidekiq/logger.rb +87 -0
  39. data/sidekiq-8.1.6/lib/sidekiq/manager.rb +134 -0
  40. data/sidekiq-8.1.6/lib/sidekiq/metrics/query.rb +184 -0
  41. data/sidekiq-8.1.6/lib/sidekiq/metrics/shared.rb +109 -0
  42. data/sidekiq-8.1.6/lib/sidekiq/metrics/tracking.rb +153 -0
  43. data/sidekiq-8.1.6/lib/sidekiq/middleware/chain.rb +207 -0
  44. data/sidekiq-8.1.6/lib/sidekiq/middleware/current_attributes.rb +120 -0
  45. data/sidekiq-8.1.6/lib/sidekiq/middleware/i18n.rb +44 -0
  46. data/sidekiq-8.1.6/lib/sidekiq/middleware/modules.rb +23 -0
  47. data/sidekiq-8.1.6/lib/sidekiq/monitor.rb +143 -0
  48. data/sidekiq-8.1.6/lib/sidekiq/paginator.rb +74 -0
  49. data/sidekiq-8.1.6/lib/sidekiq/processor.rb +298 -0
  50. data/sidekiq-8.1.6/lib/sidekiq/profiler.rb +73 -0
  51. data/sidekiq-8.1.6/lib/sidekiq/rails.rb +63 -0
  52. data/sidekiq-8.1.6/lib/sidekiq/redis_client_adapter.rb +117 -0
  53. data/sidekiq-8.1.6/lib/sidekiq/redis_connection.rb +115 -0
  54. data/sidekiq-8.1.6/lib/sidekiq/ring_buffer.rb +32 -0
  55. data/sidekiq-8.1.6/lib/sidekiq/scheduled.rb +235 -0
  56. data/sidekiq-8.1.6/lib/sidekiq/sd_notify.rb +149 -0
  57. data/sidekiq-8.1.6/lib/sidekiq/systemd.rb +26 -0
  58. data/sidekiq-8.1.6/lib/sidekiq/test_api.rb +331 -0
  59. data/sidekiq-8.1.6/lib/sidekiq/testing/inline.rb +2 -0
  60. data/sidekiq-8.1.6/lib/sidekiq/testing.rb +3 -0
  61. data/sidekiq-8.1.6/lib/sidekiq/transaction_aware_client.rb +59 -0
  62. data/sidekiq-8.1.6/lib/sidekiq/tui/controls.rb +53 -0
  63. data/sidekiq-8.1.6/lib/sidekiq/tui/filtering.rb +53 -0
  64. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/base_tab.rb +204 -0
  65. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/busy.rb +118 -0
  66. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/dead.rb +19 -0
  67. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/home.rb +144 -0
  68. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/metrics.rb +131 -0
  69. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/queues.rb +95 -0
  70. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/retries.rb +19 -0
  71. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/scheduled.rb +19 -0
  72. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs/set_tab.rb +96 -0
  73. data/sidekiq-8.1.6/lib/sidekiq/tui/tabs.rb +15 -0
  74. data/sidekiq-8.1.6/lib/sidekiq/tui.rb +382 -0
  75. data/sidekiq-8.1.6/lib/sidekiq/version.rb +10 -0
  76. data/sidekiq-8.1.6/lib/sidekiq/web/action.rb +178 -0
  77. data/sidekiq-8.1.6/lib/sidekiq/web/application.rb +480 -0
  78. data/sidekiq-8.1.6/lib/sidekiq/web/config.rb +117 -0
  79. data/sidekiq-8.1.6/lib/sidekiq/web/helpers.rb +474 -0
  80. data/sidekiq-8.1.6/lib/sidekiq/web/router.rb +91 -0
  81. data/sidekiq-8.1.6/lib/sidekiq/web.rb +140 -0
  82. data/sidekiq-8.1.6/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  83. data/sidekiq-8.1.6/lib/sidekiq.rb +161 -0
  84. data/sidekiq-8.1.6/sidekiq.gemspec +31 -0
  85. data/sidekiq-8.1.6/web/assets/images/apple-touch-icon.png +0 -0
  86. data/sidekiq-8.1.6/web/assets/images/favicon.ico +0 -0
  87. data/sidekiq-8.1.6/web/assets/images/logo.png +0 -0
  88. data/sidekiq-8.1.6/web/assets/images/status.png +0 -0
  89. data/sidekiq-8.1.6/web/assets/javascripts/application.js +204 -0
  90. data/sidekiq-8.1.6/web/assets/javascripts/base-charts.js +120 -0
  91. data/sidekiq-8.1.6/web/assets/javascripts/chart.min.js +13 -0
  92. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  93. data/sidekiq-8.1.6/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  94. data/sidekiq-8.1.6/web/assets/javascripts/dashboard-charts.js +194 -0
  95. data/sidekiq-8.1.6/web/assets/javascripts/dashboard.js +63 -0
  96. data/sidekiq-8.1.6/web/assets/javascripts/metrics.js +280 -0
  97. data/sidekiq-8.1.6/web/assets/stylesheets/style.css +776 -0
  98. data/sidekiq-8.1.6/web/locales/ar.yml +89 -0
  99. data/sidekiq-8.1.6/web/locales/cs.yml +80 -0
  100. data/sidekiq-8.1.6/web/locales/da.yml +77 -0
  101. data/sidekiq-8.1.6/web/locales/de.yml +83 -0
  102. data/sidekiq-8.1.6/web/locales/el.yml +89 -0
  103. data/sidekiq-8.1.6/web/locales/en.yml +110 -0
  104. data/sidekiq-8.1.6/web/locales/es.yml +109 -0
  105. data/sidekiq-8.1.6/web/locales/fa.yml +82 -0
  106. data/sidekiq-8.1.6/web/locales/fr.yml +100 -0
  107. data/sidekiq-8.1.6/web/locales/gd.yml +110 -0
  108. data/sidekiq-8.1.6/web/locales/he.yml +82 -0
  109. data/sidekiq-8.1.6/web/locales/hi.yml +77 -0
  110. data/sidekiq-8.1.6/web/locales/it.yml +109 -0
  111. data/sidekiq-8.1.6/web/locales/ja.yml +92 -0
  112. data/sidekiq-8.1.6/web/locales/ko.yml +70 -0
  113. data/sidekiq-8.1.6/web/locales/lt.yml +85 -0
  114. data/sidekiq-8.1.6/web/locales/nb.yml +79 -0
  115. data/sidekiq-8.1.6/web/locales/nl.yml +70 -0
  116. data/sidekiq-8.1.6/web/locales/pl.yml +61 -0
  117. data/sidekiq-8.1.6/web/locales/pt-BR.yml +97 -0
  118. data/sidekiq-8.1.6/web/locales/pt.yml +69 -0
  119. data/sidekiq-8.1.6/web/locales/ru.yml +85 -0
  120. data/sidekiq-8.1.6/web/locales/sv.yml +70 -0
  121. data/sidekiq-8.1.6/web/locales/ta.yml +77 -0
  122. data/sidekiq-8.1.6/web/locales/tr.yml +102 -0
  123. data/sidekiq-8.1.6/web/locales/uk.yml +102 -0
  124. data/sidekiq-8.1.6/web/locales/ur.yml +82 -0
  125. data/sidekiq-8.1.6/web/locales/vi.yml +85 -0
  126. data/sidekiq-8.1.6/web/locales/zh-CN.yml +107 -0
  127. data/sidekiq-8.1.6/web/locales/zh-TW.yml +103 -0
  128. data/sidekiq-8.1.6/web/views/_footer.html.erb +32 -0
  129. data/sidekiq-8.1.6/web/views/_job_info.html.erb +115 -0
  130. data/sidekiq-8.1.6/web/views/_metrics_period_select.html.erb +15 -0
  131. data/sidekiq-8.1.6/web/views/_nav.html.erb +45 -0
  132. data/sidekiq-8.1.6/web/views/_paging.html.erb +26 -0
  133. data/sidekiq-8.1.6/web/views/_poll_link.html.erb +4 -0
  134. data/sidekiq-8.1.6/web/views/_summary.html.erb +40 -0
  135. data/sidekiq-8.1.6/web/views/busy.html.erb +155 -0
  136. data/sidekiq-8.1.6/web/views/dashboard.html.erb +104 -0
  137. data/sidekiq-8.1.6/web/views/dead.html.erb +38 -0
  138. data/sidekiq-8.1.6/web/views/filtering.html.erb +6 -0
  139. data/sidekiq-8.1.6/web/views/layout.html.erb +26 -0
  140. data/sidekiq-8.1.6/web/views/metrics.html.erb +85 -0
  141. data/sidekiq-8.1.6/web/views/metrics_for_job.html.erb +58 -0
  142. data/sidekiq-8.1.6/web/views/morgue.html.erb +69 -0
  143. data/sidekiq-8.1.6/web/views/profiles.html.erb +43 -0
  144. data/sidekiq-8.1.6/web/views/queue.html.erb +57 -0
  145. data/sidekiq-8.1.6/web/views/queues.html.erb +46 -0
  146. data/sidekiq-8.1.6/web/views/retries.html.erb +77 -0
  147. data/sidekiq-8.1.6/web/views/retry.html.erb +39 -0
  148. data/sidekiq-8.1.6/web/views/scheduled.html.erb +64 -0
  149. data/sidekiq-8.1.6/web/views/scheduled_job_info.html.erb +8 -0
  150. data/ultra-fast-tool.gemspec +11 -0
  151. metadata +189 -0
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/arguments"
4
+ require "active_support/current_attributes"
5
+
6
+ module Sidekiq
7
+ ##
8
+ # Automatically save and load any current attributes in the execution context
9
+ # so context attributes "flow" from Rails actions into any associated jobs.
10
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
11
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
12
+ #
13
+ # For multiple current attributes, pass an array of current attributes.
14
+ #
15
+ # @example
16
+ #
17
+ # # in your initializer
18
+ # require "sidekiq/middleware/current_attributes"
19
+ # Sidekiq::CurrentAttributes.persist("Myapp::Current")
20
+ # # or multiple current attributes
21
+ # Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
22
+ #
23
+ module CurrentAttributes
24
+ Serializer = ::ActiveJob::Arguments
25
+
26
+ class Save
27
+ include Sidekiq::ClientMiddleware
28
+
29
+ def initialize(cattrs)
30
+ @cattrs = cattrs
31
+ end
32
+
33
+ def call(_, job, _, _)
34
+ @cattrs.each do |(key, strklass)|
35
+ if !job.has_key?(key)
36
+ attrs = strklass.constantize.attributes
37
+ # Retries can push the job N times, we don't
38
+ # want retries to reset cattr. #5692, #5090
39
+ job[key] = Serializer.serialize(attrs) if attrs.any?
40
+ end
41
+ end
42
+ yield
43
+ end
44
+ end
45
+
46
+ class Load
47
+ include Sidekiq::ServerMiddleware
48
+
49
+ def initialize(cattrs)
50
+ @cattrs = cattrs
51
+ end
52
+
53
+ def call(_, job, *, &block)
54
+ klass_attrs = {}
55
+
56
+ @cattrs.each do |(key, strklass)|
57
+ next unless job.has_key?(key)
58
+
59
+ klass_attrs[strklass.constantize] = Serializer.deserialize(job[key]).to_h
60
+ end
61
+
62
+ wrap(klass_attrs.to_a, &block)
63
+ end
64
+
65
+ private
66
+
67
+ def wrap(klass_attrs, &block)
68
+ klass, attrs = klass_attrs.shift
69
+ return block.call unless klass
70
+
71
+ retried = false
72
+
73
+ begin
74
+ set_succeeded = false
75
+ klass.set(attrs) do
76
+ set_succeeded = true
77
+ wrap(klass_attrs, &block)
78
+ end
79
+ rescue NoMethodError
80
+ # Don't retry if the no method error didn't come from current attributes
81
+ raise if retried || set_succeeded
82
+
83
+ # It is possible that the `CurrentAttributes` definition
84
+ # was changed before the job started processing.
85
+ attrs = attrs.select { |attr| klass.respond_to?(attr) }
86
+ retried = true
87
+ retry
88
+ end
89
+ end
90
+ end
91
+
92
+ class << self
93
+ def persist(klass_or_array, config = Sidekiq.default_configuration)
94
+ cattrs = build_cattrs_hash(klass_or_array)
95
+
96
+ config.client_middleware.prepend Load, cattrs
97
+ config.client_middleware.add Save, cattrs
98
+ config.server_middleware.prepend Load, cattrs
99
+ end
100
+
101
+ private
102
+
103
+ def build_cattrs_hash(klass_or_array)
104
+ if klass_or_array.is_a?(Array)
105
+ {}.tap do |hash|
106
+ klass_or_array.each_with_index do |klass, index|
107
+ hash[key_at(index)] = klass.to_s
108
+ end
109
+ end
110
+ else
111
+ {key_at(0) => klass_or_array.to_s}
112
+ end
113
+ end
114
+
115
+ def key_at(index)
116
+ (index == 0) ? "cattr" : "cattr_#{index}"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Simple middleware to save the current locale and restore it when the job executes.
5
+ # Use it by requiring it in your initializer:
6
+ #
7
+ # require 'sidekiq/middleware/i18n'
8
+ #
9
+ module Sidekiq::Middleware::I18n
10
+ # Get the current locale and store it in the message
11
+ # to be sent to Sidekiq.
12
+ class Client
13
+ include Sidekiq::ClientMiddleware
14
+
15
+ def call(_jobclass, job, _queue, _redis)
16
+ job["locale"] ||= I18n.locale
17
+ yield
18
+ end
19
+ end
20
+
21
+ # Pull the msg locale out and set the current thread to use it.
22
+ class Server
23
+ include Sidekiq::ServerMiddleware
24
+
25
+ def call(_jobclass, job, _queue, &block)
26
+ I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
27
+ end
28
+ end
29
+ end
30
+
31
+ Sidekiq.configure_client do |config|
32
+ config.client_middleware do |chain|
33
+ chain.add Sidekiq::Middleware::I18n::Client
34
+ end
35
+ end
36
+
37
+ Sidekiq.configure_server do |config|
38
+ config.client_middleware do |chain|
39
+ chain.add Sidekiq::Middleware::I18n::Client
40
+ end
41
+ config.server_middleware do |chain|
42
+ chain.add Sidekiq::Middleware::I18n::Server
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ # Server-side middleware must import this Module in order
5
+ # to get access to server resources during `call`.
6
+ module ServerMiddleware
7
+ attr_accessor :config
8
+ def redis_pool
9
+ config.redis_pool
10
+ end
11
+
12
+ def logger
13
+ config.logger
14
+ end
15
+
16
+ def redis(&block)
17
+ config.redis(&block)
18
+ end
19
+ end
20
+
21
+ # no difference for now
22
+ ClientMiddleware = ServerMiddleware
23
+ end
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "fileutils"
5
+ require "sidekiq/api"
6
+
7
+ class Sidekiq::Monitor
8
+ class Status
9
+ VALID_SECTIONS = %w[all version overview processes queues]
10
+ COL_PAD = 2
11
+
12
+ def display(section = nil)
13
+ section ||= "all"
14
+ unless VALID_SECTIONS.include? section
15
+ puts "I don't know how to check the status of '#{section}'!"
16
+ puts "Try one of these: #{VALID_SECTIONS.join(", ")}"
17
+ return
18
+ end
19
+ send(section)
20
+ end
21
+
22
+ def all
23
+ version
24
+ puts
25
+ overview
26
+ puts
27
+ processes
28
+ puts
29
+ queues
30
+ end
31
+
32
+ def version
33
+ puts "Sidekiq #{Sidekiq::VERSION}"
34
+ puts Time.now.utc
35
+ end
36
+
37
+ def overview
38
+ puts "---- Overview ----"
39
+ puts " Processed: #{delimit stats.processed}"
40
+ puts " Failed: #{delimit stats.failed}"
41
+ puts " Busy: #{delimit stats.workers_size}"
42
+ puts " Enqueued: #{delimit stats.enqueued}"
43
+ puts " Retries: #{delimit stats.retry_size}"
44
+ puts " Scheduled: #{delimit stats.scheduled_size}"
45
+ puts " Dead: #{delimit stats.dead_size}"
46
+ end
47
+
48
+ def processes
49
+ puts "---- Processes (#{process_set.size}) ----"
50
+ process_set.each_with_index do |process, index|
51
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
52
+ queues =
53
+ if process["capsules"] # 8.0.6+
54
+ process["capsules"].values.map { |x| x["weights"].keys.join(", ") }
55
+ elsif process["weights"]
56
+ process["weights"].sort_by { |queue| queue[0] }.map { |capsule| capsule.map { |name, weight| (weight > 0) ? "#{name}: #{weight}" : name }.join(", ") }
57
+ else
58
+ process["queues"].sort
59
+ end
60
+
61
+ puts "#{process["identity"]} #{tags_for(process)}"
62
+ puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
63
+ puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
64
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
65
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
66
+ puts "" unless (index + 1) == process_set.size
67
+ end
68
+ end
69
+
70
+ def queues
71
+ puts "---- Queues (#{queue_data.size}) ----"
72
+ columns = {
73
+ name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
74
+ size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
75
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
76
+ }
77
+ columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
78
+ puts
79
+ queue_data.each do |q|
80
+ columns.each do |col, (dir, width)|
81
+ print q.send(col).public_send(dir, width)
82
+ end
83
+ puts
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def delimit(number)
90
+ number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
91
+ end
92
+
93
+ def split_multiline(values, opts = {})
94
+ return "none" unless values
95
+ pad = opts[:pad] || 0
96
+ max_length = opts[:max_length] || (80 - pad)
97
+ out = []
98
+ line = +""
99
+ values.each do |value|
100
+ if (line.length + value.length) > max_length
101
+ out << line
102
+ line = " " * pad
103
+ end
104
+ line << value + "; "
105
+ end
106
+ out << line[0..-3]
107
+ out.join("\n")
108
+ end
109
+
110
+ def tags_for(process)
111
+ tags = [
112
+ process["tag"],
113
+ process["labels"],
114
+ ((process["quiet"] == "true") ? "quiet" : nil)
115
+ ].flatten.compact
116
+ tags.any? ? "[#{tags.join("] [")}]" : nil
117
+ end
118
+
119
+ def time_ago(timestamp)
120
+ seconds = Time.now - Time.at(timestamp)
121
+ return "just now" if seconds < 60
122
+ return "a minute ago" if seconds < 120
123
+ return "#{seconds.floor / 60} minutes ago" if seconds < 3600
124
+ return "an hour ago" if seconds < 7200
125
+ "#{seconds.floor / 60 / 60} hours ago"
126
+ end
127
+
128
+ QUEUE_STRUCT = Struct.new(:name, :size, :latency)
129
+ def queue_data
130
+ @queue_data ||= Sidekiq::Queue.all.map { |q|
131
+ QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
132
+ }
133
+ end
134
+
135
+ def process_set
136
+ @process_set ||= Sidekiq::ProcessSet.new
137
+ end
138
+
139
+ def stats
140
+ @stats ||= Sidekiq::Stats.new
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Paginator
5
+ TYPE_CACHE = {
6
+ "dead" => "zset",
7
+ "retry" => "zset",
8
+ "schedule" => "zset"
9
+ }
10
+
11
+ def page(key, pageidx = 1, page_size = 25, opts = nil)
12
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
13
+ pageidx = current_page - 1
14
+ total_size = 0
15
+ items = []
16
+ starting = pageidx * page_size
17
+ ending = starting + page_size - 1
18
+
19
+ Sidekiq.redis do |conn|
20
+ # horrible, think you can make this cleaner?
21
+ type = TYPE_CACHE[key]
22
+ if type
23
+ elsif key.start_with?("queue:")
24
+ type = TYPE_CACHE[key] = "list"
25
+ else
26
+ type = conn.type(key)
27
+ TYPE_CACHE[key] = type unless type == "none"
28
+ end
29
+ rev = opts && opts[:reverse]
30
+
31
+ case type
32
+ when "zset"
33
+ total_size, items = conn.multi { |transaction|
34
+ transaction.zcard(key)
35
+ if rev
36
+ transaction.zrange(key, starting, ending, "REV", "withscores")
37
+ else
38
+ transaction.zrange(key, starting, ending, "withscores")
39
+ end
40
+ }
41
+ [current_page, total_size, items]
42
+ when "list"
43
+ total_size, items = conn.multi { |transaction|
44
+ transaction.llen(key)
45
+ if rev
46
+ transaction.lrange(key, -ending - 1, -starting - 1)
47
+ else
48
+ transaction.lrange(key, starting, ending)
49
+ end
50
+ }
51
+ items.reverse! if rev
52
+ [current_page, total_size, items]
53
+ when "none"
54
+ [1, 0, []]
55
+ else
56
+ raise "can't page a #{type}"
57
+ end
58
+ end
59
+ end
60
+
61
+ def page_items(items, pageidx = 1, page_size = 25)
62
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
63
+ pageidx = current_page - 1
64
+ starting = pageidx * page_size
65
+ items = items.to_a
66
+ total_size = items.size
67
+ if starting > total_size
68
+ starting = 0
69
+ current_page = 1
70
+ end
71
+ [current_page, total_size, items[starting, page_size]]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/fetch"
4
+ require "sidekiq/job_logger"
5
+ require "sidekiq/job_retry"
6
+ require "sidekiq/profiler"
7
+
8
+ module Sidekiq
9
+ ##
10
+ # The Processor is a standalone thread which:
11
+ #
12
+ # 1. fetches a job from Redis
13
+ # 2. executes the job
14
+ # a. instantiate the job class
15
+ # b. run the middleware chain
16
+ # c. call #perform
17
+ #
18
+ # A Processor can exit due to shutdown or due to
19
+ # an error during job execution.
20
+ #
21
+ # If an error occurs in the job execution, the
22
+ # Processor calls the Manager to create a new one
23
+ # to replace itself and exits.
24
+ #
25
+ class Processor
26
+ include Sidekiq::Component
27
+
28
+ attr_reader :thread
29
+ attr_reader :job
30
+ attr_reader :capsule
31
+
32
+ def initialize(capsule, &block)
33
+ @config = @capsule = capsule
34
+ @callback = block
35
+ @down = false
36
+ @done = false
37
+ @job = nil
38
+ @thread = nil
39
+ @reloader = Sidekiq.default_configuration[:reloader]
40
+ @job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(capsule.config)
41
+ @retrier = Sidekiq::JobRetry.new(capsule)
42
+ end
43
+
44
+ def terminate(wait = false)
45
+ @done = true
46
+ return unless @thread
47
+ @thread.value if wait
48
+ end
49
+
50
+ def kill(wait = false)
51
+ @done = true
52
+ return unless @thread
53
+ # unlike the other actors, terminate does not wait
54
+ # for the thread to finish because we don't know how
55
+ # long the job will take to finish. Instead we
56
+ # provide a `kill` method to call after the shutdown
57
+ # timeout passes.
58
+ @thread.raise ::Sidekiq::Shutdown
59
+ @thread.value if wait
60
+ end
61
+
62
+ def stopping?
63
+ @done
64
+ end
65
+
66
+ def start
67
+ @thread ||= safe_thread("#{config.name}/processor", &method(:run))
68
+ end
69
+
70
+ private
71
+
72
+ def run
73
+ # By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
74
+ # instead of the global pool in +Sidekiq::Config#redis_pool+.
75
+ Thread.current[:sidekiq_capsule] = @capsule
76
+
77
+ process_one until @done
78
+ @callback.call(self)
79
+ rescue Sidekiq::Shutdown
80
+ @callback.call(self)
81
+ rescue Exception => ex
82
+ @callback.call(self, ex)
83
+ end
84
+
85
+ def process_one(&block)
86
+ @job = fetch
87
+ process(@job) if @job
88
+ @job = nil
89
+ end
90
+
91
+ def get_one
92
+ uow = capsule.fetcher.retrieve_work
93
+ if @down
94
+ logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
95
+ @down = nil
96
+ end
97
+ uow
98
+ rescue Sidekiq::Shutdown
99
+ rescue => ex
100
+ handle_fetch_exception(ex)
101
+ end
102
+
103
+ def fetch
104
+ j = get_one
105
+ if j && @done
106
+ j.requeue
107
+ nil
108
+ else
109
+ j
110
+ end
111
+ end
112
+
113
+ def handle_fetch_exception(ex)
114
+ unless @down
115
+ @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
116
+ handle_exception(ex)
117
+ end
118
+ sleep(1)
119
+ nil
120
+ end
121
+
122
+ def profile(job, &block)
123
+ return yield unless job["profile"]
124
+ Sidekiq::Profiler.new(config).call(job, &block)
125
+ end
126
+
127
+ def dispatch(job_hash, queue, jobstr)
128
+ # since middleware can mutate the job hash
129
+ # we need to clone it to report the original
130
+ # job structure to the Web UI
131
+ # or to push back to redis when retrying.
132
+ # To avoid costly and, most of the time, useless cloning here,
133
+ # we pass original String of JSON to respected methods
134
+ # to re-parse it there if we need access to the original, untouched job
135
+
136
+ @job_logger.prepare(job_hash) do
137
+ @retrier.global(jobstr, queue) do
138
+ @job_logger.call(job_hash, queue) do
139
+ stats(jobstr, queue) do
140
+ profile(job_hash) do
141
+ # Rails 5 requires a Reloader to wrap code execution. In order to
142
+ # constantize the worker and instantiate an instance, we have to call
143
+ # the Reloader. It handles code loading, db connection management, etc.
144
+ # Effectively this block denotes a "unit of work" to Rails.
145
+ @reloader.call do
146
+ klass = Object.const_get(job_hash["class"])
147
+ instance = klass.new
148
+ instance.jid = job_hash["jid"]
149
+ instance._context = self
150
+ @retrier.local(instance, jobstr, queue) do
151
+ yield instance
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
162
+ private_constant :IGNORE_SHUTDOWN_INTERRUPTS
163
+ ALLOW_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :immediate}
164
+ private_constant :ALLOW_SHUTDOWN_INTERRUPTS
165
+
166
+ def process(uow)
167
+ jobstr = uow.job
168
+ queue = uow.queue_name
169
+
170
+ # Treat malformed JSON as a special case: job goes straight to the morgue.
171
+ job_hash = nil
172
+ begin
173
+ job_hash = Sidekiq.load_json(jobstr)
174
+ rescue => ex
175
+ now = Time.now.to_f
176
+ redis do |conn|
177
+ conn.multi do |xa|
178
+ xa.zadd("dead", now.to_s, jobstr)
179
+ xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
180
+ xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
181
+ end
182
+ end
183
+ handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
184
+ return uow.acknowledge
185
+ end
186
+
187
+ ack = false
188
+ Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
189
+ Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
190
+ dispatch(job_hash, queue, jobstr) do |instance|
191
+ config.server_middleware.invoke(instance, job_hash, queue) do
192
+ execute_job(instance, job_hash["args"])
193
+ end
194
+ end
195
+ ack = true
196
+ rescue Sidekiq::Shutdown
197
+ # Had to force kill this job because it didn't finish
198
+ # within the timeout. Don't acknowledge the work since
199
+ # we didn't properly finish it.
200
+ rescue Sidekiq::JobRetry::Skip => s
201
+ # Skip means we handled this error elsewhere. We don't
202
+ # need to log or report the error.
203
+ ack = true
204
+ raise s
205
+ rescue Sidekiq::JobRetry::Handled => h
206
+ # this is the common case: job raised error and Sidekiq::JobRetry::Handled
207
+ # signals that we created a retry successfully. We can acknowledge the job.
208
+ ack = true
209
+ e = h.cause || h
210
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
211
+ raise e
212
+ rescue Exception => ex
213
+ # Unexpected error! This is very bad and indicates an exception that got past
214
+ # the retry subsystem (e.g. network partition). We won't acknowledge the job
215
+ # so it can be rescued when using Sidekiq Pro.
216
+ handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
217
+ raise ex
218
+ end
219
+ ensure
220
+ if ack
221
+ uow.acknowledge
222
+ end
223
+ end
224
+ end
225
+
226
+ def execute_job(instance, cloned_args)
227
+ instance.perform(*cloned_args)
228
+ end
229
+
230
+ # Ruby doesn't provide atomic counters out of the box so we'll
231
+ # implement something simple ourselves.
232
+ # https://bugs.ruby-lang.org/issues/14706
233
+ class Counter
234
+ def initialize
235
+ @value = 0
236
+ @lock = Mutex.new
237
+ end
238
+
239
+ def incr(amount = 1)
240
+ @lock.synchronize { @value += amount }
241
+ end
242
+
243
+ def reset
244
+ @lock.synchronize {
245
+ val = @value
246
+ @value = 0
247
+ val
248
+ }
249
+ end
250
+ end
251
+
252
+ # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
253
+ class SharedWorkState
254
+ def initialize
255
+ @work_state = {}
256
+ @lock = Mutex.new
257
+ end
258
+
259
+ def set(tid, hash)
260
+ @lock.synchronize { @work_state[tid] = hash }
261
+ end
262
+
263
+ def delete(tid)
264
+ @lock.synchronize { @work_state.delete(tid) }
265
+ end
266
+
267
+ def dup
268
+ @lock.synchronize { @work_state.dup }
269
+ end
270
+
271
+ def size
272
+ @lock.synchronize { @work_state.size }
273
+ end
274
+
275
+ def clear
276
+ @lock.synchronize { @work_state.clear }
277
+ end
278
+ end
279
+
280
+ PROCESSED = Counter.new
281
+ FAILURE = Counter.new
282
+ WORK_STATE = SharedWorkState.new
283
+
284
+ def stats(jobstr, queue)
285
+ WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
286
+
287
+ begin
288
+ yield
289
+ rescue Exception
290
+ FAILURE.incr
291
+ raise
292
+ ensure
293
+ WORK_STATE.delete(tid)
294
+ PROCESSED.incr
295
+ end
296
+ end
297
+ end
298
+ end