dynamo-autoscale 0.1.4 → 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.
@@ -1,35 +1,31 @@
1
- DynamoAutoscale.with_config 'logger' do |config|
2
- if config[:sync]
3
- STDOUT.sync = true
4
- STDERR.sync = true
5
- end
1
+ config = DynamoAutoscale.config[:logger] || {}
6
2
 
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)
3
+ if config[:sync]
4
+ STDOUT.sync = true
5
+ STDERR.sync = true
6
+ end
13
7
 
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
8
+ if config[:log_to]
9
+ STDOUT.reopen(config[:log_to])
10
+ STDERR.reopen(config[:log_to])
11
+ end
19
12
 
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
13
+ DynamoAutoscale::Logger.logger = ::Logger.new(STDOUT)
25
14
 
26
- if ENV['SILENT'] # or DynamoAutoscale.env == "test"
27
- DynamoAutoscale::Logger.logger.level = ::Logger::FATAL
28
- end
15
+ if config[:style] == "pretty"
16
+ DynamoAutoscale::Logger.logger.formatter = DynamoAutoscale::PrettyFormatter.new
17
+ else
18
+ DynamoAutoscale::Logger.logger.formatter = Logger::Formatter.new
29
19
  end
30
20
 
31
21
  if ENV['DEBUG']
32
- AWS.config({
33
- logger: DynamoAutoscale::Logger.logger,
34
- })
22
+ DynamoAutoscale::Logger.logger.level = ::Logger::DEBUG
23
+ elsif config[:level]
24
+ DynamoAutoscale::Logger.logger.level = ::Logger.const_get(config[:level])
35
25
  end
26
+
27
+ if ENV['SILENT']
28
+ DynamoAutoscale::Logger.logger.level = ::Logger::FATAL
29
+ end
30
+
31
+ AWS.config(logger: DynamoAutoscale::Logger.logger) if ENV['DEBUG']
@@ -0,0 +1,15 @@
1
+ Signal.trap("USR1") do
2
+ DynamoAutoscale.logger.info "[signal] Caught SIGUSR1. Dumping CSV for all tables in #{Dir.pwd}"
3
+
4
+ DynamoAutoscale.tables.each do |name, table|
5
+ table.to_csv! path: File.join(Dir.pwd, "#{table.name}.csv")
6
+ end
7
+ end
8
+
9
+ Signal.trap("USR2") do
10
+ DynamoAutoscale.logger.info "[signal] Caught SIGUSR2. Dumping graphs for all tables in #{Dir.pwd}"
11
+
12
+ DynamoAutoscale.tables.each do |name, table|
13
+ table.graph! path: File.join(Dir.pwd, "#{table.name}.png")
14
+ end
15
+ end
@@ -28,6 +28,7 @@ Gem::Specification.new do |gem|
28
28
  gem.add_dependency 'ruby-prof'
29
29
  gem.add_dependency 'colored'
30
30
  gem.add_dependency 'activesupport'
31
+ gem.add_dependency 'pony'
31
32
 
32
33
  # ensure the gem is built out of versioned files
33
34
  gem.files = `git ls-files -z`.split("\0")
