sidekiq 6.1.2 → 6.2.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +59 -1
- data/LICENSE +1 -1
- data/lib/sidekiq/api.rb +95 -57
- data/lib/sidekiq/cli.rb +14 -1
- data/lib/sidekiq/client.rb +1 -5
- data/lib/sidekiq/extensions/generic_proxy.rb +3 -1
- data/lib/sidekiq/fetch.rb +9 -1
- data/lib/sidekiq/job.rb +8 -0
- data/lib/sidekiq/job_logger.rb +1 -1
- data/lib/sidekiq/job_retry.rb +4 -7
- data/lib/sidekiq/launcher.rb +71 -18
- data/lib/sidekiq/logger.rb +3 -2
- data/lib/sidekiq/middleware/chain.rb +5 -3
- data/lib/sidekiq/scheduled.rb +7 -1
- data/lib/sidekiq/testing.rb +1 -3
- data/lib/sidekiq/util.rb +28 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -2
- data/lib/sidekiq/web/application.rb +14 -6
- data/lib/sidekiq/web/csrf_protection.rb +28 -6
- data/lib/sidekiq/web/helpers.rb +31 -11
- data/lib/sidekiq/web/router.rb +4 -1
- data/lib/sidekiq/web.rb +34 -78
- data/sidekiq.gemspec +10 -2
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/stylesheets/application-dark.css +18 -14
- data/web/assets/stylesheets/application.css +29 -130
- data/web/locales/ar.yml +8 -2
- data/web/locales/en.yml +3 -0
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +8 -1
- data/web/locales/ja.yml +3 -0
- data/web/locales/lt.yml +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/busy.erb +48 -17
- data/web/views/dashboard.erb +14 -6
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +1 -0
- data/web/views/morgue.erb +6 -6
- data/web/views/queue.erb +1 -1
- data/web/views/queues.erb +3 -3
- data/web/views/retries.erb +7 -7
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +1 -1
- metadata +12 -26
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
- data/.github/contributing.md +0 -32
- data/.github/workflows/ci.yml +0 -41
- data/.gitignore +0 -13
- data/.standard.yml +0 -20
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/6.0-Upgrade.md +0 -72
- data/COMM-LICENSE +0 -97
- data/Ent-2.0-Upgrade.md +0 -37
- data/Ent-Changes.md +0 -281
- data/Gemfile +0 -24
- data/Gemfile.lock +0 -192
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-4.0-Upgrade.md +0 -35
- data/Pro-5.0-Upgrade.md +0 -25
- data/Pro-Changes.md +0 -805
- data/Rakefile +0 -10
- data/code_of_conduct.md +0 -50
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -38,7 +38,7 @@ module Sidekiq
|
|
38
38
|
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
|
39
39
|
# attribute to expose the underlying thing.
|
40
40
|
h = {
|
41
|
-
class: job_hash["wrapped"] || job_hash["class"],
|
41
|
+
class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"],
|
42
42
|
jid: job_hash["jid"]
|
43
43
|
}
|
44
44
|
h[:bid] = job_hash["bid"] if job_hash["bid"]
|
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -61,6 +61,7 @@ module Sidekiq
|
|
61
61
|
#
|
62
62
|
class JobRetry
|
63
63
|
class Handled < ::RuntimeError; end
|
64
|
+
|
64
65
|
class Skip < Handled; end
|
65
66
|
|
66
67
|
include Sidekiq::Util
|
@@ -213,16 +214,12 @@ module Sidekiq
|
|
213
214
|
end
|
214
215
|
|
215
216
|
def delay_for(worker, count, exception)
|
217
|
+
jitter = rand(10) * (count + 1)
|
216
218
|
if worker&.sidekiq_retry_in_block
|
217
219
|
custom_retry_in = retry_in(worker, count, exception).to_i
|
218
|
-
return custom_retry_in if custom_retry_in > 0
|
220
|
+
return custom_retry_in + jitter if custom_retry_in > 0
|
219
221
|
end
|
220
|
-
|
221
|
-
end
|
222
|
-
|
223
|
-
# delayed_job uses the same basic formula
|
224
|
-
def seconds_to_delay(count)
|
225
|
-
(count**4) + 15 + (rand(30) * (count + 1))
|
222
|
+
(count**4) + 15 + jitter
|
226
223
|
end
|
227
224
|
|
228
225
|
def retry_in(worker, count, exception)
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -153,13 +153,21 @@ module Sidekiq
|
|
153
153
|
end
|
154
154
|
end
|
155
155
|
|
156
|
+
rtt = check_rtt
|
157
|
+
|
156
158
|
fails = procd = 0
|
159
|
+
kb = memory_usage(::Process.pid)
|
157
160
|
|
158
161
|
_, exists, _, _, msg = Sidekiq.redis { |conn|
|
159
162
|
conn.multi {
|
160
163
|
conn.sadd("processes", key)
|
161
164
|
conn.exists?(key)
|
162
|
-
conn.hmset(key, "info", to_json,
|
165
|
+
conn.hmset(key, "info", to_json,
|
166
|
+
"busy", curstate.size,
|
167
|
+
"beat", Time.now.to_f,
|
168
|
+
"rtt_us", rtt,
|
169
|
+
"quiet", @done,
|
170
|
+
"rss", kb)
|
163
171
|
conn.expire(key, 60)
|
164
172
|
conn.rpop("#{key}-signals")
|
165
173
|
}
|
@@ -180,27 +188,72 @@ module Sidekiq
|
|
180
188
|
end
|
181
189
|
end
|
182
190
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
191
|
+
# We run the heartbeat every five seconds.
|
192
|
+
# Capture five samples of RTT, log a warning if each sample
|
193
|
+
# is above our warning threshold.
|
194
|
+
RTT_READINGS = RingBuffer.new(5)
|
195
|
+
RTT_WARNING_LEVEL = 50_000
|
196
|
+
|
197
|
+
def check_rtt
|
198
|
+
a = b = 0
|
199
|
+
Sidekiq.redis do |x|
|
200
|
+
a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
201
|
+
x.ping
|
202
|
+
b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
203
|
+
end
|
204
|
+
rtt = b - a
|
205
|
+
RTT_READINGS << rtt
|
206
|
+
# Ideal RTT for Redis is < 1000µs
|
207
|
+
# Workable is < 10,000µs
|
208
|
+
# Log a warning if it's a disaster.
|
209
|
+
if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
|
210
|
+
Sidekiq.logger.warn <<~EOM
|
211
|
+
Your Redis network connection is performing extremely poorly.
|
212
|
+
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
213
|
+
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
214
|
+
EOM
|
215
|
+
RTT_READINGS.reset
|
195
216
|
end
|
217
|
+
rtt
|
218
|
+
end
|
219
|
+
|
220
|
+
MEMORY_GRABBER = case RUBY_PLATFORM
|
221
|
+
when /linux/
|
222
|
+
->(pid) {
|
223
|
+
IO.readlines("/proc/#{$$}/status").each do |line|
|
224
|
+
next unless line.start_with?("VmRSS:")
|
225
|
+
break line.split[1].to_i
|
226
|
+
end
|
227
|
+
}
|
228
|
+
when /darwin|bsd/
|
229
|
+
->(pid) {
|
230
|
+
`ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
|
231
|
+
}
|
232
|
+
else
|
233
|
+
->(pid) { 0 }
|
234
|
+
end
|
235
|
+
|
236
|
+
def memory_usage(pid)
|
237
|
+
MEMORY_GRABBER.call(pid)
|
238
|
+
end
|
239
|
+
|
240
|
+
def to_data
|
241
|
+
@data ||= {
|
242
|
+
"hostname" => hostname,
|
243
|
+
"started_at" => Time.now.to_f,
|
244
|
+
"pid" => ::Process.pid,
|
245
|
+
"tag" => @options[:tag] || "",
|
246
|
+
"concurrency" => @options[:concurrency],
|
247
|
+
"queues" => @options[:queues].uniq,
|
248
|
+
"labels" => @options[:labels],
|
249
|
+
"identity" => identity
|
250
|
+
}
|
196
251
|
end
|
197
252
|
|
198
253
|
def to_json
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
Sidekiq.dump_json(to_data)
|
203
|
-
end
|
254
|
+
# this data changes infrequently so dump it to a string
|
255
|
+
# now so we don't need to dump it every heartbeat.
|
256
|
+
@json ||= Sidekiq.dump_json(to_data)
|
204
257
|
end
|
205
258
|
end
|
206
259
|
end
|
data/lib/sidekiq/logger.rb
CHANGED
@@ -6,10 +6,11 @@ require "time"
|
|
6
6
|
module Sidekiq
|
7
7
|
module Context
|
8
8
|
def self.with(hash)
|
9
|
+
orig_context = current.dup
|
9
10
|
current.merge!(hash)
|
10
11
|
yield
|
11
12
|
ensure
|
12
|
-
|
13
|
+
Thread.current[:sidekiq_context] = orig_context
|
13
14
|
end
|
14
15
|
|
15
16
|
def self.current
|
@@ -89,7 +90,7 @@ module Sidekiq
|
|
89
90
|
return true if @logdev.nil? || severity < level
|
90
91
|
|
91
92
|
if message.nil?
|
92
|
-
if
|
93
|
+
if block
|
93
94
|
message = yield
|
94
95
|
else
|
95
96
|
message = progname
|
@@ -90,12 +90,12 @@ module Sidekiq
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def add(klass, *args)
|
93
|
-
remove(klass)
|
93
|
+
remove(klass)
|
94
94
|
entries << Entry.new(klass, *args)
|
95
95
|
end
|
96
96
|
|
97
97
|
def prepend(klass, *args)
|
98
|
-
remove(klass)
|
98
|
+
remove(klass)
|
99
99
|
entries.insert(0, Entry.new(klass, *args))
|
100
100
|
end
|
101
101
|
|
@@ -132,7 +132,7 @@ module Sidekiq
|
|
132
132
|
def invoke(*args)
|
133
133
|
return yield if empty?
|
134
134
|
|
135
|
-
chain = retrieve
|
135
|
+
chain = retrieve
|
136
136
|
traverse_chain = proc do
|
137
137
|
if chain.empty?
|
138
138
|
yield
|
@@ -144,6 +144,8 @@ module Sidekiq
|
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
+
private
|
148
|
+
|
147
149
|
class Entry
|
148
150
|
attr_reader :klass
|
149
151
|
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -49,6 +49,7 @@ module Sidekiq
|
|
49
49
|
@sleeper = ConnectionPool::TimedStack.new
|
50
50
|
@done = false
|
51
51
|
@thread = nil
|
52
|
+
@count_calls = 0
|
52
53
|
end
|
53
54
|
|
54
55
|
# Shut down this instance, will pause until the thread is dead.
|
@@ -152,8 +153,13 @@ module Sidekiq
|
|
152
153
|
end
|
153
154
|
|
154
155
|
def process_count
|
155
|
-
|
156
|
+
# The work buried within Sidekiq::ProcessSet#cleanup can be
|
157
|
+
# expensive at scale. Cut it down by 90% with this counter.
|
158
|
+
# NB: This method is only called by the scheduler thread so we
|
159
|
+
# don't need to worry about the thread safety of +=.
|
160
|
+
pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
|
156
161
|
pcount = 1 if pcount == 0
|
162
|
+
@count_calls += 1
|
157
163
|
pcount
|
158
164
|
end
|
159
165
|
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -338,7 +338,5 @@ module Sidekiq
|
|
338
338
|
end
|
339
339
|
|
340
340
|
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
|
341
|
-
|
342
|
-
puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
|
343
|
-
puts("**************************************************")
|
341
|
+
warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
|
344
342
|
end
|
data/lib/sidekiq/util.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "forwardable"
|
3
4
|
require "socket"
|
4
5
|
require "securerandom"
|
5
6
|
require "sidekiq/exception_handler"
|
@@ -8,6 +9,33 @@ module Sidekiq
|
|
8
9
|
##
|
9
10
|
# This module is part of Sidekiq core and not intended for extensions.
|
10
11
|
#
|
12
|
+
|
13
|
+
class RingBuffer
|
14
|
+
include Enumerable
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :@buf, :[], :each, :size
|
17
|
+
|
18
|
+
def initialize(size, default = 0)
|
19
|
+
@size = size
|
20
|
+
@buf = Array.new(size, default)
|
21
|
+
@index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(element)
|
25
|
+
@buf[@index % @size] = element
|
26
|
+
@index += 1
|
27
|
+
element
|
28
|
+
end
|
29
|
+
|
30
|
+
def buffer
|
31
|
+
@buf
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset(default = 0)
|
35
|
+
@buf.fill(default)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
11
39
|
module Util
|
12
40
|
include ExceptionHandler
|
13
41
|
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -15,7 +15,7 @@ module Sidekiq
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def halt(res)
|
18
|
-
throw :halt, res
|
18
|
+
throw :halt, [res, {"Content-Type" => "text/plain"}, [res.to_s]]
|
19
19
|
end
|
20
20
|
|
21
21
|
def redirect(location)
|
@@ -68,7 +68,7 @@ module Sidekiq
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def json(payload)
|
71
|
-
[200, {"Content-Type" => "application/json", "Cache-Control" => "no-
|
71
|
+
[200, {"Content-Type" => "application/json", "Cache-Control" => "private, no-store"}, [Sidekiq.dump_json(payload)]]
|
72
72
|
end
|
73
73
|
|
74
74
|
def initialize(env, block)
|
@@ -4,7 +4,6 @@ module Sidekiq
|
|
4
4
|
class WebApplication
|
5
5
|
extend WebRouter
|
6
6
|
|
7
|
-
CONTENT_LENGTH = "Content-Length"
|
8
7
|
REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human]
|
9
8
|
CSP_HEADER = [
|
10
9
|
"default-src 'self' https: http:",
|
@@ -42,6 +41,13 @@ module Sidekiq
|
|
42
41
|
# nothing, backwards compatibility
|
43
42
|
end
|
44
43
|
|
44
|
+
head "/" do
|
45
|
+
# HEAD / is the cheapest heartbeat possible,
|
46
|
+
# it hits Redis to ensure connectivity
|
47
|
+
Sidekiq.redis { |c| c.llen("queue:default") }
|
48
|
+
""
|
49
|
+
end
|
50
|
+
|
45
51
|
get "/" do
|
46
52
|
@redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k }
|
47
53
|
stats_history = Sidekiq::Stats::History.new((params["days"] || 30).to_i)
|
@@ -76,15 +82,17 @@ module Sidekiq
|
|
76
82
|
erb(:queues)
|
77
83
|
end
|
78
84
|
|
85
|
+
QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i
|
86
|
+
|
79
87
|
get "/queues/:name" do
|
80
88
|
@name = route_params[:name]
|
81
89
|
|
82
|
-
halt(404)
|
90
|
+
halt(404) if !@name || @name !~ QUEUE_NAME
|
83
91
|
|
84
92
|
@count = (params["count"] || 25).to_i
|
85
93
|
@queue = Sidekiq::Queue.new(@name)
|
86
94
|
(@current_page, @total_size, @messages) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc")
|
87
|
-
@messages = @messages.map { |msg| Sidekiq::
|
95
|
+
@messages = @messages.map { |msg| Sidekiq::JobRecord.new(msg, @name) }
|
88
96
|
|
89
97
|
erb(:queue)
|
90
98
|
end
|
@@ -105,7 +113,7 @@ module Sidekiq
|
|
105
113
|
|
106
114
|
post "/queues/:name/delete" do
|
107
115
|
name = route_params[:name]
|
108
|
-
Sidekiq::
|
116
|
+
Sidekiq::JobRecord.new(params["key_val"], name).delete
|
109
117
|
|
110
118
|
redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
|
111
119
|
end
|
@@ -306,7 +314,7 @@ module Sidekiq
|
|
306
314
|
# rendered content goes here
|
307
315
|
headers = {
|
308
316
|
"Content-Type" => "text/html",
|
309
|
-
"Cache-Control" => "no-
|
317
|
+
"Cache-Control" => "private, no-store",
|
310
318
|
"Content-Language" => action.locale,
|
311
319
|
"Content-Security-Policy" => CSP_HEADER
|
312
320
|
}
|
@@ -316,7 +324,7 @@ module Sidekiq
|
|
316
324
|
end
|
317
325
|
|
318
326
|
def self.helpers(mod = nil, &block)
|
319
|
-
if
|
327
|
+
if block
|
320
328
|
WebAction.class_eval(&block)
|
321
329
|
else
|
322
330
|
WebAction.send(:include, mod)
|
@@ -66,7 +66,31 @@ module Sidekiq
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def session(env)
|
69
|
-
env["rack.session"] || fail(
|
69
|
+
env["rack.session"] || fail(<<~EOM)
|
70
|
+
Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
|
71
|
+
make sure you mount Sidekiq::Web *inside* your application routes:
|
72
|
+
|
73
|
+
|
74
|
+
Rails.application.routes.draw do
|
75
|
+
mount Sidekiq::Web => "/sidekiq"
|
76
|
+
....
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
If this is a Rails app in API mode, you need to enable sessions.
|
81
|
+
|
82
|
+
https://guides.rubyonrails.org/api_app.html#using-session-middlewares
|
83
|
+
|
84
|
+
If this is a bare Rack app, use a session middleware before Sidekiq::Web:
|
85
|
+
|
86
|
+
# first, use IRB to create a shared secret key for sessions and commit it
|
87
|
+
require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
|
88
|
+
|
89
|
+
# now use the secret with a session cookie middleware
|
90
|
+
use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
|
91
|
+
run Sidekiq::Web
|
92
|
+
|
93
|
+
EOM
|
70
94
|
end
|
71
95
|
|
72
96
|
def accept?(env)
|
@@ -90,13 +114,11 @@ module Sidekiq
|
|
90
114
|
end
|
91
115
|
|
92
116
|
sess = session(env)
|
93
|
-
|
94
|
-
# Checks that Rack::Session::Cookie did not return empty session
|
95
|
-
# object in case the digest verification failed
|
96
|
-
return false if sess.empty?
|
97
|
-
|
98
117
|
localtoken = sess[:csrf]
|
99
118
|
|
119
|
+
# Checks that Rack::Session::Cookie actualy contains the csrf toekn
|
120
|
+
return false if localtoken.nil?
|
121
|
+
|
100
122
|
# Rotate the session token after every use
|
101
123
|
sess[:csrf] = SecureRandom.base64(TOKEN_LENGTH)
|
102
124
|
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -10,18 +10,25 @@ module Sidekiq
|
|
10
10
|
module WebHelpers
|
11
11
|
def strings(lang)
|
12
12
|
@strings ||= {}
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
13
|
+
|
14
|
+
# Allow sidekiq-web extensions to add locale paths
|
15
|
+
# so extensions can be localized
|
16
|
+
@strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
|
17
|
+
find_locale_files(lang).each do |file|
|
18
|
+
strs = YAML.load(File.open(file))
|
19
|
+
global.merge!(strs[lang])
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
24
|
+
def singularize(str, count)
|
25
|
+
if count == 1 && str.respond_to?(:singularize) # rails
|
26
|
+
str.singularize
|
27
|
+
else
|
28
|
+
str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
25
32
|
def clear_caches
|
26
33
|
@strings = nil
|
27
34
|
@locale_files = nil
|
@@ -118,7 +125,7 @@ module Sidekiq
|
|
118
125
|
# within is used by Sidekiq Pro
|
119
126
|
def display_tags(job, within = nil)
|
120
127
|
job.tags.map { |tag|
|
121
|
-
"<span class='
|
128
|
+
"<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
|
122
129
|
}.join(" ")
|
123
130
|
end
|
124
131
|
|
@@ -158,8 +165,7 @@ module Sidekiq
|
|
158
165
|
|
159
166
|
def redis_connection
|
160
167
|
Sidekiq.redis do |conn|
|
161
|
-
|
162
|
-
"redis://#{c[:location]}/#{c[:db]}"
|
168
|
+
conn.connection[:id]
|
163
169
|
end
|
164
170
|
end
|
165
171
|
|
@@ -258,7 +264,21 @@ module Sidekiq
|
|
258
264
|
end
|
259
265
|
end
|
260
266
|
|
267
|
+
def format_memory(rss_kb)
|
268
|
+
return "0" if rss_kb.nil? || rss_kb == 0
|
269
|
+
|
270
|
+
if rss_kb < 100_000
|
271
|
+
"#{number_with_delimiter(rss_kb)} KB"
|
272
|
+
elsif rss_kb < 10_000_000
|
273
|
+
"#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
|
274
|
+
else
|
275
|
+
"#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)).round(1))} GB"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
261
279
|
def number_with_delimiter(number)
|
280
|
+
return "" if number.nil?
|
281
|
+
|
262
282
|
begin
|
263
283
|
Float(number)
|
264
284
|
rescue ArgumentError, TypeError
|