drone 0.0.3 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2
data/README.md CHANGED
@@ -14,74 +14,87 @@ A fully working example is included in examples/simple, to run it
14
14
 
15
15
  The example will output the collected statistics directly on the console every second.
16
16
 
17
+ # Supported Runtimes
18
+
19
+ - MRI 1.8.7+
20
+ - Rubinius 1.2.2+
21
+
22
+
23
+ # Status
24
+ - Most of the features I wanted in are (see below for usage examples):
25
+ - timing method calls
26
+ - method calls rate
27
+ - counters
28
+ - gauges
29
+
30
+ - Good test coverage
31
+
32
+ The gem was created for a specific need and is currently used in preproduction environment,
33
+ no major bugs until now.
34
+
35
+
17
36
  # How is it done
18
37
 
19
38
  The library is split in different parts
20
39
 
21
- - the core
40
+ - the core:<br/>
22
41
  it contains all the API used to declare which data to collect and how as well as the storage for them
23
42
 
24
- - the metrics
43
+ - the metrics:<br/>
25
44
  that is all the metrics type the library know.
26
45
 
27
- - the interfaces
46
+ - the interfaces:<br/>
28
47
  those are the parts which will decides how the stored data are made available.
29
48
 
30
- - the schedulers
49
+ - the schedulers:</br>
31
50
  this is where the timers are scheduled, currently there is only one scheduler: eventmachine
32
51
 
52
+ - the storage:<br/>
53
+ this part decides where the actual data for the metrics are stored, the default is to store them
54
+ in memory but other possible options are: redis, memcached, etc...
55
+ The goal for external storage is to allow concurrent applications to share the same metrics, an
56
+ immediate example of such application is a rails application ran under passenger or any other spawner
57
+
33
58
  ## Constraints
34
59
 
35
60
  - the name of each metric can be formatted how it pleases you (note that output interfaces may expect some format)
36
61
  but the name is expected to be unique or you could end up reusing the same metric without wanting it.
37
62
  (this only applies to monitor_time and monitor_rate helpers but could apply anywhere else as needed)
38
63
 
39
-
40
- # Supported Runtimes
41
-
42
- - MRI 1.8.7+
43
- - Rubinius 1.2.2+
44
-
45
-
46
- # Status
47
- - Most of the features I wanted in are:
48
- - timing method calls
49
- - method calls rate
50
- - counters
51
- - gauges
52
-
53
- - Decent test coverage (Simplecov report ~ 87% for what is worth)
54
-
55
64
  # Usage
56
65
 
57
66
  I try to keep things as simple as possible, there is currently two ways to use
58
67
  this library:
59
68
 
60
69
  - the first one is to just instantiate metrics by hand and use them directly
70
+
71
+ ``` ruby
72
+ require 'drone'
73
+ Drone::init_drone()
74
+ @counter = Drone::Metris::Counter.new('my_counter')
61
75
 
62
- require 'drone'
63
- Drone::init_drone()
64
- @counter = Drone::Metris::Counter.new('my_counter')
65
-
66
- def some_method
67
- @counter.inc()
68
- end
76
+ def some_method
77
+ @counter.inc()
78
+ end
79
+ ```
69
80
 
70
81
  - the other way is to instrument a class:
71
82
 
72
- require 'drone'
73
- Drone::init_drone()
83
+ ``` ruby
84
+ require 'drone'
85
+ Drone::init_drone()
86
+
87
+ class User
88
+ include Drone::Monitoring
89
+
90
+ monitor_rate("users/new")
91
+ def initialize(login, pass); end
74
92
 
75
- class User
76
- include Drone::Monitoring
77
-
78
- monitor_rate("users/new")
79
- def initialize(login, pass); end
80
-
81
- monitor_time("users/rename")
82
- def rename(new_login); end
83
-
84
- end
93
+ monitor_time("users/rename")
94
+ def rename(new_login); end
95
+
96
+ end
97
+ ```
85
98
 
86
99
  This code will create three metrics:
87
100
  - "users/new" : how many users are created each second
@@ -93,13 +106,23 @@ Once you have your data you need to add a way to serve them, each lives in a sep
93
106
  gem to limit the core's dependencies so the only one in core is:
94
107
 
95
108
  - console output (puts), mainly for debug:
96
-
97
- require 'drone'
98
- Drone::init_drone()
99
- Drone::add_output(:console, 1)
109
+
110
+ ``` ruby
111
+ require 'drone'
112
+ Drone::init_drone()
113
+ Drone::add_output(:console, 1)
114
+ ```
100
115
 
