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.
@@ -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