@@ -0,0 +1,79 @@
1
+ :aws:
2
+ # REQUIRED
3
+ #
4
+ # Here is where you specify your AWS ID and key. It needs to be a pair that
5
+ # can access both CloudWatch and DynamoDB. For exact details on IAM policies,
6
+ # check the project README.
7
+ :access_key_id: "your_id"
8
+ :secret_access_key: "your_key"
9
+
10
+ # REQUIRED
11
+ #
12
+ # At the moment, only the us-east-1 region is supported. Changing this may
13
+ # have strange behvaviour. Don't do it.
14
+ :dynamo_db_endpoint: "dynamodb.us-east-1.amazonaws.com"
15
+
16
+ :logger:
17
+ # Pretty logging makes the log output colourful. Who doesn't like colours?
18
+ :style: pretty
19
+ :level: INFO
20
+
21
+ # If you want to receive email reports whenever a scale operation happens, you
22
+ # can specify this email config. If you don't want to receive emails, just
23
+ # remove this from your config file.
24
+ #
25
+ # dynamo-autoscale uses Pony to send email, this hash is just given to Pony
26
+ # verbatim. Further documentation on what options Pony accepts can be found on
27
+ # their GitHub: https://github.com/benprew/pony
28
+ :email:
29
+ :to: "john.doe@example.com"
30
+ :from: "dynamo-autoscale@example.com"
31
+ :via: :smtp
32
+ :via_options:
33
+ :port: 25
34
+ :enable_starttls_auto: false
35
+ :authentication: :plain
36
+ :address: "mailserver.example.com"
37
+ :user_name: "user"
38
+ :password: "password"
39
+
40
+ # REQUIRED
41
+ #
42
+ # Specify the path to your ruleset. Further information on the syntax and
43
+ # purpose of rulesets can be found in the README.
44
+ :ruleset: "rulesets/gradual_tail.rb"
45
+
46
+ # REQUIRED
47
+ #
48
+ # The following is an array of tables to monitor and autoscale. You need to
49
+ # specify at least one.
50
+ :tables:
51
+ - "products-api-production4_dodgy_product_weights"
52
+
53
+ # If you don't want to scale dynamo just yet, you can say that you want to run
54
+ # in "dry run" mode, which will monitor your production databases but all of the
55
+ # scaling events will only be tracked locally.
56
+ #
57
+ # Very useful for making sure your rules will do the right thing.
58
+ :dry_run: true
59
+
60
+ # Because you are very limited by how many downscales you have per day, and
61
+ # because downscaling both reads and writes at the same time only counts as a
62
+ # single downscale, the following option will queue up downscales until it can
63
+ # apply 1 for reads and 1 for writes at the same time. It is recommended that
64
+ # you turn this one.
65
+ :group_downscales: true
66
+
67
+ # This option only works in conjunction with the above group_downscales option.
68
+ # If a downscale stays queued for a long time, you can specify a timeout and
69
+ # just apply a single read or write downscale after a specified amount of time
70
+ # passes.
71
+ #
72
+ # Specified in seconds.
73
+ :flush_after: 3600
74
+
75
+ # The following two options are configurable minimums and maximums for
76
+ # provisioned throughputs. Dynamo-autoscale will not go below or above whatever
77
+ # you set here.
78
+ :minimum_throughput: 10
79
+ :maximum_throughput: 20000
@@ -1,6 +1,7 @@
1
1
  module DynamoAutoscale
2
2
  class Actioner
3
3
  include DynamoAutoscale::Logger
4
+ attr_accessor :table, :upscales, :downscales
4
5
 
5
6
  def self.minimum_throughput
6
7
  @minimum_throughput ||= 10
@@ -18,8 +19,6 @@ module DynamoAutoscale
18
19
  @maximum_throughput = new_maximum_throughput
19
20
  end
20
21
 
21
- attr_accessor :table, :upscales, :downscales
22
-
23
22
  def initialize table, opts = {}
24
23
  @table = table
25
24
  @downscales = 0
@@ -63,6 +62,13 @@ module DynamoAutoscale
63
62
  @last_scale_check = now
64
63
  end
65
64
 
65
+ # This should be overwritten by deriving classes. In the Dynamo actioner,
66
+ # this should check that the table is in an :active state. In the local
67
+ # actioner this will be faked.
68
+ def can_run?
69
+ false
70
+ end
71
+
66
72
  def upscales
67
73
  check_day_reset!
68
74
  @upscales
@@ -129,6 +135,7 @@ module DynamoAutoscale
129
135
  if result = scale(metric, to)
130
136
  @provisioned[metric][Time.now.utc] = to
131
137
  @upscales += 1
138
+ ScaleReport.new(table).send
132
139
  end
133
140
 
134
141
  return result
@@ -146,9 +153,8 @@ module DynamoAutoscale
146
153
  end
147
154
 
148
155
  if @pending[metric]
149
- previous_pending = @pending[metric].last
150
156
  logger.info "[#{metric}][scaling down] " +
151
- "#{previous_pending} -> #{to.round(2)} (overwritten pending)"
157
+ "#{@pending[metric]} -> #{to.round(2)} (overwritten pending)"
152
158
  else
153
159
  logger.info "[#{metric}][scaling down] " +
154
160
  "#{from ? from.round(2) : "Unknown"} -> #{to.round(2)}"
