puma_log_stats 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0d2365c2fea363f5a57d104816af39c026229ed86df3216935e1199b735aa7e9
4
+ data.tar.gz: 7fbaf83a73e93c3bd0eeb57929b994ec3d44138bd1b4eed0caabece2a75aa808
5
+ SHA512:
6
+ metadata.gz: 18ef30e7de888693c2a1d3df4c0ed1628f812205ce6cdb0eb7813c07275fd22eb9debaf94349d0faff169b0407e854cdb716fede6b8c69e3020484c4874aa837
7
+ data.tar.gz: b8f248a1b327958fffb319b6403e7355e213d221d102a5791bf2f39168399156f64ed520757a4483651275494aa6c42312b75eb9a10a62eb85e95ef0695a130e
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .ruby-version
2
+ Gemfile.lock
3
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in puma_log_stats.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # PumaLogStats
2
+
3
+ Puma plugin to log server stats whenever the number of concurrent requests exceeds a configured threshold.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'puma_log_stats'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install puma_log_stats
21
+
22
+ ## Usage
23
+
24
+ This plugin is loaded using Puma's plugin API. To enable, add a `plugin :log_stats` directive to your Puma config DSL, then configure the `LogStats` object with any additional configuration:
25
+
26
+ ```ruby
27
+ # config/puma.rb
28
+
29
+ plugin :log_stats
30
+ LogStats.threshold = 2
31
+ ```
32
+
33
+ ## Development
34
+
35
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
36
+
37
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wjordan/puma_log_stats.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test)
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'puma_log_stats'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma'
4
+ require 'puma/plugin'
5
+ require 'json'
6
+
7
+ # Puma plugin to log server stats whenever the number of
8
+ # concurrent requests exceeds a configured threshold.
9
+ module LogStats
10
+ class << self
11
+ # Minimum concurrent requests per process that will trigger logging server
12
+ # stats, or nil to disable logging.
13
+ # Default is the max number of threads in the server's thread pool.
14
+ # If this attribute is a Proc, it will be re-evaluated each interval.
15
+ attr_accessor :threshold
16
+ LogStats.threshold = :max
17
+
18
+ # Interval between logging attempts in seconds.
19
+ attr_accessor :interval
20
+ LogStats.interval = 1
21
+
22
+ # Proc to filter backtraces.
23
+ attr_accessor :backtrace_filter
24
+ LogStats.backtrace_filter = ->(bt) { bt }
25
+ end
26
+
27
+ Puma::Plugin.create do
28
+ attr_reader :launcher
29
+
30
+ def start(launcher)
31
+ @launcher = launcher
32
+ launcher.events.register(:state) do |state|
33
+ @state = state
34
+ stats_logger_thread if state == :running
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def stats_logger_thread
41
+ Thread.new do
42
+ if Thread.current.respond_to?(:name=)
43
+ Thread.current.name = 'puma stats logger'
44
+ end
45
+ stats_logger_loop while @state == :running
46
+ end
47
+ end
48
+
49
+ def stats_logger_loop
50
+ sleep LogStats.interval
51
+ return unless server
52
+
53
+ if should_log?
54
+ stats = server_stats
55
+ stats[:threads] = thread_backtraces
56
+ stats[:gc] = GC.stat
57
+ log stats.to_json
58
+ end
59
+ rescue StandardError => e
60
+ log "LogStats failed: #{e}\n #{e.backtrace.join("\n ")}"
61
+ end
62
+
63
+ def log(str)
64
+ launcher.events.log str
65
+ end
66
+
67
+ # Save reference to Server object from the thread-local key.
68
+ def server
69
+ @server ||= Thread.list.map { |t| t[Puma::Server::ThreadLocalKey] }.compact.first
70
+ end
71
+
72
+ STAT_METHODS = %i[backlog running pool_capacity max_threads requests_count].freeze
73
+ def server_stats
74
+ STAT_METHODS.select(&server.method(:respond_to?))
75
+ .map { |name| [name, server.send(name) || 0] }.to_h
76
+ end
77
+
78
+ # True if current server load meets configured threshold.
79
+ def should_log?
80
+ threshold = LogStats.threshold
81
+ threshold = threshold.call if threshold.is_a?(Proc)
82
+ threshold = server.max_threads if threshold == :max
83
+ threshold && (server.max_threads - server.pool_capacity) >= threshold
84
+ end
85
+
86
+ def thread_backtraces
87
+ worker_threads.map do |t|
88
+ name = t.respond_to?(:name) ? t.name : thread.object_id.to_s(36)
89
+ [name, LogStats.backtrace_filter.call(t.backtrace)]
90
+ end.sort.to_h
91
+ end
92
+
93
+ # List all non-idle worker threads in the thread pool.
94
+ def worker_threads
95
+ server.instance_variable_get(:@thread_pool)
96
+ .instance_variable_get(:@workers)
97
+ .reject { |t| t.backtrace.first.match?(/thread_pool\.rb.*sleep/) }
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PumaLogStats
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma_log_stats/version'
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/puma_log_stats/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'puma_log_stats'
7
+ spec.version = PumaLogStats::VERSION
8
+ spec.authors = ['Will Jordan']
9
+ spec.email = ['will@code.org']
10
+
11
+ spec.summary = 'Logs Puma stats when concurrent requests are high.'
12
+ spec.description = 'Puma plugin to log server stats whenever the number ' \
13
+ 'of concurrent requests exceeds a configured threshold.'
14
+ spec.homepage = 'https://github.com/wjordan/puma_log_stats'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
17
+
18
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
19
+
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = 'https://github.com/wjordan/puma_log_stats'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/wjordan/puma_log_stats/CHANGELOG.md'
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ spec.add_dependency 'puma', '>= 2.7', '< 6'
34
+
35
+ spec.add_development_dependency 'minitest'
36
+ spec.add_development_dependency 'rake'
37
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puma_log_stats
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Will Jordan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: puma
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '2.7'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6'
33
+ - !ruby/object:Gem::Dependency
34
+ name: minitest
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ description: Puma plugin to log server stats whenever the number of concurrent requests
62
+ exceeds a configured threshold.
63
+ email:
64
+ - will@code.org
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - ".gitignore"
70
+ - Gemfile
71
+ - README.md
72
+ - Rakefile
73
+ - bin/console
74
+ - bin/setup
75
+ - lib/puma/plugin/log_stats.rb
76
+ - lib/puma_log_stats.rb
77
+ - lib/puma_log_stats/version.rb
78
+ - puma_log_stats.gemspec
79
+ homepage: https://github.com/wjordan/puma_log_stats
80
+ licenses:
81
+ - MIT
82
+ metadata:
83
+ allowed_push_host: https://rubygems.org/
84
+ homepage_uri: https://github.com/wjordan/puma_log_stats
85
+ source_code_uri: https://github.com/wjordan/puma_log_stats
86
+ changelog_uri: https://github.com/wjordan/puma_log_stats/CHANGELOG.md
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 2.3.0
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.0.1
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Logs Puma stats when concurrent requests are high.
106
+ test_files: []