class Object
Constants
- CPU_COLUMN
- MEM_COLUMN
- PID_COLUMN
Public Instance Methods
asciiThreadLoad(running, spawned, total)
click to toggle source
# File lib/helpers.rb, line 32 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)
click to toggle source
# File lib/helpers.rb, line 20 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)
click to toggle source
# File lib/helpers.rb, line 15 def colorize(str, color_name) return str if ENV.key?('NO_COLOR') str.to_s.colorize(color_name) end
debug(str)
click to toggle source
# File lib/helpers.rb, line 3 def debug(str) puts str if ENV.key?('DEBUG') end
error(str)
click to toggle source
# File lib/helpers.rb, line 11 def error(str) colorize(str, :red) end
format_stats(stats)
click to toggle source
# File lib/core.rb, line 78 def format_stats(stats) master_line = "#{stats.pid} (#{stats.state_file_path}) Uptime: #{seconds_to_human(stats.uptime)}" master_line += " | Phase: #{stats.phase}" if stats.phase if stats.booting? master_line += " #{warn("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 += " #{warn("booting")}" elsif wstats.killed? worker_line += " #{error("killed")}" else worker_line += " | Load: #{color(75, 50, wstats.load, asciiThreadLoad(wstats.running_threads, wstats.spawned_threads, wstats.max_threads))}" worker_line += " | Phase: #{error(wstats.phase)}" if wstats.phase != stats.phase worker_line += " | Req: #{wstats.requests_count}" if wstats.requests_count worker_line += " Queue: #{error(wstats.backlog.to_s)}" if wstats.backlog > 0 worker_line += " Last checkin: #{error(wstats.last_checkin)}" if wstats.last_checkin >= 10 end worker_line end output.join("\n") end
get_memory_from_top(raw_memory)
click to toggle source
# File lib/core.rb, line 35 def get_memory_from_top(raw_memory) raw_memory.tr!(',', '.') # because of LC_NUMERIC separator can be , 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)
click to toggle source
# File lib/core.rb, line 8 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)
click to toggle source
# File lib/core.rb, line 52 def get_top_stats(pids) pids.each_slice(19).inject({}) do |res, pids19| top_result = `top -b -n 1 -p #{pids19.join(',')} | tail -n #{pids19.length}` 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] } .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)
click to toggle source
# 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
run()
click to toggle source
# 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.message =~ /#{state_file_path}/ errors << "#{warn(state_file_path)} doesn't exists" elsif e.message =~ /connect\(2\) for [^\/]/ 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." else errors << "#{error(state_file_path)} an unhandled error occured: #{e.inspect}" end nil rescue Errno::EISDIR => e if e.message =~ /#{state_file_path}/ errors << "#{warn(state_file_path)} isn't a state file" else errors << "#{error(state_file_path)} an unhandled error occured: #{e.inspect}" end nil rescue => e errors << "#{error(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)
click to toggle source
# File lib/helpers.rb, line 44 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
warn(str)
click to toggle source
# File lib/helpers.rb, line 7 def warn(str) colorize(str, :yellow) end