puma-status 0.2 → 1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core.rb +60 -11
- data/lib/helpers.rb +14 -6
- data/lib/puma-status.rb +14 -4
- data/lib/stats.rb +33 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ded7bd0b4bca01b8b07ca16c7380453cf96f49d0c00667d024a6317d7307cea0
|
4
|
+
data.tar.gz: a391f836ca1c5d1a40acbab75178fd4c569d25005fac3c83bfd981051e1a7d66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3968446849e0078c9cb557c721a1f5a2226a5ca1533016b319d15ec4c2e769a88062c8f354de28451fad533ba6502707a79660bf9a0469c677482f270ab1c65
|
7
|
+
data.tar.gz: e186aefc643f0ce7bfaf9515d5a36fc71558c0a871d792914e55e5867d5d0d9e4e32d04a71f27429ab1ae19f43054d06ae00c47911f209bcf423c197e4c586b6
|
data/lib/core.rb
CHANGED
@@ -1,13 +1,28 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'json'
|
3
3
|
require 'net_x/http_unix'
|
4
|
+
require 'openssl'
|
4
5
|
require 'time'
|
5
6
|
require_relative 'stats'
|
6
7
|
|
7
8
|
def get_stats(state_file_path)
|
8
9
|
puma_state = YAML.load_file(state_file_path)
|
9
10
|
|
10
|
-
|
11
|
+
uri = URI.parse(puma_state["control_url"])
|
12
|
+
|
13
|
+
address = if uri.scheme =~ /unix/i
|
14
|
+
[uri.scheme, '://', uri.host, uri.path].join
|
15
|
+
else
|
16
|
+
[uri.host, uri.path].join
|
17
|
+
end
|
18
|
+
|
19
|
+
client = NetX::HTTPUnix.new(address, uri.port)
|
20
|
+
|
21
|
+
if uri.scheme =~ /ssl/i
|
22
|
+
client.use_ssl = true
|
23
|
+
client.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV['SSL_NO_VERIFY'] == '1'
|
24
|
+
end
|
25
|
+
|
11
26
|
req = Net::HTTP::Get.new("/stats?token=#{puma_state["control_auth_token"]}")
|
12
27
|
resp = client.request(req)
|
13
28
|
raw_stats = JSON.parse(resp.body)
|
@@ -17,10 +32,27 @@ def get_stats(state_file_path)
|
|
17
32
|
hydrate_stats(stats, puma_state, state_file_path)
|
18
33
|
end
|
19
34
|
|
35
|
+
def get_memory_from_top(raw_memory)
|
36
|
+
raw_memory.tr!(',', '.') # because of LC_NUMERIC separator can be ,
|
37
|
+
|
38
|
+
case raw_memory[-1].downcase
|
39
|
+
when 'g'
|
40
|
+
(raw_memory[0...-1].to_f*1024).to_i
|
41
|
+
when 'm'
|
42
|
+
raw_memory[0...-1].to_i
|
43
|
+
else
|
44
|
+
raw_memory.to_i/1024
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
PID_COLUMN = 0
|
49
|
+
MEM_COLUMN = 5
|
50
|
+
CPU_COLUMN = 8
|
51
|
+
|
20
52
|
def get_top_stats(pids)
|
21
53
|
pids.each_slice(19).inject({}) do |res, pids19|
|
22
54
|
top_result = `top -b -n 1 -p #{pids19.join(',')} | tail -n #{pids19.length}`
|
23
|
-
top_result.split("\n").map { |row| r = row.split(' '); [r[
|
55
|
+
top_result.split("\n").map { |row| r = row.split(' '); [r[PID_COLUMN].to_i, get_memory_from_top(r[MEM_COLUMN]), r[CPU_COLUMN].to_f] }
|
24
56
|
.inject(res) { |hash, row| hash[row[0]] = { mem: row[1], pcpu: row[2] }; hash }
|
25
57
|
res
|
26
58
|
end
|
@@ -36,22 +68,39 @@ def hydrate_stats(stats, puma_state, state_file_path)
|
|
36
68
|
|
37
69
|
stats.tap do |s|
|
38
70
|
stats.workers.map do |wstats|
|
39
|
-
wstats.mem = top_stats
|
40
|
-
wstats.pcpu = top_stats
|
71
|
+
wstats.mem = top_stats.dig(wstats.pid, :mem) || 0
|
72
|
+
wstats.pcpu = top_stats.dig(wstats.pid, :pcpu) || 0
|
73
|
+
wstats.killed = !top_stats.key?(wstats.pid) || (wstats.mem <=0 && wstats.pcpu <= 0)
|
41
74
|
end
|
42
75
|
end
|
43
76
|
end
|
44
77
|
|
45
78
|
def format_stats(stats)
|
46
|
-
master_line = "#{stats.pid} (#{stats.state_file_path}) Uptime: #{seconds_to_human(stats.uptime)}
|
47
|
-
master_line += "| Phase: #{stats.phase}
|
48
|
-
|
79
|
+
master_line = "#{stats.pid} (#{stats.state_file_path}) Uptime: #{seconds_to_human(stats.uptime)}"
|
80
|
+
master_line += " | Phase: #{stats.phase}" if stats.phase
|
81
|
+
|
82
|
+
if stats.booting?
|
83
|
+
master_line += " #{warn("booting")}"
|
84
|
+
else
|
85
|
+
master_line += " | Load: #{color(75, 50, stats.load, asciiThreadLoad(stats.running_threads, stats.spawned_threads, stats.max_threads))}"
|
86
|
+
master_line += " | Req: #{stats.requests_count}" if stats.requests_count
|
87
|
+
end
|
49
88
|
|
50
89
|
output = [master_line] + stats.workers.map do |wstats|
|
51
|
-
worker_line = " └ #{wstats.pid.to_s.rjust(5, ' ')} CPU: #{color(75, 50, wstats.pcpu, wstats.pcpu.to_s.rjust(5, ' '))}% Mem: #{color(1000, 750, wstats.mem, wstats.mem.to_s.rjust(4, ' '))} MB Uptime: #{seconds_to_human(wstats.uptime)}
|
52
|
-
|
53
|
-
|
54
|
-
|
90
|
+
worker_line = " └ #{wstats.pid.to_s.rjust(5, ' ')} CPU: #{color(75, 50, wstats.pcpu, wstats.pcpu.to_s.rjust(5, ' '))}% Mem: #{color(1000, 750, wstats.mem, wstats.mem.to_s.rjust(4, ' '))} MB Uptime: #{seconds_to_human(wstats.uptime)}"
|
91
|
+
|
92
|
+
if wstats.booting?
|
93
|
+
worker_line += " #{warn("booting")}"
|
94
|
+
elsif wstats.killed?
|
95
|
+
worker_line += " #{error("killed")}"
|
96
|
+
else
|
97
|
+
worker_line += " | Load: #{color(75, 50, wstats.load, asciiThreadLoad(wstats.running_threads, wstats.spawned_threads, wstats.max_threads))}"
|
98
|
+
worker_line += " | Phase: #{error(wstats.phase)}" if wstats.phase != stats.phase
|
99
|
+
worker_line += " | Req: #{wstats.requests_count}" if wstats.requests_count
|
100
|
+
worker_line += " Queue: #{error(wstats.backlog.to_s)}" if wstats.backlog > 0
|
101
|
+
worker_line += " Last checkin: #{error(wstats.last_checkin)}" if wstats.last_checkin >= 10
|
102
|
+
end
|
103
|
+
|
55
104
|
worker_line
|
56
105
|
end
|
57
106
|
|
data/lib/helpers.rb
CHANGED
@@ -17,7 +17,8 @@ def colorize(str, color_name)
|
|
17
17
|
str.to_s.colorize(color_name)
|
18
18
|
end
|
19
19
|
|
20
|
-
def color(critical, warn, value, str)
|
20
|
+
def color(critical, warn, value, str = nil)
|
21
|
+
str = value unless str
|
21
22
|
color_level = if value >= critical
|
22
23
|
:red
|
23
24
|
elsif value < critical && value >= warn
|
@@ -28,11 +29,16 @@ def color(critical, warn, value, str)
|
|
28
29
|
colorize(str, color_level)
|
29
30
|
end
|
30
31
|
|
31
|
-
def asciiThreadLoad(
|
32
|
+
def asciiThreadLoad(running, spawned, total)
|
32
33
|
full = "█"
|
33
|
-
|
34
|
+
half= "░"
|
35
|
+
empty = " "
|
34
36
|
|
35
|
-
|
37
|
+
full_count = running
|
38
|
+
half_count = [spawned - running, 0].max
|
39
|
+
empty_count = total - half_count - full_count
|
40
|
+
|
41
|
+
"#{running}[#{full*full_count}#{half*half_count}#{empty*empty_count}]#{total}"
|
36
42
|
end
|
37
43
|
|
38
44
|
def seconds_to_human(seconds)
|
@@ -43,8 +49,10 @@ def seconds_to_human(seconds)
|
|
43
49
|
#=> 23h59m
|
44
50
|
#=> 1d 0h
|
45
51
|
#=> 24d
|
46
|
-
|
47
|
-
if seconds
|
52
|
+
|
53
|
+
if seconds <= 0
|
54
|
+
"--m--s"
|
55
|
+
elsif seconds < 60*60
|
48
56
|
"#{(seconds/60).to_s.rjust(2, ' ')}m#{(seconds%60).to_s.rjust(2, ' ')}s"
|
49
57
|
elsif seconds >= 60*60*1 && seconds < 60*60*24
|
50
58
|
"#{(seconds/(60*60*1)).to_s.rjust(2, ' ')}h#{((seconds%(60*60*1))/60).to_s.rjust(2, ' ')}m"
|
data/lib/puma-status.rb
CHANGED
@@ -17,11 +17,21 @@ def run
|
|
17
17
|
begin
|
18
18
|
debug "State file: #{state_file_path}"
|
19
19
|
format_stats(get_stats(state_file_path))
|
20
|
-
rescue Errno::ENOENT
|
21
|
-
|
20
|
+
rescue Errno::ENOENT => e
|
21
|
+
if e.message =~ /#{state_file_path}/
|
22
|
+
errors << "#{warn(state_file_path)} doesn't exists"
|
23
|
+
elsif e.message =~ /connect\(2\) for [^\/]/
|
24
|
+
errors << "#{warn("Relative Unix socket")}: the Unix socket of the control app has a relative path. Please, ensure you are running from the same folder has puma."
|
25
|
+
else
|
26
|
+
errors << "#{error(state_file_path)} an unhandled error occured: #{e.inspect}"
|
27
|
+
end
|
22
28
|
nil
|
23
|
-
rescue Errno::EISDIR
|
24
|
-
|
29
|
+
rescue Errno::EISDIR => e
|
30
|
+
if e.message =~ /#{state_file_path}/
|
31
|
+
errors << "#{warn(state_file_path)} isn't a state file"
|
32
|
+
else
|
33
|
+
errors << "#{error(state_file_path)} an unhandled error occured: #{e.inspect}"
|
34
|
+
end
|
25
35
|
nil
|
26
36
|
rescue => e
|
27
37
|
errors << "#{error(state_file_path)} an unhandled error occured: #{e.inspect}"
|
data/lib/stats.rb
CHANGED
@@ -9,6 +9,14 @@ class Stats
|
|
9
9
|
@wstats['pid']
|
10
10
|
end
|
11
11
|
|
12
|
+
def killed=(killed)
|
13
|
+
@wstats['killed'] = killed
|
14
|
+
end
|
15
|
+
|
16
|
+
def killed?
|
17
|
+
!!@wstats['killed']
|
18
|
+
end
|
19
|
+
|
12
20
|
def mem=(mem)
|
13
21
|
@wstats['mem'] = mem
|
14
22
|
end
|
@@ -25,14 +33,20 @@ class Stats
|
|
25
33
|
@wstats['pcpu']
|
26
34
|
end
|
27
35
|
|
36
|
+
def booting?
|
37
|
+
@wstats.key?('last_status') && @wstats['last_status'].empty?
|
38
|
+
end
|
39
|
+
|
28
40
|
def running
|
29
41
|
@wstats.dig('last_status', 'running') || @wstats['running'] || 0
|
30
42
|
end
|
31
43
|
alias :total_threads :running
|
44
|
+
alias :spawned_threads :running
|
32
45
|
|
33
46
|
def max_threads
|
34
47
|
@wstats.dig('last_status', 'max_threads') || @wstats['max_threads'] || 0
|
35
48
|
end
|
49
|
+
alias :total_threads :max_threads
|
36
50
|
|
37
51
|
def pool_capacity
|
38
52
|
@wstats.dig('last_status', 'pool_capacity') || @wstats['pool_capacity'] || 0
|
@@ -55,6 +69,10 @@ class Stats
|
|
55
69
|
(Time.now - Time.parse(@wstats['started_at'])).to_i
|
56
70
|
end
|
57
71
|
|
72
|
+
def requests_count
|
73
|
+
@wstats.dig('last_status', 'requests_count') || @wstats['requests_count']
|
74
|
+
end
|
75
|
+
|
58
76
|
def backlog
|
59
77
|
@wstats.dig('last_status', 'backlog') || 0
|
60
78
|
end
|
@@ -71,7 +89,7 @@ class Stats
|
|
71
89
|
end
|
72
90
|
|
73
91
|
def workers
|
74
|
-
(@stats['worker_status'] || [@stats]).map { |wstats| Worker.new(wstats) }
|
92
|
+
@workers ||= (@stats['worker_status'] || [@stats]).map { |wstats| Worker.new(wstats) }
|
75
93
|
end
|
76
94
|
|
77
95
|
def pid=(pid)
|
@@ -95,6 +113,10 @@ class Stats
|
|
95
113
|
(Time.now - Time.parse(@stats['started_at'])).to_i
|
96
114
|
end
|
97
115
|
|
116
|
+
def booting?
|
117
|
+
workers.all?(&:booting?)
|
118
|
+
end
|
119
|
+
|
98
120
|
def total_threads
|
99
121
|
workers.reduce(0) { |total, wstats| total + wstats.max_threads }
|
100
122
|
end
|
@@ -103,10 +125,20 @@ class Stats
|
|
103
125
|
workers.reduce(0) { |total, wstats| total + wstats.running_threads }
|
104
126
|
end
|
105
127
|
|
128
|
+
def spawned_threads
|
129
|
+
workers.reduce(0) { |total, wstats| total + wstats.spawned_threads }
|
130
|
+
end
|
131
|
+
|
106
132
|
def max_threads
|
107
133
|
workers.reduce(0) { |total, wstats| total + wstats.max_threads }
|
108
134
|
end
|
109
135
|
|
136
|
+
def requests_count
|
137
|
+
workers_with_requests_count = workers.select(&:requests_count)
|
138
|
+
return if workers_with_requests_count.none?
|
139
|
+
workers_with_requests_count.reduce(0) { |total, wstats| total + wstats.requests_count }
|
140
|
+
end
|
141
|
+
|
110
142
|
def running
|
111
143
|
@stats['running'] || 0
|
112
144
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma-status
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '1.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yoann Lecuyer
|
@@ -126,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
126
|
- !ruby/object:Gem::Version
|
127
127
|
version: '0'
|
128
128
|
requirements: []
|
129
|
-
rubygems_version: 3.
|
129
|
+
rubygems_version: 3.3.0.dev
|
130
130
|
signing_key:
|
131
131
|
specification_version: 4
|
132
132
|
summary: Command-line tool for puma to display information about running request/process
|