dynamo-autoscale 0.1
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 +4 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +58 -0
- data/LICENSE +21 -0
- data/README.md +400 -0
- data/Rakefile +9 -0
- data/aws.sample.yml +16 -0
- data/bin/dynamo-autoscale +131 -0
- data/config/environment/common.rb +114 -0
- data/config/environment/console.rb +2 -0
- data/config/environment/test.rb +3 -0
- data/config/logger.yml +11 -0
- data/config/services/aws.rb +20 -0
- data/config/services/logger.rb +35 -0
- data/data/.gitkeep +0 -0
- data/dynamo-autoscale.gemspec +29 -0
- data/lib/dynamo-autoscale/actioner.rb +265 -0
- data/lib/dynamo-autoscale/cw_poller.rb +49 -0
- data/lib/dynamo-autoscale/dispatcher.rb +39 -0
- data/lib/dynamo-autoscale/dynamo_actioner.rb +59 -0
- data/lib/dynamo-autoscale/ext/active_support/duration.rb +7 -0
- data/lib/dynamo-autoscale/local_actioner.rb +39 -0
- data/lib/dynamo-autoscale/local_data_poll.rb +51 -0
- data/lib/dynamo-autoscale/logger.rb +15 -0
- data/lib/dynamo-autoscale/metrics.rb +192 -0
- data/lib/dynamo-autoscale/poller.rb +41 -0
- data/lib/dynamo-autoscale/pretty_formatter.rb +27 -0
- data/lib/dynamo-autoscale/rule.rb +180 -0
- data/lib/dynamo-autoscale/rule_set.rb +69 -0
- data/lib/dynamo-autoscale/table_tracker.rb +329 -0
- data/lib/dynamo-autoscale/unit_cost.rb +41 -0
- data/lib/dynamo-autoscale/version.rb +3 -0
- data/lib/dynamo-autoscale.rb +1 -0
- data/rlib/dynamodb_graph.r +15 -0
- data/rlib/dynamodb_scatterplot.r +13 -0
- data/rulesets/default.rb +5 -0
- data/rulesets/erroneous.rb +1 -0
- data/rulesets/gradual_tail.rb +11 -0
- data/rulesets/none.rb +0 -0
- data/script/console +3 -0
- data/script/historic_data +46 -0
- data/script/hourly_wastage +40 -0
- data/script/monitor +55 -0
- data/script/simulator +40 -0
- data/script/test +52 -0
- data/script/validate_ruleset +20 -0
- data/spec/actioner_spec.rb +244 -0
- data/spec/rule_set_spec.rb +89 -0
- data/spec/rule_spec.rb +491 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/table_tracker_spec.rb +256 -0
- metadata +178 -0
data/spec/rule_spec.rb
ADDED
@@ -0,0 +1,491 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DynamoAutoscale::Rule do
|
4
|
+
let(:table) { DynamoAutoscale::TableTracker.new("test_table") }
|
5
|
+
|
6
|
+
describe 'basics' do
|
7
|
+
let(:rule) do
|
8
|
+
DynamoAutoscale::Rule.new(:consumed_reads, greater_than: 5, last: 2) do
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { rule }
|
14
|
+
its(:to_english) { should be_a String }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'invalid rules' do
|
18
|
+
describe 'no greater_than or less_than' do
|
19
|
+
it 'should throw an error' do
|
20
|
+
expect do
|
21
|
+
DynamoAutoscale::Rule.new(:consumed_reads, last: 2) do
|
22
|
+
|
23
|
+
end
|
24
|
+
end.to raise_error ArgumentError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'greater_than and less_than make no logical sense' do
|
29
|
+
it 'should throw an error' do
|
30
|
+
expect do
|
31
|
+
DynamoAutoscale::Rule.new(:consumed_reads, greater_than: 5, less_than: 2) do
|
32
|
+
|
33
|
+
end
|
34
|
+
end.to raise_error ArgumentError
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'percentages should also throw an error' do
|
38
|
+
expect do
|
39
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, greater_than: "5%", less_than: "2%") do
|
40
|
+
|
41
|
+
end
|
42
|
+
end.to raise_error ArgumentError
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'greater_than less than 0' do
|
47
|
+
it 'should throw an error' do
|
48
|
+
expect do
|
49
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, greater_than: -1) do
|
50
|
+
|
51
|
+
end
|
52
|
+
end.to raise_error ArgumentError
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'percentages should also throw an error' do
|
56
|
+
expect do
|
57
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, greater_than: "-5%") do
|
58
|
+
|
59
|
+
end
|
60
|
+
end.to raise_error ArgumentError
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'less_than less than 0' do
|
65
|
+
it 'should throw an error' do
|
66
|
+
expect do
|
67
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, less_than: -1) do
|
68
|
+
|
69
|
+
end
|
70
|
+
end.to raise_error ArgumentError
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'percentages should also throw an error' do
|
74
|
+
expect do
|
75
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, less_than: "-5%") do
|
76
|
+
|
77
|
+
end
|
78
|
+
end.to raise_error ArgumentError
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'min less than 0' do
|
83
|
+
it 'should throw an error' do
|
84
|
+
expect do
|
85
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, greater_than: 1, min: -1) do
|
86
|
+
|
87
|
+
end
|
88
|
+
end.to raise_error ArgumentError
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'max less than 0' do
|
93
|
+
it 'should throw an error' do
|
94
|
+
expect do
|
95
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, greater_than: 1, max: -1) do
|
96
|
+
|
97
|
+
end
|
98
|
+
end.to raise_error ArgumentError
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'count less than 0' do
|
103
|
+
it 'should throw an error' do
|
104
|
+
expect do
|
105
|
+
DynamoAutoscale::Rule.new(:consumed_reads, for: 1, greater_than: 1, count: -1) do
|
106
|
+
|
107
|
+
end
|
108
|
+
end.to raise_error ArgumentError
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'incorrect metrics' do
|
113
|
+
it 'should throw an error' do
|
114
|
+
expect do
|
115
|
+
DynamoAutoscale::Rule.new(:whoops, for: 1, greater_than: 1) do
|
116
|
+
|
117
|
+
end
|
118
|
+
end.to raise_error ArgumentError
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'scale' do
|
123
|
+
describe 'scale and block are not given' do
|
124
|
+
it 'should throw an error' do
|
125
|
+
expect do
|
126
|
+
DynamoAutoscale::Rule.new(:consumed_reads, last: 1, greater_than: 1)
|
127
|
+
end.to raise_error ArgumentError
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe 'scale given but not hash' do
|
132
|
+
it 'should throw an error' do
|
133
|
+
expect do
|
134
|
+
DynamoAutoscale::Rule.new(:consumed_reads, last: 1, greater_than: 1, scale: 2)
|
135
|
+
end.to raise_error ArgumentError
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'scale given without :on or :by' do
|
140
|
+
it 'should throw an error' do
|
141
|
+
expect do
|
142
|
+
DynamoAutoscale::Rule.new(:consumed_reads, last: 1, greater_than: 1, scale: {})
|
143
|
+
end.to raise_error ArgumentError
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe 'scale given with invalid :on' do
|
148
|
+
it 'should throw an error' do
|
149
|
+
expect do
|
150
|
+
DynamoAutoscale::Rule.new(:consumed_reads, last: 1, greater_than: 1, scale: { on: :whoops, by: 2 })
|
151
|
+
end.to raise_error ArgumentError
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'scale given with invalid :by' do
|
156
|
+
it 'should throw an error' do
|
157
|
+
expect do
|
158
|
+
DynamoAutoscale::Rule.new(:consumed_reads, last: 1, greater_than: 1, scale: { on: :consumed, by: -0.3 })
|
159
|
+
end.to raise_error ArgumentError
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "test" do
|
166
|
+
describe "should match" do
|
167
|
+
let(:rule) do
|
168
|
+
DynamoAutoscale::Rule.new(:consumed_reads, greater_than: 5, last: 2) do
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
before do
|
174
|
+
table.tick(3.seconds.ago, {
|
175
|
+
provisioned_writes: 50, consumed_writes: 12,
|
176
|
+
provisioned_reads: 100, consumed_reads: 20,
|
177
|
+
})
|
178
|
+
|
179
|
+
table.tick(5.seconds.ago, {
|
180
|
+
provisioned_writes: 50, consumed_writes: 12,
|
181
|
+
provisioned_reads: 100, consumed_reads: 20,
|
182
|
+
})
|
183
|
+
end
|
184
|
+
|
185
|
+
subject { rule.test(table) }
|
186
|
+
it { should be_true }
|
187
|
+
end
|
188
|
+
|
189
|
+
describe 'should not match' do
|
190
|
+
let(:rule) do
|
191
|
+
DynamoAutoscale::Rule.new(:consumed_reads, greater_than: 5, last: 2) do
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context 'too few data points' do
|
197
|
+
before do
|
198
|
+
table.tick(5.seconds.ago, {
|
199
|
+
provisioned_writes: 50, consumed_writes: 12,
|
200
|
+
provisioned_reads: 100, consumed_reads: 20,
|
201
|
+
})
|
202
|
+
end
|
203
|
+
|
204
|
+
subject { rule.test(table) }
|
205
|
+
it { should be_false }
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'rule not satisfied' do
|
209
|
+
before do
|
210
|
+
table.tick(5.seconds.ago, {
|
211
|
+
provisioned_writes: 50, consumed_writes: 12,
|
212
|
+
provisioned_reads: 100, consumed_reads: 20,
|
213
|
+
})
|
214
|
+
|
215
|
+
table.tick(10.seconds.ago, {
|
216
|
+
provisioned_writes: 50, consumed_writes: 12,
|
217
|
+
provisioned_reads: 100, consumed_reads: 0,
|
218
|
+
})
|
219
|
+
end
|
220
|
+
|
221
|
+
subject { rule.test(table) }
|
222
|
+
it { should be_false }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe 'using time ranges in rules' do
|
227
|
+
describe "should match" do
|
228
|
+
let(:rule) do
|
229
|
+
DynamoAutoscale::Rule.new(:consumed_reads, {
|
230
|
+
greater_than: 5, for: 10.minutes, min: 2
|
231
|
+
}) do
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
before do
|
237
|
+
table.tick(3.seconds.ago, {
|
238
|
+
provisioned_writes: 50, consumed_writes: 12,
|
239
|
+
provisioned_reads: 100, consumed_reads: 20,
|
240
|
+
})
|
241
|
+
|
242
|
+
table.tick(11.minutes.ago, {
|
243
|
+
provisioned_writes: 50, consumed_writes: 12,
|
244
|
+
provisioned_reads: 100, consumed_reads: 0,
|
245
|
+
})
|
246
|
+
|
247
|
+
table.tick(23.seconds.ago, {
|
248
|
+
provisioned_writes: 50, consumed_writes: 12,
|
249
|
+
provisioned_reads: 100, consumed_reads: 20,
|
250
|
+
})
|
251
|
+
end
|
252
|
+
|
253
|
+
subject { rule.test(table) }
|
254
|
+
it { should be_true }
|
255
|
+
end
|
256
|
+
|
257
|
+
describe "should not match" do
|
258
|
+
let(:rule) do
|
259
|
+
DynamoAutoscale::Rule.new(:consumed_writes, {
|
260
|
+
greater_than: 5, for: 10.minutes, min: 2
|
261
|
+
}) do
|
262
|
+
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'too few data points in range' do
|
267
|
+
before do
|
268
|
+
table.tick(12.minutes.ago, {
|
269
|
+
provisioned_writes: 50, consumed_writes: 12,
|
270
|
+
provisioned_reads: 100, consumed_reads: 20,
|
271
|
+
})
|
272
|
+
|
273
|
+
table.tick(11.minutes.ago, {
|
274
|
+
provisioned_writes: 50, consumed_writes: 12,
|
275
|
+
provisioned_reads: 100, consumed_reads: 0,
|
276
|
+
})
|
277
|
+
|
278
|
+
table.tick(23.seconds.ago, {
|
279
|
+
provisioned_writes: 50, consumed_writes: 12,
|
280
|
+
provisioned_reads: 100, consumed_reads: 20,
|
281
|
+
})
|
282
|
+
end
|
283
|
+
|
284
|
+
subject { rule.test(table) }
|
285
|
+
it { should be_false }
|
286
|
+
end
|
287
|
+
|
288
|
+
context 'rule not satisfied' do
|
289
|
+
before do
|
290
|
+
table.tick(12.minutes.ago, {
|
291
|
+
provisioned_writes: 50, consumed_writes: 12,
|
292
|
+
provisioned_reads: 100, consumed_reads: 20,
|
293
|
+
})
|
294
|
+
|
295
|
+
table.tick(9.minutes.ago, {
|
296
|
+
provisioned_writes: 50, consumed_writes: 0,
|
297
|
+
provisioned_reads: 100, consumed_reads: 0,
|
298
|
+
})
|
299
|
+
|
300
|
+
table.tick(23.seconds.ago, {
|
301
|
+
provisioned_writes: 50, consumed_writes: 12,
|
302
|
+
provisioned_reads: 100, consumed_reads: 20,
|
303
|
+
})
|
304
|
+
end
|
305
|
+
|
306
|
+
subject { rule.test(table) }
|
307
|
+
it { should be_false }
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
context 'using a :max value' do
|
312
|
+
let(:rule) do
|
313
|
+
DynamoAutoscale::Rule.new(:consumed_writes, {
|
314
|
+
greater_than: 5, for: 10.minutes, min: 2, max: 2
|
315
|
+
}) do
|
316
|
+
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe 'should match' do
|
321
|
+
before do
|
322
|
+
table.tick(6.minutes.ago, {
|
323
|
+
provisioned_writes: 50, consumed_writes: 12,
|
324
|
+
provisioned_reads: 100, consumed_reads: 20,
|
325
|
+
})
|
326
|
+
|
327
|
+
table.tick(7.minutes.ago, {
|
328
|
+
provisioned_writes: 50, consumed_writes: 12,
|
329
|
+
provisioned_reads: 100, consumed_reads: 0,
|
330
|
+
})
|
331
|
+
|
332
|
+
table.tick(9.minutes.ago, {
|
333
|
+
provisioned_writes: 50, consumed_writes: 0,
|
334
|
+
provisioned_reads: 100, consumed_reads: 20,
|
335
|
+
})
|
336
|
+
end
|
337
|
+
|
338
|
+
subject { rule.test(table) }
|
339
|
+
it { should be_true }
|
340
|
+
end
|
341
|
+
|
342
|
+
describe 'should not match' do
|
343
|
+
before do
|
344
|
+
table.tick(6.minutes.ago, {
|
345
|
+
provisioned_writes: 50, consumed_writes: 12,
|
346
|
+
provisioned_reads: 100, consumed_reads: 20,
|
347
|
+
})
|
348
|
+
|
349
|
+
table.tick(9.minutes.ago, {
|
350
|
+
provisioned_writes: 50, consumed_writes: 0,
|
351
|
+
provisioned_reads: 100, consumed_reads: 0,
|
352
|
+
})
|
353
|
+
|
354
|
+
table.tick(10.minutes.ago, {
|
355
|
+
provisioned_writes: 50, consumed_writes: 12,
|
356
|
+
provisioned_reads: 100, consumed_reads: 20,
|
357
|
+
})
|
358
|
+
end
|
359
|
+
|
360
|
+
subject { rule.test(table) }
|
361
|
+
it { should be_false }
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe 'using percentage values' do
|
367
|
+
let(:rule) do
|
368
|
+
DynamoAutoscale::Rule.new(:consumed_writes, {
|
369
|
+
greater_than: "50%", for: 10.minutes, min: 2
|
370
|
+
}) do
|
371
|
+
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe 'should match' do
|
376
|
+
before do
|
377
|
+
table.tick(6.minutes.ago, {
|
378
|
+
provisioned_writes: 100, consumed_writes: 80,
|
379
|
+
provisioned_reads: 100, consumed_reads: 20,
|
380
|
+
})
|
381
|
+
|
382
|
+
table.tick(7.minutes.ago, {
|
383
|
+
provisioned_writes: 100, consumed_writes: 80,
|
384
|
+
provisioned_reads: 100, consumed_reads: 0,
|
385
|
+
})
|
386
|
+
|
387
|
+
table.tick(9.minutes.ago, {
|
388
|
+
provisioned_writes: 100, consumed_writes: 51,
|
389
|
+
provisioned_reads: 100, consumed_reads: 20,
|
390
|
+
})
|
391
|
+
end
|
392
|
+
|
393
|
+
subject { rule.test(table) }
|
394
|
+
it { should be_true }
|
395
|
+
end
|
396
|
+
|
397
|
+
describe 'should not match' do
|
398
|
+
before do
|
399
|
+
table.tick(6.minutes.ago, {
|
400
|
+
provisioned_writes: 100, consumed_writes: 51,
|
401
|
+
provisioned_reads: 100, consumed_reads: 20,
|
402
|
+
})
|
403
|
+
|
404
|
+
table.tick(7.minutes.ago, {
|
405
|
+
provisioned_writes: 100, consumed_writes: 50,
|
406
|
+
provisioned_reads: 100, consumed_reads: 0,
|
407
|
+
})
|
408
|
+
|
409
|
+
table.tick(8.minutes.ago, {
|
410
|
+
provisioned_writes: 100, consumed_writes: 12,
|
411
|
+
provisioned_reads: 100, consumed_reads: 20,
|
412
|
+
})
|
413
|
+
end
|
414
|
+
|
415
|
+
subject { rule.test(table) }
|
416
|
+
it { should be_false }
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
describe 'using a :times variable' do
|
421
|
+
let :rule do
|
422
|
+
DynamoAutoscale::Rule.new(:consumed_reads, {
|
423
|
+
greater_than: 5, last: 2, times: 3, min: 2
|
424
|
+
}) do
|
425
|
+
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
describe 'should not match' do
|
430
|
+
before do
|
431
|
+
table.clear_data
|
432
|
+
table.tick(8.minutes.ago, {
|
433
|
+
provisioned_writes: 100, consumed_writes: 12,
|
434
|
+
provisioned_reads: 100, consumed_reads: 20,
|
435
|
+
})
|
436
|
+
|
437
|
+
rule.test(table)
|
438
|
+
|
439
|
+
table.tick(7.minutes.ago, {
|
440
|
+
provisioned_writes: 100, consumed_writes: 12,
|
441
|
+
provisioned_reads: 100, consumed_reads: 20,
|
442
|
+
})
|
443
|
+
|
444
|
+
rule.test(table)
|
445
|
+
|
446
|
+
table.tick(6.minutes.ago, {
|
447
|
+
provisioned_writes: 100, consumed_writes: 12,
|
448
|
+
provisioned_reads: 100, consumed_reads: 20,
|
449
|
+
})
|
450
|
+
end
|
451
|
+
|
452
|
+
subject { rule.test(table) }
|
453
|
+
it { should be_false }
|
454
|
+
end
|
455
|
+
|
456
|
+
describe 'should match' do
|
457
|
+
before do
|
458
|
+
table.clear_data
|
459
|
+
table.tick(5.minutes.ago, {
|
460
|
+
provisioned_writes: 100, consumed_writes: 12,
|
461
|
+
provisioned_reads: 100, consumed_reads: 20,
|
462
|
+
})
|
463
|
+
|
464
|
+
rule.test(table)
|
465
|
+
|
466
|
+
table.tick(6.minutes.ago, {
|
467
|
+
provisioned_writes: 100, consumed_writes: 12,
|
468
|
+
provisioned_reads: 100, consumed_reads: 20,
|
469
|
+
})
|
470
|
+
|
471
|
+
rule.test(table)
|
472
|
+
|
473
|
+
table.tick(7.minutes.ago, {
|
474
|
+
provisioned_writes: 100, consumed_writes: 12,
|
475
|
+
provisioned_reads: 100, consumed_reads: 20,
|
476
|
+
})
|
477
|
+
|
478
|
+
rule.test(table)
|
479
|
+
|
480
|
+
table.tick(8.minutes.ago, {
|
481
|
+
provisioned_writes: 100, consumed_writes: 12,
|
482
|
+
provisioned_reads: 100, consumed_reads: 20,
|
483
|
+
})
|
484
|
+
end
|
485
|
+
|
486
|
+
subject { rule.test(table) }
|
487
|
+
it { should be_true }
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end # describe 'test'
|
491
|
+
end # describe DynamoAutoscale::Rule
|
data/spec/spec_helper.rb
ADDED