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.
- data/.gitignore +1 -1
- data/CHANGELOG +6 -0
- data/Gemfile.lock +27 -8
- data/README.md +85 -47
- data/bin/dynamo-autoscale +15 -113
- data/config/dynamo-autoscale-test.yml +11 -0
- data/config/environment/common.rb +100 -25
- data/config/environment/test.rb +3 -0
- data/config/services/aws.rb +2 -19
- data/config/services/logger.rb +23 -27
- data/config/services/signals.rb +15 -0
- data/dynamo-autoscale.gemspec +1 -0
- data/dynamo-autoscale.sample.yml +79 -0
- data/lib/dynamo-autoscale/actioner.rb +27 -13
- data/lib/dynamo-autoscale/cw_poller.rb +1 -0
- data/lib/dynamo-autoscale/dispatcher.rb +10 -2
- data/lib/dynamo-autoscale/dynamo_actioner.rb +6 -5
- data/lib/dynamo-autoscale/local_actioner.rb +15 -2
- data/lib/dynamo-autoscale/local_data_poll.rb +2 -0
- data/lib/dynamo-autoscale/logger.rb +4 -0
- data/lib/dynamo-autoscale/metrics.rb +1 -1
- data/lib/dynamo-autoscale/poller.rb +7 -5
- data/lib/dynamo-autoscale/rule.rb +1 -0
- data/lib/dynamo-autoscale/rule_set.rb +4 -0
- data/lib/dynamo-autoscale/scale_report.rb +39 -0
- data/lib/dynamo-autoscale/table_tracker.rb +26 -19
- data/lib/dynamo-autoscale/version.rb +1 -1
- data/script/historic_data +17 -8
- data/script/monitor +9 -26
- data/script/simulator +16 -14
- data/script/test +29 -23
- data/script/validate_ruleset +1 -3
- data/spec/actioner_spec.rb +6 -0
- data/spec/rule_spec.rb +6 -0
- data/templates/scale_report_email.erb +17 -0
- metadata +23 -3
- data/config/logger.yml +0 -11
data/config/services/logger.rb
CHANGED
@@ -1,35 +1,31 @@
|
|
1
|
-
DynamoAutoscale.
|
2
|
-
if config[:sync]
|
3
|
-
STDOUT.sync = true
|
4
|
-
STDERR.sync = true
|
5
|
-
end
|
1
|
+
config = DynamoAutoscale.config[:logger] || {}
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
8
|
+
if config[:log_to]
|
9
|
+
STDOUT.reopen(config[:log_to])
|
10
|
+
STDERR.reopen(config[:log_to])
|
11
|
+
end
|
19
12
|
|
20
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
33
|
-
|
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
|
data/dynamo-autoscale.gemspec
CHANGED
@@ -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
|
-
"#{
|
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] =
|
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
|
-
|
185
|
-
|
192
|
+
wvalue = @pending[:writes]
|
193
|
+
rvalue = @pending[:reads]
|
186
194
|
|
187
195
|
if result = scale_both(rvalue, wvalue)
|
188
|
-
@provisioned[:writes][
|
189
|
-
@provisioned[:reads][
|
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
|
-
|
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
|
-
|
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,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.
|
30
|
-
|
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
|
-
|
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,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
|
15
|
+
@tables = opts[:tables] || []
|
16
|
+
@filters = opts[:filters] || []
|
13
17
|
end
|
14
18
|
|
15
19
|
def run &block
|
16
|
-
poll(
|
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
|
-
|
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
|
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
|
-
|
53
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/script/historic_data
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
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, {
|