1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00
puma--puma/test/test_cli.rb
schneems 441a42e185 Expose top level Puma.stats API
Right now to get stats for a puma process the only way is to go through the control server. The puma-stats-logger does this by reading in a state file then querying the control server manually.

7ad7798e9d/lib/puma_stats_logger/middleware.rb (L28)

Instead, I’m proposing adding a top level `Puma.stats` method that will allow anyone inside of the same process to get access to the stats.

This could be instrumented by a gem to theoretically export these stats to say a Heroku dashboard where we could list out backlog or thread count.

The format of stats is a hash, and will change depending on if the server is in “single” or “clustered” mode.

Clustered:

```
{ "workers": 2, "phase": 0, "booted_workers": 2, "old_workers": 0, "worker_status": [{ "pid": 19832, "index": 0, "phase": 0, "booted": true, "last_checkin": "2018-03-12T16:03:12Z", "last_status": { "backlog":0, "running":5 } },{ "pid": 19833, "index": 1, "phase": 0, "booted": true, "last_checkin": "2018-03-12T16:03:12Z", "last_status": { "backlog":0, "running":5 } }] }
```

Single:

```
{ "backlog": 0, "running": 2 }
```

Alternatively if we could somehow enable another process to get these stats from Puma via pumactl by default without requiring any additional in app config, that would also work.
2018-03-20 11:24:32 -05:00

264 lines
6.6 KiB
Ruby

