drone 0.0.3 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # A meter metric which measures mean throughput and one-, five-, and
9
+ ##
10
+ # A meter measures mean throughput and one-, five-, and
9
11
  # fifteen-minute exponentially-weighted moving average throughputs.
10
- class Meter
12
+ #
13
+ class Meter < Metric
11
14
  INTERVAL = 5
12
15
 
13
- attr_reader :count, :name
14
-
15
16
  def initialize(name)
16
- @name = name
17
- @start_time = Time.now
18
- @count = 0
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){ tick() }
28
+ Drone::schedule_periodic(INTERVAL) do
29
+ Fiber.new{ tick() }.resume
30
+ end
26
31
  end
27
32
 
28
33
  def tick
29
- @rates.values.each(&: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
30
46
  end
31
47
 
32
48
  def mark(events = 1)
33
- @count += events
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
- if @count == 0
60
+ count = @count.get
61
+ if count == 0
41
62
  0.0
42
63
  else
43
- elapsed = Time.now.to_f - @start_time.to_f
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
@@ -1,48 +1,55 @@
1
-
1
+ require 'forwardable'
2
2
  require File.expand_path('../histogram', __FILE__)
3
- require File.expand_path('..//meter', __FILE__)
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
- class Timer
8
- attr_reader :name
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 = 'calls')
11
- @name = name
12
- @histogram = Histogram.new(Histogram::TYPE_BIASED)
13
- clear()
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: milliseconds
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
@@ -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::register_meter(meter)
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::register_meter(timer)
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
@@ -1,50 +1,55 @@
1
- # ruby adaptation of the metrics library version by Coda Hale
2
- class EWMA
3
- M1_ALPHA = (1 - Math.exp(-5 / 60.0)).freeze
4
- M5_ALPHA = (1 - Math.exp(-5 / 60.0 / 5)).freeze
5
- M15_ALPHA = (1 - Math.exp(-5 / 60.0 / 15)).freeze
6
-
7
- def self.one_minute_ewma
8
- new(M1_ALPHA, 5000)
9
- end
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
- def self.five_minutes_ewma
12
- new(M5_ALPHA, 5000)
13
- end
13
+ def self.five_minutes_ewma(id)
14
+ new(id, M5_ALPHA, 5000)
15
+ end
14
16
 
15
- def self.fifteen_minutes_ewma
16
- new(M15_ALPHA, 5000)
17
- end
17
+ def self.fifteen_minutes_ewma(id)
18
+ new(id, M15_ALPHA, 5000)
19
+ end
18
20
 
19
21
 
20
- # interval: in ms
21
- def initialize(alpha, interval)
22
- @alpha = alpha
23
- @interval = interval.to_f # * (1000*1000)
24
- @uncounted = 0
25
- @rate = nil
26
- end
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
- def update(n)
29
- @uncounted += n
30
- end
30
+ def update(n)
31
+ @uncounted.inc(n)
32
+ end
31
33
 
32
- def tick()
33
- count = @uncounted
34
- @uncounted = 0
35
- instant_rate = count / @interval
36
- if @rate
37
- @rate += (@alpha * (instant_rate - @rate))
38
- else
39
- @rate = instant_rate
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
- def rate(as = :seconds)
44
- case as
45
- when :ms then @rate
46
- when :seconds then @rate * 1000
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