drone 0.0.1

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