@@ -158,8 +164,7 @@ module DynamoAutoscale
158
164
  end
159
165
 
160
166
  def queue_operation! metric, value
161
- @pending[metric] = [Time.now.utc, value]
162
-
167
+ @pending[metric] = value
163
168
  try_flush!
164
169
  end
165
170
 
@@ -168,6 +173,8 @@ module DynamoAutoscale
168
173
  if flush_operations!
169
174
  @downscales += 1
170
175
  @last_action = Time.now.utc
176
+ ScaleReport.new(table).send
177
+
171
178
  return true
172
179
  else
173
180
  return false
@@ -179,31 +186,38 @@ module DynamoAutoscale
179
186
 
180
187
  def flush_operations!
181
188
  result = nil
189
+ now = Time.now.utc
182
190
 
183
191
  if @pending[:writes] and @pending[:reads]
184
- _, wvalue = @pending[:writes]
185
- _, rvalue = @pending[:reads]
192
+ wvalue = @pending[:writes]
193
+ rvalue = @pending[:reads]
186
194
 
187
195
  if result = scale_both(rvalue, wvalue)
188
- @provisioned[:writes][Time.now.utc] = wvalue
189
- @provisioned[:reads][Time.now.utc] = rvalue
196
+ @provisioned[:writes][now] = wvalue
197
+ @provisioned[:reads][now] = rvalue
198
+
199
+ table.scale_events[now] = {
200
+ writes: wvalue,
201
+ reads: rvalue,
202
+ }
190
203
 
191
204
  @pending[:writes] = nil
192
205
  @pending[:reads] = nil
193
206
  end
194
207
  elsif @pending[:writes]
195
- time, value = @pending[:writes]
208
+ value = @pending[:writes]
196
209
 
197
210
  if result = scale(:writes, value)
198
211
  @provisioned[:writes][Time.now.utc] = value
199
-
212
+ table.scale_events[now] = { writes: value }
200
213
  @pending[:writes] = nil
201
214
  end
202
215
  elsif @pending[:reads]
203
- time, value = @pending[:reads]
216
+ value = @pending[:reads]
204
217
 
205
218
  if result = scale(:reads, value)
206
219
  @provisioned[:reads][Time.now.utc] = value
220
+ table.scale_events[now] = { reads: value }
207
221
  @pending[:reads] = nil
208
222
  end
209
223
  end
@@ -1,5 +1,6 @@
1
1
  module DynamoAutoscale
2
2
  class CWPoller < Poller
3
+ include DynamoAutoscale::Logger
3
4
  INTERVAL = 1.minute
4
5
 
5
6
  def poll tables, &block
@@ -1,5 +1,7 @@
1
1
  module DynamoAutoscale
2
2
  class Dispatcher
3
+ include DynamoAutoscale::Logger
4
+
3
5
  def initialize
4
6
  @last_check = {}
5
7
  end
@@ -26,8 +28,14 @@ module DynamoAutoscale
26
28
  block.call(table, time, datum) if block
27
29
 
28
30
  if @last_check[table.name].nil? or @last_check[table.name] < time
29
- DynamoAutoscale.rules.test(table)
30
- @last_check[table.name] = time
31
+ if DynamoAutoscale.actioners[table].can_run?
32
+ logger.debug "[dispatcher] Checking rules..."
33
+ DynamoAutoscale.rules.test(table)
34
+ @last_check[table.name] = time
35
+ else
36
+ logger.debug "[dispatcher] Skipped rule check, table is not ready " +
37
+ "to have its throughputs modified."
38
+ end
31
39
  else
32
40
  logger.debug "[dispatcher] Skipped rule check, already checked for " +
33
41
  "a later data point."
@@ -1,5 +1,7 @@
1
1
  module DynamoAutoscale
2
2
  class DynamoActioner < Actioner
3
+ include DynamoAutoscale::Logger
4
+
3
5
  def dynamo
4
6
  @dynamo ||= AWS::DynamoDB.new.tables[table.name]
5
7
  end
@@ -19,14 +21,13 @@ module DynamoAutoscale
19
21
  dynamo_scale(read_capacity_units: reads, write_capacity_units: writes)
20
22
  end
21
23
 