101
116
  The values will be printed on the console at the inter
102
117
 
118
+ The others output are available in their own gems:
119
+
120
+ - drone_json:<br/>
121
+ The stats are served by a thin server in json
122
+
123
+ - drone_collectd:<br/>
124
+ The stats are send to a collectd daemon.
125
+
103
126
  # Goals
104
127
 
105
128
  My goal is to be able to serve stats efficiently from any ruby 1.9 application built
@@ -113,23 +136,24 @@ gem to limit the core's dependencies so the only one in core is:
113
136
  optional part instead of in the core. There should not be any problem to implements it
114
137
  in an includable module not included as default (it may requires some modifications in the core):
115
138
 
116
- require 'drone'
117
- require 'drone/threadsafe'
139
+ ``` ruby
140
+ require 'drone'
141
+ require 'drone/threadsafe'
118
142
 
119
- [...]
120
-
143
+ # [...]
144
+ ```
121
145
 
122
146
  # Development
123
147
 
124
148
  Installing the development environment is pretty simple thanks to bundler:
125
-
149
+
126
150
  gem install bundler
127
151
  bundle
128
152
 
129
153
  ## Running specs
130
154
 
131
155
  The specs are written with bacon, mocha and em-spec, they can be ran with:
132
-
156
+
133
157
  rake spec
134
158
 
135
159
  ## Build the doc
data/Rakefile CHANGED
@@ -15,6 +15,6 @@ begin
15
15
  require 'yard'
16
16
  require 'bluecloth'
17
17
  YARD::Rake::YardocTask.new(:doc)
18
- rescue
18
+ rescue LoadError
19
19
 
20
20
  end
@@ -1,8 +1,7 @@
1
- require 'rubygems'
2
- require 'bundler/setup'
1
+ require File.expand_path('../common', __FILE__)
2
+ init_environment()
3
3
 
4
- $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
5
- require 'drone'
4
+ require 'fiber'
6
5
 
7
6
  Drone::init_drone()
8
7
  Drone::register_gauge("cpu:0/user"){ rand(200) }
@@ -22,11 +21,9 @@ class User
22
21
  monitor_time("users:do_something:time")
23
22
  monitor_rate("users:do_something:rate")
24
23
  def do_something
25
- # just eat some cpu
26
- 0.upto(rand(2000)) do |n|
27
- str = "a"
28
- 200.times{ str << "b" }
29
- end
24
+ # fb = Fiber.current
25
+ # EM::add_timer(1){ fb.resume() }
26
+ # Fiber.yield
30
27
  end
31
28
  end
32
29
 
@@ -45,7 +42,9 @@ EM::run do
45
42
  counter1.increment()
46
43
  end
47
44
 
48
- EM::add_periodic_timer(1) do
49
- a.do_something()
45
+ EM::add_periodic_timer(2) do
46
+ Fiber.new do
47
+ a.do_something()
48
+ end.resume
50
49
  end
51
50
  end
@@ -1,6 +1,10 @@
1
+ require 'forwardable'
2
+
1
3
 
2
4
  require File.expand_path('../schedulers/eventmachine', __FILE__)
3
5
 
6
+ require File.expand_path('../storage/memory', __FILE__)
7
+
4
8
  module Drone
5
9
  ##
6
10
  # This module contains all the metrics you can use to collect data
@@ -20,11 +24,20 @@ module Drone
20
24
  #
21
25
  module Schedulers; end
22
26
 
27
+
28
+ ##
29
+ # This module contains the class used for storage,
30
+ # they determine where the metric's data are stored
31
+ #
32
+ module Storage; end
33
+
23
34
  class <<self
35
+ extend Forwardable
24
36
 
25
- def init_drone(scheduler = Schedulers::EMScheduler)
26
- @meters = []
37
+ def init_drone(scheduler = Schedulers::EMScheduler, storage = Storage::Memory.new)
38
+ @metrics = []
27
39
  @scheduler = scheduler
40
+ @storage = storage
28
41
  @monitored_classes = []
29
42
  @output_modules = []
30
43
  end
@@ -41,7 +54,7 @@ module Drone
41
54
 
42
55
  def each_metric
43
56
  raise "Block expected" unless block_given?
44
- @meters.each{|m| yield(m) }
57
+ @metrics.each{|m| yield(m) }
45
58
  end
46
59
 
47
60
 
@@ -51,7 +64,7 @@ module Drone
51
64
  # @param [String] name The mtric's name
52
65
  #
53
66
  def find_metric(name)
