dynamo-autoscale 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +58 -0
  4. data/LICENSE +21 -0
  5. data/README.md +400 -0
  6. data/Rakefile +9 -0
  7. data/aws.sample.yml +16 -0
  8. data/bin/dynamo-autoscale +131 -0
  9. data/config/environment/common.rb +114 -0
  10. data/config/environment/console.rb +2 -0
  11. data/config/environment/test.rb +3 -0
  12. data/config/logger.yml +11 -0
  13. data/config/services/aws.rb +20 -0
  14. data/config/services/logger.rb +35 -0
  15. data/data/.gitkeep +0 -0
  16. data/dynamo-autoscale.gemspec +29 -0
  17. data/lib/dynamo-autoscale/actioner.rb +265 -0
  18. data/lib/dynamo-autoscale/cw_poller.rb +49 -0
  19. data/lib/dynamo-autoscale/dispatcher.rb +39 -0
  20. data/lib/dynamo-autoscale/dynamo_actioner.rb +59 -0
  21. data/lib/dynamo-autoscale/ext/active_support/duration.rb +7 -0
  22. data/lib/dynamo-autoscale/local_actioner.rb +39 -0
  23. data/lib/dynamo-autoscale/local_data_poll.rb +51 -0
  24. data/lib/dynamo-autoscale/logger.rb +15 -0
  25. data/lib/dynamo-autoscale/metrics.rb +192 -0
  26. data/lib/dynamo-autoscale/poller.rb +41 -0
  27. data/lib/dynamo-autoscale/pretty_formatter.rb +27 -0
  28. data/lib/dynamo-autoscale/rule.rb +180 -0
  29. data/lib/dynamo-autoscale/rule_set.rb +69 -0
  30. data/lib/dynamo-autoscale/table_tracker.rb +329 -0
  31. data/lib/dynamo-autoscale/unit_cost.rb +41 -0
  32. data/lib/dynamo-autoscale/version.rb +3 -0
  33. data/lib/dynamo-autoscale.rb +1 -0
  34. data/rlib/dynamodb_graph.r +15 -0
  35. data/rlib/dynamodb_scatterplot.r +13 -0
  36. data/rulesets/default.rb +5 -0
  37. data/rulesets/erroneous.rb +1 -0
  38. data/rulesets/gradual_tail.rb +11 -0
  39. data/rulesets/none.rb +0 -0
  40. data/script/console +3 -0
  41. data/script/historic_data +46 -0
  42. data/script/hourly_wastage +40 -0
  43. data/script/monitor +55 -0
  44. data/script/simulator +40 -0
  45. data/script/test +52 -0
  46. data/script/validate_ruleset +20 -0
  47. data/spec/actioner_spec.rb +244 -0
  48. data/spec/rule_set_spec.rb +89 -0
  49. data/spec/rule_spec.rb +491 -0
  50. data/spec/spec_helper.rb +4 -0
  51. data/spec/table_tracker_spec.rb +256 -0
  52. metadata +178 -0
