drone 0.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.
@@ -0,0 +1,61 @@
1
+ require 'eventmachine'
2
+
3
+ module Drone
4
+ module Schedulers
5
+ module EMScheduler
6
+
7
+ @started = false
8
+ @timers_once = []
9
+ @timers_periodic = []
10
+
11
+ ##
12
+ # Schedule a block to be called immediatly and after
13
+ # that at a specified interval
14
+ #
15
+ # @param [Number] delay the interval
16
+ #
17
+ def self.schedule_periodic(delay, &block)
18
+ raise "Block required" unless block
19
+ if @started
20
+ block.call()
21
+ EM::add_periodic_timer(delay, &block)
22
+ else
23
+ @timers_periodic << [delay, block]
24
+ end
25
+ end
26
+
27
+
28
+ ##
29
+ # Schedule a block to be called after a specified
30
+ # delay
31
+ #
32
+ # @param [Number] delay the interval
33
+ #
34
+ def self.schedule_once(delay, &block)
35
+ raise "Block required" unless block
36
+ if @started
37
+ EM::add_timer(delay, &block)
38
+ else
39
+ @timers_once << [delay, block]
40
+ end
41
+ end
42
+
43
+
44
+ ##
45
+ # Start the timers.
46
+ #
47
+ def self.start
48
+ @started = true
49
+ @timers_once.each do |(delay, block)|
50
+ schedule_once(delay, &block)
51
+ end
52
+
53
+ @timers_periodic.each do |(delay, block)|
54
+ schedule_periodic(delay, &block)
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,50 @@
1
+ # ruby adaptation of the metrics library version by Coda Hale
2
+ class EWMA
3
+ M1_ALPHA = (1 - Math.exp(-5 / 60.0)).freeze
4
+ M5_ALPHA = (1 - Math.exp(-5 / 60.0 / 5)).freeze
5
+ M15_ALPHA = (1 - Math.exp(-5 / 60.0 / 15)).freeze
6
+
7
+ def self.one_minute_ewma
8
+ new(M1_ALPHA, 5000)
9
+ end
10
+
11
+ def self.five_minutes_ewma
12
+ new(M5_ALPHA, 5000)
13
+ end
14
+
15
+ def self.fifteen_minutes_ewma
16
+ new(M15_ALPHA, 5000)
17
+ end
18
+
19
+
20
+ # interval: in ms
21
+ def initialize(alpha, interval)
22
+ @alpha = alpha
23
+ @interval = interval.to_f # * (1000*1000)
24
+ @uncounted = 0
25
+ @rate = nil
26
+ end
27
+
28
+ def update(n)
29
+ @uncounted += n
30
+ end
31
+
32
+ def tick()
33
+ count = @uncounted
34
+ @uncounted = 0
35
+ instant_rate = count / @interval
36
+ if @rate
37
+ @rate += (@alpha * (instant_rate - @rate))
38
+ else
39
+ @rate = instant_rate
40
+ end
41
+ end
42
+
43
+ def rate(as = :seconds)
44
+ case as
45
+ when :ms then @rate
46
+ when :seconds then @rate * 1000
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,71 @@
1
+
2
+ class ExponentiallyDecayingSample
3
+ # 1 hour in ms
4
+ RESCALE_THRESHOLD = (1 * 60 * 60 * 1000).freeze
5
+
6
+ def initialize(reservoir_size, alpha)
7
+ @values = {}
8
+ @alpha = alpha
9
+ @reservoir_size = reservoir_size
10
+ clear()
11
+ end
12
+
13
+ def clear
14
+ @values.clear()
15
+ @count = 0
16
+ @start_time = Time.now
17
+ @next_scale_time = current_time() + RESCALE_THRESHOLD
18
+ end
19
+
20
+ def size
21
+ (@values.size < @count) ? @values.size : @count
22
+ end
23
+
24
+
25
+ def update(val, time = Time.now)
26
+ priority = weight(time - @start_time) / rand()
27
+ @count += 1
28
+ if @count <= @reservoir_size
29
+ @values[priority] = val
30
+ else
31
+ first = @values.keys.min
32
+ if first < priority
33
+ @values[priority] = val
34
+ while @values.delete(first) == nil
35
+ first = @values.keys.min
36
+ end
37
+ end
38
+ end
39
+
40
+ now = current_time()
41
+ if now >= @next_scale_time
42
+ rescale(now, @next_scale_time)
43
+ end
44
+ end
45
+
46
+ def values
47
+ @values.values
48
+ end
49
+
50
+ def rescale(now)
51
+ @next_scale_time = current_time() + RESCALE_THRESHOLD
52
+ old_start = @start_time
53
+ @start_time = Time.now
54
+
55
+ @values = Hash[ @values.map{ |k,v|
56
+ [k * Math.exp(-@alpha * (@start_time - old_start)), v]
57
+ }]
58
+
59
+ end
60
+
61
+ private
62
+
63
+ def current_time
64
+ Time.now.to_f * 1000
65
+ end
66
+
67
+ def weight(n)
68
+ Math.exp(@alpha * n)
69
+ end
70
+
71
+ end
@@ -0,0 +1,37 @@
1
+ class UniformSample
2
+
3
+ def initialize(size)
4
+ @values = Array.new(size)
5
+ clear()
6
+ end
7
+
8
+ def clear
9
+ @values.size.times do |n|
10
+ @values[n] = 0
11
+ end
12
+
13
+ @count = 0
14
+ end
15
+
16
+ def size
17
+ (@count > @values.size) ? @values.size : @count
18
+ end
19
+
20
+ def update(val)
21
+ @count += 1
22
+ if @count <= @values.size
23
+ @values[@count - 1] = val
24
+ else
25
+ r = rand(2**64 - 1) % @count
26
+ if r < @values.size
27
+ @values[r] = val
28
+ end
29
+ end
30
+ end
31
+
32
+ def values
33
+ # only return @count elements
34
+ @values[0,@count]
35
+ end
36
+
37
+ end
@@ -0,0 +1,3 @@
1
+ module Drone
2
+ VERSION = "0.0.1"
3
+ end
data/specs/common.rb ADDED
@@ -0,0 +1,63 @@
1
+ $:.reject! { |e| e.include? 'TextMate' }
2
+
3
+ require 'rubygems'
4
+
5
+ puts "Testing with ruby #{RUBY_VERSION} and rubygems #{Gem::VERSION}"
6
+
7
+ require 'bundler/setup'
8
+
9
+ if (RUBY_VERSION >= "1.9") && ENV['COVERAGE']
10
+ require 'simplecov'
11
+ ROOT = File.expand_path('../../', __FILE__)
12
+
13
+ puts "[[ SimpleCov enabled ]]"
14
+
15
+ SimpleCov.start do
16
+ add_filter '/gems/'
17
+ add_filter '/specs/'
18
+
19
+ root(ROOT)
20
+ end
21
+ end
22
+
23
+ require 'bacon'
24
+ require 'mocha'
25
+ require 'delorean'
26
+ require 'em-spec/bacon'
27
+ EM.spec_backend = EventMachine::Spec::Bacon
28
+
29
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
30
+
31
+ module Bacon
32
+ module MochaRequirementsCounter
33
+ def self.increment
34
+ Counter[:requirements] += 1
35
+ end
36
+ end
37
+
38
+ class Context
39
+ include Mocha::API
40
+
41
+ alias_method :it_before_mocha, :it
42
+
43
+ def it(description)
44
+ it_before_mocha(description) do
45
+ begin
46
+ mocha_setup
47
+ yield
48
+ mocha_verify(MochaRequirementsCounter)
49
+ rescue Mocha::ExpectationError => e
50
+ raise Error.new(:failed, "#{e.message}\n#{e.backtrace[0...10].join("\n")}")
51
+ ensure
52
+ mocha_teardown
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def focus(test_label)
60
+ Bacon.const_set(:RestrictName, %r{#{test_label}})
61
+ end
62
+
63
+ Bacon.summary_on_exit()
@@ -0,0 +1,41 @@
1
+ require File.expand_path('../../common', __FILE__)
2
+
3
+ require 'drone/metrics/counter'
4
+ include Drone
5
+
6
+ describe 'Counter Metrics' do
7
+ before do
8
+ @counter = Metrics::Counter.new('something')
9
+ end
10
+
11
+ should "start at zero" do
12
+ @counter.value.should == 0
13
+ end
14
+
15
+ should "increment by one" do
16
+ @counter.inc()
17
+ @counter.value.should == 1
18
+ end
19
+
20
+ should "increment by an arbitrary delta" do
21
+ @counter.inc(3)
22
+ @counter.value.should == 3
23
+ end
24
+
25
+ should "decrement by one" do
26
+ @counter.dec()
27
+ @counter.value.should == -1
28
+ end
29
+
30
+ should "decrement by an arbitrary delta" do
31
+ @counter.dec(3)
32
+ @counter.value.should == -3
33
+ end
34
+
35
+ should "be zero after being cleared" do
36
+ @counter.inc(3)
37
+ @counter.clear()
38
+ @counter.value.should == 0
39
+ end
40
+
41
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path('../../common', __FILE__)
2
+
3
+ require 'drone/metrics/gauge'
4
+ include Drone
5
+
6
+ describe 'Geuge Metric' do
7
+ before do
8
+ @n = 0
9
+ @gauge = Metrics::Gauge.new("temperature"){ @n+= 1 }
10
+ end
11
+
12
+ should 'require a block' do
13
+ err = proc{
14
+ Metrics::Gauge.new('dummy')
15
+ }.should.raise(RuntimeError)
16
+
17
+ err.message.should.include?('Block expected')
18
+ end
19
+
20
+ should 'call block when value is asked' do
21
+ @n.should == 0
22
+ @gauge.value.should == 1
23
+ @n.should == 1
24
+
25
+ @gauge.value.should == 2
26
+ end
27
+
28
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../../common', __FILE__)
2
+
3
+ require 'drone/metrics/meter'
4
+
5
+ include Drone
6
+
7
+ EM.describe 'Meter Metrics' do
8
+ before do
9
+ Drone::init_drone()
10
+ end
11
+
12
+ describe "A meter metric with no events" do
13
+ before do
14
+ @meter = Metrics::Meter.new("thangs")
15
+ end
16
+
17
+ should "have a count of zero" do
18
+ @meter.count.should == 0
19
+ done
20
+ end
21
+
22
+ should "have a mean rate of 0 events/sec" do
23
+ @meter.mean_rate.should == 0.0
24
+ done
25
+ end
26
+ end
27
+
28
+ describe "A meter metric with three events" do
29
+ before do
30
+ @meter = Metrics::Meter.new("thangs")
31
+ @meter.mark(3)
32
+ end
33
+
34
+ should "have a count of three" do
35
+ @meter.count.should == 3
36
+ done
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,131 @@
1
+ require File.expand_path('../../common', __FILE__)
2
+
3
+ require 'drone/metrics/timer'
4
+ include Drone
5
+
6
+ EM.describe 'Timer Metrics' do
7
+ before do
8
+ Drone::init_drone()
9
+ Drone::start_monitoring()
10
+ end
11
+
12
+ describe "A blank timer" do
13
+ before do
14
+ @timer = Metrics::Timer.new()
15
+ end
16
+
17
+ should "have a max of zero" do
18
+ @timer.max.should.be.close?(0, 0.001)
19
+ done
20
+ end
21
+
22
+ should "have a min of zero" do
23
+ @timer.min.should.be.close?(0, 0.001)
24
+ done
25
+ end
26
+
27
+ should "have a mean of zero" do
28
+ @timer.mean.should.be.close?(0, 0.001)
29
+ done
30
+ end
31
+
32
+ should "have a count of zero" do
33
+ @timer.count.should == 0
34
+ done
35
+ end
36
+
37
+ should "have a standard deviation of zero" do
38
+ @timer.stdDev.should.be.close?(0, 0.001)
39
+ done
40
+ end
41
+
42
+ should "have a median/p95/p98/p99/p999 of zero" do
43
+ median, p95, p98, p99, p999 = @timer.percentiles(0.5, 0.95, 0.98, 0.99, 0.999)
44
+ median.should.be.close?(0, 0.001)
45
+ p95.should.be.close?(0, 0.001)
46
+ p98.should.be.close?(0, 0.001)
47
+ p99.should.be.close?(0, 0.001)
48
+ p999.should.be.close?(0, 0.001)
49
+ done
50
+ end
51
+
52
+ should "have a mean rate of zero" do
53
+ @timer.mean_rate.should.be.close?(0, 0.001)
54
+ done
55
+ end
56
+
57
+ should "have a one-minute rate of zero" do
58
+ @timer.one_minute_rate.should.be.close?(0, 0.001)
59
+ done
60
+ end
61
+
62
+ should "have a five-minute rate of zero" do
63
+ @timer.five_minutes_rate.should.be.close?(0, 0.001)
64
+ done
65
+ end
66
+
67
+ should "have a fifteen-minute rate of zero" do
68
+ @timer.fifteen_minutes_rate.should.be.close?(0, 0.001)
69
+ done
70
+ end
71
+
72
+ should "have no values" do
73
+ @timer.values.should == []
74
+ done
75
+ end
76
+ end
77
+
78
+
79
+
80
+ describe "Timing a series of events" do
81
+ before do
82
+ @timer = Metrics::Timer.new()
83
+ @timer.update(10)
84
+ @timer.update(20)
85
+ @timer.update(20)
86
+ @timer.update(30)
87
+ @timer.update(40)
88
+ end
89
+
90
+ should "record the count" do
91
+ @timer.count.should == 5
92
+ done
93
+ end
94
+
95
+ should "calculate the minimum duration" do
96
+ @timer.min.should.be.close?(10, 0.001)
97
+ done
98
+ end
99
+
100
+ should "calclate the maximum duration" do
101
+ @timer.max.should.be.close?(40, 0.001)
102
+ done
103
+ end
104
+
105
+ should "calclate the mean duration" do
106
+ @timer.mean.should.be.close?(24, 0.001)
107
+ done
108
+ end
109
+
110
+ should "calclate the standard deviation" do
111
+ @timer.stdDev.should.be.close?(11.401, 0.001)
112
+ done
113
+ end
114
+
115
+ should "calculate the median/p95/p98/p99/p999" do
116
+ median, p95, p98, p99, p999 = @timer.percentiles(0.5, 0.95, 0.98, 0.99, 0.999)
117
+ median.should.be.close?(20, 0.001)
118
+ p95.should.be.close?(40, 0.001)
119
+ p98.should.be.close?(40, 0.001)
120
+ p99.should.be.close?(40, 0.001)
121
+ p999.should.be.close?(40, 0.001)
122
+ done
123
+ end
124
+
125
+ should "have a series of values" do
126
+ @timer.values.sort.should == [10, 20, 20, 30, 40]
127
+ done
128
+ end
129
+ end
130
+
131
+ end