├── .editorconfig ├── .github └── workflows │ └── workflow.yml ├── .overcommit.yml ├── .rubocop.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── lib └── puma │ ├── metrics │ ├── app.rb │ ├── dsl.rb │ ├── parser.rb │ └── version.rb │ └── plugin │ └── metrics.rb ├── puma-metrics.gemspec ├── renovate.json └── test ├── helpers.rb ├── test_cluster_more.rb ├── test_cluster_one.rb ├── test_config.rb ├── test_signals.rb └── test_single.rb /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: steps 2 | on: 3 | push: 4 | branches: [main, master] 5 | pull_request: 6 | branches: [main, master] 7 | jobs: 8 | test: 9 | name: test on ruby ${{ matrix.ruby }} 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - uses: ruby/setup-ruby@master 14 | with: 15 | ruby-version: ${{ matrix.ruby }} 16 | - run: gem install bundler 17 | - run: bundle install 18 | - run: bundle exec rake 19 | - run: bundle exec overcommit --sign 20 | - env: 21 | GIT_AUTHOR_NAME: John Doe 22 | GIT_AUTHOR_EMAIL: johndoe@example.com 23 | run: bundle exec overcommit --run 24 | strategy: 25 | matrix: 26 | ruby: ['3.0', '3.1', '3.2', '3.3'] 27 | deploy: 28 | if: github.ref == 'refs/heads/main' 29 | name: to rubygems 30 | needs: test 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@master 34 | - uses: ruby/setup-ruby@master 35 | with: 36 | ruby-version: 3.3 37 | - env: 38 | RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} 39 | run: | 40 | mkdir -p $HOME/.gem 41 | touch $HOME/.gem/credentials 42 | chmod 0600 $HOME/.gem/credentials 43 | printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials 44 | gem build *.gemspec 45 | gem push *.gem 46 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Use this file to configure the Overcommit hooks you wish to use. This will 2 | # extend the default configuration defined in: 3 | # https://github.com/brigade/overcommit/blob/master/config/default.yml 4 | # 5 | # At the topmost level of this YAML file is a key representing type of hook 6 | # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can 7 | # customize each hook, such as whether to only run it on certain files (via 8 | # `include`), whether to only display output if it fails (via `quiet`), etc. 9 | # 10 | # For a complete list of hooks, see: 11 | # https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook 12 | # 13 | # For a complete list of options that you can use to customize hooks, see: 14 | # https://github.com/brigade/overcommit#configuration 15 | # 16 | # Uncomment the following lines to make the configuration take effect. 17 | 18 | #PreCommit: 19 | # RuboCop: 20 | # enabled: true 21 | # on_warn: fail # Treat all warnings as failures 22 | # 23 | # TrailingWhitespace: 24 | # enabled: true 25 | # exclude: 26 | # - '**/db/structure.sql' # Ignore trailing whitespace in generated files 27 | # 28 | #PostCheckout: 29 | # ALL: # Special hook name that customizes all hooks of this type 30 | # quiet: true # Change all post-checkout hooks to only display output on failure 31 | # 32 | # IndexTags: 33 | # enabled: true # Generate a tags file with `ctags` each time HEAD changes 34 | 35 | PreCommit: 36 | RuboCop: 37 | enabled: true 38 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: enable 3 | TargetRubyVersion: 3.0 4 | 5 | Gemspec/RequireMFA: 6 | Enabled: false 7 | 8 | Naming/FileName: 9 | Exclude: 10 | - puma-metrics.gemspec 11 | 12 | Layout/LineLength: 13 | Max: 120 14 | 15 | Lint/MissingSuper: 16 | Exclude: 17 | - lib/puma/metrics/parser.rb 18 | 19 | Style/Documentation: 20 | Enabled: false 21 | Style/NumericPredicate: 22 | Enabled: false 23 | Style/MissingRespondToMissing: 24 | Exclude: 25 | - lib/puma/metrics/parser.rb 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ## 1.4.0 6 | 7 | Housekeeping: 8 | - Drop support for ruby 2.7 9 | - Add support for ruby 3.3 10 | - Update dependencies 11 | 12 | ## 1.3.0 13 | 14 | Changes: 15 | - Added support for puma 6.0 or newer (#43) 16 | 17 | Housekeeping: 18 | - Drop support for ruby 2.6 19 | - Update dependencies 20 | 21 | ## 1.2.5 22 | 23 | Changes: 24 | - Require puma 5.0 or newer as older versions don't support the `on_stopped` introduced in version puma-metrics 1.2.4. 25 | 26 | Housekeeping: 27 | - Update dependencies 28 | 29 | ## 1.2.4 30 | 31 | Changes: 32 | - Shut down metrics server in `on_stopped` so it only stops when main puma process stops and ignores when workers are 33 | stopped. This allows us to maintain a running metrics server when workers restart or crash. 34 | 35 | Housekeeping: 36 | - Update dependencies 37 | - Test on ruby 3.1 38 | 39 | ## 1.2.3 40 | 41 | Housekeeping: 42 | - Update dependencies 43 | 44 | ## 1.2.2 45 | 46 | Changes: 47 | - Expose new metric `puma_requests_count` when using Puma 5 48 | 49 | ## 1.2.1 50 | 51 | Changes: 52 | - Drop support for ruby 2.5 53 | 54 | Housekeeping: 55 | - Update dependencies 56 | - Updates for Puma 5 57 | 58 | ## 1.2.0 59 | 60 | Changes: 61 | - Drop support for ruby 2.4 62 | - Relax prometheus-client to '>= 0.10' 63 | 64 | Housekeeping: 65 | - Update development dependencies 66 | 67 | ## 1.1.0 68 | 69 | Changes: 70 | - Upgrade prometheus-client to '~> 0.10' 71 | 72 | Housekeeping: 73 | - Set target version to 2.6 74 | - Added editorconfig 75 | 76 | ## 1.0.3 77 | 78 | Features: 79 | - can be used with puma 3 or puma 4 80 | 81 | ## 1.0.2 82 | 83 | Bugfixes: 84 | - terminate metrics server without IO errors [#7](https://github.com/harmjanblok/puma-metrics/pull/7) 85 | 86 | ## 1.0.1 87 | 88 | Bugfixes: 89 | - `metrics_url` in `config/puma.rb` should be optional 90 | 91 | ## 1.0.0 92 | 93 | Initial release of the `puma-metrics` gem. 94 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | gemspec 5 | 6 | gem 'bundler', '>= 2.0.0' 7 | gem 'minitest' 8 | gem 'overcommit' 9 | gem 'rake' 10 | gem 'rubocop' 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | puma-metrics (1.4.0) 5 | prometheus-client (>= 0.10) 6 | puma (>= 6.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | ast (2.4.2) 12 | childprocess (5.0.0) 13 | iniparse (1.5.0) 14 | json (2.7.2) 15 | language_server-protocol (3.17.0.3) 16 | minitest (5.22.3) 17 | nio4r (2.7.0) 18 | overcommit (0.63.0) 19 | childprocess (>= 0.6.3, < 6) 20 | iniparse (~> 1.4) 21 | rexml (~> 3.2) 22 | parallel (1.24.0) 23 | parser (3.3.0.5) 24 | ast (~> 2.4.1) 25 | racc 26 | prometheus-client (4.2.2) 27 | puma (6.4.2) 28 | nio4r (~> 2.0) 29 | racc (1.7.3) 30 | rainbow (3.1.1) 31 | rake (13.2.1) 32 | regexp_parser (2.9.0) 33 | rexml (3.2.6) 34 | rubocop (1.63.2) 35 | json (~> 2.3) 36 | language_server-protocol (>= 3.17.0) 37 | parallel (~> 1.10) 38 | parser (>= 3.3.0.2) 39 | rainbow (>= 2.2.2, < 4.0) 40 | regexp_parser (>= 1.8, < 3.0) 41 | rexml (>= 3.2.5, < 4.0) 42 | rubocop-ast (>= 1.31.1, < 2.0) 43 | ruby-progressbar (~> 1.7) 44 | unicode-display_width (>= 2.4.0, < 3.0) 45 | rubocop-ast (1.31.2) 46 | parser (>= 3.3.0.4) 47 | ruby-progressbar (1.13.0) 48 | unicode-display_width (2.5.0) 49 | 50 | PLATFORMS 51 | ruby 52 | 53 | DEPENDENCIES 54 | bundler (>= 2.0.0) 55 | minitest 56 | overcommit 57 | puma-metrics! 58 | rake 59 | rubocop 60 | 61 | BUNDLED WITH 62 | 2.5.6 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 harmjanblok 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # puma-metrics 2 | 3 | [![Release](https://github.com/harmjanblok/puma-metrics/actions/workflows/workflow.yml/badge.svg)](https://github.com/harmjanblok/puma-metrics/actions/workflows/workflow.yml) 4 | 5 | A puma plugin to export Puma's internal statistics as Prometheus metrics. 6 | 7 | 8 | ## Installation 9 | 10 | Add this line to your application's Gemfile: 11 | 12 | ```ruby 13 | gem 'puma-metrics' 14 | ``` 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | Or install it yourself as: 21 | 22 | $ gem install puma-metrics 23 | 24 | 25 | ## Usage 26 | 27 | Add following lines to your puma `config.rb` (see 28 | [Configuration File](https://github.com/puma/puma#configuration-file)): 29 | 30 | ```ruby 31 | # config/puma.rb 32 | # Load the metrics plugin 33 | plugin 'metrics' 34 | 35 | # Bind the metric server to "url". "tcp://" is the only accepted protocol. 36 | # 37 | # The default is "tcp://0.0.0.0:9393". 38 | # metrics_url 'tcp://0.0.0.0:9393' 39 | ``` 40 | 41 | ## Credits 42 | 43 | The gem is inspired by the following projects: 44 | * https://github.com/puma/puma 45 | * https://github.com/puma/puma-heroku 46 | 47 | ## License 48 | 49 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 50 | 51 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rake/testtask' 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << 'test' 8 | t.libs << 'lib' 9 | t.test_files = FileList['test/**/test_*.rb'] 10 | end 11 | 12 | task :server do 13 | require 'puma' 14 | require 'puma/configuration' 15 | require 'puma/events' 16 | require 'puma/plugin/metrics' 17 | 18 | configuration = Puma::Configuration.new do |config| 19 | config.bind 'tcp://127.0.0.1:0' 20 | config.metrics_url 'tcp://127.0.0.1:9494' 21 | config.plugin 'metrics' 22 | config.workers 1 23 | config.app do |_env| 24 | [200, {}, ['hello world']] 25 | end 26 | end 27 | 28 | events = Puma::Events.new $stdout, $stderr 29 | launcher = Puma::Launcher.new(configuration, events: events) 30 | launcher.run 31 | end 32 | 33 | task default: :test 34 | -------------------------------------------------------------------------------- /lib/puma/metrics/app.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'prometheus/client/formats/text' 5 | require 'puma/metrics/parser' 6 | 7 | module Puma 8 | module Metrics 9 | class App 10 | def initialize(launcher) 11 | @launcher = launcher 12 | clustered = (@launcher.options[:workers] || 0) > 0 13 | @parser = Parser.new(clustered: clustered) 14 | end 15 | 16 | def call(_env) 17 | retrieve_and_parse_stats! 18 | [ 19 | 200, 20 | { 'Content-Type' => 'text/plain' }, 21 | [Prometheus::Client::Formats::Text.marshal(Prometheus::Client.registry)] 22 | ] 23 | end 24 | 25 | def retrieve_and_parse_stats! 26 | puma_stats = @launcher.stats 27 | if puma_stats.is_a?(Hash) # Modern Puma outputs stats as a Symbol-keyed Hash 28 | @parser.parse(puma_stats) 29 | else 30 | @parser.parse(JSON.parse(puma_stats, symbolize_names: true)) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/puma/metrics/dsl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Puma 4 | class DSL 5 | def metrics_url(url) 6 | @options[:metrics_url] = url 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/puma/metrics/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'prometheus/client' 4 | 5 | module Puma 6 | module Metrics 7 | class Parser 8 | def initialize(clustered: false) 9 | register_default_metrics 10 | register_clustered_metrics if clustered 11 | end 12 | 13 | def parse(symbol_keyed_stats, labels = {}) 14 | symbol_keyed_stats.each do |key, value| 15 | value.each { |s| parse(s, labels.merge(index: s[:index])) } if key == :worker_status 16 | parse(value, labels) if key == :last_status 17 | update_metric(key, value, labels) 18 | end 19 | end 20 | 21 | private 22 | 23 | def register_clustered_metrics 24 | registry.gauge(:puma_booted_workers, 25 | docstring: 'Number of booted workers') 26 | .set(1) 27 | registry.gauge(:puma_old_workers, 28 | docstring: 'Number of old workers') 29 | .set(0) 30 | end 31 | 32 | def register_default_metrics # rubocop:disable Metrics/MethodLength 33 | registry.gauge(:puma_backlog, 34 | docstring: 'Number of established but unaccepted connections in the backlog', 35 | labels: [:index], 36 | preset_labels: { index: 0 }) 37 | registry.gauge(:puma_running, 38 | docstring: 'Number of running worker threads', 39 | labels: [:index], 40 | preset_labels: { index: 0 }) 41 | registry.gauge(:puma_pool_capacity, 42 | docstring: 'Number of allocatable worker threads', 43 | labels: [:index], 44 | preset_labels: { index: 0 }) 45 | registry.gauge(:puma_max_threads, 46 | docstring: 'Maximum number of worker threads', 47 | labels: [:index], 48 | preset_labels: { index: 0 }) 49 | registry.gauge(:puma_requests_count, 50 | docstring: 'Number of processed requests', 51 | labels: [:index], 52 | preset_labels: { index: 0 }) 53 | registry.gauge(:puma_workers, 54 | docstring: 'Number of configured workers') 55 | .set(1) 56 | end 57 | 58 | def registry 59 | Prometheus::Client.registry 60 | end 61 | 62 | def update_metric(key, value, labels) 63 | return if registry.get("puma_#{key}").nil? 64 | 65 | registry.get("puma_#{key}").set(value, labels: labels) 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/puma/metrics/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Puma 4 | module Metrics 5 | VERSION = '1.4.0' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/puma/plugin/metrics.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puma/metrics/dsl' 4 | 5 | Puma::Plugin.create do 6 | # rubocop:disable Metrics/MethodLength, Metrics/AbcSize 7 | def start(launcher) 8 | str = launcher.options[:metrics_url] || 'tcp://0.0.0.0:9393' 9 | 10 | require 'puma/metrics/app' 11 | 12 | app = Puma::Metrics::App.new launcher 13 | uri = URI.parse str 14 | 15 | metrics = Puma::Server.new app, launcher.events, min_threads: 0, max_threads: 1 16 | 17 | case uri.scheme 18 | when 'tcp' 19 | launcher.log_writer.log "* Starting metrics server on #{str}" 20 | metrics.add_tcp_listener uri.host, uri.port 21 | else 22 | launcher.events.error "Invalid control URI: #{str}" 23 | end 24 | 25 | launcher.events.on_stopped do 26 | metrics.stop(true) unless metrics.shutting_down? 27 | end 28 | 29 | metrics.run 30 | end 31 | # rubocop:enable Metrics/MethodLength, Metrics/AbcSize 32 | end 33 | -------------------------------------------------------------------------------- /puma-metrics.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require 'puma/metrics/version' 7 | 8 | Gem::Specification.new do |spec| 9 | spec.authors = ['Harm-Jan Blok'] 10 | spec.description = 'Puma plugin to export puma stats as prometheus metrics' 11 | spec.homepage = 'https://github.com/harmjanblok/puma-metrics' 12 | spec.license = 'MIT' 13 | spec.name = 'puma-metrics' 14 | spec.require_paths = ['lib'] 15 | spec.summary = spec.description 16 | spec.version = Puma::Metrics::VERSION 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 19 | 20 | spec.metadata['rubygems_mfa_required'] = 'false' 21 | 22 | spec.required_ruby_version = '>= 3.0' 23 | 24 | spec.add_runtime_dependency 'prometheus-client', '>= 0.10' 25 | spec.add_runtime_dependency 'puma', '>= 6.0' 26 | end 27 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'net/http' 4 | require 'prometheus/client' 5 | require 'puma' 6 | require 'puma/configuration' 7 | require 'puma/events' 8 | require 'puma/metrics/parser' 9 | require 'puma/plugin/metrics' 10 | require 'timeout' 11 | 12 | module Helpers 13 | def response 14 | @response ||= Net::HTTP.start uri.host, uri.port do |http| 15 | http.request Net::HTTP::Get.new('/metrics') 16 | end.body 17 | end 18 | 19 | def uri 20 | URI.parse(@configuration.options[:metrics_url] || 'tcp://127.0.0.1:9393') 21 | end 22 | 23 | def start_server(configuration) 24 | @wait, @ready = IO.pipe 25 | 26 | @events = Puma::Events.new 27 | @events.on_booted { @ready << '!' } 28 | 29 | @configuration = configuration 30 | @launcher = Puma::Launcher.new(@configuration, events: @events) 31 | 32 | @launcher_thread = Thread.new do 33 | Thread.current.abort_on_exception = true 34 | @launcher.run 35 | end 36 | wait_booted 37 | end 38 | 39 | def stop_server 40 | Prometheus::Client.instance_eval { @registry = nil } 41 | @launcher.stop 42 | @wait.close 43 | @ready.close 44 | @launcher_thread.join 45 | end 46 | 47 | def assert_response_includes_metrics(metrics) 48 | metrics.each do |metric| 49 | assert_includes response, "# TYPE #{metric[:name]} #{metric[:type]}\n" 50 | metric[:labels].each do |label| 51 | assert_includes response, "#{metric[:name]}#{label} #{metric[:value]}\n" 52 | end 53 | end 54 | end 55 | 56 | def cluster_booted? 57 | worker_status = JSON.parse(Puma.stats)['worker_status'] 58 | 59 | (worker_status.length == @configuration.options[:workers]) && 60 | (worker_status.all? { |w| w.key?('last_status') && w['last_status'].key?('backlog') }) 61 | end 62 | 63 | def wait_booted 64 | @wait.sysread 1 65 | return unless @configuration.options[:workers] > 0 66 | 67 | # Wait for workers to report 'last_status' 68 | Timeout.timeout(15) do 69 | sleep 0.2 until cluster_booted? 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/test_cluster_more.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'helpers' 4 | require 'minitest/autorun' 5 | require 'puma/configuration' 6 | 7 | class TestClusterMore < Minitest::Test 8 | include Helpers 9 | 10 | def setup 11 | start_server(configuration) 12 | end 13 | 14 | def teardown 15 | stop_server 16 | end 17 | 18 | def configuration 19 | Puma::Configuration.new do |config| 20 | config.app do |_env| 21 | [200, {}, ['hello world']] 22 | end 23 | config.bind 'tcp://127.0.0.1:0' 24 | config.plugin 'metrics' 25 | config.quiet 26 | config.threads(0, 16) # default for non MRI 27 | config.workers 2 28 | end 29 | end 30 | 31 | def l 32 | @l ||= ['{index="0"}', '{index="1"}'] 33 | end 34 | 35 | def metrics 36 | [{ name: 'puma_backlog', type: 'gauge', labels: l, value: 0.0 }, 37 | { name: 'puma_booted_workers', type: 'gauge', labels: [], value: 2.0 }, 38 | { name: 'puma_max_threads', type: 'gauge', labels: l, value: 16.0 }, 39 | { name: 'puma_old_workers', type: 'gauge', labels: [], value: 0.0 }, 40 | { name: 'puma_pool_capacity', type: 'gauge', labels: l, value: 16.0 }, 41 | { name: 'puma_requests_count', type: 'gauge', labels: l, value: 0.0 }, 42 | { name: 'puma_running', type: 'gauge', labels: l, value: 0.0 }, 43 | { name: 'puma_workers', type: 'gauge', labels: [], value: 2.0 }] 44 | end 45 | 46 | def test_metrics 47 | assert_response_includes_metrics(metrics) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/test_cluster_one.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'helpers' 4 | require 'minitest/autorun' 5 | require 'puma/configuration' 6 | 7 | class TestClusterOne < Minitest::Test 8 | include Helpers 9 | 10 | def setup 11 | start_server(configuration) 12 | end 13 | 14 | def teardown 15 | stop_server 16 | end 17 | 18 | def configuration 19 | Puma::Configuration.new do |config| 20 | config.app do |_env| 21 | [200, {}, ['hello world']] 22 | end 23 | config.bind 'tcp://127.0.0.1:0' 24 | config.plugin 'metrics' 25 | config.quiet 26 | config.threads(0, 16) # default for non MRI 27 | config.workers 1 28 | end 29 | end 30 | 31 | def l 32 | @l ||= ['{index="0"}'] 33 | end 34 | 35 | def metrics 36 | [{ name: 'puma_backlog', type: 'gauge', labels: l, value: 0.0 }, 37 | { name: 'puma_booted_workers', type: 'gauge', labels: [], value: 1.0 }, 38 | { name: 'puma_max_threads', type: 'gauge', labels: l, value: 16.0 }, 39 | { name: 'puma_old_workers', type: 'gauge', labels: [], value: 0.0 }, 40 | { name: 'puma_pool_capacity', type: 'gauge', labels: l, value: 16.0 }, 41 | { name: 'puma_requests_count', type: 'gauge', labels: l, value: 0.0 }, 42 | { name: 'puma_running', type: 'gauge', labels: l, value: 0.0 }, 43 | { name: 'puma_workers', type: 'gauge', labels: [], value: 1.0 }] 44 | end 45 | 46 | def test_metrics 47 | assert_response_includes_metrics(metrics) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/test_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'helpers' 4 | require 'minitest/autorun' 5 | require 'puma/configuration' 6 | 7 | class TestConfig < Minitest::Test 8 | include Helpers 9 | 10 | def test_default_metrics_url 11 | configuration = Puma::Configuration.new do |config| 12 | config.bind 'tcp://127.0.0.1:0' 13 | config.plugin 'metrics' 14 | config.quiet 15 | config.app { [200, {}, ['hello world']] } 16 | end 17 | 18 | start_server(configuration) 19 | assert_includes response, '# TYPE puma_backlog gauge' 20 | stop_server 21 | end 22 | 23 | def test_plugin_disabled 24 | configuration = Puma::Configuration.new do |config| 25 | config.bind 'tcp://127.0.0.1:0' 26 | config.quiet 27 | config.app { [200, {}, ['hello world']] } 28 | end 29 | 30 | start_server(configuration) 31 | assert_raises(Errno::ECONNREFUSED) { response } 32 | stop_server 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/test_signals.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'helpers' 4 | require 'minitest/autorun' 5 | require 'puma/configuration' 6 | 7 | class TestSignals < Minitest::Test 8 | include Helpers 9 | 10 | def setup 11 | start_server(configuration) 12 | assert_includes response, '# TYPE puma_backlog gauge' 13 | end 14 | 15 | def teardown 16 | stop_server 17 | end 18 | 19 | def configuration 20 | Puma::Configuration.new do |config| 21 | config.bind 'tcp://127.0.0.1:0' 22 | config.plugin 'metrics' 23 | config.quiet 24 | config.app { [200, {}, ['hello world']] } 25 | end 26 | end 27 | 28 | def test_stop 29 | @launcher.stop 30 | sleep 1 31 | assert_raises(Errno::ECONNREFUSED) do 32 | Net::HTTP.start uri.host, uri.port do |http| 33 | http.request Net::HTTP::Get.new('/metrics') 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/test_single.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'helpers' 4 | require 'minitest/autorun' 5 | require 'puma/configuration' 6 | 7 | class TestSingle < Minitest::Test 8 | include Helpers 9 | 10 | def setup 11 | start_server(configuration) 12 | end 13 | 14 | def teardown 15 | stop_server 16 | end 17 | 18 | def configuration 19 | Puma::Configuration.new do |config| 20 | config.app do |_env| 21 | [200, {}, ['hello world']] 22 | end 23 | config.bind 'tcp://127.0.0.1:0' 24 | config.plugin 'metrics' 25 | config.quiet 26 | config.threads(0, 16) # default for non MRI 27 | end 28 | end 29 | 30 | def l 31 | @l ||= ['{index="0"}'] 32 | end 33 | 34 | def metrics 35 | [{ name: 'puma_backlog', type: 'gauge', labels: l, value: 0.0 }, 36 | { name: 'puma_max_threads', type: 'gauge', labels: l, value: 16.0 }, 37 | { name: 'puma_pool_capacity', type: 'gauge', labels: l, value: 16.0 }, 38 | { name: 'puma_requests_count', type: 'gauge', labels: l, value: 0.0 }, 39 | { name: 'puma_running', type: 'gauge', labels: l, value: 0.0 }, 40 | { name: 'puma_workers', type: 'gauge', labels: [], value: 1.0 }] 41 | end 42 | 43 | def test_metrics 44 | assert_response_includes_metrics(metrics) 45 | end 46 | end 47 | --------------------------------------------------------------------------------