@@ -0,0 +1,114 @@
1
+ require 'logger'
2
+ require 'time'
3
+ require 'csv'
4
+ require 'tempfile'
5
+ require 'aws-sdk'
6
+ require 'active_support/all'
7
+ require 'rbtree'
8
+ require 'colored'
9
+
10
+ require_relative '../../lib/dynamo-autoscale/logger'
11
+ require_relative '../../lib/dynamo-autoscale/poller'
12
+
13
+ module DynamoAutoscale
14
+ include DynamoAutoscale::Logger
15
+
16
+ DEFAULT_AWS_REGION = 'us-east-1'
17
+
18
+ def self.root
19
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
20
+ end
21
+
22
+ def self.data_dir
23
+ File.join(self.root, 'data')
24
+ end
25
+
26
+ def self.env
27
+ ENV['RACK_ENV'] || 'development'
28
+ end
29
+
30
+ def self.with_config name, opts ={}
31
+ path = nil
32
+
33
+ if opts[:absolute]
34
+ path = name
35
+ else
36
+ path = File.join(root, 'config', "#{name}.yml")
37
+ end
38
+
39
+ conf = YAML.load_file(path)[env]
40
+ yield conf if block_given?
41
+ conf
42
+ end
43
+
44
+ def self.require_all path
45
+ Dir[File.join(root, path, '*.rb')].each { |file| require file }
46
+ end
47
+
48
+ def self.dispatcher= new_dispatcher
49
+ @@dispatcher = new_dispatcher
50
+ end
51
+
52
+ def self.dispatcher
53
+ @@dispatcher ||= Dispatcher.new
54
+ end
55
+
56
+ def self.poller= new_poller
57
+ @@poller = new_poller
58
+ end
59
+
60
+ def self.poller
61
+ @@poller ||= LocalDataPoll.new
62
+ end
63
+
64
+ def self.actioner_class= klass
65
+ @@actioner_class = klass
66
+ end
67
+
68
+ def self.actioner_class
69
+ @@actioner_class ||= LocalActioner
70
+ end
71
+
72
+ def self.actioner_opts= new_opts
73
+ @@actioner_opts = new_opts
74
+ end
75
+
76
+ def self.actioner_opts
77
+ @@actioner_opts ||= {}
78
+ end
79
+
80
+ def self.actioners
81
+ @@actioners ||= Hash.new { |h, k| h[k] = actioner_class.new(k, actioner_opts) }
82
+ end
83
+
84
+ def self.tables= new_tables
85
+ @@tables = new_tables
86
+ end
87
+
88
+ def self.tables
89
+ @@tables ||= Hash.new { |h, k| h[k] = TableTracker.new(k) }
90
+ end
91
+
92
+ def self.current_table= new_current_table
93
+ @@current_table = new_current_table
94
+ end
95
+
96
+ def self.current_table
97
+ @@current_table
98
+ end
99
+
100
+ def self.rules= new_rules
101
+ @@rules = new_rules
102
+ end
103
+
104
+ def self.rules
105
+ @@rules ||= RuleSet.new
106
+ end
107
+ end
108
+
109
+ DynamoAutoscale.require_all 'lib/dynamo-autoscale'
110
+ DynamoAutoscale.require_all 'lib/dynamo-autoscale/ext/**'
111
+
112
+ Dir[File.join(DynamoAutoscale.root, 'config', 'services', '*.rb')].each do |path|
113
+ load path
114
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'common'
2
+ require 'pp'
@@ -0,0 +1,3 @@
1
+ ENV['RACK_ENV'] = "test"
2
+ require_relative 'common'
3
+ require 'timecop'
data/config/logger.yml ADDED
@@ -0,0 +1,11 @@
1
+ test:
2
+ :level: INFO
3
+ :sync: false
4
+
5
+ development:
6
+ :level: INFO
7
+ :sync: true
8
+
9
+ production:
10
+ :level: INFO
11
+ :sync: true
@@ -0,0 +1,20 @@
1
+ config_location = nil
2
+
3
+ if File.exists? './aws.yml'
4
+ config_location = './aws.yml'
5
+ elsif ENV['AWS_CONFIG'] and File.exists? ENV['AWS_CONFIG']
6
+ config_location = ENV['AWS_CONFIG']
7
+ elsif File.exists?(File.join(DynamoAutoscale.root, 'config', 'aws.yml'))
8
+ config_location = File.join(DynamoAutoscale.root, 'config', 'aws.yml')
9
+ end
10
+
11
+ if config_location.nil?
12
+ STDERR.puts "Could not load AWS configuration. Searched in: ./aws.yml and " +
13
+ "ENV['AWS_CONFIG']"
14
+
15
+ exit 1
16
+ end
17
+
18
+ DynamoAutoscale.with_config(config_location, absolute: true) do |config|
19
+ AWS.config(config)
20
+ end
@@ -0,0 +1,35 @@
1
+ DynamoAutoscale.with_config 'logger' do |config|
2
+ if config[:sync]
3
+ STDOUT.sync = true
4
+ STDERR.sync = true
5
+ end
6
+
7
+ if config[:log_to]
8
+ STDOUT.reopen(config[:log_to])
9
+ STDERR.reopen(config[:log_to])
10
+ end
11
+
12
+ DynamoAutoscale::Logger.logger = ::Logger.new(STDOUT)
13
+
14
+ if ENV['PRETTY_LOG']
15
+ DynamoAutoscale::Logger.logger.formatter = DynamoAutoscale::PrettyFormatter.new
16
+ else
17
+ DynamoAutoscale::Logger.logger.formatter = Logger::Formatter.new
18
+ end
19
+
20
+ if ENV['DEBUG']
21
+ DynamoAutoscale::Logger.logger.level = ::Logger::DEBUG
22
+ elsif config[:level]
23
+ DynamoAutoscale::Logger.logger.level = ::Logger.const_get(config[:level])
24
+ end
25
+
26
+ if ENV['SILENT'] # or DynamoAutoscale.env == "test"
27
+ DynamoAutoscale::Logger.logger.level = ::Logger::FATAL
28
+ end
29
+ end
30
+
31
+ if ENV['DEBUG']
32
+ AWS.config({
33
+ logger: DynamoAutoscale::Logger.logger,
34
+ })
35
+ end
data/data/.gitkeep ADDED
File without changes
@@ -0,0 +1,29 @@
1
+ require 'date'
2
+ require './lib/dynamo-autoscale/version'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'dynamo-autoscale'
6
+ gem.version = DynamoAutoscale::VERSION
7
+ gem.date = Date.today.to_s
8
+
9
+ gem.summary = "Autoscaling for DynamoDB provisioned throughputs."
10
+ gem.description = "Will automatically monitor DynamoDB tables and scale them based on rules."
11
+
12
+ gem.authors = ['InvisibleHand']
13
+ gem.email = 'developers@getinvisiblehand.com'
14
+ gem.homepage = 'http://github.com/invisiblehand/dynamo-autoscale'
15
+
16
+ gem.bindir = ['bin']
17
+ gem.executables = ['dynamo-autoscale']
18
+
19
+ gem.license = 'MIT'
20
+
21
+ gem.add_dependency 'aws-sdk'
22
+ gem.add_dependency 'rbtree'
23
+ gem.add_dependency 'ruby-prof'
24
+ gem.add_dependency 'colored'
25
+ gem.add_dependency 'activesupport'
26
+
27
+ # ensure the gem is built out of versioned files
28
+ gem.files = `git ls-files -z`.split("\0")
29
+ end
@@ -0,0 +1,265 @@
1
+ module DynamoAutoscale
2
+ class Actioner
3
+ include DynamoAutoscale::Logger
4
+
5
+ def self.minimum_throughput
6
+ @minimum_throughput ||= 10
7
+ end
8
+
9
+ def self.minimum_throughput= new_minimum_throughput
10
+ @minimum_throughput = new_minimum_throughput
11
+ end
12
+
13
+ def self.maximum_throughput
14
+ @maximum_throughput ||= 20000
15
+ end
16
+
17
+ def self.maximum_throughput= new_maximum_throughput
18
+ @maximum_throughput = new_maximum_throughput
19
+ end
20
+
21
+ attr_accessor :table, :upscales, :downscales
22
+
23
+ def initialize table, opts = {}
24
+ @table = table
25
+ @downscales = 0
26
+ @upscales = 0
27
+ @provisioned = { reads: RBTree.new, writes: RBTree.new }
28
+ @pending = { reads: nil, writes: nil }
29
+ @last_action = Time.now.utc
30
+ @last_scale_check = Time.now.utc
31
+ @downscale_warn = false
32
+ @opts = opts
33
+ end
34
+
35
+ def provisioned_for metric
36
+ @provisioned[normalize_metric(metric)]
37
+ end
38
+
39
+ def provisioned_writes
40
+ @provisioned[:writes]
41
+ end
42
+
43
+ def provisioned_reads
44
+ @provisioned[:reads]
45
+ end
46
+
47
+ def check_day_reset!
48
+ now = Time.now.utc
49
+
50
+ if now >= (check = (@last_scale_check + 1.day).midnight)
51
+ logger.info "[scales] New day! Reset scaling counts back to 0."
52
+ logger.debug "[scales] now: #{now}, comp: #{check}"
53
+
54
+ if @downscales < 4
55
+ logger.warn "[scales] Unused downscales. Used: #{@downscales}"
56
+ end
57
+
58
+ @upscales = 0
59
+ @downscales = 0
60
+ @downscale_warn = false
61
+ end
62
+
63
+ @last_scale_check = now
64
+ end
65
+
66
+ def upscales
67
+ check_day_reset!
68
+ @upscales
69
+ end
70
+
71
+ def downscales new_val = nil
72
+ check_day_reset!
73
+ @downscales
74
+ end
75
+
76
+ def set metric, to
77
+ check_day_reset!
78
+
79
+ metric = normalize_metric(metric)
80
+ ptime, _ = provisioned_for(metric).last
81
+
82
+ if ptime and ptime > 2.minutes.ago
83
+ logger.warn "[actioner] Attempted to scale the same metric more than " +
84
+ "once in a 2 minute window. Disallowing."
85
+ return false
86
+ end
87
+
88
+ from = table.last_provisioned_for(metric)
89
+
90
+ if from and to > (from * 2)
91
+ to = from * 2
92
+
93
+ logger.warn "[#{metric}] Attempted to scale up " +
94
+ "more than allowed. Capped scale to #{to}."
95
+ end
96
+
97
+ if to < Actioner.minimum_throughput
98
+ to = Actioner.minimum_throughput
99
+
100
+ logger.warn "[#{metric}] Attempted to scale down to " +
101
+ "less than minimum throughput. Capped scale to #{to}."
102
+ end
103
+
104
+ if to > Actioner.maximum_throughput
105
+ to = Actioner.maximum_throughput
106
+
107
+ logger.warn "[#{metric}] Attempted to scale up to " +
108
+ "greater than maximum throughput. Capped scale to #{to}."
109
+ end
110
+
111
+ if from and from == to
112
+ logger.info "[#{metric}] Attempted to scale to same value. Ignoring..."
113
+ return false
114
+ end
115
+
116
+ if from and from > to
117
+ downscale metric, from, to
118
+ else
119
+ upscale metric, from, to
120
+ end
121
+ end
122
+
123
+ def upscale metric, from, to
124
+ logger.info "[#{metric}][scaling up] " +
125
+ "#{from ? from.round(2) : "Unknown"} -> #{to.round(2)}"
126
+
127
+
128
+ # Because upscales are not limited, we don't need to queue this operation.
129
+ if result = scale(metric, to)
130
+ @provisioned[metric][Time.now.utc] = to
131
+ @upscales += 1
132
+ end
133
+
134
+ return result
135
+ end
136
+
137
+ def downscale metric, from, to
138
+ if @downscales >= 4
139
+ unless @downscale_warn
140
+ @downscale_warn = true
141
+ logger.warn "[#{metric.to_s.ljust(6)}][scaling failed]" +
142
+ " Hit upper limit of downward scales per day."
143
+ end
144
+
145
+ return false
146
+ end
147
+
148
+ if @pending[metric]
149
+ previous_pending = @pending[metric].last
150
+ logger.info "[#{metric}][scaling down] " +
151
+ "#{previous_pending} -> #{to.round(2)} (overwritten pending)"
152
+ else
153
+ logger.info "[#{metric}][scaling down] " +
154
+ "#{from ? from.round(2) : "Unknown"} -> #{to.round(2)}"
155
+ end
156
+
157
+ queue_operation! metric, to
158
+ end
159
+
160
+ def queue_operation! metric, value
161
+ if @pending[metric]
162
+ logger.debug "[#{metric}] Overwriting pending op with #{value.round(2)}"
163
+ end
164
+
165
+ @pending[metric] = [Time.now.utc, value]
166
+
167
+ try_flush!
168
+ end
169
+
170
+ def try_flush!
171
+ if should_flush?
172
+ if flush_operations!
173
+ @downscales += 1
174
+ @last_action = Time.now.utc
175
+ return true
176
+ else
177
+ return false
178
+ end
179
+ else
180
+ return false
181
+ end
182
+ end
183
+
184
+ def flush_operations!
185
+ result = nil
186
+
187
+ if @pending[:writes] and @pending[:reads]
188
+ _, wvalue = @pending[:writes]
189
+ _, rvalue = @pending[:reads]
190
+
191
+ if result = scale_both(rvalue, wvalue)
192
+ @provisioned[:writes][Time.now.utc] = wvalue
193
+ @provisioned[:reads][Time.now.utc] = rvalue
194
+
195
+ @pending[:writes] = nil
196
+ @pending[:reads] = nil
197
+ end
198
+ elsif @pending[:writes]
199
+ time, value = @pending[:writes]
200
+
201
+ if result = scale(:writes, value)
202
+ @provisioned[:writes][Time.now.utc] = value
203
+
204
+ @pending[:writes] = nil
205
+ end
206
+ elsif @pending[:reads]
207
+ time, value = @pending[:reads]
208
+
209
+ if result = scale(:reads, value)
210
+ @provisioned[:reads][Time.now.utc] = value
211
+ @pending[:reads] = nil
212
+ end
213
+ end
214
+
215
+ logger.info "[flush] All pending downscales have been flushed."
216
+ return result
217
+ end
218
+
219
+ def should_flush?
220
+ if @opts[:group_downscales].nil?
221
+ logger.info "[flush] Downscales are not being grouped. Should flush."
222
+ return true
223
+ end
224
+
225
+ if @pending[:reads] and @pending[:writes]
226
+ logger.info "[flush] Both a read and a write are pending. Should flush."
227
+ return true
228
+ end
229
+
230
+ now = Time.now.utc
231
+
232
+ # I know what you're thinking. How would the last action ever be in the
233
+ # future? Locally, we use Timecop to fake out the time. Unfortunately it
234
+ # doesn't kick in until after the first data point, so when this object is
235
+ # created the @last_action is set to Time.now.utc, then the time gets
236
+ # rolled back, causing the last action to be in the future. This hack
237
+ # fixes that.
238
+ @last_action = now if @last_action > now
239
+
240
+ if (@opts[:flush_after] and @last_action and
241
+ (now > @last_action + @opts[:flush_after]))
242
+
243
+ logger.info "[flush] Flush timeout of #{@opts[:flush_after]} reached."
244
+ return true
245
+ end
246
+
247
+ logger.info "[flush] Flushing conditions not met. Pending operations: " +
248
+ "#{@pending[:reads] ? "1 read" : "no reads"}, " +
249
+ "#{@pending[:writes] ? "1 write" : "no writes"}"
250
+
251
+ return false
252
+ end
253
+
254
+ private
255
+
256
+ def normalize_metric metric
257
+ case metric
258
+ when :reads, :provisioned_reads, :consumed_reads
259
+ :reads
260
+ when :writes, :provisioned_writes, :consumed_writes
261
+ :writes
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,49 @@
1
+ module DynamoAutoscale
2
+ class CWPoller < Poller
3
+ INTERVAL = 1.minute
4
+
5
+ def poll tables, &block
6
+ if tables.nil?
7
+ tables = AWS::DynamoDB.new.tables.to_a.map(&:name)
8
+ end
9
+
10
+ loop do
11
+ # Sleep until the next interval occurrs. This calculation ensures that
12
+ # polling always happens on interval boundaries regardless of how long
13
+ # polling takes.
14
+ sleep_duration = INTERVAL - ((Time.now.to_i + INTERVAL) % INTERVAL)
15
+ logger.debug "Sleeping for #{sleep_duration} seconds..."
16
+ sleep(sleep_duration)
17
+
18
+ do_poll(tables, &block)
19
+ end
20
+ end
21
+
22
+ def do_poll tables, &block
23
+ logger.debug "Beginning CloudWatch poll..."
24
+ now = Time.now
25
+
26
+ tables.each do |table|
27
+ # This code will dispatch a message to the listening table that looks
28
+ # like this:
29
+ #
30
+ # {
31
+ # :consumed_reads=>{
32
+ # 2013-06-19 12:22:00 UTC=>2.343117697349672
33
+ # },
34
+ # :consumed_writes=>{
35
+ # 2013-06-19 12:22:00 UTC=>3.0288461538461537
36
+ # }
37
+ # }
38
+ #
39
+ # There may also be :provisioned_reads and :provisioned_writes
40
+ # depending on how the CloudWatch API feels.
41
+ block.call(table, Metrics.all_metrics(table, {
42
+ period: 5.minutes,
43
+ start_time: now - 20.minutes,
44
+ end_time: now,
45
+ }))
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ module DynamoAutoscale
2
+ class Dispatcher
3
+ def initialize
4
+ @last_check = {}
5
+ end
6
+
7
+ def dispatch table, time, datum, &block
8
+ DynamoAutoscale.current_table = table
9
+ logger.debug "#{time}: Dispatching to #{table.name} with data: #{datum}"
10
+
11
+ if datum[:provisioned_reads] and (datum[:consumed_reads] > datum[:provisioned_reads])
12
+ lost_reads = datum[:consumed_reads] - datum[:provisioned_reads]
13
+
14
+ logger.warn "[reads] Lost units: #{lost_reads} " +
15
+ "(#{datum[:consumed_reads]} - #{datum[:provisioned_reads]})"
16
+ end
17
+
18
+ if datum[:provisioned_writes] and (datum[:consumed_writes] > datum[:provisioned_writes])
19
+ lost_writes = datum[:consumed_writes] - datum[:provisioned_writes]
20
+
21
+ logger.warn "[writes] Lost units: #{lost_writes} " +
22
+ "(#{datum[:consumed_writes]} - #{datum[:provisioned_writes]})"
23
+ end
24
+
25
+ table.tick(time, datum)
26
+ block.call(table, time, datum) if block
27
+
28
+ if @last_check[table.name].nil? or @last_check[table.name] < time
29
+ DynamoAutoscale.rules.test(table)
30
+ @last_check[table.name] = time
31
+ else
32
+ logger.debug "#{table.name}: Skipped rule check, already checked for " +
33
+ "a later data point."
34
+ end
35
+
36
+ DynamoAutoscale.current_table = nil
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ module DynamoAutoscale
2
+ class DynamoActioner < Actioner
3
+ def scale metric, value
4
+ aws_throughput_key = case metric
5
+ when :reads
6
+ :read_capacity_units
7
+ when :writes
8
+ :write_capacity_units
9
+ end
10
+
11
+ dynamo_scale(table, aws_throughput_key => metric)
12
+ end
13
+
14
+ def scale_both reads, writes
15
+ dynamo_scale(table, {
16
+ read_capacity_units: reads, write_capacity_units: writes
17
+ })
18
+ end
19
+
20
+ private
21
+
22
+ def self.dynamo_scale opts
23
+ aws_table = AWS::DynamoDB.new.tables[table.name]
24
+
25
+ if aws_table.status == :updating
26
+ logger.warn "Cannot scale throughputs. Table is updating."
27
+ return false
28
+ end
29
+
30
+ aws_table.provision_throughput(opts)
31
+ return true
32
+ rescue AWS::DynamoDB::Errors::ValidationException => e
33
+ # When you try to set throughput to a negative value or the same value it
34
+ # was previously you get this.
35
+ logger.warn "[#{e.class}] #{e.message}"
36
+ return false
37
+ rescue AWS::DynamoDB::Errors::ResourceInUseException => e
38
+ # When you try to update a table that is being updated you get this.
39
+ logger.warn "[#{e.class}] #{e.message}"
40
+ return false
41
+ rescue AWS::DynamoDB::Errors::LimitExceededException => e
42
+ # When you try to increase throughput greater than 2x or you try to
43
+ # decrease more than 4 times per day you get this.
44
+
45
+ aws_description = self.describe_table(table)
46
+ decreases_today = aws_description[:provisioned_throughput][:number_of_decreases_today]
47
+
48
+ downscales(table, decreases_today)
49
+ logger.warn "[#{e.class}] #{e.message}"
50
+ return false
51
+ end
52
+
53
+ def self.describe_table
54
+ data = AWS::DynamoDB::ClientV2.new.describe_table(table_name: table.name)
55
+
56
+ data[:table]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveSupport
2
+ class Duration
3
+ def inspect
4
+ "#{self.to_i}.seconds"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ module DynamoAutoscale
2
+ class LocalActioner < Actioner
3
+ # Dummy scaling method.
4
+ def scale metric, value
5
+ return true
6
+ end
7
+
8
+ def scale_both reads, writes
9
+ return true
10
+ end
11
+
12
+ # These filters use the arrays inside the local actioner to fake the
13
+ # provisioned reads and writes when the local data enters the system. It
14
+ # makes it look like we're actually modifying the provisioned numbers.
15
+ def self.faux_provisioning_filters
16
+ [Proc.new do |table, time, datum|
17
+ actioner = DynamoAutoscale.actioners[table]
18
+
19
+ actioner.provisioned_reads.reverse_each do |rtime, reads|
20
+ logger.debug "Checking if #{time} > #{rtime}"
21
+ if time > rtime
22
+ logger.debug "[filter] Faked provisioned_reads to be #{reads} at #{time}"
23
+ datum[:provisioned_reads] = reads
24
+ break
25
+ end
26
+ end
27
+
28
+ actioner.provisioned_writes.reverse_each do |wtime, writes|
29
+ logger.debug "Checking if #{time} > #{wtime}"
30
+ if time > wtime
31
+ logger.debug "[filter] Faked provisioned_writes to be #{writes} at #{time}"
32
+ datum[:provisioned_writes] = writes
33
+ break
34
+ end
35
+ end
36
+ end]
37
+ end
38
+ end
39
+ end