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,83 @@
1
+ require File.expand_path('../base', __FILE__)
2
+
3
+ module Drone
4
+ module Interfaces
5
+ ##
6
+ # This interface is meant for debug mainly, it will
7
+ # simply output all the available metrics at a regular
8
+ # interval on the console.
9
+ #
10
+ # @example
11
+ # require 'drone'
12
+ # Drone::init_drone()
13
+ # Drone::add_output(:console, 1)
14
+ #
15
+ class Console < Base
16
+
17
+ def output()
18
+ puts ""
19
+ puts "[#{Time.now.strftime('%M:%S')}] Drone report:"
20
+ Drone::each_metric do |m|
21
+ case m
22
+ when Metrics::Gauge
23
+ puts "[Gauge] #{m.name} : #{m.value}"
24
+
25
+ when Metrics::Counter
26
+ puts "[Counter] #{m.name} : #{m.value}"
27
+
28
+ when Metrics::Timer
29
+ puts "[Timer] #{m.name}"
30
+ print_meter(m)
31
+ print_histogram(m)
32
+
33
+ when Metrics::Meter
34
+ puts "[Meter] #{m.name}"
35
+ print_meter(m)
36
+
37
+ when Metrics::Histogram
38
+ puts "[Histogram] #{m.name}"
39
+ print_histogram(m)
40
+
41
+ else
42
+ puts "Unknown metric: #{m}"
43
+ end
44
+ end
45
+ end
46
+
47
+
48
+ private
49
+ def print_meter(m)
50
+ puts format("%20s : %d", "count", m.count)
51
+
52
+ {
53
+ 'mean rate' => m.mean_rate,
54
+ '1-minute rate' => m.one_minute_rate,
55
+ '5-minute rate' => m.five_minutes_rate,
56
+ '15-minute rate' => m.fifteen_minutes_rate
57
+ }.each do |label, value|
58
+ puts format("%20s : %2.2f", label, value)
59
+ end
60
+ end
61
+
62
+ def print_histogram(m)
63
+ percentiles = m.percentiles(0.5, 0.75, 0.95, 0.98, 0.99, 0.999)
64
+ {
65
+ 'min' => m.min,
66
+ 'max' => m.max,
67
+ 'mean' => m.mean,
68
+ 'stddev' => m.stdDev,
69
+ 'median' => percentiles[0],
70
+ '75%' => percentiles[1],
71
+ '%95' => percentiles[2],
72
+ '%98' => percentiles[3],
73
+ '%99' => percentiles[4],
74
+ '%99.9' => percentiles[5]
75
+ }.each do |label, value|
76
+ puts format("%20s : %2.2f", label, value)
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,28 @@
1
+ module Drone
2
+ module Metrics
3
+
4
+ class Counter
5
+ attr_reader :value, :name
6
+
7
+ def initialize(name, initial_value = 0)
8
+ @name = name
9
+ @value = initial_value
10
+ end
11
+
12
+ def increment(n = 1)
13
+ @value += n
14
+ end
15
+ alias :inc :increment
16
+
17
+ def decrement(n = 1)
18
+ @value -= n
19
+ end
20
+ alias :dec :decrement
21
+
22
+ def clear
23
+ @value = 0
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module Drone
2
+ module Metrics
3
+
4
+ ##
5
+ # Gauge are linked to a block of code which
6
+ # will be called when the value is asked, the block
7
+ # is expected to return a number
8
+ #
9
+ class Gauge
10
+ attr_reader :name
11
+
12
+ def initialize(name, &block)
13
+ raise "Block expected" unless block
14
+ @name = name
15
+ @block = block
16
+ end
17
+
18
+ def value
19
+ @block.call()
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,132 @@
1
+ require File.expand_path('../../utils/uniform_sample', __FILE__)
2
+ require File.expand_path('../../utils/exponentially_decaying_sample', __FILE__)
3
+
4
+ module Drone
5
+ class Histogram
6
+ TYPE_UNIFORM = lambda{ UniformSample.new(1028) }
7
+ TYPE_BIASED = lambda{ ExponentiallyDecayingSample.new(1028, 0.015) }
8
+
9
+ MIN = (-(2**63)).freeze
10
+ MAX = ((2**64) - 1).freeze
11
+
12
+ def initialize(sample_or_type = TYPE_UNIFORM)
13
+ if sample_or_type.is_a?(Proc)
14
+ @sample = sample_or_type.call()
15
+ else
16
+ @sample = sample_or_type
17
+ end
18
+
19
+ clear()
20
+ end
21
+
22
+ def clear
23
+ @sample.clear()
24
+ @count = 0
25
+ @_min = MAX
26
+ @_max = MIN
27
+ @_sum = 0
28
+ @varianceM = -1
29
+ @varianceS = 0
30
+ end
31
+
32
+ def update(val)
33
+ @count += 1
34
+ @sample.update(val)
35
+ set_max(val);
36
+ set_min(val);
37
+ @_sum += val
38
+ update_variance(val)
39
+ end
40
+
41
+ def count
42
+ @count
43
+ end
44
+
45
+ def max
46
+ (@count > 0) ? @_max : 0.0
47
+ end
48
+
49
+ def min
50
+ (@count > 0) ? @_min : 0.0
51
+ end
52
+
53
+ def mean
54
+ (@count > 0) ? @_sum.to_f / @count : 0.0
55
+ end
56
+
57
+ def stdDev
58
+ (@count > 0) ? Math.sqrt( variance() ) : 0.0
59
+ end
60
+
61
+ def percentiles(*percentiles)
62
+ scores = Array.new(percentiles.size, 0)
63
+ if @count > 0
64
+ values = @sample.values.sort
65
+ percentiles.each.with_index do |p, i|
66
+ pos = p * (values.size + 1)
67
+ if pos < 1
68
+ scores[i] = values[0]
69
+ elsif pos >= values.size
70
+ scores[i] = values[-1]
71
+ else
72
+ lower = values[pos - 1]
73
+ upper = values[pos]
74
+ scores[i] = lower + (pos - pos.floor) * (upper - lower)
75
+ end
76
+ end
77
+ end
78
+
79
+ scores
80
+ end
81
+
82
+ def values
83
+ @sample.values
84
+ end
85
+
86
+ private
87
+
88
+ def doubleToLongBits(n)
89
+ [n].pack('D').unpack('q')[0]
90
+ end
91
+
92
+ def longBitsToDouble(n)
93
+ [n].pack('q').unpack('D')[0]
94
+ end
95
+
96
+ def update_variance(val)
97
+ if @varianceM == -1
98
+ @varianceM = doubleToLongBits(val)
99
+ else
100
+ oldMCas = @varianceM
101
+ oldM = longBitsToDouble(oldMCas)
102
+ newM = oldM + ((val - oldM) / count())
103
+
104
+ oldSCas = @varianceS
105
+ oldS = longBitsToDouble(oldSCas)
106
+ newS = oldS + ((val - oldM) * (val - newM))
107
+
108
+ @varianceM = doubleToLongBits(newM)
109
+ @varianceS = doubleToLongBits(newS)
110
+ end
111
+ end
112
+
113
+ def variance
114
+ if @count <= 1
115
+ 0.0
116
+ else
117
+ longBitsToDouble(@varianceS) / (count() - 1)
118
+ end
119
+ end
120
+
121
+ def set_max(val)
122
+ (@_max >= val) || @_max = val
123
+ end
124
+
125
+ def set_min(val)
126
+ (@_min <= val) || @_min = val
127
+ end
128
+
129
+
130
+
131
+ end
132
+ end
@@ -0,0 +1,62 @@
1
+ require 'eventmachine'
2
+
3
+ require File.expand_path('../../core', __FILE__)
4
+ require File.expand_path('../../utils/ewma', __FILE__)
5
+
6
+ module Drone
7
+ module Metrics
8
+ # A meter metric which measures mean throughput and one-, five-, and
9
+ # fifteen-minute exponentially-weighted moving average throughputs.
10
+ class Meter
11
+ INTERVAL = 5
12
+
13
+ attr_reader :count, :name
14
+
15
+ def initialize(name)
16
+ @name = name
17
+ @start_time = Time.now
18
+ @count = 0
19
+ @rates = {
20
+ 1 => EWMA.one_minute_ewma,
21
+ 5 => EWMA.five_minutes_ewma,
22
+ 15 => EWMA.fifteen_minutes_ewma
23
+ }
24
+
25
+ Drone::schedule_periodic(INTERVAL){ tick() }
26
+ end
27
+
28
+ def tick
29
+ @rates.values.each(&:tick)
30
+ end
31
+
32
+ def mark(events = 1)
33
+ @count += events
34
+ @rates.each do |_, r|
35
+ r.update(events)
36
+ end
37
+ end
38
+
39
+ def mean_rate
40
+ if @count == 0
41
+ 0.0
42
+ else
43
+ elapsed = Time.now.to_f - @start_time.to_f
44
+ @count / elapsed
45
+ end
46
+ end
47
+
48
+ def one_minute_rate
49
+ @rates[1].rate()
50
+ end
51
+
52
+ def five_minutes_rate
53
+ @rates[5].rate()
54
+ end
55
+
56
+ def fifteen_minutes_rate
57
+ @rates[15].rate()
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,62 @@
1
+
2
+ require File.expand_path('../histogram', __FILE__)
3
+ require File.expand_path('..//meter', __FILE__)
4
+
5
+ module Drone
6
+ module Metrics
7
+ class Timer
8
+ def initialize(name = 'calls')
9
+ @histogram = Histogram.new(Histogram::TYPE_BIASED)
10
+ @meter = Meter.new(name)
11
+ clear()
12
+ end
13
+
14
+ def count
15
+ @histogram.count
16
+ end
17
+
18
+ [:fifteen_minutes_rate, :five_minutes_rate, :mean_rate, :one_minute_rate].each do |attr_name|
19
+ define_method(attr_name) do
20
+ @meter.send(attr_name)
21
+ end
22
+ end
23
+
24
+ # may requires a conversion... or not
25
+ [:count, :min, :max, :mean, :stdDev, :percentiles, :values].each do |attr_name|
26
+ define_method(attr_name) do |*args|
27
+ @histogram.send(attr_name, *args)
28
+ end
29
+ end
30
+
31
+ def clear
32
+ @histogram.clear()
33
+ end
34
+
35
+ #
36
+ # duration: milliseconds
37
+ def update(duration)
38
+ if duration >= 0
39
+ @histogram.update(duration)
40
+ @meter.mark()
41
+ end
42
+ end
43
+
44
+ #
45
+ # time and record the duration of the block
46
+ def time
47
+ started_at = Time.now.to_f
48
+ yield()
49
+ ensure
50
+ update(Time.now.to_f - started_at.to_f)
51
+ end
52
+
53
+
54
+
55
+
56
+ def name
57
+ @meter.name
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -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_meter(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_meter(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("instrumented_#{m}") do |*args, &block|
79
+ rate.mark()
80
+ send("original_#{m}", *args, &block)
81
+ end
82
+
83
+ alias_method "original_#{m}", m
84
+ alias_method m, "instrumented_#{m}"
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("instrumented_#{m}") do |*args, &block|
95
+ timer.time do
96
+ send("original_#{m}", *args, &block)
97
+ end
98
+ end
99
+
100
+ alias_method "original_#{m}", m
101
+ alias_method m, "instrumented_#{m}"
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+ end