Skip to content

Commit

Permalink
Add pumactl command to print thread backtraces
Browse files Browse the repository at this point in the history
Completes 1 of 2 items from #1964

This commit adds an endpoint to the status app to print thread
backtraces, and control cli command to call that endpoint.

I tried this locally by starting a server with:

```sh
bundle exec bin/puma test/rackup/hello.ru \
  --control-url="unix://test.sock" \
  --control-token="token"
```

and then printing the backtraces with:

```sh
bundle exec bin/pumactl thread-backtraces \
  --control-url="unix://test.sock" \
  --control-token="token"
```
  • Loading branch information
composerinteralia committed Oct 26, 2019
1 parent cea74d7 commit a52fd51
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 19 deletions.
1 change: 1 addition & 0 deletions History.md
Expand Up @@ -4,6 +4,7 @@
* Strip whitespace at end of HTTP headers (#2010)
* Optimize HTTP parser for JRuby (#2012)
* Add SSL support for the control app (#2046)
* Add pumactl `thread-backtraces` command to print thread backtraces (#2053)

* Bugfixes
* Fix Errno::EINVAL when SSL is enabled and browser rejects cert (#1564)
Expand Down
5 changes: 5 additions & 0 deletions lib/puma/app/status.rb
Expand Up @@ -55,6 +55,11 @@ def call(env)

when /\/stats$/
rack_response(200, @cli.stats)

when /\/thread-backtraces$/
strings = Puma::Events.strings
@cli.log_thread_status(strings)
rack_response(200, strings.stdout.string)
else
rack_response 404, "Unsupported action", 'text/plain'
end
Expand Down
5 changes: 3 additions & 2 deletions lib/puma/control_cli.rb
Expand Up @@ -11,7 +11,8 @@
module Puma
class ControlCLI

COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats thread-backtraces}
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}

def initialize(argv, stdout=STDOUT, stderr=STDERR)
@state = nil
Expand Down Expand Up @@ -181,7 +182,7 @@ def send_request
end

message "Command #{@command} sent success"
message response.last if @command == "stats" || @command == "gc-stats"
message response.last if PRINTABLE_COMMANDS.include?(@command)
end
ensure
server.close if server && !server.closed?
Expand Down
32 changes: 16 additions & 16 deletions lib/puma/launcher.rb
Expand Up @@ -205,6 +205,21 @@ def close_binder_listeners
@binder.close_listeners
end

def log_thread_status(events)
Thread.list.each do |thread|
events.log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
logstr += " #{thread.name}" if thread.respond_to?(:name)
events.log logstr

if thread.backtrace
events.log thread.backtrace.join("\n")
else
events.log "<no backtrace available>"
end
end
end

private

# If configured, write the pid of the current process out
Expand Down Expand Up @@ -323,21 +338,6 @@ def graceful_stop
log "- Goodbye!"
end

def log_thread_status
Thread.list.each do |thread|
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
logstr += " #{thread.name}" if thread.respond_to?(:name)
log logstr

if thread.backtrace
log thread.backtrace.join("\n")
else
log "<no backtrace available>"
end
end
end

def set_process_title
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
end
Expand Down Expand Up @@ -457,7 +457,7 @@ def setup_signals

begin
Signal.trap "SIGINFO" do
log_thread_status
log_thread_status(@events)
end
rescue Exception
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
Expand Down
24 changes: 24 additions & 0 deletions test/test_cli.rb
Expand Up @@ -203,6 +203,30 @@ def test_control_stop
t.join if UNIX_SKT_EXIST
end

def test_control_thread_backtraces
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST
url = "unix://#{@tmp_path}"

cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
"--control-url", url,
"--control-token", "",
"test/rackup/lobster.ru"], @events

t = Thread.new { cli.run }

wait_booted

s = UNIXSocket.new @tmp_path
s << "GET /thread-backtraces HTTP/1.0\r\n\r\n"
body = s.read
s.close

assert_match %r{Thread: TID-.* puma threadpool}, body.split("\r\n").last
ensure
cli.launcher.stop if cli
t.join if UNIX_SKT_EXIST
end

def control_gc_stats(uri, cntl)
cli = Puma::CLI.new ["-b", uri,
"--control-url", cntl,
Expand Down
2 changes: 1 addition & 1 deletion test/test_launcher.rb
Expand Up @@ -57,7 +57,7 @@ def test_puma_wild_location_is_an_absolute_path
end

def test_prints_thread_traces
launcher.send(:log_thread_status)
launcher.log_thread_status(events)
events.stdout.rewind

assert_match "Thread TID", events.stdout.read
Expand Down

0 comments on commit a52fd51

Please sign in to comment.