drone 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ require File.expand_path('../metrics/meter', __FILE__)
2
+ require File.expand_path('../metrics/timer', __FILE__)
3
+
4
+ module Drone
5
+ ##
6
+ # This module contains what is needed to instruments
7
+ # class methods easily
8
+ #
9
+ module Monitoring
10
+ def self.included(base)
11
+ base.class_eval do
12
+ extend ClassMethods
13
+ end
14
+
15
+ Drone::register_monitored_class(base)
16
+ end
17
+
18
+ module ClassMethods
19
+ # external API
20
+
21
+ ##
22
+ # Monitor the call rate of the following method
23
+ #
24
+ # @param [String] name metric name, it must be unique and will be shared
25
+ # among all the objects of this class
26
+ # @api public
27
+ #
28
+ def monitor_rate(name)
29
+ meter = Drone::find_metric(name) || Metrics::Meter.new(name)
30
+ unless meter.is_a?(Metrics::Meter)
31
+ raise(TypeError, "metric #{name} is already defined as #{rate.class}")
32
+ end
33
+
34
+ Drone::register_metric(meter)
35
+ @_rate_waiting = meter
36
+ end
37
+
38
+ ##
39
+ # Monitor the time of execution as well as the
40
+ # call rate
41
+ #
42
+ # @param [String] name metric name, it must be unique and will be shared
43
+ # among all the objects of this class
44
+ #
45
+ # @api public
46
+ #
47
+ def monitor_time(name)
48
+ timer = Drone::find_metric(name) || Metrics::Timer.new(name)
49
+ unless timer.is_a?(Metrics::Timer)
50
+ raise(TypeError, "metric #{name} is already defined as #{rate.class}")
51
+ end
52
+ Drone::register_metric(timer)
53
+ @_timer_waiting = timer
54
+ end
55
+
56
+
57
+ # internals
58
+
59
+ ##
60
+ # @private
61
+ #
62
+ def method_added(m)
63
+ return if @_ignore_added
64
+
65
+ @_ignore_added = true
66
+ ma_rate_meter(m) if @_rate_waiting
67
+ ma_timer_meter(m) if @_timer_waiting
68
+ @_ignore_added = false
69
+ end
70
+
71
+ ##
72
+ # @private
73
+ #
74
+ def ma_rate_meter(m)
75
+ rate = @_rate_waiting
76
+ @_rate_waiting = nil
77
+
78
+ define_method("#{m}_with_meter") do |*args, &block|
79
+ rate.mark()
80
+ send("#{m}_without_meter", *args, &block)
81
+ end
82
+
83
+ alias_method "#{m}_without_meter", m
84
+ alias_method m, "#{m}_with_meter"
85
+ end
86
+
87
+ ##
88
+ # @private
89
+ #
90
+ def ma_timer_meter(m)
91
+ timer = @_timer_waiting
92
+ @_timer_waiting = nil
93
+
94
+ define_method("#{m}_with_timer") do |*args, &block|
95
+ timer.time do
96
+ send("#{m}_without_timer", *args, &block)
97
+ end
98
+ end
99
+
100
+ alias_method "#{m}_without_timer", m
101
+ alias_method m, "#{m}_with_timer"
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,70 @@
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
+ ##
60
+ # @private
61
+ #
62
+ def self.reset
63
+ @timers_once.clear()
64
+ @timers_periodic.clear()
65
+ @started = false
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,122 @@
1
+ module Drone
2
+ module Storage
3
+
4
+ ##
5
+ # Represents a number but procide a specific api
6
+ # allowing this number to shared anywhere
7
+ #
8
+ class SharedNumber
9
+ ##
10
+ # Constructor
11
+ #
12
+ # @param [Number] initial_value The initial value
13
+ #
14
+ def initialize(initial_value)
15
+ raise "needs to be redefined"
16
+ end
17
+
18
+ ##
19
+ # Increment the value
20
+ #
21
+ # @param [Number] n Increment by n
22
+ #
23
+ def inc(n = 1)
24
+ raise "needs to be redefined"
25
+ end
26
+
27
+ ##
28
+ # Decrement the value
29
+ #
30
+ # @param [Number] n Decrement by n
31
+ #
32
+ def dec(n = 1)
33
+ raise "needs to be redefined"
34
+ end
35
+
36
+ ##
37
+ # Set the value
38
+ #
39
+ # @param [Number] n The new value
40
+ #
41
+ def set(n)
42
+ raise "needs to be redefined"
43
+ end
44
+
45
+ ##
46
+ # Get the current value
47
+ #
48
+ # @return [Number] The current value
49
+ #
50
+ def get
51
+ raise "needs to be redefined"
52
+ end
53
+
54
+ ##
55
+ # Set a new value and return the old one
56
+ #
57
+ # @param [Number] n The new value
58
+ #
59
+ # @return [Number] The old value
60
+ #
61
+ def get_and_set(n)
62
+ raise "needs to be redefined"
63
+ end
64
+
65
+ ##
66
+ # Set the new value but only if the current value
67
+ # is equal to expected.
68
+ #
69
+ # @param [Number] expected The expected current value
70
+ # @param [Number] new_value The new value
71
+ #
72
+ def compare_and_set(expected, new_value)
73
+ raise "needs to be redefined"
74
+ end
75
+
76
+ end
77
+
78
+ class Base
79
+
80
+ ##
81
+ # Request a fixed size array.
82
+ #
83
+ # @param [String] id Any string which makes sense in the context
84
+ # @param [Number] size The Array size
85
+ # @param [Number] initial_value The default value for the cells
86
+ #
87
+ # @return [Object] Returns an object which share the same external interface as
88
+ # the Array class
89
+ #
90
+ def request_fixed_size_array(id, size, initial_value = nil)
91
+ raise "needs to be redefined"
92
+ end
93
+
94
+ ##
95
+ # Request a hash.
96
+ #
97
+ # @param [String] id Any string which makes sense in the context
98
+ #
99
+ # @return [Object] Returns an object which share the same external interface as
100
+ # the Hash class
101
+ #
102
+ def request_hash(id)
103
+ raise "needs to be redefined"
104
+ end
105
+
106
+ ##
107
+ # Request a number "slot".
108
+ #
109
+ # @param [String] id Any string which makes sense in the context
110
+ # @param [Number] initial_value The intial value
111
+ #
112
+ # @return [SharedNumber] An intance of a class inheriting SharedNumber
113
+ #
114
+ # @see SharedNumber
115
+ #
116
+ def request_number(id, initial_value = 0)
117
+ raise "needs to be redefined"
118
+ end
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path('../base', __FILE__)
2
+
3
+ module Drone
4
+ module Storage
5
+
6
+ class Memory < Base
7
+
8
+ class MemorySharedNumber
9
+ def initialize(initial_value)
10
+ @store = initial_value
11
+ end
12
+
13
+ def inc(n = 1)
14
+ @store += n
15
+ end
16
+
17
+ def dec(n = 1)
18
+ @store -= n
19
+ end
20
+
21
+ def set(n)
22
+ @store = n
23
+ end
24
+
25
+ def get
26
+ @store
27
+ end
28
+
29
+ def get_and_set(n)
30
+ ret = @store
31
+ set(n)
32
+ ret
33
+ end
34
+
35
+ def compare_and_set(expected, new_value)
36
+ # dummy implementation, with memory storage nothing can
37
+ # happen to our data
38
+ set(new_value)
39
+ end
40
+
41
+ end
42
+
43
+ def request_fixed_size_array(id, size, initial_value = nil)
44
+ Array.new(size, initial_value)
45
+ end
46
+
47
+ def request_hash(id)
48
+ Hash.new
49
+ end
50
+
51
+ def request_number(id, initial_value = 0)
52
+ MemorySharedNumber.new(initial_value)
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,55 @@
1
+ require File.expand_path('../../core', __FILE__)
2
+
3
+ module Drone
4
+ class EWMA
5
+ M1_ALPHA = (1 - Math.exp(-5 / 60.0)).freeze
6
+ M5_ALPHA = (1 - Math.exp(-5 / 60.0 / 5)).freeze
7
+ M15_ALPHA = (1 - Math.exp(-5 / 60.0 / 15)).freeze
8
+
9
+ def self.one_minute_ewma(id)
10
+ new(id, M1_ALPHA, 5000)
11
+ end
12
+
13
+ def self.five_minutes_ewma(id)
14
+ new(id, M5_ALPHA, 5000)
15
+ end
16
+
17
+ def self.fifteen_minutes_ewma(id)
18
+ new(id, M15_ALPHA, 5000)
19
+ end
20
+
21
+
22
+ # interval: in ms
23
+ def initialize(name, alpha, interval)
24
+ @alpha = alpha
25
+ @interval = interval.to_f # * (1000*1000)
26
+ @uncounted = Drone::request_number("#{name}:uncounted", 0)
27
+ @rate = Drone::request_number("#{name}:rate", nil)
28
+ end
29
+
30
+ def update(n)
31
+ @uncounted.inc(n)
32
+ end
33
+
34
+ def tick()
35
+ count = @uncounted.get_and_set(0)
36
+
37
+ instant_rate = count / @interval
38
+ rate = @rate.get
39
+
40
+ if rate
41
+ @rate.inc( @alpha * (instant_rate - rate) )
42
+ else
43
+ @rate.set( instant_rate )
44
+ end
45
+ end
46
+
47
+ def rate(as = :seconds)
48
+ case as
49
+ when :ms then @rate.get
50
+ when :seconds then @rate.get * 1000
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,81 @@
1
+ require File.expand_path('../../core', __FILE__)
2
+
3
+ module Drone
4
+ class ExponentiallyDecayingSample
5
+ # 1 hour in ms
6
+ RESCALE_THRESHOLD = (1 * 60 * 60 * 1000).freeze
7
+
8
+ def initialize(id, reservoir_size, alpha)
9
+ @id = id
10
+ @values = Drone::request_hash("#{@id}:values")
11
+ @count = Drone::request_number("#{@id}:count", 0)
12
+ @start_time = Drone::request_number("#{@id}:start_time", current_time())
13
+ @next_scale_time = Drone::request_number("#{@id}:next_scale_time", current_time() + RESCALE_THRESHOLD)
14
+
15
+ @alpha = alpha
16
+ @reservoir_size = reservoir_size
17
+ end
18
+
19
+ def clear
20
+ @values.clear()
21
+ @count.set(0)
22
+ @start_time.set(current_time())
23
+ @next_scale_time.set( current_time() + RESCALE_THRESHOLD )
24
+ end
25
+
26
+ def size
27
+ count = @count.get
28
+ (@values.size < count) ? @values.size : count
29
+ end
30
+
31
+
32
+ def update(val, time = current_time)
33
+ priority = weight(time - @start_time.get) / rand()
34
+ count = @count.inc
35
+ if count <= @reservoir_size
36
+ @values[priority] = val
37
+ else
38
+ first = @values.keys.min
39
+ if first < priority
40
+ @values[priority] = val
41
+ while @values.delete(first) == nil
42
+ first = @values.keys.min
43
+ end
44
+ end
45
+ end
46
+
47
+ now = current_time()
48
+ if now >= @next_scale_time.get
49
+ rescale(now)
50
+ end
51
+ end
52
+
53
+ def values
54
+ @values.keys.sort.inject([]) do |buff, key|
55
+ buff << @values[key]
56
+ end
57
+ end
58
+
59
+ def rescale(now)
60
+ @next_scale_time.set( current_time() + RESCALE_THRESHOLD )
61
+ new_start = current_time()
62
+ old_start = @start_time.get_and_set(new_start)
63
+
64
+ @values = Hash[ @values.map{ |k,v|
65
+ [k * Math.exp(-@alpha * (new_start - old_start)), v]
66
+ }]
67
+
68
+ end
69
+
70
+ private
71
+
72
+ def current_time
73
+ Time.now.to_f * 1000
74
+ end
75
+
76
+ def weight(n)
77
+ Math.exp(@alpha * n)
78
+ end
79
+
80
+ end
81
+ end