54
- @meters.detect{|m| m.name == name }
67
+ @metrics.detect{|m| m.name == name }
55
68
  end
56
69
 
57
70
  ##
@@ -67,59 +80,62 @@ module Drone
67
80
  @output_modules << klass.new(*args)
68
81
  end
69
82
 
70
- ##
71
- # Register a monitored class.
72
- # @private
73
- #
74
- def register_monitored_class(klass)
75
- @monitored_classes << klass
76
- end
77
-
78
83
  ##
79
84
  # Register a new counter
85
+ # @see Drone::Metrics::Counter
80
86
  # @param [String] type Name of this metric
81
87
  # @api public
82
88
  #
83
89
  def register_counter(type)
84
- register_meter( Drone::Metrics::Counter.new(type) )
90
+ register_metric( Drone::Metrics::Counter.new(type) )
91
+ end
92
+
93
+
94
+ ##
95
+ # Register an Histogram
96
+ # @see Drone::Metrics::Histogram
97
+ # @param [String] name Name of this metric
98
+ # @param [optional,Enum] type one of Drone::Metrics::Histogram::TYPE_UNIFORM or Drone::Metrics::Histogram::TYPE_BIASED
99
+ #
100
+ def register_histogram(name, type = :uniform)
101
+ register_metric( Drone::Metrics::Histogram.new(name, type) )
85
102
  end
86
103
 
87
104
  ##
88
105
  # Register a new gauge
106
+ # @see Drone::Metrics::Gauge
89
107
  # @param [String] type Name of this metric
90
108
  # @api public
91
109
  #
92
110
  def register_gauge(type, &block)
93
- register_meter( Drone::Metrics::Gauge.new(type, &block) )
111
+ register_metric( Drone::Metrics::Gauge.new(type, &block) )
94
112
  end
95
113
 
96
114
  ##
97
- # Register a new meter
115
+ # Register a new metric
98
116
  # This method can be used bu the user but the prefered
99
117
  # way is to use the register_counter / register_gauge methods
100
118
  #
101
- # @param [Meter] meter The Meter to register
102
- # @api private
119
+ # @param [Metric] metric The Metric to register
103
120
  # @private
104
121
  #
105
- def register_meter(meter)
106
- @meters << meter
107
- meter
122
+ def register_metric(metric)
123
+ @metrics << metric
124
+ metric
108
125
  end
109
126
 
110
- ##
111
- # (see EMScheduler#schedule_periodic)
112
- #
113
- def schedule_periodic(*args, &block)
114
- @scheduler.schedule_periodic(*args, &block)
115
- end
127
+
128
+ def_delegators :@storage, :request_fixed_size_array, :request_number, :request_hash
129
+ def_delegators :@scheduler, :schedule_periodic, :schedule_once
130
+
131
+
116
132
 
117
133
  ##
118
- # (see EMScheduler#schedule_once)
134
+ # Register a monitored class.
135
+ # @private
119
136
  #
120
- def schedule_once(*args, &block)
121
- @scheduler.schedule_once(*args, &block)
137
+ def register_monitored_class(klass)
138
+ @monitored_classes << klass
122
139
  end
123
-
124
140
  end
125
141
  end
@@ -1,26 +1,38 @@
1
+ require File.expand_path('../metric', __FILE__)
2
+
1
3
  module Drone
2
4
  module Metrics
3
5
 
4
- class Counter
5
- attr_reader :value, :name
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
6
13
 
7
14
  def initialize(name, initial_value = 0)
8
- @name = name
9
- @value = initial_value
15
+ super(name)
16
+
17
+ @value = Drone::request_number("#{name}:value", initial_value)
18
+ end
19
+
20
+ def value
21
+ @value.get
10
22
  end
11
23
 
12
24
  def increment(n = 1)
13
- @value += n
25
+ @value.inc(n)
14
26
  end
15
27
  alias :inc :increment
16
28
 
17
29
  def decrement(n = 1)
18
- @value -= n
30
+ @value.dec(n)
19
31
  end
20
32
  alias :dec :decrement
21
33
 
22
34
  def clear
23
- @value = 0
35
+ @value.set(0)
24
36
  end
25
37
  end
26
38
 
@@ -1,3 +1,5 @@
1
+ require File.expand_path('../metric', __FILE__)
2
+
1
3
  module Drone
2
4
  module Metrics
3
5
 
@@ -6,12 +8,11 @@ module Drone
6
8
  # will be called when the value is asked, the block
7
9
  # is expected to return a number
8
10
  #
9
- class Gauge
10
- attr_reader :name
11
+ class Gauge < Metric
11
12
 
