heroku-vector 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +95 -0
- data/README.md +171 -0
- data/Rakefile +10 -0
- data/bin/heroku_vector +91 -0
- data/config.rb.example +28 -0
- data/diagrams/Architecture.pptx +0 -0
- data/diagrams/Architecture/Slide1.png +0 -0
- data/examples/upstart_conf +10 -0
- data/heroku-vector.gemspec +38 -0
- data/lib/heroku_vector.rb +133 -0
- data/lib/heroku_vector/dyno_scaler.rb +133 -0
- data/lib/heroku_vector/engine/heroku.rb +63 -0
- data/lib/heroku_vector/helper.rb +23 -0
- data/lib/heroku_vector/process_manager.rb +61 -0
- data/lib/heroku_vector/sampler.rb +50 -0
- data/lib/heroku_vector/source/new_relic.rb +50 -0
- data/lib/heroku_vector/source/sidekiq.rb +49 -0
- data/lib/heroku_vector/version.rb +3 -0
- data/lib/heroku_vector/worker.rb +49 -0
- data/test/fixtures/vcr_cassettes/heroku_dyno_types.yml +69 -0
- data/test/fixtures/vcr_cassettes/heroku_scale_dynos.yml +65 -0
- data/test/fixtures/vcr_cassettes/newrelic_account.yml +78 -0
- data/test/fixtures/vcr_cassettes/newrelic_app.yml +134 -0
- data/test/fixtures/vcr_cassettes/newrelic_threshold_values.yml +192 -0
- data/test/heroku_vector/dyno_scaler_test.rb +240 -0
- data/test/heroku_vector/engine/heroku_test.rb +91 -0
- data/test/heroku_vector/sampler_test.rb +61 -0
- data/test/heroku_vector/source/new_relic_test.rb +77 -0
- data/test/heroku_vector/source/sidekiq_test.rb +78 -0
- data/test/test_helper.rb +40 -0
- metadata +313 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 00d6ccf09122d48c58eabcc34fb1a8bb16a3696e
|
4
|
+
data.tar.gz: be88b82fe45f6764c76dd0d35e7fb020e1223a92
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 20ddb99b29041e06d78111a9dd8a3092160855bf1b1ff9e640228caae7964a6f46198f709bf68cf4cc7a105429f8f171bebd4872463764f91506b898d5f1260f
|
7
|
+
data.tar.gz: 5c0052f0b3e3b3f227ee0124ed971a8ff341e66a8aec3f92c6632d16923a51fb1342e447a1a56c2d1deb00a51811e279f6d517a524ff435421f742de6ba5389f
|
data/.gitignore
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
# Gemfile.lock
|
30
|
+
# .ruby-version
|
31
|
+
# .ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
35
|
+
.env
|
36
|
+
config.rb
|
37
|
+
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
heroku-vector (0.0.2)
|
5
|
+
activeresource
|
6
|
+
dotenv
|
7
|
+
eventmachine
|
8
|
+
heroku-api
|
9
|
+
newrelic_api
|
10
|
+
redis
|
11
|
+
redis-namespace
|
12
|
+
sidekiq
|
13
|
+
|
14
|
+
GEM
|
15
|
+
remote: https://rubygems.org/
|
16
|
+
specs:
|
17
|
+
activemodel (4.1.6)
|
18
|
+
activesupport (= 4.1.6)
|
19
|
+
builder (~> 3.1)
|
20
|
+
activeresource (4.0.0)
|
21
|
+
activemodel (~> 4.0)
|
22
|
+
activesupport (~> 4.0)
|
23
|
+
rails-observers (~> 0.1.1)
|
24
|
+
activesupport (4.1.6)
|
25
|
+
i18n (~> 0.6, >= 0.6.9)
|
26
|
+
json (~> 1.7, >= 1.7.7)
|
27
|
+
minitest (~> 5.1)
|
28
|
+
thread_safe (~> 0.1)
|
29
|
+
tzinfo (~> 1.1)
|
30
|
+
addressable (2.3.6)
|
31
|
+
builder (3.2.2)
|
32
|
+
celluloid (0.15.2)
|
33
|
+
timers (~> 1.1.0)
|
34
|
+
coderay (1.1.0)
|
35
|
+
connection_pool (2.0.0)
|
36
|
+
crack (0.4.2)
|
37
|
+
safe_yaml (~> 1.0.0)
|
38
|
+
dotenv (0.11.1)
|
39
|
+
dotenv-deployment (~> 0.0.2)
|
40
|
+
dotenv-deployment (0.0.2)
|
41
|
+
eventmachine (1.0.3)
|
42
|
+
excon (0.39.5)
|
43
|
+
heroku-api (0.3.19)
|
44
|
+
excon (~> 0.38)
|
45
|
+
multi_json (~> 1.8)
|
46
|
+
i18n (0.6.11)
|
47
|
+
json (1.8.1)
|
48
|
+
metaclass (0.0.4)
|
49
|
+
method_source (0.8.2)
|
50
|
+
minitest (5.4.0)
|
51
|
+
mocha (1.1.0)
|
52
|
+
metaclass (~> 0.0.1)
|
53
|
+
multi_json (1.10.1)
|
54
|
+
newrelic_api (1.2.4)
|
55
|
+
pry (0.10.1)
|
56
|
+
coderay (~> 1.1.0)
|
57
|
+
method_source (~> 0.8.1)
|
58
|
+
slop (~> 3.4)
|
59
|
+
rails-observers (0.1.2)
|
60
|
+
activemodel (~> 4.0)
|
61
|
+
rake (10.3.2)
|
62
|
+
redis (3.1.0)
|
63
|
+
redis-namespace (1.5.1)
|
64
|
+
redis (~> 3.0, >= 3.0.4)
|
65
|
+
safe_yaml (1.0.3)
|
66
|
+
sidekiq (3.2.5)
|
67
|
+
celluloid (= 0.15.2)
|
68
|
+
connection_pool (>= 2.0.0)
|
69
|
+
json
|
70
|
+
redis (>= 3.0.6)
|
71
|
+
redis-namespace (>= 1.3.1)
|
72
|
+
slop (3.6.0)
|
73
|
+
thread_safe (0.3.4)
|
74
|
+
timecop (0.7.1)
|
75
|
+
timers (1.1.0)
|
76
|
+
tzinfo (1.2.2)
|
77
|
+
thread_safe (~> 0.1)
|
78
|
+
vcr (2.9.2)
|
79
|
+
webmock (1.18.0)
|
80
|
+
addressable (>= 2.3.6)
|
81
|
+
crack (>= 0.3.2)
|
82
|
+
|
83
|
+
PLATFORMS
|
84
|
+
ruby
|
85
|
+
|
86
|
+
DEPENDENCIES
|
87
|
+
bundler (~> 1.6)
|
88
|
+
heroku-vector!
|
89
|
+
minitest
|
90
|
+
mocha
|
91
|
+
pry
|
92
|
+
rake
|
93
|
+
timecop
|
94
|
+
vcr
|
95
|
+
webmock
|
data/README.md
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
# Heroku Vector
|
2
|
+
|
3
|
+
```bash
|
4
|
+
___ ___ __
|
5
|
+
/ | \ ____ _______ ____ | | __ __ __
|
6
|
+
/ ~ \_/ __ \ \_ __ \ / _ \ | |/ /| | \
|
7
|
+
\ Y /\ ___/ | | \/( <_> )| < | | /
|
8
|
+
\___|_ / \___ > |__| \____/ |__|_ \|____/
|
9
|
+
____ ____/ __ \/
|
10
|
+
\ \ / / ____ ____ _/ |_ ____ _______
|
11
|
+
\ Y / _/ __ \ _/ ___\ \ __\ / _ \ \_ __ \
|
12
|
+
\ / \ ___/ \ \___ | | ( <_> ) | | \/
|
13
|
+
\___/ \___ > \___ > |__| \____/ |__|
|
14
|
+
\/ \/
|
15
|
+
```
|
16
|
+
|
17
|
+
Simple, linear auto scaling for Heroku dynos.
|
18
|
+
|
19
|
+
Heroku Vector runs as a multi-threaded process that samples production metrics and linearly scales Heroku dynos up or down as they change.
|
20
|
+
|
21
|
+
Web Dynos can be scaled using the amount of traffic (RPM) from NewRelic. Sidekiq dynos can be scaled based on how many worker threads are busy.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Install this gem:
|
26
|
+
|
27
|
+
$ gem install heroku_vector
|
28
|
+
|
29
|
+
And then run the scaler proces:
|
30
|
+
|
31
|
+
$ heroku_vector --help
|
32
|
+
|
33
|
+
## Configuration
|
34
|
+
|
35
|
+
This auto scaler will sample metrics and use the metric values to scale different types of Heroku Dynos up or down.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
|
39
|
+
# Example config.rb file
|
40
|
+
HerokuVector.configure do |config|
|
41
|
+
|
42
|
+
# Scale 1 web dyno for each 300rpm of traffic
|
43
|
+
config.add_dyno_scaler('web', {
|
44
|
+
source: HerokuVector::Source::NewRelic,
|
45
|
+
period: 60,
|
46
|
+
min_dynos: 1,
|
47
|
+
max_dynos: 10,
|
48
|
+
min_value: 100,
|
49
|
+
max_value: 300
|
50
|
+
})
|
51
|
+
|
52
|
+
# Manually specify app config in config.rb like so
|
53
|
+
# Alternately, use environment variables
|
54
|
+
config.newrelic_api_key = '222222222222222'
|
55
|
+
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
Application config can be wired up in the config.rb file, or passed in via the system Environment or an Environment file:
|
60
|
+
|
61
|
+
```bash
|
62
|
+
HEROKU_APP_NAME=your-app-name
|
63
|
+
HEROKU_API_KEY=1111111111111111
|
64
|
+
REDIS_URL=redis://redis.yourcompany.com/
|
65
|
+
SIDEKIQ_REDIS_NAMESPACE=sidekiq
|
66
|
+
NEWRELIC_API_KEY=222222222222222
|
67
|
+
```
|
68
|
+
|
69
|
+
## heroku_vector daemon:
|
70
|
+
```bash
|
71
|
+
[master ~/src/heroku-vector]$> ./bin/heroku_vector --help
|
72
|
+
heroku_vector: auto-scale dynos on Heroku
|
73
|
+
|
74
|
+
Usage: heroku_vector [options]
|
75
|
+
heroku_vector -s
|
76
|
+
|
77
|
+
-s, --sample Sample values and exit
|
78
|
+
-d, --daemonize Daemonize process
|
79
|
+
-e, --envfile PATH Environment file (default: .env)
|
80
|
+
-c, --config PATH Config file (default: config.rb)
|
81
|
+
-p, --pidfile PATH Daemon pid file (default: heroku_vector.pid)
|
82
|
+
-x, --loglevel LEVEL Logging level [fatal/warn/info/debug] (default is info)
|
83
|
+
-l, --logfile PATH Logfile path for daemon
|
84
|
+
-h, --help Show this message
|
85
|
+
```
|
86
|
+
|
87
|
+
Once you've configured your API keys and host names, try taking a sample from all your sources:
|
88
|
+
|
89
|
+
```bash
|
90
|
+
heroku_vector: {:daemonize=>false, :envfile=>"/Users/wpeterson/src/heroku-vector/.env", :config=>"/Users/wpeterson/src/heroku-vector/config.rb", :sample=>true}
|
91
|
+
HerokuVector::Source::NewRelic: 8490.0 RPM
|
92
|
+
HerokuVector::Source::Sidekiq: 23 busy threads
|
93
|
+
```
|
94
|
+
|
95
|
+
## Logging and Debugging
|
96
|
+
|
97
|
+
Heroku Vector logs either to `STDOUT` or a logfile with useful information about state changes and scaling events.
|
98
|
+
|
99
|
+
```bash
|
100
|
+
2014-09-12T16:05:48.473Z INFO: Loading config from '/home/ubuntu/polar-auto-scale/config.rb'
|
101
|
+
2014-09-12T16:05:48.474Z INFO: Loading Scaler: web, {:source=>HerokuVector::Source::NewRelic, :period=>60, :min_dynos=>1, :max_dynos=>4, :min_value=>1000, :max_value=>3000}
|
102
|
+
2014-09-12T16:05:48.474Z INFO: Loading Scaler: worker, {:source=>HerokuVector::Source::Sidekiq, :period=>5, :min_dynos=>1, :max_dynos=>10, :min_value=>0.5, :max_value=>3, :scale_up_by=>3, :scale_down_by=>1}
|
103
|
+
2014-09-12T16:45:01.742Z INFO: Heroku.scale_dynos(worker, 4)
|
104
|
+
2014-09-12T16:50:03.164Z INFO: worker: 4 dynos - 0.4 busy threads below 2.0 - scaling down
|
105
|
+
2014-09-12T16:50:03.247Z INFO: Heroku.scale_dynos(worker, 3)
|
106
|
+
2014-09-12T16:55:04.559Z INFO: worker: 3 dynos - 0.2 busy threads below 1.5 - scaling down
|
107
|
+
2014-09-12T16:55:04.646Z INFO: Heroku.scale_dynos(worker, 2)
|
108
|
+
2014-09-12T17:00:08.391Z INFO: worker: 2 dynos - 0.9 busy threads below 1.0 - scaling down
|
109
|
+
2014-09-12T17:00:08.492Z INFO: Heroku.scale_dynos(worker, 1)
|
110
|
+
```
|
111
|
+
|
112
|
+
If you're debugging a problem, you can turn on verbose logging by setting the `debug` log level:
|
113
|
+
|
114
|
+
$ heroku_vector -x debug
|
115
|
+
|
116
|
+
## Architecture
|
117
|
+
|
118
|
+
![Architecture](https://dl.dropboxusercontent.com/s/a133uy8e0ohwp9t/Architecture.png?dl=0)
|
119
|
+
|
120
|
+
The auto-scaler runs as a single process, either interactively or as a daemon (`ProcessManager`). Within that process, the `Worker` spawn an EventMachine event loop and runs each `DynoScaler` in it's own thread. Periodically, each `DynoScaler` will sample data and evaluate the scale of your dynos. When the scale of your dynos doesn't match your traffic, the `DynoScaler` will use the Heroku API to scale your dynos up or down.
|
121
|
+
|
122
|
+
## Adding a new Source
|
123
|
+
|
124
|
+
Data is sampled from generic `Source` classes in the Ruby namespace `HerokuVector::Source` and wired up in your configuration. Source objects have a simple contract:
|
125
|
+
|
126
|
+
* `#sample` - returns a numeric value for this source at this time
|
127
|
+
* `#units` - string that describes the data, like 'RPM' for NewRelic
|
128
|
+
|
129
|
+
You can define your own `Source` classes and then reference them within your config:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
# my_data_source.rb
|
133
|
+
module HerokuVector::Source
|
134
|
+
class MyDataSource
|
135
|
+
def sample
|
136
|
+
# Always returns 1, simple test case
|
137
|
+
1
|
138
|
+
end
|
139
|
+
|
140
|
+
def units
|
141
|
+
'foos'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Wire Up Your Source in config.rb
|
147
|
+
require 'path/to/my_data_source'
|
148
|
+
|
149
|
+
HerokuVector.configure do |config|
|
150
|
+
|
151
|
+
# Scale 1 web dyno for each 300rpm of traffic
|
152
|
+
config.add_dyno_scaler('web', {
|
153
|
+
source: HerokuVector::Source::MyDataSource,
|
154
|
+
period: 1
|
155
|
+
})
|
156
|
+
|
157
|
+
```
|
158
|
+
|
159
|
+
## Contributors: :heart:
|
160
|
+
|
161
|
+
* Your Name Could Go Here
|
162
|
+
|
163
|
+
## Contributing
|
164
|
+
|
165
|
+
1. Fork it
|
166
|
+
2. Bundle Install (`bundle install`)
|
167
|
+
3. Run the Tests (`rake test`)
|
168
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
169
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
170
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
171
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/heroku_vector
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
|
6
|
+
require 'dotenv'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
options = {
|
10
|
+
:daemonize => false,
|
11
|
+
:envfile => File.expand_path( File.dirname(__FILE__)+'/../.env' ),
|
12
|
+
:config => File.expand_path( File.dirname(__FILE__)+'/../config.rb' ),
|
13
|
+
:sample => false
|
14
|
+
}
|
15
|
+
|
16
|
+
def show_usage_and_exit
|
17
|
+
puts @parser
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
@parser = OptionParser.new do |opts|
|
22
|
+
opts.banner = <<EOL
|
23
|
+
heroku_vector: auto-scale dynos on Heroku
|
24
|
+
|
25
|
+
Usage: heroku_vector [options]
|
26
|
+
heroku_vector -s
|
27
|
+
|
28
|
+
EOL
|
29
|
+
|
30
|
+
opts.on('-s', '--sample', "Sample values and exit") {|v| options[:sample] = true }
|
31
|
+
opts.on('-d', '--daemonize', "Daemonize process") {|v| options[:daemonize] = true }
|
32
|
+
opts.on('-e', '--envfile PATH', "Environment file (default: .env)") {|v| options[:envfile] = v }
|
33
|
+
opts.on('-c', '--config PATH', "Config file (default: config.rb)") {|v| options[:config] = v }
|
34
|
+
opts.on('-p', '--pidfile PATH', "Daemon pid file (default: heroku_vector.pid)") {|v| options[:pidfile] = v }
|
35
|
+
opts.on('-x', '--loglevel LEVEL', "Logging level [fatal/warn/info/debug] (default is info)") {|v| options[:loglevel] = v }
|
36
|
+
opts.on('-l', '--logfile PATH', "Logfile path for daemon") {|v| options[:logfile] = v }
|
37
|
+
opts.on_tail("-h", "--help", "Show this message") { show_usage_and_exit }
|
38
|
+
end
|
39
|
+
@parser.parse!(ARGV)
|
40
|
+
|
41
|
+
if options[:envfile] && File.exist?(options[:envfile])
|
42
|
+
env = Dotenv.load(options[:envfile])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Write a default pidfile when daemonized
|
46
|
+
if options[:daemonize]
|
47
|
+
options[:pidfile] ||= File.expand_path( File.dirname(__FILE__)+'/../heroku_vector.pid' )
|
48
|
+
options[:logfile] ||= File.expand_path( File.dirname(__FILE__)+'/../heroku_vector.log' )
|
49
|
+
end
|
50
|
+
|
51
|
+
puts "heroku_vector: #{options.inspect}"
|
52
|
+
|
53
|
+
# Load heroku_vector after all options parses and environment loaded
|
54
|
+
require 'heroku_vector'
|
55
|
+
|
56
|
+
if options[:loglevel]
|
57
|
+
level = Logger.const_get(options[:loglevel].upcase) rescue nil
|
58
|
+
HerokuVector.logger.level = level if level
|
59
|
+
end
|
60
|
+
|
61
|
+
if options[:sample]
|
62
|
+
# Print out a sample value for each valid Source class
|
63
|
+
HerokuVector::Source.constants.each do |constant|
|
64
|
+
clazz = HerokuVector::Source.const_get(constant)
|
65
|
+
next unless clazz.class == Class
|
66
|
+
|
67
|
+
source = clazz.new
|
68
|
+
puts "#{clazz}: #{source.sample} #{source.unit}"
|
69
|
+
end
|
70
|
+
|
71
|
+
exit 0
|
72
|
+
end
|
73
|
+
|
74
|
+
logo_ascii = <<EOL
|
75
|
+
|
76
|
+
___ ___ __
|
77
|
+
/ | \\ ____ _______ ____ | | __ __ __
|
78
|
+
/ ~ \\_/ __ \\ \\_ __ \\ / _ \\ | |/ /| | \\
|
79
|
+
\\ Y /\\ ___/ | | \\/( <_> )| < | | /
|
80
|
+
\\___|_ / \\___ > |__| \\____/ |__|_ \\|____/
|
81
|
+
____ ____/ __ \\/
|
82
|
+
\\ \\ / / ____ ____ _/ |_ ____ _______
|
83
|
+
\\ Y / _/ __ \\ _/ ___\\ \\ __\\ / _ \\ \\_ __ \\
|
84
|
+
\\ / \\ ___/ \\ \\___ | | ( <_> ) | | \\/
|
85
|
+
\\___/ \\___ > \\___ > |__| \\____/ |__|
|
86
|
+
\\/ \\/
|
87
|
+
EOL
|
88
|
+
puts logo_ascii
|
89
|
+
|
90
|
+
manager = HerokuVector::ProcessManager.new(options)
|
91
|
+
manager.start
|
data/config.rb.example
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Example config.rb file
|
2
|
+
# Copy this to config.rb and edit for your configuration
|
3
|
+
|
4
|
+
HerokuVector.configure do |config|
|
5
|
+
|
6
|
+
# Scale 1 web dyno for each 300rpm of traffic
|
7
|
+
config.add_dyno_scaler('web', {
|
8
|
+
source: HerokuVector::Source::NewRelic,
|
9
|
+
period: 60,
|
10
|
+
min_dynos: 1,
|
11
|
+
max_dynos: 10,
|
12
|
+
min_value: 100,
|
13
|
+
max_value: 300
|
14
|
+
})
|
15
|
+
|
16
|
+
# Scale up Sidekiq workers when there are 6 or more busy threads per worker
|
17
|
+
# Scale up two dynos at a time, but scale down 1 dyno at a time
|
18
|
+
config.add_dyno_scaler('worker', {
|
19
|
+
source: HerokuVector::Source::Sidekiq,
|
20
|
+
period: 5,
|
21
|
+
min_dynos: 1,
|
22
|
+
max_dynos: 10,
|
23
|
+
min_value: 0.5,
|
24
|
+
max_value: 6,
|
25
|
+
scale_up_by: 2,
|
26
|
+
scale_down_by: 1
|
27
|
+
})
|
28
|
+
end
|