From b9565e0ea63cff3567d4b4dc8a3d8ccf7e69a42a Mon Sep 17 00:00:00 2001 From: Bart de Water Date: Mon, 9 Dec 2019 18:04:05 -0500 Subject: [PATCH] Add Puma.stats_hash Change Puma.stats to Puma.stats_json to help disambiguate. An alias is provided for backwards compatibility Having access to the hash allows to produce stats in other ways (such as StatsD) without having to parse JSON of data that is available in memory. An example of this workaround is https://github.com/yob/puma-plugin-statsd/blob/fa6ba1f5074473618643ef7cf99747801a001dec/lib/puma/plugin/statsd.rb#L112-L114 --- History.md | 1 + lib/puma.rb | 10 ++++++++-- lib/puma/app/status.rb | 2 +- lib/puma/cluster.rb | 32 ++++++++++++++++++++++++++++---- lib/puma/launcher.rb | 9 +++++++-- lib/puma/single.rb | 20 ++++++++++++++------ test/test_app_status.rb | 2 +- test/test_cli.rb | 6 ++++-- 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/History.md b/History.md index 58ab594045..9bfc6ad4a2 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,7 @@ * Features * Add pumactl `thread-backtraces` command to print thread backtraces (#2053) * Configuration: `environment` is read from `RAILS_ENV`, if `RACK_ENV` can't be found (#2022) + * `Puma.stats` was renamed to `Puma.stats_json`, `Puma.stats_hash` has been added to provide the same data as a Hash * Bugfixes * Your bugfix goes here (#Github Number) diff --git a/lib/puma.rb b/lib/puma.rb index cb46f8ca35..4765c9f20b 100644 --- a/lib/puma.rb +++ b/lib/puma.rb @@ -19,10 +19,16 @@ def self.stats_object=(val) @get_stats = val end - def self.stats - @get_stats.stats + def self.stats_hash + @get_stats.stats_hash end + + def self.stats_json + @get_stats.stats_json + end + singleton_class.send(:alias_method, :stats, :stats_json) + # Thread name is new in Ruby 2.3 def self.set_thread_name(name) return unless Thread.current.respond_to?(:name=) diff --git a/lib/puma/app/status.rb b/lib/puma/app/status.rb index 19f6d877de..cfa806cdea 100644 --- a/lib/puma/app/status.rb +++ b/lib/puma/app/status.rb @@ -54,7 +54,7 @@ def call(env) rack_response(200, GC.stat.to_json) when /\/stats$/ - rack_response(200, @cli.stats) + rack_response(200, @cli.stats_json) when /\/thread-backtraces$/ backtraces = [] diff --git a/lib/puma/cluster.rb b/lib/puma/cluster.rb index fb9b63fa0b..70de30353d 100644 --- a/lib/puma/cluster.rb +++ b/lib/puma/cluster.rb @@ -350,12 +350,36 @@ def reload_worker_directory # Inside of a child process, this will return all zeroes, as @workers is only populated in # the master process. - def stats + def stats_hash old_worker_count = @workers.count { |w| w.phase != @phase } - booted_worker_count = @workers.count { |w| w.booted? } - worker_status = '[' + @workers.map { |w| %Q!{ "started_at": "#{w.started_at.utc.iso8601}", "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']' - %Q!{ "started_at": "#{@started_at.utc.iso8601}", "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }! + worker_status = @workers.map do |w| + { + started_at: w.started_at.utc.iso8601, + pid: w.pid, + index: w.index, + phase: w.phase, + booted: w.booted?, + last_checkin: w.last_checkin.utc.iso8601, + last_status: w.last_status, + } + end + + { + started_at: @started_at.utc.iso8601, + workers: @workers.size, + phase: @phase, + booted_workers: worker_status.count { |w| w[:booted] }, + old_workers: old_worker_count, + worker_status: worker_status, + } + end + + def stats_json + h = stats_hash + worker_status = '[' + h[:worker_status].map { |w| %Q!{ "started_at": "#{w[:started_at]}", "pid": #{w[:pid]}, "index": #{w[:index]}, "phase": #{w[:phase]}, "booted": #{w[:booted]}, "last_checkin": "#{w[:last_checkin]}", "last_status": #{w[:last_status]} }!}.join(",") + ']' + %Q!{ "started_at": "#{h[:started_at]}", "workers": #{h[:workers]}, "phase": #{h[:phase]}, "booted_workers": #{h[:booted_workers]}, "old_workers": #{h[:old_workers]}, "worker_status": #{worker_status} }! end + #alias_method :stats, :stats_json def preload? @options[:preload_app] diff --git a/lib/puma/launcher.rb b/lib/puma/launcher.rb index 4b96588a57..a05063b2cc 100644 --- a/lib/puma/launcher.rb +++ b/lib/puma/launcher.rb @@ -95,10 +95,15 @@ def initialize(conf, launcher_args={}) attr_reader :binder, :events, :config, :options, :restart_dir # Return stats about the server - def stats - @runner.stats + def stats_hash + @runner.stats_hash end + def stats_json + @runner.stats_json + end + alias_method :stats, :stats_json + # Write a state file that can be used by pumactl to control # the server def write_state diff --git a/lib/puma/single.rb b/lib/puma/single.rb index a1673a8eb7..3a3d3f59ce 100644 --- a/lib/puma/single.rb +++ b/lib/puma/single.rb @@ -13,14 +13,22 @@ module Puma # gets created via the `start_server` method from the `Puma::Runner` class # that this inherits from. class Single < Runner - def stats - b = @server.backlog || 0 - r = @server.running || 0 - t = @server.pool_capacity || 0 - m = @server.max_threads || 0 - %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }! + def stats_hash + { + started_at: @started_at.utc.iso8601, + backlog: @server.backlog || 0, + running: @server.running || 0, + pool_capacity: @server.pool_capacity || 0, + max_threads: @server.max_threads || 0, + } end + def stats_json + h = stats_hash + %Q!{ "started_at": "#{h[:started_at]}", "backlog": #{h[:backlog]}, "running": #{h[:running]}, "pool_capacity": #{h[:pool_capacity]}, "max_threads": #{h[:max_threads]} }! + end + alias_method :stats, :stats_json + def restart @server.begin_restart end diff --git a/test/test_app_status.rb b/test/test_app_status.rb index c8cd2af6e5..7e342f673d 100644 --- a/test/test_app_status.rb +++ b/test/test_app_status.rb @@ -23,7 +23,7 @@ def halt @status = :halt end - def stats + def stats_json "{}" end end diff --git a/test/test_cli.rb b/test/test_cli.rb index 5e75cb2eb0..c5166307f8 100644 --- a/test/test_cli.rb +++ b/test/test_cli.rb @@ -58,7 +58,8 @@ def test_control_for_tcp s.close assert_match(/{ "started_at": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }/, body.split(/\r?\n/).last) - assert_match(/{ "started_at": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }/, Puma.stats) + assert_match(/{ "started_at": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }/, Puma.stats_json) + assert_equal([:started_at, :backlog, :running, :pool_capacity, :max_threads], Puma.stats_hash.keys) ensure cli.launcher.stop @@ -94,7 +95,8 @@ def test_control_for_ssl expected_stats = /{ "started_at": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", "backlog": 0, "running": 0, "pool_capacity": 16, "max_threads": 16 }/ assert_match(expected_stats, body.split(/\r?\n/).last) - assert_match(expected_stats, Puma.stats) + assert_match(expected_stats, Puma.stats_json) + assert_equal([:started_at, :backlog, :running, :pool_capacity, :max_threads], Puma.stats_hash.keys) ensure cli.launcher.stop