24
+ def can_run?
25
+ dynamo.status == :active
26
+ end
27
+
22
28
  private
23
29
 
24
30
  def dynamo_scale opts
25
- if dynamo.status == :updating
26
- logger.warn "[actioner] Cannot scale throughputs. Table is updating."
27
- return false
28
- end
29
-
30
31
  dynamo.provision_throughput(opts)
31
32
  return true
32
33
  rescue AWS::DynamoDB::Errors::ValidationException => e
@@ -1,14 +1,28 @@
1
1
  module DynamoAutoscale
2
2
  class LocalActioner < Actioner
3
- # Dummy scaling method.
3
+ include DynamoAutoscale::Logger
4
+
4
5
  def scale metric, value
6
+ @updating_until = rand(4.0..7.0).minutes.from_now.utc
5
7
  return true
6
8
  end
7
9
 
8
10
  def scale_both reads, writes
11
+ @updating_until = rand(4.0..7.0).minutes.from_now.utc
9
12
  return true
10
13
  end
11
14
 
15
+ def can_run?
16
+ return true if @updating_until.nil?
17
+
18
+ if Time.now.utc > @updating_until
19
+ @updating_until = nil
20
+ return true
21
+ end
22
+
23
+ return false
24
+ end
25
+
12
26
  # These filters use the arrays inside the local actioner to fake the
13
27
  # provisioned reads and writes when the local data enters the system. It
14
28
  # makes it look like we're actually modifying the provisioned numbers.
@@ -17,7 +31,6 @@ module DynamoAutoscale
17
31
  actioner = DynamoAutoscale.actioners[table]
18
32
 
19
33
  actioner.provisioned_reads.reverse_each do |rtime, reads|
20
- logger.debug "Checking if #{time} > #{rtime}"
21
34
  if time > rtime
22
35
  logger.debug "[filter] Faked provisioned_reads to be #{reads} at #{time}"
23
36
  datum[:provisioned_reads] = reads
@@ -1,5 +1,7 @@
1
1
  module DynamoAutoscale
2
2
  class LocalDataPoll < Poller
3
+ include DynamoAutoscale::Logger
4
+
3
5
  def initialize *args
4
6
  super(*args)
5
7
  @cache = Hash.new { |h, k| h[k] = {} }
@@ -11,5 +11,9 @@ module DynamoAutoscale
11
11
  def logger
12
12
  DynamoAutoscale::Logger.logger
13
13
  end
14
+
15
+ def self.included base
16
+ base.extend DynamoAutoscale::Logger
17
+ end
14
18
  end
15
19
  end
@@ -1,6 +1,6 @@
1
1
  module DynamoAutoscale
2
2
  class Metrics
3
- extend DynamoAutoscale::Logger
3
+ include DynamoAutoscale::Logger
4
4
 
5
5
  DEFAULT_OPTS = {
6
6
  namespace: 'AWS/DynamoDB',
@@ -1,5 +1,8 @@
1
1
  module DynamoAutoscale
2
2
  class Poller
3
+ include DynamoAutoscale::Logger
4
+ attr_accessor :tables, :filters
5
+
3
6
  # The poller constructor accepts a hash of options. The following arguments
4
7
  # are valid but optional:
5
8
  #
@@ -9,11 +12,12 @@ module DynamoAutoscale
9
12
  # each datum before it gets sent to the dispatcher. It helps fake setting
10
13
  # provisioned throughput.
11
14
  def initialize opts = {}
12
- @opts = opts
15
+ @tables = opts[:tables] || []
16
+ @filters = opts[:filters] || []
13
17
  end
14
18
 
15
19
  def run &block
16
- poll(@opts[:tables]) do |table_name, data|
20
+ poll(tables) do |table_name, data|
17
21
  logger.debug "[poller] Got data: #{data}"
18
22
  table = DynamoAutoscale.tables[table_name]
19
23
 
@@ -29,9 +33,7 @@ module DynamoAutoscale
29
33
  consumed_reads: data[:consumed_reads][time],
30
34
  }
31
35
 
32
- if @opts[:filters]
33
- @opts[:filters].each { |filter| filter.call(table, time, datum) }
34
- end
36
+ filters.each { |filter| filter.call(table, time, datum) }
35
37
 
