heroku-vector 0.0.2
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 +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
|
+

|
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
|