puma-status 0.1.0

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 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: []