drone 0.0.3 → 1.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/.rvmrc +1 -0
- data/README.md +73 -49
- data/Rakefile +1 -1
- data/examples/simple.rb +10 -11
- data/lib/drone/core.rb +46 -30
- data/lib/drone/metrics/counter.rb +19 -7
- data/lib/drone/metrics/gauge.rb +4 -3
- data/lib/drone/metrics/histogram.rb +47 -26
- data/lib/drone/metrics/meter.rb +36 -16
- data/lib/drone/metrics/metric.rb +16 -0
- data/lib/drone/metrics/timer.rb +26 -19
- data/lib/drone/monitoring.rb +2 -2
- data/lib/drone/storage/base.rb +122 -0
- data/lib/drone/storage/memory.rb +58 -0
- data/lib/drone/utils/ewma.rb +44 -39
- data/lib/drone/utils/exponentially_decaying_sample.rb +61 -51
- data/lib/drone/utils/uniform_sample.rb +43 -28
- data/lib/drone/version.rb +1 -1
- data/specs/metrics/counter_spec.rb +2 -0
- data/specs/metrics/timer_spec.rb +2 -2
- data/specs/unit/ewma_spec.rb +6 -3
- data/specs/unit/exponentially_decaying_sample_spec.rb +6 -3
- data/specs/unit/histogram_spec.rb +6 -2
- data/specs/unit/monitoring_spec.rb +3 -3
- data/specs/unit/uniform_sample_spec.rb +5 -2
- metadata +6 -2
data/lib/drone/metrics/meter.rb
CHANGED
@@ -1,47 +1,67 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
|
3
|
+
require File.expand_path('../metric', __FILE__)
|
3
4
|
require File.expand_path('../../core', __FILE__)
|
4
5
|
require File.expand_path('../../utils/ewma', __FILE__)
|
5
6
|
|
6
7
|
module Drone
|
7
8
|
module Metrics
|
8
|
-
|
9
|
+
##
|
10
|
+
# A meter measures mean throughput and one-, five-, and
|
9
11
|
# fifteen-minute exponentially-weighted moving average throughputs.
|
10
|
-
|
12
|
+
#
|
13
|
+
class Meter < Metric
|
11
14
|
INTERVAL = 5
|
12
15
|
|
13
|
-
attr_reader :count, :name
|
14
|
-
|
15
16
|
def initialize(name)
|
16
|
-
|
17
|
-
@start_time = Time.now
|
18
|
-
@
|
17
|
+
super(name)
|
18
|
+
@start_time = Drone::request_number("#{name}:start_time", Time.now)
|
19
|
+
@next_tick = Drone::request_number("#{name}:next_tick_lock", 1)
|
20
|
+
|
21
|
+
@count = Drone::request_number("#{name}:count", 0)
|
19
22
|
@rates = {
|
20
|
-
1 => EWMA.one_minute_ewma,
|
21
|
-
5 => EWMA.five_minutes_ewma,
|
22
|
-
15 => EWMA.fifteen_minutes_ewma
|
23
|
+
1 => EWMA.one_minute_ewma("#{name}:rate1"),
|
24
|
+
5 => EWMA.five_minutes_ewma("#{name}:rate5"),
|
25
|
+
15 => EWMA.fifteen_minutes_ewma("#{name}:rate15")
|
23
26
|
}
|
24
27
|
|
25
|
-
Drone::schedule_periodic(INTERVAL)
|
28
|
+
Drone::schedule_periodic(INTERVAL) do
|
29
|
+
Fiber.new{ tick() }.resume
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
33
|
def tick
|
29
|
-
|
34
|
+
# init if required
|
35
|
+
@local_next_tick ||= @next_tick.get
|
36
|
+
|
37
|
+
# ensure only one process will trigger the tick
|
38
|
+
if @next_tick.compare_and_set(@local_next_tick, @local_next_tick + 1)
|
39
|
+
@rates.values.each(&:tick)
|
40
|
+
@local_next_tick += 1
|
41
|
+
else
|
42
|
+
# reset the tick counter to give a chance to this
|
43
|
+
# process to trigger the next tick
|
44
|
+
@local_next_tick = @next_tick.get()
|
45
|
+
end
|
30
46
|
end
|
31
47
|
|
32
48
|
def mark(events = 1)
|
33
|
-
@count
|
49
|
+
@count.inc(events)
|
34
50
|
@rates.each do |_, r|
|
35
51
|
r.update(events)
|
36
52
|
end
|
37
53
|
end
|
54
|
+
|
55
|
+
def count
|
56
|
+
@count.get
|
57
|
+
end
|
38
58
|
|
39
59
|
def mean_rate
|
40
|
-
|
60
|
+
count = @count.get
|
61
|
+
if count == 0
|
41
62
|
0.0
|
42
63
|
else
|
43
|
-
|
44
|
-
@count / elapsed
|
64
|
+
count / (Time.now.to_f - @start_time.get.to_f)
|
45
65
|
end
|
46
66
|
end
|
47
67
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Drone
|
2
|
+
class Metric
|
3
|
+
##
|
4
|
+
# Every metric must have a name to be referenced by
|
5
|
+
#
|
6
|
+
# @attr_reader [String] name The metric's name
|
7
|
+
# (which is also its id)
|
8
|
+
#
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/drone/metrics/timer.rb
CHANGED
@@ -1,48 +1,55 @@
|
|
1
|
-
|
1
|
+
require 'forwardable'
|
2
2
|
require File.expand_path('../histogram', __FILE__)
|
3
|
-
require File.expand_path('
|
3
|
+
require File.expand_path('../meter', __FILE__)
|
4
|
+
require File.expand_path('../metric', __FILE__)
|
4
5
|
|
5
6
|
module Drone
|
6
7
|
module Metrics
|
7
|
-
|
8
|
-
|
8
|
+
##
|
9
|
+
# The timer metric will record the time spent in a given method
|
10
|
+
# or any block of code.
|
11
|
+
#
|
12
|
+
# All the times are in milliseconds.
|
13
|
+
#
|
14
|
+
class Timer < Metric
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegators :@histogram, :count, :min, :max, :mean, :stdDev, :percentiles, :values
|
9
18
|
|
10
|
-
def initialize(name
|
11
|
-
|
12
|
-
|
13
|
-
|
19
|
+
def initialize(name)
|
20
|
+
super(name)
|
21
|
+
|
22
|
+
@histogram = Histogram.new("#{name}:histogram", :biased)
|
14
23
|
end
|
15
24
|
|
16
25
|
def count
|
17
26
|
@histogram.count
|
18
27
|
end
|
19
28
|
|
20
|
-
# may requires a conversion... or not
|
21
|
-
[:count, :min, :max, :mean, :stdDev, :percentiles, :values].each do |attr_name|
|
22
|
-
define_method(attr_name) do |*args|
|
23
|
-
@histogram.send(attr_name, *args)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
29
|
def clear
|
28
30
|
@histogram.clear()
|
29
31
|
end
|
30
32
|
|
31
|
-
|
32
|
-
# duration
|
33
|
+
##
|
34
|
+
# Method used to record a new duration
|
35
|
+
#
|
36
|
+
# @param [Float] duration A duration in milliseconds
|
37
|
+
#
|
33
38
|
def update(duration)
|
34
39
|
if duration >= 0
|
35
40
|
@histogram.update(duration)
|
36
41
|
end
|
37
42
|
end
|
38
43
|
|
39
|
-
|
44
|
+
##
|
40
45
|
# time and record the duration of the block
|
46
|
+
# @yield [] The block to time
|
47
|
+
#
|
41
48
|
def time
|
42
49
|
started_at = Time.now.to_f
|
43
50
|
yield()
|
44
51
|
ensure
|
45
|
-
update(Time.now.to_f - started_at.to_f)
|
52
|
+
update((Time.now.to_f - started_at.to_f) * 1000)
|
46
53
|
end
|
47
54
|
|
48
55
|
end
|
data/lib/drone/monitoring.rb
CHANGED
@@ -31,7 +31,7 @@ module Drone
|
|
31
31
|
raise(TypeError, "metric #{name} is already defined as #{rate.class}")
|
32
32
|
end
|
33
33
|
|
34
|
-
Drone::
|
34
|
+
Drone::register_metric(meter)
|
35
35
|
@_rate_waiting = meter
|
36
36
|
end
|
37
37
|
|
@@ -49,7 +49,7 @@ module Drone
|
|
49
49
|
unless timer.is_a?(Metrics::Timer)
|
50
50
|
raise(TypeError, "metric #{name} is already defined as #{rate.class}")
|
51
51
|
end
|
52
|
-
Drone::
|
52
|
+
Drone::register_metric(timer)
|
53
53
|
@_timer_waiting = timer
|
54
54
|
end
|
55
55
|
|
@@ -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
|
data/lib/drone/utils/ewma.rb
CHANGED
@@ -1,50 +1,55 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
def self.five_minutes_ewma(id)
|
14
|
+
new(id, M5_ALPHA, 5000)
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
def self.fifteen_minutes_ewma(id)
|
18
|
+
new(id, M15_ALPHA, 5000)
|
19
|
+
end
|
18
20
|
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
def update(n)
|
31
|
+
@uncounted.inc(n)
|
32
|
+
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
40
45
|
end
|
41
|
-
end
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
def rate(as = :seconds)
|
48
|
+
case as
|
49
|
+
when :ms then @rate.get
|
50
|
+
when :seconds then @rate.get * 1000
|
51
|
+
end
|
47
52
|
end
|
48
|
-
end
|
49
53
|
|
54
|
+
end
|
50
55
|
end
|