12
13
  def initialize(name, &block)
13
14
  raise "Block expected" unless block
14
- @name = name
15
+ super(name)
15
16
  @block = block
16
17
  end
17
18
 
@@ -1,22 +1,42 @@
1
1
  require File.expand_path('../../utils/uniform_sample', __FILE__)
2
2
  require File.expand_path('../../utils/exponentially_decaying_sample', __FILE__)
3
+ require File.expand_path('../metric', __FILE__)
3
4
 
4
5
  module Drone
5
- class Histogram
6
- TYPE_UNIFORM = lambda{ UniformSample.new(1028) }
7
- TYPE_BIASED = lambda{ ExponentiallyDecayingSample.new(1028, 0.015) }
8
-
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
9
16
  MIN = (-(2**63)).freeze
10
17
  MAX = ((2**64) - 1).freeze
11
18
 
12
- def initialize(sample_or_type = TYPE_UNIFORM)
13
- if sample_or_type.is_a?(Proc)
14
- @sample = sample_or_type.call()
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
15
29
  else
16
30
  @sample = sample_or_type
17
31
  end
18
32
 
19
- clear()
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
+
20
40
  end
21
41
 
22
42
  def clear
@@ -30,37 +50,37 @@ module Drone
30
50
  end
31
51
 
32
52
  def update(val)
33
- @count += 1
53
+ @count.inc
34
54
  @sample.update(val)
35
55
  set_max(val);
36
56
  set_min(val);
37
- @_sum += val
57
+ @_sum.inc(val)
38
58
  update_variance(val)
39
59
  end
40
60
 
41
61
  def count
42
- @count
62
+ @count.get
43
63
  end
44
64
 
45
65
  def max
46
- (@count > 0) ? @_max : 0.0
66
+ (@count.get > 0) ? @_max.get : 0.0
47
67
  end
48
68
 
49
69
  def min
50
- (@count > 0) ? @_min : 0.0
70
+ (@count.get > 0) ? @_min.get : 0.0
51
71
  end
52
72
 
53
73
  def mean
54
- (@count > 0) ? @_sum.to_f / @count : 0.0
74
+ (@count.get > 0) ? @_sum.get.to_f / @count.get : 0.0
55
75
  end
56
76
 
57
77
  def stdDev
58
- (@count > 0) ? Math.sqrt( variance() ) : 0.0
78
+ (@count.get > 0) ? Math.sqrt( variance() ) : 0.0
59
79
  end
60
80
 
61
81
  def percentiles(*percentiles)
62
82
  scores = Array.new(percentiles.size, 0)
63
- if @count > 0
83
+ if @count.get > 0
64
84
  values = @sample.values.sort
65
85
  percentiles.each.with_index do |p, i|
66
86
  pos = p * (values.size + 1)
@@ -94,36 +114,37 @@ module Drone
94
114
  end
95
115
 
96
116
  def update_variance(val)
97
- if @varianceM == -1
98
- @varianceM = doubleToLongBits(val)
117
+ if @varianceM.get == -1
118
+ @varianceM.set( doubleToLongBits(val) )
99
119
  else
100
- oldMCas = @varianceM
120
+ oldMCas = @varianceM.get
101
121
  oldM = longBitsToDouble(oldMCas)
102
122
  newM = oldM + ((val - oldM) / count())
103
123
 
104
- oldSCas = @varianceS
124
+ oldSCas = @varianceS.get
105
125
  oldS = longBitsToDouble(oldSCas)
106
126
  newS = oldS + ((val - oldM) * (val - newM))
107
127
 
108
- @varianceM = doubleToLongBits(newM)
109
- @varianceS = doubleToLongBits(newS)
128
+ @varianceM.set( doubleToLongBits(newM) )
129
+ @varianceS.set( doubleToLongBits(newS) )
110
130
  end
111
131
  end
112
132
 
113
133
  def variance
114
- if @count <= 1
134
+ count = @count.get
135
+ if count <= 1
115
136
  0.0
116
137
  else
117
- longBitsToDouble(@varianceS) / (count() - 1)
138
+ longBitsToDouble(@varianceS.get) / (count - 1)
118
139
  end
119
140
  end
120
141
 
121
142
  def set_max(val)
122
- (@_max >= val) || @_max = val
143
+ (@_max.get >= val) || @_max.set(val)
123
144
  end
124
145
 
125
146
  def set_min(val)
126
- (@_min <= val) || @_min = val
147
+ (@_min.get <= val) || @_min.set(val)
127
148
  end
128
149
 
129
150