puma-status 0.1.5 → 1.2
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.
- checksums.yaml +4 -4
- data/lib/core.rb +63 -15
- data/lib/helpers.rb +19 -5
- data/lib/puma-status.rb +34 -3
- data/lib/stats.rb +27 -1
- metadata +22 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec73cc842d7b353e3f3994d554c4fdc6b1b7eab51452c578581d855610af613d
|
4
|
+
data.tar.gz: 6d44df05c3a303e3440406146921f4955a85fbded8ae131b29f1fec83f56b8fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab70d17e5b5c35bacce07819392f2818f62020287d7070618af64fa58e1da9f71fe7de126f60d2886a29c2d13d6e31698f071784f6f98c9c5773a7ee934e16b6
|
7
|
+
data.tar.gz: 41a1df505a6bb1299973a8972522b700d16b9d40232f567e237b20ce637de89733a72e7eae4944dcd0950c0c7b13a755353fbca601da55dc4d56167b5b2f8a66
|
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,25 +68,41 @@ 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
|
-
def
|
46
|
-
master_line = "#{stats.pid} (#{stats.state_file_path}) Uptime: #{seconds_to_human(stats.uptime)}
|
47
|
-
master_line += "| Phase: #{stats.phase}
|
48
|
-
master_line += "| Load: #{color(75, 50, stats.load, asciiThreadLoad(stats.running_threads, stats.max_threads))}"
|
78
|
+
def format_stats(stats)
|
79
|
+
master_line = "#{stats.pid} (#{stats.state_file_path}) Uptime: #{seconds_to_human(stats.uptime)}"
|
80
|
+
master_line += " | Phase: #{stats.phase}" if stats.phase
|
49
81
|
|
50
|
-
|
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.max_threads))}"
|
86
|
+
master_line += " | Req: #{stats.requests_count}" if stats.requests_count
|
87
|
+
end
|
51
88
|
|
52
|
-
stats.workers.
|
53
|
-
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)}
|
54
|
-
worker_line += " #{("Queue: " + wstats.backlog.to_s).colorize(:red)}" if wstats.backlog > 0
|
55
|
-
worker_line += " Last checkin: #{wstats.last_checkin}" if wstats.last_checkin >= 10
|
56
|
-
worker_line += " Phase: #{wstats.phase}" if wstats.phase != stats.phase
|
89
|
+
output = [master_line] + stats.workers.map do |wstats|
|
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)}"
|
57
91
|
|
58
|
-
|
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.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
|
+
|
104
|
+
worker_line
|
59
105
|
end
|
106
|
+
|
107
|
+
output.join("\n")
|
60
108
|
end
|
data/lib/helpers.rb
CHANGED
@@ -4,17 +4,29 @@ def debug(str)
|
|
4
4
|
puts str if ENV.key?('DEBUG')
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
7
|
+
def warn(str)
|
8
|
+
colorize(str, :yellow)
|
9
|
+
end
|
10
|
+
|
11
|
+
def error(str)
|
12
|
+
colorize(str, :red)
|
13
|
+
end
|
14
|
+
|
15
|
+
def colorize(str, color_name)
|
8
16
|
return str if ENV.key?('NO_COLOR')
|
17
|
+
str.to_s.colorize(color_name)
|
18
|
+
end
|
9
19
|
|
10
|
-
|
20
|
+
def color(critical, warn, value, str = nil)
|
21
|
+
str = value unless str
|
22
|
+
color_level = if value >= critical
|
11
23
|
:red
|
12
24
|
elsif value < critical && value >= warn
|
13
25
|
:yellow
|
14
26
|
else
|
15
27
|
:green
|
16
28
|
end
|
17
|
-
|
29
|
+
colorize(str, color_level)
|
18
30
|
end
|
19
31
|
|
20
32
|
def asciiThreadLoad(idx, total)
|
@@ -32,8 +44,10 @@ def seconds_to_human(seconds)
|
|
32
44
|
#=> 23h59m
|
33
45
|
#=> 1d 0h
|
34
46
|
#=> 24d
|
35
|
-
|
36
|
-
if seconds
|
47
|
+
|
48
|
+
if seconds <= 0
|
49
|
+
"--m--s"
|
50
|
+
elsif seconds < 60*60
|
37
51
|
"#{(seconds/60).to_s.rjust(2, ' ')}m#{(seconds%60).to_s.rjust(2, ' ')}s"
|
38
52
|
elsif seconds >= 60*60*1 && seconds < 60*60*24
|
39
53
|
"#{(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
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative './helpers'
|
2
2
|
require_relative './core.rb'
|
3
|
+
require 'parallel'
|
3
4
|
|
4
5
|
def run
|
5
6
|
debug "puma-status"
|
@@ -10,8 +11,38 @@ def run
|
|
10
11
|
exit -1
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
errors = []
|
15
|
+
|
16
|
+
outputs = Parallel.map(ARGV, in_threads: ARGV.count) do |state_file_path|
|
17
|
+
begin
|
18
|
+
debug "State file: #{state_file_path}"
|
19
|
+
format_stats(get_stats(state_file_path))
|
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
|
28
|
+
nil
|
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
|
35
|
+
nil
|
36
|
+
rescue => e
|
37
|
+
errors << "#{error(state_file_path)} an unhandled error occured: #{e.inspect}"
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
outputs.compact.each { |output| puts output }
|
43
|
+
|
44
|
+
if errors.any?
|
45
|
+
puts ""
|
46
|
+
errors.each { |error| puts error }
|
16
47
|
end
|
17
48
|
end
|
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,6 +33,10 @@ 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
|
@@ -55,6 +67,10 @@ class Stats
|
|
55
67
|
(Time.now - Time.parse(@wstats['started_at'])).to_i
|
56
68
|
end
|
57
69
|
|
70
|
+
def requests_count
|
71
|
+
@wstats.dig('last_status', 'requests_count') || @wstats['requests_count']
|
72
|
+
end
|
73
|
+
|
58
74
|
def backlog
|
59
75
|
@wstats.dig('last_status', 'backlog') || 0
|
60
76
|
end
|
@@ -71,7 +87,7 @@ class Stats
|
|
71
87
|
end
|
72
88
|
|
73
89
|
def workers
|
74
|
-
(@stats['worker_status'] || [@stats]).map { |wstats| Worker.new(wstats) }
|
90
|
+
@workers ||= (@stats['worker_status'] || [@stats]).map { |wstats| Worker.new(wstats) }
|
75
91
|
end
|
76
92
|
|
77
93
|
def pid=(pid)
|
@@ -95,6 +111,10 @@ class Stats
|
|
95
111
|
(Time.now - Time.parse(@stats['started_at'])).to_i
|
96
112
|
end
|
97
113
|
|
114
|
+
def booting?
|
115
|
+
workers.all?(&:booting?)
|
116
|
+
end
|
117
|
+
|
98
118
|
def total_threads
|
99
119
|
workers.reduce(0) { |total, wstats| total + wstats.max_threads }
|
100
120
|
end
|
@@ -107,6 +127,12 @@ class Stats
|
|
107
127
|
workers.reduce(0) { |total, wstats| total + wstats.max_threads }
|
108
128
|
end
|
109
129
|
|
130
|
+
def requests_count
|
131
|
+
workers_with_requests_count = workers.select(&:requests_count)
|
132
|
+
return if workers_with_requests_count.none?
|
133
|
+
workers_with_requests_count.reduce(0) { |total, wstats| total + wstats.requests_count }
|
134
|
+
end
|
135
|
+
|
110
136
|
def running
|
111
137
|
@stats['running'] || 0
|
112
138
|
end
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma-status
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '1.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yoann Lecuyer
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2019-07-14 00:00:00.000000000 Z
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: parallel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,8 +94,8 @@ dependencies:
|
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0.9'
|
83
|
-
description:
|
84
|
-
email:
|
97
|
+
description:
|
98
|
+
email:
|
85
99
|
executables:
|
86
100
|
- puma-status
|
87
101
|
extensions: []
|
@@ -97,7 +111,7 @@ homepage: https://github.com/ylecuyer/puma-status
|
|
97
111
|
licenses:
|
98
112
|
- MIT
|
99
113
|
metadata: {}
|
100
|
-
post_install_message:
|
114
|
+
post_install_message:
|
101
115
|
rdoc_options: []
|
102
116
|
require_paths:
|
103
117
|
- lib
|
@@ -105,16 +119,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
119
|
requirements:
|
106
120
|
- - ">="
|
107
121
|
- !ruby/object:Gem::Version
|
108
|
-
version:
|
122
|
+
version: 2.3.0
|
109
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
124
|
requirements:
|
111
125
|
- - ">="
|
112
126
|
- !ruby/object:Gem::Version
|
113
127
|
version: '0'
|
114
128
|
requirements: []
|
115
|
-
|
116
|
-
|
117
|
-
signing_key:
|
129
|
+
rubygems_version: 3.2.3
|
130
|
+
signing_key:
|
118
131
|
specification_version: 4
|
119
132
|
summary: Command-line tool for puma to display information about running request/process
|
120
133
|
test_files: []
|