puma_auto_tune 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/README.md +200 -0
- data/Rakefile +15 -0
- data/lib/puma_auto_tune.rb +44 -0
- data/lib/puma_auto_tune/defaults/ram/hooks.rb +52 -0
- data/lib/puma_auto_tune/defaults/ram/wrappers.rb +19 -0
- data/lib/puma_auto_tune/hook.rb +60 -0
- data/lib/puma_auto_tune/master.rb +45 -0
- data/lib/puma_auto_tune/memory.rb +41 -0
- data/lib/puma_auto_tune/version.rb +3 -0
- data/lib/puma_auto_tune/worker.rb +35 -0
- data/my.log +0 -0
- data/puma_auto_tune.gemspec +25 -0
- data/test/fixtures/app.ru +14 -0
- data/test/fixtures/config.rb +11 -0
- data/test/puma_auto_tune_test.rb +15 -0
- data/test/test_helper.rb +50 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ae138aad2de4355bff1e6ca326819e19e314833f
|
4
|
+
data.tar.gz: c4a2411f07991c426e532929e6822167bb18c671
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0232c6f1ed6ba0ce7c4070758c2a4d5fbc4bef59224183afe867c7659a1a74ab9c5f56bb0d78ac1279793fde4681b2048e516444e893d8e3d83c7a8e7b9b4471
|
7
|
+
data.tar.gz: 9f8812bb72f44646c69ef51ba7cec68c49bc37cdc95aa9a3cd9e51f8802916872f275279c6defa04c036598caa4201908126263410125e717a37a8f3b3940455
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# Puma Auto Tune
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/schneems/puma_auto_tune.png?branch=master)](https://travis-ci.org/schneems/puma_auto_tune)
|
4
|
+
|
5
|
+
## What
|
6
|
+
|
7
|
+
Performance without the (T)pain: `puma_auto_tune` will automatically adjust the number of [puma](https://github.com/puma/puma) workers to optimize the performance of your Ruby web application.
|
8
|
+
|
9
|
+
## How
|
10
|
+
|
11
|
+
Puma is a web server that allows you to adjust the amount of processes and threads it uses to process requests. At a very simple level the more processes and threads you have the more requests you can process concurrently. However this comes at a cost, more processes means more RAM and more threads means more CPU usage. You want to get as close to maxing out your resources without going over.
|
12
|
+
|
13
|
+
The amount of memory and CPU your program consumes is also a factor of your code, as well as the amount of load it is under. Larger applications require more RAM. More requests mean more Ruby objects are created and garbage collected as your application generates web pages. Because of these factors, there is no one size fits all number for workers and threads, that's where Puma Auto Tune comes in.
|
14
|
+
|
15
|
+
Run Puma Auto Tune in production under load, or in staging while simulating load with tools like [siege](http://www.joedog.org/siege-home/), [blitz.io](https://www.blitz.io/), or [flood.io](https://flood.io/) for a long enough time and we will compute and set your application numbers to maximize concurrent requests without going over your system limits.
|
16
|
+
|
17
|
+
Currently Puma Auto Tune will optimize the number of workers (processes) based on RAM.
|
18
|
+
|
19
|
+
## Install
|
20
|
+
|
21
|
+
In your `Gemfile` add:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'puma_auto_tune'
|
25
|
+
```
|
26
|
+
|
27
|
+
Then run `$ bundle install`.
|
28
|
+
|
29
|
+
## Use
|
30
|
+
|
31
|
+
In your application call:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
PumaAutoTune.start
|
35
|
+
```
|
36
|
+
|
37
|
+
In Rails you could place this in an initializer such as `config/initializers/puma_auto_tune.rb`.
|
38
|
+
|
39
|
+
Puma Auto Tune will attempt to find an ideal number of workers for your application.
|
40
|
+
|
41
|
+
|
42
|
+
## Config
|
43
|
+
|
44
|
+
You will need to configure your Puma Auto Tune to be aware of the maximum amount of RAM it can use.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
PumaAutoTune.config do |config|
|
48
|
+
config.ram = 512 # mb: available on system
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
The default is `512` which matches the amount of ram available on a Heroku dyno. There are a few other advanced config options:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
PumaAutoTune.config do |config|
|
56
|
+
config.ram = 1024 # mb: available on system
|
57
|
+
config.frequency = 20 # seconds: the duration to check memory usage
|
58
|
+
config.reap_duration = 30 # seconds: how long `reap_cycle` will be run for
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
To see defaults check out [puma_auto_tune.rb](lib/puma_auto_tune/puma_auto_tune.rb)
|
63
|
+
|
64
|
+
|
65
|
+
## Hitting the Sweet Spot
|
66
|
+
|
67
|
+
Puma Auto Tune is designed to tune the number of workers for a given application while it is running. Once you restart the program the tuning must start over. Once the algorithm has found the "sweet spot" you can maximize your application throughput by manually setting the number of `workers` that puma starts with. To help you do this Puma Auto Tune outputs semi-regular logs with formatted values.
|
68
|
+
|
69
|
+
```
|
70
|
+
puma.resource_ram_mb=476.6328125 puma.current_cluster_size=5
|
71
|
+
```
|
72
|
+
|
73
|
+
You can use a service such as [librato](https://metrics.librato.com/) to pull values out of your logs and graph them. When you see over time that your server settles on a given `cluster_size` you should set this as your default `puma -w $PUMA_WORKERS` if you're using the CLI to start your app or if you're using a `config/puma.rb` file:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
workers Integer(ENV['PUMA_WORKERS'] || 3)
|
77
|
+
```
|
78
|
+
|
79
|
+
## Puma Worker Killer
|
80
|
+
|
81
|
+
Do not use with `puma_worker_killer` gem. Puma Auto Tune takes care of memory leaks in addition to tuning your puma workers.
|
82
|
+
|
83
|
+
|
84
|
+
## How it Works: Tuning Algorithm (RAM)
|
85
|
+
|
86
|
+
Simple by default, custom for true Puma hackers. The best way to think of the tuner is to start with the different states of memory consumption Puma can be under:
|
87
|
+
|
88
|
+
- Unused RAM: we can add a worker
|
89
|
+
- Memory leak (too much RAM usage): we should restart a worker
|
90
|
+
- Too much RAM usage: we can remove a worker
|
91
|
+
- Just right: No need to scale up or down.
|
92
|
+
|
93
|
+
The algorithm will periodically get the total memory used by Puma and take action appropriately.
|
94
|
+
|
95
|
+
#### Memory States: Unused RAM
|
96
|
+
|
97
|
+
The memory of the smallest worker is recorded. If adding another worker does not put the total memory over the threshold then one will be added.
|
98
|
+
|
99
|
+
#### Memory States: Memory Leak (too much RAM usage)
|
100
|
+
|
101
|
+
When the amount of memory is more than that on the system, we assume a memory leak and restart the largest worker. This will trigger a check to determine if the result was due to a memory leak or because we have too many workers.
|
102
|
+
|
103
|
+
#### Memory States: Too much RAM Usage
|
104
|
+
|
105
|
+
After a worker has been restarted we will aggressively check for memory usage for a fixed period of time, default is 90 seconds(`PumaAutoTune.reap_reap_duration`). If memory goes over the limit, it is assumed that the cause is due to excess workers. The number of workers will be decreased by one. Puma Auto Tune will record the number of total workers that were present when we went over and set this as a new maximum worker number. After removing a process, Puma Auto Tune again checks for memory overages for the same duration and continues to decrement the number of workers until the total memory consumed is under the maximum.
|
106
|
+
|
107
|
+
#### Memory States: Just Right
|
108
|
+
|
109
|
+
Periodically the tuner will wake up and take note of memory usage. If it cannot scale up, and doesn't need to scale down it goes back to sleep.
|
110
|
+
|
111
|
+
## Customizing the Algorithm
|
112
|
+
|
113
|
+
Here's the fun part. You can write your own algorithm using the included hook system. The default algorithm is implemented as a series of [pre-defined hooks](lib/puma_auto_tune/defaults/ram/hooks.rb).
|
114
|
+
|
115
|
+
You can over-write one or more of the hooks to add custom behavior. To define hooks call:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
PumaAutoTune.hooks(:ram) do |auto|
|
119
|
+
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
Each hook has a name and can be over-written by calling `set` and passing in the symbol of the hook you wish to over-write. These are the default RAM hooks:
|
124
|
+
|
125
|
+
- `:cycle`
|
126
|
+
- `:reap_cycle`
|
127
|
+
- `:out_of_memory`
|
128
|
+
- `:under_memory`
|
129
|
+
- `:add_worker`
|
130
|
+
- `:remove_worker`
|
131
|
+
|
132
|
+
|
133
|
+
Once you have the hook object you can use the `call` method to jump to other hooks.
|
134
|
+
|
135
|
+
### Cycle
|
136
|
+
|
137
|
+
This is the main event loop of your program. This code will be called every `PumaAutoTune.frequency` seconds. To over-write you can do this:
|
138
|
+
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
PumaAutoTune.hooks(:ram) do |auto|
|
142
|
+
auto.set(:cycle) do |memory, master, workers|
|
143
|
+
if memory > PumaAutoTune.ram # mb
|
144
|
+
auto.call(:out_of_memory)
|
145
|
+
else
|
146
|
+
auto.call(:under_memory) if memory + workers.last.memory
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### Reap Cycle
|
153
|
+
|
154
|
+
When you think you might run out of memory call the `reap_cycle`. The code in this hook will be called in a loop for `PumaAutoTune.reap_duration` seconds.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
PumaAutoTune.hooks do |auto|
|
158
|
+
auto.set(:reap_cycle) do |memory, master, workers|
|
159
|
+
if memory > PumaAutoTune.ram
|
160
|
+
auto.call(:remove_worker)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
## Add Worker
|
167
|
+
|
168
|
+
Bumps up the worker size by one.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
PumaAutoTune.hooks do |auto|
|
172
|
+
auto.set(:add_worker) do |memory, master, workers|
|
173
|
+
auto.log "Cluster too small. Resizing to add one more worker"
|
174
|
+
master.add_worker
|
175
|
+
auto.call(:reap_cycle)
|
176
|
+
e
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
Here we're calling `:reap_cycle` just in case we accidentally went over our memory limit after the increase.
|
181
|
+
|
182
|
+
## Remove Worker
|
183
|
+
|
184
|
+
Removes a worker. When `remove_worker` is called it will automatically set `PumaAutoTune.max_workers` to be one less than the current number of workers.
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
PumaAutoTune.hooks do |hook|
|
188
|
+
auto.set(:remove_worker) do |memory, master, workers|
|
189
|
+
auto.log "Cluster too large. Resizing to remove one worker"
|
190
|
+
master.remove_worker
|
191
|
+
auto.call(:reap_cycle)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
In case removing one worker wasn't enough we call `reap_cycle` again. Once a worker has been flagged with `restart` it will report zero RAM usage even if it has not completely terminated.
|
197
|
+
|
198
|
+
## License
|
199
|
+
|
200
|
+
MIT
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
require 'rake'
|
6
|
+
require 'rake/testtask'
|
7
|
+
|
8
|
+
task :default => [:test]
|
9
|
+
|
10
|
+
test_task = Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.libs << 'test'
|
13
|
+
t.pattern = 'test/**/*_test.rb'
|
14
|
+
t.verbose = false
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'get_process_mem'
|
2
|
+
|
3
|
+
module PumaAutoTune; end
|
4
|
+
|
5
|
+
require 'puma_auto_tune/version'
|
6
|
+
require 'puma_auto_tune/master'
|
7
|
+
require 'puma_auto_tune/worker'
|
8
|
+
require 'puma_auto_tune/memory'
|
9
|
+
|
10
|
+
|
11
|
+
module PumaAutoTune
|
12
|
+
INFINITY = 1/0.0
|
13
|
+
RESOURCES = { ram: PumaAutoTune::Memory.new }
|
14
|
+
|
15
|
+
extend self
|
16
|
+
|
17
|
+
attr_accessor :ram, :max_workers, :frequency, :reap_duration
|
18
|
+
self.ram = 512 # mb
|
19
|
+
self.max_workers = INFINITY
|
20
|
+
self.frequency = 10 # seconds
|
21
|
+
self.reap_duration = 90 # seconds
|
22
|
+
|
23
|
+
|
24
|
+
def self.config
|
25
|
+
yield self
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.hooks(name = nil, resource = nil, &block)
|
30
|
+
@hooks ||= {}
|
31
|
+
return @hooks if name.nil?
|
32
|
+
resource ||= RESOURCES[name] || raise("no default resource specified for #{name.inspect}")
|
33
|
+
@hooks[name] ||= Hook.new(resource)
|
34
|
+
block.call(@hooks[name]) if block
|
35
|
+
@hooks[name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
hooks.map {|name, hook| hook.auto_cycle}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
require 'puma_auto_tune/hook'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
## This is the default algorithm
|
2
|
+
PumaAutoTune.hooks(:ram) do |auto|
|
3
|
+
# Runs in a continual loop controlled by PumaAutoTune.frequency
|
4
|
+
auto.set(:cycle) do |memory, master, workers|
|
5
|
+
if memory > PumaAutoTune.ram # mb
|
6
|
+
auto.call(:out_of_memory)
|
7
|
+
else
|
8
|
+
auto.call(:under_memory) if memory + workers.last.memory
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Called repeatedly for `PumaAutoTune.reap_duration`.
|
13
|
+
# call when you think you may have too many workers
|
14
|
+
auto.set(:reap_cycle) do |memory, master, workers|
|
15
|
+
if memory > PumaAutoTune.ram
|
16
|
+
auto.call(:remove_worker)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Called when puma is using too much memory
|
21
|
+
auto.set(:out_of_memory) do |memory, master, workers|
|
22
|
+
largest_worker = workers.last # ascending worker size
|
23
|
+
auto.log "Potential memory leak. Reaping largest worker", largest_worker_memory_mb: largest_worker.memory
|
24
|
+
largest_worker.restart
|
25
|
+
auto.call(:reap_cycle)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Called when puma is not using all available memory
|
29
|
+
# PumaAutoTune.max_workers is tracked automatically by `remove_worker`
|
30
|
+
auto.set(:under_memory) do |memory, master, workers|
|
31
|
+
theoretical_max_mb = memory + workers.first.memory # assending worker size
|
32
|
+
if theoretical_max_mb < PumaAutoTune.ram && workers.size + 1 < PumaAutoTune.max_workers
|
33
|
+
auto.call(:add_worker)
|
34
|
+
else
|
35
|
+
auto.log "All is well"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Called to add an extra worker
|
40
|
+
auto.set(:add_worker) do |memory, master, workers|
|
41
|
+
auto.log "Cluster too small. Resizing to add one more worker"
|
42
|
+
master.add_worker
|
43
|
+
auto.call(:reap_cycle)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Called to remove 1 worker from pool. Sets maximum size
|
47
|
+
auto.set(:remove_worker) do |memory, master, workers|
|
48
|
+
auto.log "Cluster too large. Resizing to remove one worker"
|
49
|
+
master.remove_worker
|
50
|
+
auto.call(:reap_cycle)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
PumaAutoTune.hooks(:ram) do |auto|
|
2
|
+
auto.wrap(:reap_cycle) do |block|
|
3
|
+
Proc.new do |resource, master, workers|
|
4
|
+
ends_at = Time.now - PumaAutoTune.reap_duration
|
5
|
+
while Time.now < ends_at
|
6
|
+
sleep 1
|
7
|
+
block.call(*auto.args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
auto.wrap(:remove_worker) do |block|
|
13
|
+
Proc.new do |resource, master, workers|
|
14
|
+
resource.reset
|
15
|
+
PumaAutoTune.max_workers = workers.size - 1
|
16
|
+
block.call(*auto.args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module PumaAutoTune
|
2
|
+
class Hook
|
3
|
+
|
4
|
+
def initialize(resource)
|
5
|
+
@resource = resource
|
6
|
+
@started = Time.now
|
7
|
+
@hooks = {}
|
8
|
+
@wraps = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def define_hook(name, &block)
|
12
|
+
wrap_hook(name)
|
13
|
+
@hooks[name] = block
|
14
|
+
end
|
15
|
+
alias :set :define_hook
|
16
|
+
|
17
|
+
def call(name)
|
18
|
+
hook = @hooks[name] or raise "No such hook #{name.inspect}. Available: #{@hooks.keys.inspect}"
|
19
|
+
hook.call(self.args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def wrap_hook(name, &block)
|
23
|
+
if block
|
24
|
+
@wraps[name] = block
|
25
|
+
else
|
26
|
+
if wrap = @wraps[name]
|
27
|
+
@hooks[name] = wrap.call(@hooks[name])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias :wrap :wrap_hook
|
32
|
+
|
33
|
+
def auto_cycle
|
34
|
+
Thread.new do
|
35
|
+
loop do
|
36
|
+
sleep PumaAutoTune.frequency
|
37
|
+
call(:cycle) if @resource.master.running?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def log(msg, options = {})
|
43
|
+
elapsed = (Time.now - @started).ceil
|
44
|
+
msg = ["PumaAutoTune (#{elapsed}s): #{msg}"]
|
45
|
+
|
46
|
+
options[@resource.name] = @resource.amount
|
47
|
+
options["current_cluster_size"] = @resource.workers.size
|
48
|
+
options.each { |k, v| msg << "puma.#{k.to_s.downcase}=#{v}" }
|
49
|
+
puts msg.join(" ")
|
50
|
+
end
|
51
|
+
|
52
|
+
def args
|
53
|
+
@resource.reset
|
54
|
+
[@resource.amount, @resource.master, @resource.workers]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
require 'puma_auto_tune/defaults/ram/wrappers'
|
60
|
+
require 'puma_auto_tune/defaults/ram/hooks'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module PumaAutoTune
|
2
|
+
class Master
|
3
|
+
def initialize(master = nil)
|
4
|
+
@master = master || get_master
|
5
|
+
end
|
6
|
+
|
7
|
+
def running?
|
8
|
+
@master && workers.any?
|
9
|
+
end
|
10
|
+
|
11
|
+
# https://github.com/puma/puma/blob/master/docs/signals.md#puma-signals
|
12
|
+
def remove_worker
|
13
|
+
send_signal("TTOU")
|
14
|
+
end
|
15
|
+
|
16
|
+
# https://github.com/puma/puma/blob/master/docs/signals.md#puma-signals
|
17
|
+
def add_worker
|
18
|
+
send_signal("TTIN")
|
19
|
+
end
|
20
|
+
|
21
|
+
# less cryptic interface
|
22
|
+
def send_signal(signal, pid = Process.pid)
|
23
|
+
Process.kill(signal, pid)
|
24
|
+
end
|
25
|
+
|
26
|
+
def memory
|
27
|
+
@memory
|
28
|
+
end
|
29
|
+
alias :mb :memory
|
30
|
+
|
31
|
+
def get_memory
|
32
|
+
@memory = ::GetProcessMem.new(Process.pid).mb
|
33
|
+
end
|
34
|
+
|
35
|
+
def workers
|
36
|
+
@master.instance_variable_get("@workers").map {|w| PumaAutoTune::Worker.new(w) }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def get_master
|
42
|
+
ObjectSpace.each_object(Puma::Cluster).map { |obj| obj }.first if defined?(Puma::Cluster)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module PumaAutoTune
|
4
|
+
|
5
|
+
class Memory
|
6
|
+
attr_accessor :master, :workers
|
7
|
+
|
8
|
+
def initialize(master = PumaAutoTune::Master.new)
|
9
|
+
@master = master
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
"resource_ram_mb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def amount
|
17
|
+
@mb ||= begin
|
18
|
+
worker_memory = workers.map {|w| w.memory }.inject(&:+) || 0
|
19
|
+
worker_memory + @master.get_memory
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def largest_worker
|
24
|
+
workers.last
|
25
|
+
end
|
26
|
+
|
27
|
+
def smallest_worker
|
28
|
+
workers.first
|
29
|
+
end
|
30
|
+
|
31
|
+
def workers
|
32
|
+
workers ||= @master.workers.sort_by! {|w| w.get_memory }
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset
|
36
|
+
raise "must set master" unless @master
|
37
|
+
@workers = nil
|
38
|
+
@mb = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module PumaAutoTune
|
2
|
+
class Worker
|
3
|
+
|
4
|
+
def initialize(worker)
|
5
|
+
@worker = worker
|
6
|
+
end
|
7
|
+
|
8
|
+
def memory
|
9
|
+
@memory || get_memory
|
10
|
+
end
|
11
|
+
alias :mb :memory
|
12
|
+
|
13
|
+
def get_memory
|
14
|
+
@memory = if restarting?
|
15
|
+
0
|
16
|
+
else
|
17
|
+
::GetProcessMem.new(self.pid).mb
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def restarting?
|
22
|
+
@restarting
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def restart
|
27
|
+
@restarting = true
|
28
|
+
@worker.term
|
29
|
+
end
|
30
|
+
|
31
|
+
def pid
|
32
|
+
@worker.pid
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/my.log
ADDED
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'puma_auto_tune/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "puma_auto_tune"
|
8
|
+
gem.version = PumaAutoTune::VERSION
|
9
|
+
gem.authors = ["Richard Schneeman"]
|
10
|
+
gem.email = ["richard.schneeman+rubygems@gmail.com"]
|
11
|
+
gem.description = %q{ Puma performance without all the (T)pain }
|
12
|
+
gem.summary = %q{ }
|
13
|
+
gem.homepage = "https://github.com/schneems/puma_auto_tune"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
|
22
|
+
gem.add_dependency "puma", "~> 2.7"
|
23
|
+
gem.add_dependency "get_process_mem", "~> 0"
|
24
|
+
gem.add_development_dependency "rake", "~> 10.1"
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/server'
|
3
|
+
|
4
|
+
run Proc.new {|env| [200, {}, ['Hello World']] }
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
require 'puma_auto_tune'
|
9
|
+
|
10
|
+
PumaAutoTune.config do |config|
|
11
|
+
config.ram = Integer(ENV['PUMA_RAM']) if ENV['PUMA_RAM']
|
12
|
+
config.frequency = Integer(ENV['PUMA_FREQUENCY']) if ENV['PUMA_FREQUENCY']
|
13
|
+
end
|
14
|
+
PumaAutoTune.start
|
@@ -0,0 +1,11 @@
|
|
1
|
+
threads Integer(ENV['MIN_THREADS'] || 1), Integer(ENV['MAX_THREADS'] || 16)
|
2
|
+
workers Integer(ENV['PUMA_WORKERS'] || 3)
|
3
|
+
|
4
|
+
port ENV['PORT'] || 0 # using 0 tells the OS to grab first open port
|
5
|
+
environment ENV['RACK_ENV'] || 'development'
|
6
|
+
preload_app!
|
7
|
+
|
8
|
+
Thread.abort_on_exception = true
|
9
|
+
|
10
|
+
on_worker_boot do
|
11
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
Bundler.require
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class PumaRemote
|
6
|
+
|
7
|
+
attr_accessor :path, :frequency, :config, :log, :ram, :pid
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@path = options[:path] || fixture_path("app.ru")
|
11
|
+
@frequency = options[:frequency] || 1
|
12
|
+
@config = options[:config] || fixture_path("config.rb")
|
13
|
+
@log = options[:log] || new_log_file
|
14
|
+
@ram = options[:ram] || 512
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait
|
18
|
+
until log.read.match %r{booted}
|
19
|
+
sleep 1
|
20
|
+
end
|
21
|
+
sleep 1
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def shutdown
|
26
|
+
if pid
|
27
|
+
Process.kill('TERM', pid)
|
28
|
+
Process.wait(pid)
|
29
|
+
end
|
30
|
+
|
31
|
+
FileUtils.remove_entry_secure log
|
32
|
+
end
|
33
|
+
|
34
|
+
def spawn
|
35
|
+
FileUtils.mkdir_p(log.dirname)
|
36
|
+
FileUtils.touch(log)
|
37
|
+
@pid = Process.spawn("exec env PUMA_FREQUENCY=#{frequency} PUMA_RAM=#{ram} bundle exec puma #{path} -C #{config} > #{log}")
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_log_file
|
42
|
+
Pathname.new("test/logs/puma_#{rand(1...2000)}_#{Time.now.to_f}.log")
|
43
|
+
end
|
44
|
+
|
45
|
+
def fixture_path(name = nil)
|
46
|
+
path = Pathname.new(File.expand_path("../fixtures", __FILE__))
|
47
|
+
return path.join(name) if name
|
48
|
+
path
|
49
|
+
end
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: puma_auto_tune
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Schneeman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-03 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
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.7'
|
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: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.1'
|
55
|
+
description: " Puma performance without all the (T)pain "
|
56
|
+
email:
|
57
|
+
- richard.schneeman+rubygems@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/puma_auto_tune.rb
|
68
|
+
- lib/puma_auto_tune/defaults/ram/hooks.rb
|
69
|
+
- lib/puma_auto_tune/defaults/ram/wrappers.rb
|
70
|
+
- lib/puma_auto_tune/hook.rb
|
71
|
+
- lib/puma_auto_tune/master.rb
|
72
|
+
- lib/puma_auto_tune/memory.rb
|
73
|
+
- lib/puma_auto_tune/version.rb
|
74
|
+
- lib/puma_auto_tune/worker.rb
|
75
|
+
- my.log
|
76
|
+
- puma_auto_tune.gemspec
|
77
|
+
- test/fixtures/app.ru
|
78
|
+
- test/fixtures/config.rb
|
79
|
+
- test/puma_auto_tune_test.rb
|
80
|
+
- test/test_helper.rb
|
81
|
+
homepage: https://github.com/schneems/puma_auto_tune
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.2.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: ''
|
105
|
+
test_files:
|
106
|
+
- test/fixtures/app.ru
|
107
|
+
- test/fixtures/config.rb
|
108
|
+
- test/puma_auto_tune_test.rb
|
109
|
+
- test/test_helper.rb
|