36
38
  DynamoAutoscale.dispatcher.dispatch(table, time, datum, &block)
37
39
  end
@@ -118,6 +118,7 @@ module DynamoAutoscale
118
118
  if @opts[:times].nil? or @count[table.name] == @opts[:times]
119
119
  @count[table.name] = 0
120
120
  logger.info "[rule] Triggered rule: #{self.to_english}"
121
+ table.triggered_rules[Time.now.utc] = self
121
122
 
122
123
  if scale = @opts[:scale]
123
124
  new_val = table.send("last_#{scale[:on]}_for", @metric) * scale[:by]
@@ -1,5 +1,7 @@
1
1
  module DynamoAutoscale
2
2
  class RuleSet
3
+ include DynamoAutoscale::Logger
4
+
3
5
  attr_accessor :rules
4
6
 
5
7
  def initialize path = nil, &block
@@ -23,10 +25,12 @@ module DynamoAutoscale
23
25
  rules = self.for(table.name)
24
26
 
25
27
  rules.select(&:reads?).each do |rule|
28
+ # logger.debug "[rule_set] Checking rule: #{rule.to_english}"
26
29
  break result = true if rule.test(table)
27
30
  end
28
31
 
29
32
  rules.select(&:writes?).each do |rule|
33
+ # logger.debug "[rule_set] Checking rule: #{rule.to_english}"
30
34
  break result = true if rule.test(table)
31
35
  end
32
36
 
@@ -0,0 +1,39 @@
1
+ module DynamoAutoscale
2
+ class ScaleReport
3
+ include DynamoAutoscale::Logger
4
+
5
+ TEMPLATE = File.join(DynamoAutoscale.root, 'templates', 'scale_report_email.erb')
6
+
7
+ def initialize table
8
+ @table = table
9
+ @erb = ERB.new(File.read(TEMPLATE))
10
+
11
+ if config = DynamoAutoscale.config[:email]
12
+ @enabled = true
13
+ Pony.options = config
14
+ else
15
+ @enabled = false
16
+ end
17
+ end
18
+
19
+ def send
20
+ return false unless @enabled
21
+
22
+ result = Pony.mail({
23
+ subject: "Scale event for #{@table.name}",
24
+ body: @erb.result(binding),
25
+ })
26
+
27
+ if result
28
+ logger.info "[mailer] Mail sent successfully."
29
+ result
30
+ else
31
+ logger.error "[mailer] Failed to send email. Result: #{result.inspect}"
32
+ false
33
+ end
34
+ rescue => e
35
+ logger.error "[mailer] Encountered an error: #{e.class}:#{e.message}"
36
+ false
37
+ end
38
+ end
39
+ end
@@ -5,7 +5,7 @@ module DynamoAutoscale
5
5
  # TODO: This time window may need changing.
6
6
  TIME_WINDOW = 7.days
7
7
 
8
- attr_reader :name, :data
8
+ attr_reader :name, :data, :triggered_rules, :scale_events
9
9
 
10
10
  def initialize name
11
11
  @name = name
@@ -13,7 +13,9 @@ module DynamoAutoscale
13
13
  end
14
14
 
15
15
  def clear_data
16
- @data = RBTree.new
16
+ @data = RBTree.new
17
+ @triggered_rules = RBTree.new
18
+ @scale_events = RBTree.new
17
19
  end
18
20
 
19
21
  # `tick` takes two arguments. The first is a Time object, the second is
@@ -48,13 +50,9 @@ module DynamoAutoscale
48
50
  end
49
51
 
50
52
  @data[time] = datum
51
-
52
- # The code below here just makes sure that we're trimming data points that
53
- # are outside of the time window.
54
- logger.debug "[table] Pruning data that may be outside of time window..."
55
- now = Time.now.utc
56
- to_delete = @data.each.take_while { |key, _| key < (now - TIME_WINDOW) }
57
- to_delete.each { |key, _| @data.delete(key) }
53
+ remove_expired_data! @data
54
+ remove_expired_data! @triggered_rules
55
+ remove_expired_data! @scale_events
58
56
  end
59
57
 
60
58
  # Gets the last amount of provisioned throughput for whatever metric you
@@ -281,11 +279,18 @@ module DynamoAutoscale
281
279
 
282
280
  if $? != 0
