drone 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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