Top Level Namespace
Defined Under Namespace
Classes: Stats
Constant Summary collapse
- PID_COLUMN =
0
- MEM_COLUMN =
5
- CPU_COLUMN =
8
- OPEN3_STDOUT =
1
Instance Method Summary collapse
- #asciiThreadLoad(running, spawned, total) ⇒ Object
- #color(critical, warn, value, str = nil) ⇒ Object
- #colorize(str, color_name) ⇒ Object
- #debug(str) ⇒ Object
- #format_stats(stats) ⇒ Object
- #get_memory_from_top(raw_memory) ⇒ Object
- #get_stats(state_file_path) ⇒ Object
- #get_top_stats(pids) ⇒ Object
- #hydrate_stats(stats, puma_state, state_file_path) ⇒ Object
- #red(str) ⇒ Object
- #run ⇒ Object
- #seconds_to_human(seconds) ⇒ Object
- #yellow(str) ⇒ Object
Instance Method Details
#asciiThreadLoad(running, spawned, total) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/helpers.rb', line 39 def asciiThreadLoad(running, spawned, total) full = "█" half= "░" empty = " " full_count = running half_count = [spawned - running, 0].max empty_count = total - half_count - full_count "#{running}[#{full*full_count}#{half*half_count}#{empty*empty_count}]#{total}" end |
#color(critical, warn, value, str = nil) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/helpers.rb', line 27 def color(critical, warn, value, str = nil) str = value unless str color_level = if value >= critical :red elsif value < critical && value >= warn :yellow else :green end colorize(str, color_level) end |
#colorize(str, color_name) ⇒ Object
13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/helpers.rb', line 13 def colorize(str, color_name) return str if ENV.key?('NO_COLOR') case color_name when :red "\e[0;31;49m#{str}\e[0m" when :yellow "\e[0;33;49m#{str}\e[0m" when :green "\e[0;32;49m#{str}\e[0m" else str end end |
#debug(str) ⇒ Object
1 2 3 |
# File 'lib/helpers.rb', line 1 def debug(str) puts str if ENV.key?('DEBUG') end |
#format_stats(stats) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/core.rb', line 78 def format_stats(stats) master_line = "#{stats.pid} (#{stats.state_file_path})" master_line += " Version: #{stats.version} |" if stats.version master_line += " Uptime: #{seconds_to_human(stats.uptime)}" master_line += " | Phase: #{stats.phase}" if stats.phase if stats.booting? master_line += " #{yellow("booting")}" else master_line += " | Load: #{color(75, 50, stats.load, asciiThreadLoad(stats.running_threads, stats.spawned_threads, stats.max_threads))}" master_line += " | Req: #{stats.requests_count}" if stats.requests_count end output = [master_line] + stats.workers.map do |wstats| 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)}" if wstats.booting? worker_line += " #{yellow("booting")}" elsif wstats.killed? worker_line += " #{red("killed")}" else worker_line += " | Load: #{color(75, 50, wstats.load, asciiThreadLoad(wstats.running_threads, wstats.spawned_threads, wstats.max_threads))}" worker_line += " | Phase: #{red(wstats.phase)}" if wstats.phase != stats.phase worker_line += " | Req: #{wstats.requests_count}" if wstats.requests_count worker_line += " Queue: #{red(wstats.backlog.to_s)}" if wstats.backlog > 0 worker_line += " Last checkin: #{red(wstats.last_checkin)}" if wstats.last_checkin >= 10 end worker_line end output.join("\n") end |
#get_memory_from_top(raw_memory) ⇒ Object
36 37 38 39 40 41 42 43 44 45 |
# File 'lib/core.rb', line 36 def get_memory_from_top(raw_memory) case raw_memory[-1].downcase when 'g' (raw_memory[0...-1].to_f*1024).to_i when 'm' raw_memory[0...-1].to_i else raw_memory.to_i/1024 end end |
#get_stats(state_file_path) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/core.rb', line 9 def get_stats(state_file_path) puma_state = YAML.load_file(state_file_path) uri = URI.parse(puma_state["control_url"]) address = if uri.scheme =~ /unix/i [uri.scheme, '://', uri.host, uri.path].join else [uri.host, uri.path].join end client = NetX::HTTPUnix.new(address, uri.port) if uri.scheme =~ /ssl/i client.use_ssl = true client.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV['SSL_NO_VERIFY'] == '1' end req = Net::HTTP::Get.new("/stats?token=#{puma_state["control_auth_token"]}") resp = client.request(req) raw_stats = JSON.parse(resp.body) debug raw_stats stats = Stats.new(raw_stats) hydrate_stats(stats, puma_state, state_file_path) end |
#get_top_stats(pids) ⇒ Object
52 53 54 55 56 57 58 59 |
# File 'lib/core.rb', line 52 def get_top_stats(pids) pids.each_slice(19).inject({}) do |res, pids19| top_result = Open3.popen3({ 'LC_ALL' => 'C' }, "top -b -n 1 -p #{pids19.map(&:to_i).join(',')}")[OPEN3_STDOUT].read top_result.split("\n").last(pids19.length).map { |row| r = row.split(' '); [r[PID_COLUMN].to_i, get_memory_from_top(r[MEM_COLUMN]), r[CPU_COLUMN].to_f] } .inject(res) { |hash, row| hash[row[0]] = { mem: row[1], pcpu: row[2] }; hash } res end end |
#hydrate_stats(stats, puma_state, state_file_path) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/core.rb', line 61 def hydrate_stats(stats, puma_state, state_file_path) stats.pid = puma_state['pid'] stats.state_file_path = state_file_path workers_pids = stats.workers.map(&:pid) top_stats = get_top_stats(workers_pids) stats.tap do |s| stats.workers.map do |wstats| wstats.mem = top_stats.dig(wstats.pid, :mem) || 0 wstats.pcpu = top_stats.dig(wstats.pid, :pcpu) || 0 wstats.killed = !top_stats.key?(wstats.pid) || (wstats.mem <=0 && wstats.pcpu <= 0) end end end |
#red(str) ⇒ Object
9 10 11 |
# File 'lib/helpers.rb', line 9 def red(str) colorize(str, :red) end |
#run ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/puma-status.rb', line 5 def run debug "puma-status" if ARGV.count < 1 puts "Call with:" puts "\tpuma-status path/to/puma.state" exit -1 end errors = [] outputs = Parallel.map(ARGV, in_threads: ARGV.count) do |state_file_path| begin debug "State file: #{state_file_path}" format_stats(get_stats(state_file_path)) rescue Errno::ENOENT => e if e. =~ /#{state_file_path}/ errors << "#{yellow(state_file_path)} doesn't exist" elsif e. =~ /connect\(2\) for [^\/]/ errors << "#{yellow("Relative Unix socket")}: the Unix socket of the control app has a relative path. Please, ensure you are running from the same folder as puma." else errors << "#{red(state_file_path)} an unhandled error occured: #{e.inspect}" end nil rescue Errno::EISDIR => e if e. =~ /#{state_file_path}/ errors << "#{yellow(state_file_path)} isn't a state file" else errors << "#{red(state_file_path)} an unhandled error occured: #{e.inspect}" end nil rescue => e errors << "#{red(state_file_path)} an unhandled error occured: #{e.inspect}" nil end end outputs.compact.each { |output| puts output } if errors.any? puts "" errors.each { |error| puts error } end end |
#seconds_to_human(seconds) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/helpers.rb', line 51 def seconds_to_human(seconds) #=> 0m 0s #=> 59m59s #=> 1h 0m #=> 23h59m #=> 1d 0h #=> 24d if seconds <= 0 "--m--s" elsif seconds < 60*60 "#{(seconds/60).to_s.rjust(2, ' ')}m#{(seconds%60).to_s.rjust(2, ' ')}s" elsif seconds >= 60*60*1 && seconds < 60*60*24 "#{(seconds/(60*60*1)).to_s.rjust(2, ' ')}h#{((seconds%(60*60*1))/60).to_s.rjust(2, ' ')}m" elsif seconds > 60*60*24 && seconds < 60*60*24*10 "#{(seconds/(60*60*24)).to_s.rjust(2, ' ')}d#{((seconds%(60*60*24))/(60*60*1)).to_s.rjust(2, ' ')}h" else "#{seconds/(60*60*24)}d".rjust(6, ' ') end end |
#yellow(str) ⇒ Object
5 6 7 |
# File 'lib/helpers.rb', line 5 def yellow(str) colorize(str, :yellow) end |