283
281
  logger.error "[table] Failed to create graph."
282
+ return false
284
283
  else
285
- `open #{png_tmp}` if opts[:open]
284
+ if opts[:open]
285
+ `open #{png_tmp}`
286
+ if $? != 0
287
+ logger.error "[table] Failed to open graph."
288
+ return false
289
+ else
290
+ return png_tmp
291
+ end
292
+ end
286
293
  end
287
-
288
- png_tmp
289
294
  end
290
295
 
291
296
  def scatterplot_for! metric
@@ -314,16 +319,18 @@ module DynamoAutoscale
314
319
  puts " Lost w/units: #{lost_write_units.round(2)} (#{lost_write_percent.round(2)}%)"
315
320
  puts " Upscales: #{DynamoAutoscale.actioners[self].upscales}"
316
321
  puts " Downscales: #{DynamoAutoscale.actioners[self].downscales}"
317
- puts " Fitness: #{fitness}"
318
322
  end
319
323
 
320
- def fitness
321
- lost_weight = 100
322
- wasted_weight = 1
323
- lost = ((lost_read_percent + lost_write_percent) / 2)
324
- wasted = ((wasted_read_percent + wasted_write_percent) / 2)
324
+ private
325
325
 
326
- ((lost * lost_weight) + (wasted * wasted_weight)) / (lost_weight + wasted_weight)
326
+ # Helper function to remove data from an RBTree object keyed on a Time
327
+ # object where the key is outside of the time window defined by the
328
+ # TIME_WINDOW constant.
329
+ def remove_expired_data! data
330
+ # logger.debug "[table] Pruning data that may be outside of time window..."
331
+ now = Time.now.utc
332
+ to_delete = data.each.take_while { |key, _| key < (now - TIME_WINDOW) }
333
+ to_delete.each { |key, _| data.delete(key) }
327
334
  end
328
335
  end
329
336
  end
@@ -1,3 +1,3 @@
1
1
  module DynamoAutoscale
2
- VERSION = '0.1.4'
2
+ VERSION = '0.2'
3
3
  end
data/script/historic_data CHANGED
@@ -1,15 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../config/environment/common'
4
- require 'pp'
5
- require 'fileutils'
3
+ # This script will fetch the 6 days of previous data from all of the tables that
4
+ # you have specified in the config passed in as ARGV[0].
5
+ #
6
+ # It will store this data into the `data/` directory of this project in a format
7
+ # that the rest of the tool scripts understands.
6
8
 
9
+ require_relative '../config/environment/common'
7
10
  include DynamoAutoscale
8
11
 
9
- tables = ARGV
12
+ if ARGV[0]
13
+ DynamoAutoscale.setup_from_config(ARGV[0])
14
+ elsif ARGV[0].nil?
15
+ STDERR.puts "Usage: script/historic_data path/to/config.yml"
16
+
17
+ exit 1
18
+ elsif ARGV[0] and !File.exists?(ARGV[0])
19
+ STDERR.puts "Usage: script/historic_data path/to/config.yml"
20
+ STDERR.puts "Error: The path you specified is to a file that does not exist."
10
21
 
11
- if tables.empty?
12
- STDERR.puts "Usage: script/historic_data table_name [another_table_name ...]"
13
22
  exit 1
14
23
  end
15
24
 
@@ -18,7 +27,7 @@ range = (Date.today - 5.days).upto(Date.today)
18
27
  logger.info "Date range: #{range.to_a}"
19
28
 
20
29
  # Filter out tables that do not exist in Dynamo.
21
- tables.select! do |table|
30
+ DynamoAutoscale.poller.tables.select! do |table|
22
31
  if dynamo.tables[table].exists?
23
32
  true
24
33
  else
@@ -33,7 +42,7 @@ range.each do |start_day|
33
42
 
34
43
  FileUtils.mkdir(dir) unless Dir.exists?(dir)
35
44
 
36
- tables.each do |table|
45
+ DynamoAutoscale.poller_opts[:tables].each do |table|
37
46
  logger.info "Collecting data for #{table} on #{start_day}..."
38
47
  File.open(File.join(dir, "#{table}.json"), 'w') do |file|
39
48
  file.write(JSON.pretty_generate(Metrics.all_metrics(table, {