require_relative "helper"
require "puma/cli"
class TestCLI < Minitest::Test
def setup
@environment = 'production'
@tmp_file = Tempfile.new("puma-test")
@tmp_path = @tmp_file.path
@tmp_file.close!
@tmp_path2 = "#{@tmp_path}2"
File.unlink @tmp_path if File.exist? @tmp_path
File.unlink @tmp_path2 if File.exist? @tmp_path2
@wait, @ready = IO.pipe
@events = Puma::Events.strings
@events.on_booted { @ready << "!" }
end
def wait_booted
@wait.sysread 1
end
def teardown
File.unlink @tmp_path if File.exist? @tmp_path
File.unlink @tmp_path2 if File.exist? @tmp_path2
@wait.close
@ready.close
end
def test_pid_file
cli = Puma::CLI.new ["--pidfile", @tmp_path]
cli.launcher.write_pid
assert_equal File.read(@tmp_path).strip.to_i, Process.pid
end
def test_control_for_tcp
url = "tcp://127.0.0.1:9877/"
cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:9876",
"--control", url,
"--control-token", "",
"test/rackup/lobster.ru"], @events
t = Thread.new do
Thread.current.abort_on_exception = true
cli.run
end
wait_booted
s = TCPSocket.new "127.0.0.1", 9877
s << "GET /stats HTTP/1.0\r\n\r\n"
body = s.read
assert_equal '{ "backlog": 0, "running": 0 }', body.split(/\r?\n/).last
assert_equal '{ "backlog": 0, "running": 0 }', Puma.stats
cli.launcher.stop
t.join
end
unless Puma.jruby? || Puma.windows?
def test_control_clustered
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
"-t", "2:2",
"-w", "2",
"--control", url,
"--control-token", "",
"test/rackup/lobster.ru"], @events
t = Thread.new { cli.run }
t.abort_on_exception = true
wait_booted
sleep 2
s = UNIXSocket.new @tmp_path
s << "GET /stats HTTP/1.0\r\n\r\n"
body = s.read
require 'json'
status = JSON.parse(body.split("\n").last)
assert_equal 2, status["workers"]
# wait until the first status ping has come through
sleep 6
s = UNIXSocket.new @tmp_path
s << "GET /stats HTTP/1.0\r\n\r\n"
body = s.read
assert_match(/\{ "workers": 2, "phase": 0, "booted_workers": 2, "old_workers": 0, "worker_status": \[\{ "pid": \d+, "index": 0, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2 \} \},\{ "pid": \d+, "index": 1, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2 \} \}\] \}/, body.split("\r\n").last)
cli.launcher.stop
t.join
end
def test_control
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
"--control", url,
"--control-token", "",
"test/rackup/lobster.ru"], @events
t = Thread.new { cli.run }
t.abort_on_exception = true
wait_booted
s = UNIXSocket.new @tmp_path
s << "GET /stats HTTP/1.0\r\n\r\n"
body = s.read
assert_equal '{ "backlog": 0, "running": 0 }', body.split("\r\n").last
cli.launcher.stop
t.join
end
def test_control_stop
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
"--control", url,
"--control-token", "",
"test/rackup/lobster.ru"], @events
t = Thread.new { cli.run }
t.abort_on_exception = true
wait_booted
s = UNIXSocket.new @tmp_path
s << "GET /stop HTTP/1.0\r\n\r\n"
body = s.read
assert_equal '{ "status": "ok" }', body.split("\r\n").last
t.join
end
def test_control_gc_stats
url = "unix://#{@tmp_path}"
cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
"--control", url,
"--control-token", "",
"test/rackup/lobster.ru"], @events
t = Thread.new { cli.run }
t.abort_on_exception = true
wait_booted
s = UNIXSocket.new @tmp_path
s << "GET /gc-stats HTTP/1.0\r\n\r\n"
body = s.read
s.close
lines = body.split("\r\n")
json_line = lines.detect { |l| l[0] == "{" }
pairs = json_line.scan(/\"[^\"]+\": [^,]+/)
gc_stats = {}
pairs.each do |p|
p =~ /\"([^\"]+)\": ([^,]+)/ || raise("Can't parse #{p.inspect}!")
gc_stats[$1] = $2
end
gc_count_before = gc_stats["count"].to_i
s = UNIXSocket.new @tmp_path
s << "GET /gc HTTP/1.0\r\n\r\n"
body = s.read # Ignored
s.close
s = UNIXSocket.new @tmp_path
s << "GET /gc-stats HTTP/1.0\r\n\r\n"
body = s.read
s.close
lines = body.split("\r\n")
json_line = lines.detect { |l| l[0] == "{" }
pairs = json_line.scan(/\"[^\"]+\": [^,]+/)
gc_stats = {}
pairs.each do |p|
p =~ /\"([^\"]+)\": ([^,]+)/ || raise("Can't parse #{p.inspect}!")
gc_stats[$1] = $2
end
gc_count_after = gc_stats["count"].to_i
# Hitting the /gc route should increment the count by 1
assert_equal gc_count_before + 1, gc_count_after
cli.launcher.stop
t.join
end
def test_tmp_control
url = "tcp://127.0.0.1:8232"
cli = Puma::CLI.new ["--state", @tmp_path, "--control", "auto"]
cli.launcher.write_state
data = YAML.load File.read(@tmp_path)
assert_equal Process.pid, data["pid"]
url = data["control_url"]
m = %r!unix://(.*)!.match(url)
assert m, "'#{url}' is not a URL"
end
def test_state_file_callback_filtering
cli = Puma::CLI.new [ "--config", "test/config/state_file_testing_config.rb",
"--state", @tmp_path ]
cli.launcher.write_state
data = YAML.load_file(@tmp_path)
keys_not_stripped = data.keys & Puma::CLI::KEYS_NOT_TO_PERSIST_IN_STATE
assert_empty keys_not_stripped
end
end # JRUBY or Windows
def test_state
url = "tcp://127.0.0.1:8232"
cli = Puma::CLI.new ["--state", @tmp_path, "--control", url]
cli.launcher.write_state
data = YAML.load File.read(@tmp_path)
assert_equal Process.pid, data["pid"]
assert_equal url, data["control_url"]
end
def test_load_path
Puma::CLI.new ["--include", 'foo/bar']
assert_equal 'foo/bar', $LOAD_PATH[0]
$LOAD_PATH.shift
Puma::CLI.new ["--include", 'foo/bar:baz/qux']
assert_equal 'foo/bar', $LOAD_PATH[0]
$LOAD_PATH.shift
assert_equal 'baz/qux', $LOAD_PATH[0]
$LOAD_PATH.shift
end
def test_environment
ENV.delete 'RACK_ENV'
Puma::CLI.new ["--environment", @environment]
assert_equal ENV['RACK_ENV'], @environment
end
end