puma-status 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b7c4c76896b8d5f9b831403b258e7a377826767a18854292064e75a8eaf4974
4
+ data.tar.gz: 49d28d79e5a509e91bd02e6d2a56ac50cbe33c4379d3ae5be404ff3d0e739293
5
+ SHA512:
6
+ metadata.gz: eb4396e097e5cdb2d35bc758c0a6de1ae2679e22cff729a5f0e10c6716068427d716640a223addde2ea07cb2df7ef65ad5ea7d88f32d95a3ae95ad6499f605d8
7
+ data.tar.gz: 3b680055e5c4062f03cd7e8afb4c8c56b1c9afb20e0f070fe92c9ced43f015e61109a8dc49386d286f4bf95d0e96e8166ed70db69cc3a5cd1b7fbf8677168a57
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [year] [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/bin/puma-status ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require './lib/puma-status'
4
+
5
+ run
data/lib/core.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'net_x/http_unix'
4
+ require 'time'
5
+ require_relative 'stats'
6
+
7
+ def get_stats(state_file_path)
8
+ puma_state = YAML.load_file(state_file_path)
9
+
10
+ client = NetX::HTTPUnix.new(puma_state["control_url"])
11
+ req = Net::HTTP::Get.new("/stats?token=#{puma_state["control_auth_token"]}")
12
+ resp = client.request(req)
13
+ raw_stats = JSON.parse(resp.body)
14
+ debug raw_stats
15
+ stats = Stats.new(raw_stats)
16
+
17
+ hydrate_stats(stats, puma_state, state_file_path)
18
+ end
19
+
20
+ def get_top_stats(pids)
21
+ top_result = `top -b -n 1 -p #{pids.join(',')} | tail -n #{pids.length}`
22
+ top_stats = top_result.split("\n").map { |row| r = row.split(' '); [r[0].to_i, r[5].to_i/1024, r[8].to_f] }
23
+ .reduce({}) { |hash, row| hash[row[0]] = { mem: row[1], pcpu: row[2] }; hash }
24
+ end
25
+
26
+ def hydrate_stats(stats, puma_state, state_file_path)
27
+ stats.pid = puma_state['pid']
28
+ stats.state_file_path = state_file_path
29
+
30
+ workers_pids = stats.workers.map(&:pid)
31
+
32
+ top_stats = get_top_stats(workers_pids)
33
+
34
+ stats.tap do |s|
35
+ stats.workers.map do |wstats|
36
+ wstats.mem = top_stats[wstats.pid][:mem]
37
+ wstats.pcpu = top_stats[wstats.pid][:pcpu]
38
+ end
39
+ end
40
+ end
41
+
42
+ def display_stats(stats)
43
+ puts "#{stats.pid} (#{stats.state_file_path}) Uptime: #{seconds_to_human(stats.uptime)} | Load: #{color(75, 50, stats.load, asciiThreadLoad(stats.running_threads, stats.total_threads))}"
44
+
45
+ stats.workers.each do |wstats|
46
+ 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)} | Load: #{color(75, 50, wstats.load, asciiThreadLoad(wstats.running_threads, wstats.total_threads))}"
47
+ worker_line += " #{("Queue: " + wstats.backlog.to_s).colorize(:red)}" if wstats.backlog > 0
48
+ worker_line += " Last checkin: #{wstats.last_checkin}" if wstats.last_checkin >= 10
49
+
50
+ puts worker_line
51
+ end
52
+ end
data/lib/helpers.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'colorize'
2
+
3
+ def debug(str)
4
+ puts str if ENV.key?('DEBUG')
5
+ end
6
+
7
+ def color(critical, warn, value, str)
8
+ return str if ENV.key?('NO_COLOR')
9
+
10
+ color = if value >= critical
11
+ :red
12
+ elsif value < critical && value >= warn
13
+ :yellow
14
+ else
15
+ :green
16
+ end
17
+ str.to_s.colorize(color)
18
+ end
19
+
20
+ def asciiThreadLoad(idx, total)
21
+ full = "█"
22
+ empty= "░"
23
+
24
+ "#{idx}[#{full*idx}#{empty*(total-idx)}]#{total}"
25
+ end
26
+
27
+ def seconds_to_human(seconds)
28
+
29
+ #=> 0m 0s
30
+ #=> 59m59s
31
+ #=> 1h 0m
32
+ #=> 23h59m
33
+ #=> 1d 0h
34
+ #=> 24d
35
+
36
+ if seconds < 60*60
37
+ "#{(seconds/60).to_s.rjust(2, ' ')}m#{(seconds%60).to_s.rjust(2, ' ')}s"
38
+ elsif seconds >= 60*60*1 && seconds < 60*60*24
39
+ "#{(seconds/(60*60*1)).to_s.rjust(2, ' ')}h#{((seconds%(60*60*1))/60).to_s.rjust(2, ' ')}m"
40
+ elsif seconds > 60*60*24 && seconds < 60*60*24*10
41
+ "#{(seconds/(60*60*24)).to_s.rjust(2, ' ')}d#{((seconds%(60*60*24))/(60*60*1)).to_s.rjust(2, ' ')}h"
42
+ else
43
+ "#{seconds/(60*60*24)}d".rjust(6, ' ')
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ require_relative './helpers'
2
+ require_relative './core.rb'
3
+
4
+ def run
5
+ debug "puma-status"
6
+ state_file_path = ARGV[0]
7
+ debug "State file: #{state_file_path}"
8
+ display_stats(get_stats(state_file_path))
9
+ end
data/lib/stats.rb ADDED
@@ -0,0 +1,107 @@
1
+ class Stats
2
+
3
+ class Worker
4
+ def initialize(wstats)
5
+ @wstats = wstats
6
+ end
7
+
8
+ def pid
9
+ @wstats['pid']
10
+ end
11
+
12
+ def mem=(mem)
13
+ @wstats['mem'] = mem
14
+ end
15
+
16
+ def mem
17
+ @wstats['mem']
18
+ end
19
+
20
+ def pcpu=(pcpu)
21
+ @wstats['pcpu'] = pcpu
22
+ end
23
+
24
+ def pcpu
25
+ @wstats['pcpu']
26
+ end
27
+
28
+ def running
29
+ @wstats.dig('last_status', 'running') || @wstats['running'] || 0
30
+ end
31
+ alias :total_threads :running
32
+
33
+ def pool_capacity
34
+ @wstats.dig('last_status', 'pool_capacity') || @wstats['pool_capacity'] || 0
35
+ end
36
+
37
+ def running_threads
38
+ running - pool_capacity
39
+ end
40
+
41
+ def load
42
+ running_threads/total_threads.to_f*100
43
+ end
44
+
45
+ def uptime
46
+ (Time.now - Time.parse(@wstats['started_at'])).to_i
47
+ end
48
+
49
+ def backlog
50
+ @wstats.dig('last_status', 'backlog') || 0
51
+ end
52
+
53
+ def last_checkin
54
+ (Time.now - Time.parse(@wstats['last_checkin'])).round
55
+ rescue
56
+ 0
57
+ end
58
+ end
59
+
60
+ def initialize(stats)
61
+ @stats = stats
62
+ end
63
+
64
+ def workers
65
+ (@stats['worker_status'] || [@stats]).map { |wstats| Worker.new(wstats) }
66
+ end
67
+
68
+ def pid=(pid)
69
+ @stats['pid'] = pid
70
+ end
71
+
72
+ def pid
73
+ @stats['pid']
74
+ end
75
+
76
+ def state_file_path=(state_file_path)
77
+ @stats['state_file_path'] = state_file_path
78
+ end
79
+
80
+ def state_file_path
81
+ @stats['state_file_path']
82
+ end
83
+
84
+ def uptime
85
+ (Time.now - Time.parse(@stats['started_at'])).to_i
86
+ end
87
+
88
+ def total_threads
89
+ workers.reduce(0) { |total, wstats| total + wstats.running }
90
+ end
91
+
92
+ def running_threads
93
+ workers.reduce(0) { |total, wstats| total + (wstats.running - wstats.pool_capacity) }
94
+ end
95
+
96
+ def running
97
+ @stats['running'] || 0
98
+ end
99
+
100
+ def pool_capacity
101
+ @stats['pool_capacity'] || 0
102
+ end
103
+
104
+ def load
105
+ running_threads/total_threads.to_f*100
106
+ end
107
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puma-status
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yoann Lecuyer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net_http_unix
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: climate_control
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ description:
84
+ email:
85
+ executables:
86
+ - puma-status
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - LICENSE
91
+ - bin/puma-status
92
+ - lib/core.rb
93
+ - lib/helpers.rb
94
+ - lib/puma-status.rb
95
+ - lib/stats.rb
96
+ homepage:
97
+ licenses:
98
+ - MIT
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 2.7.6.2
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Display puma status of running puma server
120
+ test_files: []