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.
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
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
@@ -0,0 +1,4 @@
1
+ require_relative '../config/environment/test'
2
+
3
+ RSpec.configure do |config|
4
+ end