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 puma#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 25, 2019
1 parent cea74d7 commit 0bba584
Show file tree
Hide file tree
Showing 5 changed files with 26 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
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 0bba584

Please sign in to comment.