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,17 @@
1
+ module Drone
2
+ module Interfaces
3
+
4
+ class Base
5
+ def initialize(period)
6
+ @period = period
7
+ Drone::schedule_periodic(period){ output() }
8
+ end
9
+
10
+ def output
11
+ raise "Uninmplemented"
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,82 @@
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_histogram(m)
31
+
32
+ when Metrics::Meter
33
+ puts "[Meter] #{m.name}"
34
+ print_meter(m)
35
+
36
+ when Metrics::Histogram
37
+ puts "[Histogram] #{m.name}"
38
+ print_histogram(m)
39
+
40
+ else
41
+ puts "Unknown metric: #{m}"
42
+ end
43
+ end
44
+ end
45
+
46
+
47
+ private
48
+ def print_meter(m)
49
+ puts format("%20s : %d", "count", m.count)
50
+
51
+ {
52
+ 'mean rate' => m.mean_rate,
53
+ '1-minute rate' => m.one_minute_rate,
54
+ '5-minute rate' => m.five_minutes_rate,
55
+ '15-minute rate' => m.fifteen_minutes_rate
56
+ }.each do |label, value|
57
+ puts format("%20s : %2.2f", label, value)
58
+ end
59
+ end
60
+
61
+ def print_histogram(m)
62
+ percentiles = m.percentiles(0.5, 0.75, 0.95, 0.98, 0.99, 0.999)
63
+ {
64
+ 'min' => m.min,
65
+ 'max' => m.max,
66
+ 'mean' => m.mean,
67
+ 'stddev' => m.stdDev,
68
+ 'median' => percentiles[0],
69
+ '75%' => percentiles[1],
70
+ '%95' => percentiles[2],
71
+ '%98' => percentiles[3],
72
+ '%99' => percentiles[4],
73
+ '%99.9' => percentiles[5]
74
+ }.each do |label, value|
75
+ puts format("%20s : %2.2f", label, value)
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../metric', __FILE__)
2
+
3
+ module Drone
4
+ module Metrics
5
+
6
+ ##
7
+ # A Counter store a number which can go up or down,
8
+ # the counter can change a counter value with
9
+ # the methods increment and decrement aliased
10
+ # as inc and dec
11
+ #
12
+ class Counter < Metric
13
+
14
+ def initialize(name, initial_value = 0)
15
+ super(name)
16
+
17
+ @value = Drone::request_number("#{name}:value", initial_value)
18
+ end
19
+
20
+ def value
21
+ @value.get
22
+ end
23
+
24
+ def increment(n = 1)
25
+ @value.inc(n)
26
+ end
27
+ alias :inc :increment
28
+
29
+ def decrement(n = 1)
30
+ @value.dec(n)
31
+ end
32
+ alias :dec :decrement
33
+
34
+ def clear
35
+ @value.set(0)
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path('../metric', __FILE__)
2
+
3
+ module Drone
4
+ module Metrics
5
+
6
+ ##
7
+ # Gauge are linked to a block of code which
8
+ # will be called when the value is asked, the block
9
+ # is expected to return a number
10
+ #
11
+ class Gauge < Metric
12
+
13
+ def initialize(name, &block)
14
+ raise "Block expected" unless block
15
+ super(name)
16
+ @block = block
17
+ end
18
+
19
+ def value
20
+ @block.call()
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,153 @@
1
+ require File.expand_path('../../utils/uniform_sample', __FILE__)
2
+ require File.expand_path('../../utils/exponentially_decaying_sample', __FILE__)
3
+ require File.expand_path('../metric', __FILE__)
4
+
5
+ module Drone
6
+
7
+ ##
8
+ # An Histogram store a list of values (1028) and can
9
+ # compute on demand statistics on those values:
10
+ # - min/max
11
+ # - mean
12
+ # - stddev
13
+ # - percentiles
14
+ #
15
+ class Histogram < Metric
16
+ MIN = (-(2**63)).freeze
17
+ MAX = ((2**64) - 1).freeze
18
+
19
+ def initialize(name, sample_or_type = :uniform)
20
+ super(name)
21
+
22
+ if sample_or_type.is_a?(Symbol)
23
+ case sample_or_type
24
+ when :uniform then @sample = UniformSample.new("#{name}:sample", 1028)
25
+ when :biased then @sample = ExponentiallyDecayingSample.new("#{name}:sample", 1028, 0.015)
26
+ else
27
+ raise ArgumentError, "unknown type: #{sample_or_type}"
28
+ end
29
+ else
30
+ @sample = sample_or_type
31
+ end
32
+
33
+ @count = Drone::request_number("#{name}:count", 0)
34
+ @_min = Drone::request_number("#{name}:min", MAX)
35
+ @_max = Drone::request_number("#{name}:max", MIN)
36
+ @_sum = Drone::request_number("#{name}:max", 0)
37
+ @varianceM = Drone::request_number("#{name}:varianceM", -1)
38
+ @varianceS = Drone::request_number("#{name}:varianceS", 0)
39
+
40
+ end
41
+
42
+ def clear
43
+ @sample.clear()
44
+ @count = 0
45
+ @_min = MAX
46
+ @_max = MIN
47
+ @_sum = 0
48
+ @varianceM = -1
49
+ @varianceS = 0
50
+ end
51
+
52
+ def update(val)
53
+ @count.inc
54
+ @sample.update(val)
55
+ set_max(val);
56
+ set_min(val);
57
+ @_sum.inc(val)
58
+ update_variance(val)
59
+ end
60
+
61
+ def count
62
+ @count.get
63
+ end
64
+
65
+ def max
66
+ (@count.get > 0) ? @_max.get : 0.0
67
+ end
68
+
69
+ def min
70
+ (@count.get > 0) ? @_min.get : 0.0
71
+ end
72
+
73
+ def mean
74
+ (@count.get > 0) ? @_sum.get.to_f / @count.get : 0.0
75
+ end
76
+
77
+ def stdDev
78
+ (@count.get > 0) ? Math.sqrt( variance() ) : 0.0
79
+ end
80
+
81
+ def percentiles(*percentiles)
82
+ scores = Array.new(percentiles.size, 0)
83
+ if @count.get > 0
84
+ values = @sample.values.sort
85
+ percentiles.each.with_index do |p, i|
86
+ pos = p * (values.size + 1)
87
+ if pos < 1
88
+ scores[i] = values[0]
89
+ elsif pos >= values.size
90
+ scores[i] = values[-1]
91
+ else
92
+ lower = values[pos - 1]
93
+ upper = values[pos]
94
+ scores[i] = lower + (pos - pos.floor) * (upper - lower)
95
+ end
96
+ end
97
+ end
98
+
99
+ scores
100
+ end
101
+
102
+ def values
103
+ @sample.values
104
+ end
105
+
106
+ private
107
+
108
+ def doubleToLongBits(n)
109
+ [n].pack('D').unpack('q')[0]
110
+ end
111
+
112
+ def longBitsToDouble(n)
113
+ [n].pack('q').unpack('D')[0]
114
+ end
115
+
116
+ def update_variance(val)
117
+ if @varianceM.get == -1
118
+ @varianceM.set( doubleToLongBits(val) )
119
+ else
120
+ oldMCas = @varianceM.get
121
+ oldM = longBitsToDouble(oldMCas)
122
+ newM = oldM + ((val - oldM) / count())
123
+
124
+ oldSCas = @varianceS.get
125
+ oldS = longBitsToDouble(oldSCas)
126
+ newS = oldS + ((val - oldM) * (val - newM))
127
+
128
+ @varianceM.set( doubleToLongBits(newM) )
129
+ @varianceS.set( doubleToLongBits(newS) )
130
+ end
131
+ end
132
+
133
+ def variance
134
+ count = @count.get
135
+ if count <= 1
136
+ 0.0
137
+ else
138
+ longBitsToDouble(@varianceS.get) / (count - 1)
139
+ end
140
+ end
141
+
142
+ def set_max(val)
143
+ (@_max.get >= val) || @_max.set(val)
144
+ end
145
+
146
+ def set_min(val)
147
+ (@_min.get <= val) || @_min.set(val)
148
+ end
149
+
150
+
151
+
152
+ end
153
+ end
@@ -0,0 +1,82 @@
1
+ require 'eventmachine'
2
+
3
+ require File.expand_path('../metric', __FILE__)
4
+ require File.expand_path('../../core', __FILE__)
5
+ require File.expand_path('../../utils/ewma', __FILE__)
6
+
7
+ module Drone
8
+ module Metrics
9
+ ##
10
+ # A meter measures mean throughput and one-, five-, and
11
+ # fifteen-minute exponentially-weighted moving average throughputs.
12
+ #
13
+ class Meter < Metric
14
+ INTERVAL = 5
15
+
16
+ def initialize(name)
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)
22
+ @rates = {
23
+ 1 => EWMA.one_minute_ewma("#{name}:rate1"),
24
+ 5 => EWMA.five_minutes_ewma("#{name}:rate5"),
25
+ 15 => EWMA.fifteen_minutes_ewma("#{name}:rate15")
26
+ }
27
+
28
+ Drone::schedule_periodic(INTERVAL) do
29
+ Fiber.new{ tick() }.resume
30
+ end
31
+ end
32
+
33
+ def tick
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
46
+ end
47
+
48
+ def mark(events = 1)
49
+ @count.inc(events)
50
+ @rates.each do |_, r|
51
+ r.update(events)
52
+ end
53
+ end
54
+
55
+ def count
56
+ @count.get
57
+ end
58
+
59
+ def mean_rate
60
+ count = @count.get
61
+ if count == 0
62
+ 0.0
63
+ else
64
+ count / (Time.now.to_f - @start_time.get.to_f)
65
+ end
66
+ end
67
+
68
+ def one_minute_rate
69
+ @rates[1].rate()
70
+ end
71
+
72
+ def five_minutes_rate
73
+ @rates[5].rate()
74
+ end
75
+
76
+ def fifteen_minutes_rate
77
+ @rates[15].rate()
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -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
@@ -0,0 +1,57 @@
1
+ require 'forwardable'
2
+ require File.expand_path('../histogram', __FILE__)
3
+ require File.expand_path('../meter', __FILE__)
4
+ require File.expand_path('../metric', __FILE__)
5
+
6
+ module Drone
7
+ module Metrics
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
18
+
19
+ def initialize(name)
20
+ super(name)
21
+
22
+ @histogram = Histogram.new("#{name}:histogram", :biased)
23
+ end
24
+
25
+ def count
26
+ @histogram.count
27
+ end
28
+
29
+ def clear
30
+ @histogram.clear()
31
+ end
32
+
33
+ ##
34
+ # Method used to record a new duration
35
+ #
36
+ # @param [Float] duration A duration in milliseconds
37
+ #
38
+ def update(duration)
39
+ if duration >= 0
40
+ @histogram.update(duration)
41
+ end
42
+ end
43
+
44
+ ##
45
+ # time and record the duration of the block
46
+ # @yield [] The block to time
47
+ #
48
+ def time
49
+ started_at = Time.now.to_f
50
+ yield()
51
+ ensure
52
+ update((Time.now.to_f - started_at.to_f) * 1000)
53
+ end
54
+
55
+ end
56
+ end
57
+ end