puma_doctor 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +93 -0
- data/Rakefile +1 -0
- data/lib/puma_doctor/capistrano.rb +62 -0
- data/lib/puma_doctor/daemon_template.rb.erb +12 -0
- data/lib/puma_doctor/doctor.rb +67 -0
- data/lib/puma_doctor/logger.rb +23 -0
- data/lib/puma_doctor/version.rb +3 -0
- data/lib/puma_doctor.rb +36 -0
- data/puma_doctor.gemspec +26 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 71aabad56a6b33023c08b80b5a51e3abd7019aac
|
4
|
+
data.tar.gz: 0dbe0a44f19e7dbbdf7d587c8b6ec2562a9da3f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7a575219806300a993916df39ae762cbfd35a013cf93fc2dcf452169a28e09f77e6201d35ebd652e91a2d327f44d0477d7a693c5cd7924a37ae776cbbc95bada
|
7
|
+
data.tar.gz: acb853b60daaedcff2f915e013879b5d8815c7ab1e7742977289e1266f8c0d6cc250d384eb664c2ca833d54a1f505d2f2c353b6bad7b36a3f5e3f95fb5bd79dc
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Alex Krasynskyi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# PumaDoctor
|
2
|
+
|
3
|
+
Inspired by ( https://github.com/schneems/puma_worker_killer ). Idea is to run
|
4
|
+
separate process as a daemon to measure puma memory and restart worker when memory
|
5
|
+
threshold reached.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
### Running from ruby code.
|
10
|
+
To run from your code:
|
11
|
+
|
12
|
+
PumaDoctor.start(frequency: 60, memory_threshold: 2000, puma_pid: 99999)
|
13
|
+
|
14
|
+
This is not very useful in production since it blocks execution, but you can play
|
15
|
+
around with options locally. Available options with defaults are:
|
16
|
+
|
17
|
+
frequency: 60 # Interval in seconds
|
18
|
+
puma_pid_file: 'puma.pid' # Location of puma pid file
|
19
|
+
memory_threshold: 4000 # Amount in MB
|
20
|
+
log_file: 'puma_doctor.log' # Name and location of log file
|
21
|
+
|
22
|
+
### Running as a daemon.
|
23
|
+
|
24
|
+
To run as daemon you can create file with content below(Ex.: doctor.rb)
|
25
|
+
|
26
|
+
require 'puma_doctor'
|
27
|
+
require 'daemons'
|
28
|
+
|
29
|
+
pid_dir = '../' # Path to directory to store pid.
|
30
|
+
Daemons.run_proc('puma_doctor', { dir: pid_dir }) do
|
31
|
+
PumaDoctor.start(frequency: 60, memory_threshold: 1000)
|
32
|
+
end
|
33
|
+
|
34
|
+
Then control it with(for more details visit https://github.com/thuehlinger/daemons):
|
35
|
+
|
36
|
+
bundle exec ruby doctor.rb start
|
37
|
+
bundle exec ruby doctor.rb stop
|
38
|
+
bundle exec ruby doctor.rb restart
|
39
|
+
|
40
|
+
### Using with capistrano.
|
41
|
+
|
42
|
+
Probably the easiest way to run `puma_doctor` in production is to use `capistrano`. Require script in `Capfile`:
|
43
|
+
|
44
|
+
require 'puma_doctor/capistrano'
|
45
|
+
|
46
|
+
This will add hook to start/restart daemon on `after deploy:finished`. If you want to start/stop from capistrano manually - this tasks are available:
|
47
|
+
|
48
|
+
cap puma_doctor:check # Check if config file exixts on server
|
49
|
+
cap puma_doctor:config # Config daemon
|
50
|
+
cap puma_doctor:restart # Restart daemon
|
51
|
+
cap puma_doctor:start # Start daemon
|
52
|
+
cap puma_doctor:stop # Stop daemon
|
53
|
+
|
54
|
+
Available options with defaults:
|
55
|
+
|
56
|
+
set :puma_doctor_pid, -> { File.join(shared_path, 'tmp', 'pids', 'puma_doctor.pid') }
|
57
|
+
set :puma_doctor_frequency, 30 #seconds
|
58
|
+
set :puma_doctor_memory_threshold, 4000 #mb
|
59
|
+
set :puma_doctor_daemon_file, -> { File.join(shared_path, 'puma_doctor_daemon.rb') }
|
60
|
+
set :puma_doctor_log_file, -> { File.join(shared_path, 'log', 'puma_doctor.log') }
|
61
|
+
set :puma_pid, -> { File.join(shared_path, 'tmp', 'pids', 'puma.pid') }
|
62
|
+
|
63
|
+
|
64
|
+
### Logging
|
65
|
+
|
66
|
+
You can always see what `puma_doctor` is doing by reading logs.
|
67
|
+
|
68
|
+
|
69
|
+
## Installation
|
70
|
+
|
71
|
+
Add this line to your application's Gemfile:
|
72
|
+
|
73
|
+
gem 'puma_doctor'
|
74
|
+
|
75
|
+
And then execute:
|
76
|
+
|
77
|
+
$ bundle
|
78
|
+
|
79
|
+
Or install it yourself as:
|
80
|
+
|
81
|
+
$ gem install puma_doctor
|
82
|
+
|
83
|
+
## TODO
|
84
|
+
|
85
|
+
Test
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
1. Fork it ( http://github.com/spilin/puma_doctor/fork )
|
90
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
91
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
92
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
93
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
namespace :load do
|
2
|
+
task :defaults do
|
3
|
+
set :puma_doctor_pid, -> { File.join(shared_path, 'tmp', 'pids', 'puma_doctor.pid') }
|
4
|
+
set :puma_doctor_frequency, 30 #seconds
|
5
|
+
set :puma_doctor_memory_threshold, 4000 #mb
|
6
|
+
set :puma_doctor_daemon_file, -> { File.join(shared_path, 'puma_doctor_daemon.rb') }
|
7
|
+
set :puma_doctor_log_file, -> { File.join(shared_path, 'log', 'puma_doctor.log') }
|
8
|
+
set :puma_pid, -> { File.join(shared_path, 'tmp', 'pids', 'puma.pid') }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace :puma_doctor do
|
13
|
+
desc 'Config daemon. Generate and send puma_doctor.rb'
|
14
|
+
task :config do
|
15
|
+
on roles(:app), in: :sequence, wait: 5 do
|
16
|
+
path = File.expand_path("../daemon_template.rb.erb", __FILE__)
|
17
|
+
if File.file?(path)
|
18
|
+
erb = File.read(path)
|
19
|
+
upload! StringIO.new(ERB.new(erb).result(binding)), fetch(:puma_doctor_daemon_file)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Start daemon'
|
25
|
+
task :start do
|
26
|
+
on roles(:app), in: :sequence, wait: 5 do
|
27
|
+
within release_path do
|
28
|
+
invoke 'puma_doctor:check'
|
29
|
+
execute :bundle, :exec, :ruby, fetch(:puma_doctor_daemon_file), 'start'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Stop daemon'
|
35
|
+
task :stop do
|
36
|
+
on roles(:app), in: :sequence, wait: 5 do
|
37
|
+
within release_path do
|
38
|
+
invoke 'puma_doctor:check'
|
39
|
+
execute :bundle, :exec, :ruby, fetch(:puma_doctor_daemon_file), 'stop'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Restart daemon'
|
45
|
+
task :restart do
|
46
|
+
on roles(:app), in: :sequence, wait: 5 do
|
47
|
+
within release_path do
|
48
|
+
invoke 'puma_doctor:check'
|
49
|
+
execute :bundle, :exec, :ruby, fetch(:puma_doctor_daemon_file), 'restart'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Check if config file exixts on server. If not - create and upload one.'
|
55
|
+
task :check do
|
56
|
+
on roles(:app), in: :sequence, wait: 5 do
|
57
|
+
invoke 'puma_doctor:config' unless test "[ -f #{fetch(:puma_doctor_daemon_file)} ]"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
after 'deploy:finished', 'puma_doctor:restart'
|
62
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'puma_doctor'
|
2
|
+
require 'daemons'
|
3
|
+
|
4
|
+
Daemons.run_proc('puma_doctor', { dir: '<%= File.dirname(fetch(:puma_doctor_pid)) %>' }) do
|
5
|
+
options = {
|
6
|
+
frequency: <%= fetch(:puma_doctor_frequency) %>,
|
7
|
+
memory_threshold: <%= fetch(:puma_doctor_memory_threshold) %>,
|
8
|
+
puma_pid_file: '<%= fetch(:puma_pid) %>',
|
9
|
+
log_file: '<%= fetch(:puma_doctor_log_file) %>'
|
10
|
+
}
|
11
|
+
PumaDoctor.start(options)
|
12
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module PumaDoctor
|
2
|
+
class Doctor
|
3
|
+
def initialize(options = {})
|
4
|
+
@memory_threshold = options[:memory_threshold]
|
5
|
+
@puma_pid_file = options[:puma_pid_file]
|
6
|
+
@puma_pid = options[:puma_pid] && options[:puma_pid].to_i
|
7
|
+
@logger = options[:logger]
|
8
|
+
end
|
9
|
+
|
10
|
+
def examine
|
11
|
+
@master_pid = get_master_pid(@master_pid)
|
12
|
+
return if @master_pid.nil?
|
13
|
+
workers = get_workers(@master_pid) # worker pids with size, last one is the largest one
|
14
|
+
used_memory = workers.inject(0) {|memo, v| memo += v.last } + GetProcessMem.new(@master_pid).mb
|
15
|
+
logger.info "[Puma Doctor] Total memory used: #{used_memory} mb. Workers online: #{workers.size}"
|
16
|
+
if used_memory > @memory_threshold
|
17
|
+
kill_largest_worker(workers)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def get_master_pid(current_puma_pid)
|
24
|
+
if current_puma_pid && process_is_running?(current_puma_pid)
|
25
|
+
current_puma_pid
|
26
|
+
elsif current_puma_pid && (@puma_pid_file.nil? || !File.exists?(@puma_pid_file))
|
27
|
+
logger.warn "[Puma Doctor] Master pid is no longer represents running process.
|
28
|
+
Reload failed because pid file is not set or invalid(File: '#{@puma_pid_file}')"
|
29
|
+
nil
|
30
|
+
elsif current_puma_pid && (current_puma_pid = File.read(@puma_pid_file).to_i) && process_is_running?(current_puma_pid)
|
31
|
+
logger.warn "[Puma Doctor] Master pid is no longer represents running process. Successfully Reloaded pid file."
|
32
|
+
current_puma_pid
|
33
|
+
elsif @puma_pid && process_is_running?(@puma_pid)
|
34
|
+
current_puma_pid = @puma_pid
|
35
|
+
elsif @puma_pid_file && File.exists?(@puma_pid_file) && process_is_running?(current_puma_pid = File.read(@puma_pid_file).to_i)
|
36
|
+
current_puma_pid
|
37
|
+
else
|
38
|
+
logger.warn "[Puma Doctor] Puma master pidfile is not found"
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_workers(puma_pid)
|
44
|
+
`pgrep -P #{puma_pid} -d ','`.split(',').compact.map do |pid|
|
45
|
+
[pid.to_i, GetProcessMem.new(pid).mb]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def kill_largest_worker(workers)
|
50
|
+
pid, memory_used = workers.max_by {|a| a[1]}
|
51
|
+
Process.kill('TERM', pid)
|
52
|
+
logger.info "[Puma Doctor] Doctor killed worker(#{pid}).It was using #{memory_used} mb. Workers online: #{workers.size - 1}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_is_running?(pid)
|
56
|
+
Process.getpgid(pid)
|
57
|
+
true
|
58
|
+
rescue Errno::ESRCH
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
def logger
|
63
|
+
@logger
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module PumaDoctor
|
4
|
+
class Logger
|
5
|
+
def initialize(options = {})
|
6
|
+
@logger = ::Logger.new(options[:log_file])
|
7
|
+
@logger.level = options[:log_level] || ::Logger::INFO
|
8
|
+
end
|
9
|
+
|
10
|
+
def info(text)
|
11
|
+
@logger.info(text)
|
12
|
+
end
|
13
|
+
|
14
|
+
def warn(text)
|
15
|
+
@logger.warn(text)
|
16
|
+
end
|
17
|
+
|
18
|
+
def log_start
|
19
|
+
@logger.info "[Puma Doctor] Starting..."
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/puma_doctor.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "puma_doctor/version"
|
2
|
+
require "get_process_mem"
|
3
|
+
|
4
|
+
require 'puma_doctor/doctor'
|
5
|
+
require 'puma_doctor/logger'
|
6
|
+
|
7
|
+
module PumaDoctor
|
8
|
+
extend self
|
9
|
+
|
10
|
+
attr_accessor :frequency, :pid_file, :puma_pid, :puma_pid_file, :memory_threshold, :log_file
|
11
|
+
attr_reader :logger
|
12
|
+
self.frequency = 60 # seconds
|
13
|
+
self.pid_file = 'puma_doctor.pid'
|
14
|
+
self.puma_pid_file = 'puma.pid'
|
15
|
+
self.memory_threshold = 4000 # mb
|
16
|
+
self.log_file = 'puma_doctor.log'
|
17
|
+
|
18
|
+
def start(options = {})
|
19
|
+
@logger = ::PumaDoctor::Logger.new(log_file: options[:log_file] || self.log_file, log_level: options[:log_level])
|
20
|
+
@logger.log_start
|
21
|
+
doctor = Doctor.new(default_options.merge(options).merge(logger: @logger))
|
22
|
+
loop do
|
23
|
+
doctor.examine
|
24
|
+
sleep(options[:frequency] || self.frequency)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_options
|
29
|
+
{
|
30
|
+
memory_threshold: self.memory_threshold,
|
31
|
+
puma_pid_file: self.puma_pid_file,
|
32
|
+
puma_pid: self.puma_pid
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/puma_doctor.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'puma_doctor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "puma_doctor"
|
8
|
+
spec.version = PumaDoctor::VERSION
|
9
|
+
spec.authors = ["Alex Krasynskyi"]
|
10
|
+
spec.email = ["lyoshakr@gmail.com"]
|
11
|
+
spec.summary = %q{Process to keep your puma workers healthy.}
|
12
|
+
spec.description = %q{Kills largest worker. Runs seperate daemon, managed with sidekiq.}
|
13
|
+
spec.homepage = "https://github.com/spilin/puma_doctor"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = []
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "daemons"
|
22
|
+
spec.add_dependency "get_process_mem"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: puma_doctor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Krasynskyi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: daemons
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: get_process_mem
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Kills largest worker. Runs seperate daemon, managed with sidekiq.
|
70
|
+
email:
|
71
|
+
- lyoshakr@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/puma_doctor.rb
|
82
|
+
- lib/puma_doctor/capistrano.rb
|
83
|
+
- lib/puma_doctor/daemon_template.rb.erb
|
84
|
+
- lib/puma_doctor/doctor.rb
|
85
|
+
- lib/puma_doctor/logger.rb
|
86
|
+
- lib/puma_doctor/version.rb
|
87
|
+
- puma_doctor.gemspec
|
88
|
+
homepage: https://github.com/spilin/puma_doctor
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.2.0
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Process to keep your puma workers healthy.
|
112
|
+
test_files: []
|