ruby-metrics 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-metrics (0.7.0)
4
+ ruby-metrics (0.8.0)
5
5
  json
6
6
  quantity
7
7
 
@@ -11,6 +11,9 @@ GEM
11
11
  diff-lcs (1.1.2)
12
12
  json (1.5.1)
13
13
  quantity (0.1.1)
14
+ rack (1.2.2)
15
+ rack-test (0.5.7)
16
+ rack (>= 1.0)
14
17
  rspec (2.5.0)
15
18
  rspec-core (~> 2.5.0)
16
19
  rspec-expectations (~> 2.5.0)
@@ -27,6 +30,7 @@ PLATFORMS
27
30
  ruby
28
31
 
29
32
  DEPENDENCIES
33
+ rack-test
30
34
  rspec
31
35
  ruby-metrics!
32
36
  simplecov (>= 0.3.8)
data/README.md CHANGED
@@ -16,26 +16,68 @@ Right now, I have:
16
16
  * Gauges
17
17
  * Histograms w/ uniform sampling
18
18
  * Histograms w/ exponentially decaying sampling
19
-
20
- Upcoming:
21
-
22
- * Timers
19
+ * Timers
23
20
 
24
21
  ## Getting Started
25
22
 
26
23
  The goal of ruby-metrics is to get up and running quickly. You start an agent, register some instruments, and they're exported over HTTP via JSON. For example, getting started with a counter would look like this:
27
24
 
28
25
  @metrics = Metrics::Agent.new
29
- @metrics.start
30
26
 
31
27
  counter = @metrics.counter :my_counter
32
28
  counter.incr
33
29
  counter.incr
34
30
 
35
- Then, hitting localhost:8001/status would yield:
31
+ puts @metrics.to_json
32
+ #=> {"my_counter":"2"}
33
+
34
+
35
+ ## Integration
36
+
37
+ Integrating ruby-metrics into existing applications is entirely up to your needs. Provided options include:
38
+
39
+ * Embedded WEBrick listener:
40
+
41
+ This runs a background thread and enables HTTP access to a local port (8001 by default) for a JSON representation of the current metrics.
42
+
43
+ ``` ruby
44
+ require 'ruby-metrics/integration/webrick'
45
+ @agent = Metrics::Agent.new
46
+ @agent.start(:port => 8081)
47
+ ```
48
+
49
+ * Rack Middleware:
50
+
51
+ This will add metrics such as `requests` (a timer) as well as counters for each class of HTTP status code (1xx, 2xx, etc). Also counts uncaught exceptions before reraising.
52
+ Provides a configurable path option (`:show`) to trigger the return of the metrics (as JSON) when the request path matches exactly (a string), as a regular expression, or as any object that responds to `call` for custom logic (passed the whole `env`).
53
+
54
+ ``` ruby
55
+ require 'ruby-metrics'
56
+ @agent = Metrics::Agent.new
57
+
58
+ use Metrics::Integration::Rack::Middleware, :agent => @agent, :show => '/stats'
59
+
60
+ run app
61
+ ```
62
+
63
+ * Rack Endpoint:
64
+
65
+ Use this to expose an endpoint for external consumption for your metrics.
66
+ Works best when used with a URLMap or mounted in addition to other routes, like Rails' `mount` route matcher.
67
+
68
+ ``` ruby
69
+ require 'ruby-metrics'
70
+ @agent = Metrics::Agent.new
71
+
72
+ run Metrics::Integration::Rack::Endpoint.new(:agent => @agent)
73
+ ```
36
74
 
37
- {"my_counter":"2"}
75
+ or
38
76
 
77
+ ``` ruby
78
+ # in config/router.rb
79
+ mount Metrics::Integration::Rack::Endpoint.new(:agent => @agent)
80
+ ```
39
81
 
40
82
  [metrics]: https://github.com/codahale/metrics
41
83
 
data/examples/counter.rb CHANGED
@@ -2,7 +2,6 @@ require 'rubygems'
2
2
  require '../lib/ruby-metrics'
3
3
 
4
4
  @metrics = Metrics::Agent.new
5
- @metrics.start
6
5
 
7
6
  counter = @metrics.counter :my_counter
8
7
  counter.incr
data/examples/gauge.rb CHANGED
@@ -2,7 +2,6 @@ require 'rubygems'
2
2
  require '../lib/ruby-metrics'
3
3
 
4
4
  @metrics = Metrics::Agent.new
5
- @metrics.start
6
5
 
7
6
  hit_count = 42
8
7
  http_requests = 53
@@ -1,5 +1,5 @@
1
1
  require 'rubygems'
2
- require '../lib/ruby-metrics'
2
+ require '../lib/ruby-metrics/integration/webrick'
3
3
 
4
4
  @metrics = Metrics::Agent.new
5
5
  @metrics.start
@@ -16,7 +16,7 @@ end
16
16
  step = 0
17
17
 
18
18
  # This is here so that we will run indefinitely so you can hit the
19
- # status page on localhost:8001/status
19
+ # status page on localhost:8001/stats
20
20
  loop do
21
21
  sleep 1
22
22
 
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+
3
+ # Ran with:
4
+ # rackup -p 8000 ./examples/integration/rack_endpoint.ru
5
+ #
6
+ # Make requests to: http://localhost:8000/
7
+ # See stats at : http://localhost:8000/stats
8
+
9
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ruby-metrics')
10
+ @agent = Metrics::Agent.new
11
+
12
+ counter = @agent.counter(:my_counter)
13
+
14
+ app = proc do |env|
15
+ counter.incr
16
+ [200, {'Content-Type' => 'text/plain'}, ["Counted!"]]
17
+ end
18
+
19
+ map = Rack::URLMap.new({
20
+ '/stats' => Metrics::Integration::Rack::Endpoint.new(:agent => @app),
21
+ '/' => app
22
+ })
23
+
24
+ run map
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+
3
+ # Ran with:
4
+ # rackup -p 8000 ./examples/integration/rack_middleware.ru
5
+ #
6
+ # Make requests to: http://localhost:8000/
7
+ # See stats at : http://localhost:8000/stats
8
+
9
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ruby-metrics')
10
+ @agent = Metrics::Agent.new
11
+
12
+ counter = @agent.counter(:my_counter)
13
+
14
+ use Metrics::Integration::Rack::Middleware, :agent => @agent
15
+
16
+ app = proc do |env|
17
+ counter.incr
18
+ [200, {'Content-Type' => 'text/plain'}, ["Counted!"]]
19
+ end
20
+
21
+ run app
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ruby-metrics'))
3
+
4
+ # Run with:
5
+ # ruby examples/integration/webrick.rb
6
+
7
+ @agent = Metrics::Agent.new
8
+
9
+ counter = @agent.counter(:my_counter)
10
+ counter.incr
11
+ counter.incr
12
+
13
+ Metrics::Integration::WEBrick.start(:port => 8001,
14
+ :agent => @agent)
15
+
16
+ sleep
17
+ # Now navigate to: http://localhost:8001/stats
data/examples/meter.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  require 'rubygems'
2
- require '../lib/ruby-metrics'
2
+ require '../lib/ruby-metrics/integration/webrick'
3
3
 
4
- # Specify a port for the agent
5
- @metrics = Metrics::Agent.new(8081)
6
- @metrics.start
4
+ @metrics = Metrics::Agent.new
5
+ @metrics.start :port => 8081 # optional
7
6
 
8
7
  timer = @metrics.meter :my_meter
9
8
  timer.mark(500)
@@ -11,7 +10,7 @@ timer.mark(500)
11
10
  step = 0
12
11
 
13
12
  # This is here so that we will run indefinitely so you can hit the
14
- # status page on localhost:8081/status
13
+ # status page on localhost:8081/stats
15
14
  loop do
16
15
  sleep 1
17
16
 
data/examples/timer.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require '../lib/ruby-metrics/integration/webrick'
3
+
4
+ @metrics = Metrics::Agent.new
5
+ @metrics.start :port => 8081 # optional
6
+
7
+ timer = @metrics.timer :my_timer
8
+ timer.update(500, :milliseconds)
9
+ timer.update(5, :seconds)
10
+ timer.update(4242, :nanoseconds)
11
+
12
+ msec_timer = @metrics.timer :msec_timer, {:duration_unit => :microseconds, :rate_unit => :seconds}
13
+
14
+ step = 0
15
+
16
+ # This is here so that we will run indefinitely so you can hit the
17
+ # status page on localhost:8081/stats
18
+ loop do
19
+ sleep 1
20
+
21
+ modifier = rand(200).to_i
22
+ step += 1
23
+
24
+ if (step % 2)
25
+ modifier *= -1
26
+ end
27
+
28
+ timer.update(500 + modifier, :microseconds)
29
+ msec_timer.update(500 + modifier, :microseconds)
30
+
31
+ end
@@ -1,26 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), 'logging')
2
2
  require File.join(File.dirname(__FILE__), 'instruments')
3
- require 'webrick'
4
-
5
- class Status < WEBrick::HTTPServlet::AbstractServlet
6
-
7
- def initialize(server, instruments)
8
- @instruments = instruments
9
- end
10
-
11
- def do_GET(request, response)
12
- status, content_type, body = do_stuff_with(request)
13
-
14
- response.status = status
15
- response['Content-Type'] = content_type
16
- response.body = body
17
- end
18
-
19
- def do_stuff_with(request)
20
- return 200, "text/plain", @instruments.to_json
21
- end
22
-
23
- end
3
+ require File.join(File.dirname(__FILE__), 'integration')
24
4
 
25
5
  module Metrics
26
6
  class Agent
@@ -29,28 +9,14 @@ module Metrics
29
9
 
30
10
  attr_reader :instruments
31
11
 
32
- def initialize(port = 8001)
12
+ def initialize
33
13
  logger.debug "Initializing Metrics..."
34
14
  @instruments = Metrics::Instruments
35
- @port = port
36
15
  end
37
16
 
38
- def start
39
- start_daemon_thread
17
+ def to_json
18
+ @instruments.to_json
40
19
  end
41
20
 
42
- protected
43
- def start_daemon_thread(connection_options = {})
44
- logger.debug "Creating Metrics daemon thread."
45
- @daemon_thread = Thread.new do
46
- begin
47
- server = WEBrick::HTTPServer.new ({:Port => @port})
48
- server.mount "/status", Status, @instruments
49
- server.start
50
- rescue Exception => e
51
- logger.error "Error in worker thread: #{e.class.name}: #{e}\n #{e.backtrace.join("\n ")}"
52
- end # begin
53
- end # thread new
54
- end
55
21
  end
56
22
  end
@@ -4,27 +4,24 @@ require File.join(File.dirname(__FILE__), 'statistics', 'sample')
4
4
  require File.join(File.dirname(__FILE__), 'statistics', 'uniform_sample')
5
5
  require File.join(File.dirname(__FILE__), 'statistics', 'exponential_sample')
6
6
 
7
- require File.join(File.dirname(__FILE__), 'instruments', 'base')
8
- require File.join(File.dirname(__FILE__), 'instruments', 'counter')
9
- require File.join(File.dirname(__FILE__), 'instruments', 'meter')
10
- require File.join(File.dirname(__FILE__), 'instruments', 'gauge')
11
- require File.join(File.dirname(__FILE__), 'instruments', 'histogram')
12
- require File.join(File.dirname(__FILE__), 'instruments', 'timer')
13
-
14
-
15
7
  require 'json'
16
8
 
17
9
  module Metrics
18
10
  module Instruments
19
11
  @instruments = {}
20
-
21
- @types = {
22
- :counter => Counter,
23
- :meter => Meter,
24
- :gauge => Gauge,
25
- :exponential_histogram => ExponentialHistogram,
26
- :uniform_histogram => UniformHistogram
27
- }
12
+ @types = {}
13
+
14
+ def self.register_instrument(type, klass)
15
+ @types[type] = klass
16
+ end
17
+
18
+ def self.registered_instruments
19
+ @types
20
+ end
21
+
22
+ def self.register_with_options(type, name, options = {})
23
+ @instruments[name] = @types[type].new(options)
24
+ end
28
25
 
29
26
  def self.register(type, name, &block)
30
27
  @instruments[name] = @types[type].new(&block)
@@ -48,6 +45,10 @@ module Metrics
48
45
  Metrics::Instruments.register(type, name, &block)
49
46
  end
50
47
 
48
+ def register_with_options(type, name, options)
49
+ Metrics::Instruments.register_with_options(type, name, options)
50
+ end
51
+
51
52
  def counter(name)
52
53
  register(:counter, name)
53
54
  end
@@ -60,6 +61,10 @@ module Metrics
60
61
  register(:gauge, name, &block)
61
62
  end
62
63
 
64
+ def timer(name, options = {})
65
+ register_with_options(:timer, name, options)
66
+ end
67
+
63
68
  def uniform_histogram(name)
64
69
  register(:uniform_histogram, name)
65
70
  end
@@ -74,3 +79,11 @@ module Metrics
74
79
 
75
80
  end
76
81
  end
82
+
83
+ require File.join(File.dirname(__FILE__), 'instruments', 'base')
84
+ require File.join(File.dirname(__FILE__), 'instruments', 'counter')
85
+ require File.join(File.dirname(__FILE__), 'instruments', 'meter')
86
+ require File.join(File.dirname(__FILE__), 'instruments', 'gauge')
87
+ require File.join(File.dirname(__FILE__), 'instruments', 'histogram')
88
+ require File.join(File.dirname(__FILE__), 'instruments', 'timer')
89
+
@@ -29,5 +29,7 @@ module Metrics
29
29
  end
30
30
 
31
31
  end
32
+
33
+ register_instrument(:counter, Counter)
32
34
  end
33
35
  end
@@ -16,5 +16,7 @@ module Metrics
16
16
  end
17
17
 
18
18
  end
19
+
20
+ register_instrument(:gauge, Gauge)
19
21
  end
20
22
  end
@@ -166,13 +166,16 @@ module Metrics
166
166
  super(:exponential)
167
167
  end
168
168
  end
169
-
169
+
170
+ register_instrument(:exponential_histogram, ExponentialHistogram)
171
+
170
172
  class UniformHistogram < Histogram
171
173
  def initialize
172
174
  super(:uniform)
173
175
  end
174
176
  end
175
-
177
+
178
+ register_instrument(:uniform_histogram, UniformHistogram)
176
179
  end
177
180
 
178
181
  end
@@ -1,6 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'time_units')
2
+
1
3
  module Metrics
2
4
  module Instruments
3
5
  class Meter < Base
6
+ include Metrics::TimeConversion
7
+
4
8
  # From http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf
5
9
  INTERVAL = 5.0
6
10
  INTERVAL_IN_NS = 5000000000.0
@@ -39,8 +43,8 @@ module Metrics
39
43
  end
40
44
 
41
45
  def calc_rate(rate, factor, count)
42
- rate = rate + (factor * (count - rate))
43
- rate
46
+ rate = rate.to_f + (factor.to_f * (count.to_f - rate.to_f))
47
+ rate.to_f
44
48
  end
45
49
 
46
50
  def tick
@@ -59,24 +63,24 @@ module Metrics
59
63
  end
60
64
 
61
65
  def one_minute_rate(rate_unit = :seconds)
62
- scale_to_ns @one_minute_rate, rate_unit
66
+ convert_to_ns @one_minute_rate, rate_unit
63
67
  end
64
68
 
65
69
  def five_minute_rate(rate_unit = :seconds)
66
- scale_to_ns @five_minute_rate, rate_unit
70
+ convert_to_ns @five_minute_rate, rate_unit
67
71
  end
68
72
 
69
73
  def fifteen_minute_rate(rate_unit = :seconds)
70
- scale_to_ns @fifteen_minute_rate, rate_unit
74
+ convert_to_ns @fifteen_minute_rate, rate_unit
71
75
  end
72
76
 
73
- def mean_rate(rate_unit = seconds)
77
+ def mean_rate(rate_unit = :seconds)
74
78
  count = @count
75
79
  if count == 0
76
80
  return 0.0;
77
81
  else
78
82
  elapsed = Time.now.to_f - @start_time.to_f
79
- scale_to_ns (count / elapsed), rate_unit
83
+ convert_to_ns (count.to_f / elapsed.to_f), rate_unit
80
84
  end
81
85
  end
82
86
 
@@ -88,23 +92,8 @@ module Metrics
88
92
  }.to_json
89
93
  end
90
94
 
91
- private
92
- def scale_to_ns(value, unit)
93
- mult = case unit
94
- when :seconds
95
- Seconds.to_nsec
96
- when :minutes
97
- Minutes.to_nsec
98
- when :hours
99
- Hours.to_nsec
100
- when :days
101
- Days.to_nsec
102
- when :weeks
103
- Weeks.to_nsec
104
- end
105
-
106
- value * mult
107
- end
108
95
  end
96
+
97
+ register_instrument(:meter, Meter)
109
98
  end
110
99
  end
@@ -7,12 +7,12 @@ module Metrics
7
7
 
8
8
  attr_reader :duration_unit, :rate_unit
9
9
 
10
- def initialize(duration_unit = :seconds, rate_unit = :seconds)
10
+ def initialize(options = {})
11
11
  @meter = Meter.new
12
12
  @histogram = ExponentialHistogram.new
13
13
 
14
- @duration_unit = duration_unit
15
- @rate_unit = rate_unit
14
+ @duration_unit = options[:duration_unit] || :seconds
15
+ @rate_unit = options[:rate_unit] || :seconds
16
16
 
17
17
  clear
18
18
  end
@@ -30,7 +30,8 @@ module Metrics
30
30
  start_time = Time.now.to_f
31
31
  result = block.call
32
32
  time_diff = Time.now.to_f - start_time
33
- update_timer(time_diff)
33
+ time_in_ns = convert_to_ns time_diff, :seconds
34
+ update_timer(time_in_ns)
34
35
  result
35
36
  end
36
37
 
@@ -97,12 +98,33 @@ module Metrics
97
98
  end
98
99
  end
99
100
 
101
+ def to_s
102
+ {
103
+ :count => self.count,
104
+ :rates => {
105
+ :one_minute_rate => self.one_minute_rate,
106
+ :five_minute_rate => self.five_minute_rate,
107
+ :fifteen_minute_rate => self.fifteen_minute_rate,
108
+ :unit => @rate_unit
109
+ },
110
+ :durations => {
111
+ :min => self.min,
112
+ :max => self.max,
113
+ :mean => self.mean,
114
+ :percentiles => self.quantiles([0.25, 0.50, 0.75, 0.95, 0.97, 0.98, 0.99]),
115
+ :unit => @duration_unit
116
+ }
117
+ }.to_json
118
+ end
119
+
100
120
  private
101
121
  def scale_duration_to_ns(value, unit)
102
- value / convert_to_ns(1, unit)
122
+ value.to_f / convert_to_ns(1, unit).to_f
103
123
  end
104
124
 
105
125
  end
126
+
127
+ register_instrument(:timer, Timer)
106
128
  end
107
129
  end
108
130
 
@@ -0,0 +1,12 @@
1
+ module Metrics
2
+ module Integration
3
+
4
+ autoload :WEBrick, File.join(File.dirname(__FILE__), 'integration', 'webrick')
5
+
6
+ module Rack
7
+ autoload :Middleware, File.join(File.dirname(__FILE__), 'integration', 'rack_middleware')
8
+ autoload :Endpoint, File.join(File.dirname(__FILE__), 'integration', 'rack_endpoint')
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ # Provides:
2
+ # * configurable agent
3
+ # * endpoint for accessing metrics JSON
4
+ #
5
+ module Metrics
6
+ module Integration
7
+ module Rack
8
+ class Endpoint
9
+
10
+ attr_accessor :app, :options, :agent,
11
+ # integration metrics
12
+ :requests, :uncaught_exceptions,
13
+ :status_codes
14
+
15
+ def initialize(options ={})
16
+ @options = options
17
+ @agent = @options.delete(:agent) || Agent.new
18
+ end
19
+
20
+ def call(_)
21
+ body = @agent.to_json
22
+
23
+ [ 200,
24
+ { 'Content-Type' => 'application/json',
25
+ 'Content-Length' => body.size.to_s },
26
+ [body]
27
+ ]
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,82 @@
1
+ # Provides:
2
+ # * configurable agent
3
+ # * configurable endpoint for current metrics
4
+ # * strings == path_info
5
+ # * regexp =~ path_info
6
+ # * proc.call(env) #=> boolean
7
+ # * env['metrics.agent'] upstream
8
+ # * specific metrics by default
9
+ # * requests (timer)
10
+ # * uncaught_exceptions (counter)
11
+ # * response_1xx through response_5xx (counter)
12
+ #
13
+ module Metrics
14
+ module Integration
15
+ module Rack
16
+ class Middleware
17
+
18
+ attr_accessor :app, :options, :agent,
19
+ # integration metrics
20
+ :requests, :uncaught_exceptions,
21
+ :status_codes
22
+
23
+ def initialize(app, options ={})
24
+ @app = app
25
+ @options = {:show => "/stats"}.merge(options)
26
+ @agent = @options.delete(:agent) || Agent.new
27
+
28
+ # Integration Metrics
29
+ @requests = @agent.timer(:_requests)
30
+ @uncaught_exceptions = @agent.counter(:_uncaught_exceptions)
31
+
32
+ # HTTP Status Codes
33
+ @status_codes = {
34
+ 1 => @agent.counter(:_status_1xx),
35
+ 2 => @agent.counter(:_status_2xx),
36
+ 3 => @agent.counter(:_status_3xx),
37
+ 4 => @agent.counter(:_status_4xx),
38
+ 5 => @agent.counter(:_status_5xx)
39
+ }
40
+ end
41
+
42
+ def call(env)
43
+ return show(env) if show?(env)
44
+
45
+ env['metrics.agent'] = @agent
46
+
47
+ status, headers, body = self.requests.time{ @app.call(env) }
48
+
49
+ if status_counter = self.status_codes[status / 100]
50
+ status_counter.incr
51
+ end
52
+
53
+ [status, headers, body]
54
+ rescue Exception
55
+ # TODO: add "last_uncaught_exception" with string of error
56
+ self.uncaught_exceptions.incr
57
+ raise
58
+ end
59
+
60
+ def show?(env, test = self.options[:show])
61
+ case
62
+ when String === test; env['PATH_INFO'] == test
63
+ when Regexp === test; env['PATH_INFO'] =~ test
64
+ when test.respond_to?(:call); test.call(env)
65
+ else test
66
+ end
67
+ end
68
+
69
+ def show(_)
70
+ body = @agent.to_json
71
+
72
+ [ 200,
73
+ { 'Content-Type' => 'application/json',
74
+ 'Content-Length' => body.size.to_s },
75
+ [body]
76
+ ]
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'ruby-metrics')
2
+
3
+ require 'webrick'
4
+
5
+ module Metrics
6
+
7
+ class Agent
8
+ def start(options = {})
9
+ Integration::WEBrick.start(options.merge(:agent => self))
10
+ end
11
+ end
12
+
13
+ module Integration
14
+ class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
15
+ include Logging
16
+
17
+ def self.start(options = {})
18
+ connection_options = {:Port => options.delete(:port) || options.delete(:Port) || 8001}
19
+ agent = options.delete(:agent) || Agent.new
20
+
21
+ logger.debug "Creating Metrics daemon thread."
22
+ @thread = Thread.new do
23
+ begin
24
+ server = ::WEBrick::HTTPServer.new(connection_options)
25
+ server.mount "/stats", self, agent
26
+ server.start
27
+ rescue Exception => e
28
+ logger.error "Error in thread: %s: %s\n\t%s" % [e.class.to_s,
29
+ e.message,
30
+ e.backtrace.join("\n\t")]
31
+ end
32
+ end
33
+ end
34
+
35
+ def initialize(server, agent)
36
+ @agent = agent
37
+ end
38
+
39
+ def do_GET(request, response)
40
+ response.status = 200
41
+ response['Content-Type'] = 'application/json'
42
+ response.body = @agent.to_json
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -1,3 +1,3 @@
1
1
  module Metrics
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
data/ruby-metrics.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.add_development_dependency "rspec"
21
21
  s.add_development_dependency "simplecov", [">= 0.3.8"] #, :require => false
22
+ s.add_development_dependency "rack-test"
22
23
 
23
24
  s.files = `git ls-files`.split("\n")
24
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
data/spec/agent_spec.rb CHANGED
@@ -45,16 +45,4 @@ describe Metrics::Agent do
45
45
  @agent.meter(:test_meter).should == @meter
46
46
  end
47
47
 
48
- it "should start the WEBrick daemon" do
49
- Thread.stub!(:new).and_return do |block|
50
- block.call
51
- end
52
-
53
- mock_server = mock(WEBrick::HTTPServer)
54
- WEBrick::HTTPServer.should_receive(:new).and_return mock_server
55
- mock_server.should_receive(:mount)
56
- mock_server.should_receive(:start)
57
- @agent.start
58
- end
59
-
60
- end
48
+ end
@@ -9,7 +9,7 @@ describe Metrics::Instruments::Timer do
9
9
 
10
10
  context "An empty timer" do
11
11
  before(:each) do
12
- @timer = Metrics::Instruments::Timer.new()
12
+ @timer = Metrics::Instruments::Timer.new
13
13
  end
14
14
 
15
15
  it "should have a max of zero" do
@@ -56,7 +56,7 @@ describe Metrics::Instruments::Timer do
56
56
 
57
57
  context "Timing some events" do
58
58
  before(:each) do
59
- @timer = Metrics::Instruments::Timer.new(:milliseconds, :seconds)
59
+ @timer = Metrics::Instruments::Timer.new({:duration_unit => :milliseconds, :rate_unit => :seconds})
60
60
  @timer.update(10, :milliseconds)
61
61
  @timer.update(20, :milliseconds)
62
62
  @timer.update(20, :milliseconds)
@@ -91,23 +91,27 @@ describe Metrics::Instruments::Timer do
91
91
  it "should contain the series added" do
92
92
  @timer.values.sort.should == [10, 20, 20, 30, 40]
93
93
  end
94
+
95
+ it "should accurately represent itself using JSON" do
96
+ @timer.to_s.should == "{\"count\":5,\"rates\":{\"one_minute_rate\":0.0,\"five_minute_rate\":0.0,\"fifteen_minute_rate\":0.0,\"unit\":\"seconds\"},\"durations\":{\"min\":10.0,\"max\":40.0,\"mean\":24.0,\"percentiles\":{\"0.25\":20.0,\"0.5\":20.0,\"0.75\":30.0,\"0.95\":38.0,\"0.97\":38.8,\"0.98\":39.2,\"0.99\":39.6},\"unit\":\"milliseconds\"}}"
97
+ end
94
98
  end
95
99
 
96
100
  context "Timing blocks of code" do
97
101
  before(:each) do
98
- @timer = Metrics::Instruments::Timer.new(:nanoseconds, :nanoseconds)
102
+ @timer = Metrics::Instruments::Timer.new({:duration_unit => :nanoseconds, :rate_unit => :nanoseconds})
99
103
  end
100
104
 
101
105
  it "should return the result of the block passed" do
102
106
  result = @timer.time do
103
- sleep(5)
107
+ sleep(0.25)
104
108
  "narf"
105
109
  end
106
110
 
107
111
  result.should == "narf"
108
112
 
109
- @timer.max.should >= 5.0
110
- @timer.max.should <= 6.0
113
+ @timer.max.should >= 250000000
114
+ @timer.max.should <= 300000000
111
115
 
112
116
  end
113
117
  end
@@ -58,5 +58,19 @@ describe Metrics::Instruments do
58
58
  proc = Proc.new { "result" }
59
59
  @instruments.register :gauge, :test_gauge, &proc
60
60
  end
61
-
61
+
62
+ context '#register_instrument' do
63
+ it 'registers a new instrument' do
64
+ lambda { Metrics::Instruments.register_instrument(:test, String) }.should_not raise_error
65
+ end
66
+
67
+ it 'returns the list of registered instruments' do
68
+ Metrics::Instruments.register_instrument(:str, String)
69
+ Metrics::Instruments.register_instrument(:int, Fixnum)
70
+
71
+ inst = Metrics::Instruments.registered_instruments
72
+ inst[:str].should == String
73
+ inst[:int].should == Fixnum
74
+ end
75
+ end
62
76
  end
@@ -0,0 +1,60 @@
1
+ require "rack/test"
2
+
3
+ describe Metrics::Integration::Rack::Endpoint do
4
+ include Rack::Test::Methods
5
+
6
+ def app(options = {})
7
+ @app ||= Metrics::Integration::Rack::Endpoint.new(options)
8
+ end
9
+ def agent
10
+ app.agent
11
+ end
12
+
13
+ it "should show stats" do
14
+ get "/"
15
+ last_response.should be_ok
16
+ last_response.body.should == agent.to_json
17
+ end
18
+
19
+ context "configuring" do
20
+
21
+ context "agent" do
22
+ it "should create an agent by default" do
23
+ app(:agent => nil)
24
+ agent.should be_a(Metrics::Agent)
25
+ end
26
+
27
+ it "should use an agent if provided" do
28
+ @agent = Metrics::Agent.new
29
+ app(:agent => @agent)
30
+ agent.should be_a(Metrics::Agent)
31
+ agent.should == @agent
32
+ end
33
+ end
34
+
35
+ context "show" do
36
+ it "should match a string to the PATH_INFO exactly" do
37
+ app(:show => "/foo")
38
+ get '/foo'
39
+ last_response.should be_ok
40
+ last_response.body.should == agent.to_json
41
+ end
42
+
43
+ it "should match a regular expression to the PATH_INFO" do
44
+ app(:show => /bar/)
45
+ get '/foobarbaz'
46
+ last_response.should be_ok
47
+ last_response.body.should == agent.to_json
48
+ end
49
+
50
+ it "should call `call` on the show option if it responds to it" do
51
+ app(:show => lambda{ |env| env['PATH_INFO'] == "/bing" })
52
+ get '/bing'
53
+ last_response.should be_ok
54
+ last_response.body.should == agent.to_json
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,129 @@
1
+ require "rack/test"
2
+
3
+ describe Metrics::Integration::Rack::Middleware do
4
+ include Rack::Test::Methods
5
+
6
+ def app(status = 200, body = ["Hello, World!"], headers = {})
7
+ @app ||= lambda{ |env| [status, {'Content-Type' => 'text/plain', 'Content-Length' => body.to_s.size.to_s}.merge(headers), body] }
8
+ end
9
+ alias_method :original_app, :app
10
+
11
+ describe "without integration" do
12
+ it "should work normally" do
13
+ get "/"
14
+
15
+ last_response.should be_ok
16
+ last_response.body.should == "Hello, World!"
17
+ end
18
+ end
19
+
20
+ describe "with integration" do
21
+ def app(options = {})
22
+ @integrated_app ||= Metrics::Integration::Rack::Middleware.new(original_app, options)
23
+ end
24
+ def agent
25
+ app.agent
26
+ end
27
+
28
+ it "should work normally" do
29
+ get "/"
30
+
31
+ last_response.should be_ok
32
+ last_response.body.should == "Hello, World!"
33
+ end
34
+
35
+ it "should show stats for default endpoint" do
36
+ get "/stats"
37
+ last_response.should be_ok
38
+ last_response.body.should == agent.to_json
39
+ end
40
+
41
+ it "should make 'metrics.agent' available to the upstream environment" do
42
+ @app = lambda do |env|
43
+ env['metrics.agent'].should be_a(Metrics::Agent)
44
+ env['metrics.agent'].should == agent
45
+ [200, {}, []]
46
+ end
47
+
48
+ get '/'
49
+ last_response.should be_ok
50
+ end
51
+
52
+ describe "integration metrics" do
53
+
54
+ it "should count all requests" do
55
+ lambda do
56
+ get '/'
57
+ end.should change{ app.requests.count }.by(1)
58
+ end
59
+
60
+ it "should count uncaught exceptions" do
61
+ @app = lambda{ |env| raise }
62
+ lambda do
63
+ lambda do
64
+ get '/'
65
+ end.should raise_error
66
+ end.should change{ app.uncaught_exceptions.to_i }.by(1)
67
+ end
68
+
69
+ it "should time request length" do
70
+ length = 0.1
71
+ @app = lambda{ |env| sleep(length); [200, {}, ['']] }
72
+ get '/'
73
+ app.requests.mean.should be_within(length / 10).of(length)
74
+ end
75
+
76
+ [ 200, 304, 404, 500].each do |status|
77
+ it "should count #{status} HTTP status code as #{status / 100}xx" do
78
+ @app = lambda{ |env| [status, {}, []] }
79
+ lambda do
80
+ get '/'
81
+ end.should change{ app.status_codes[status / 100].to_i }.by(1)
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ context "configuring" do
88
+
89
+ context "agent" do
90
+ it "should create an agent by default" do
91
+ app(:agent => nil)
92
+ agent.should be_a(Metrics::Agent)
93
+ end
94
+
95
+ it "should use an agent if provided" do
96
+ @agent = Metrics::Agent.new
97
+ app(:agent => @agent)
98
+ agent.should be_a(Metrics::Agent)
99
+ agent.should == @agent
100
+ end
101
+ end
102
+
103
+ context "show" do
104
+ it "should match a string to the PATH_INFO exactly" do
105
+ app(:show => "/foo")
106
+ get '/foo'
107
+ last_response.should be_ok
108
+ last_response.body.should == agent.to_json
109
+ end
110
+
111
+ it "should match a regular expression to the PATH_INFO" do
112
+ app(:show => /bar/)
113
+ get '/foobarbaz'
114
+ last_response.should be_ok
115
+ last_response.body.should == agent.to_json
116
+ end
117
+
118
+ it "should call `call` on the show option if it responds to it" do
119
+ app(:show => lambda{ |env| env['PATH_INFO'] == "/bing" })
120
+ get '/bing'
121
+ last_response.should be_ok
122
+ last_response.body.should == agent.to_json
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,18 @@
1
+ require "rack/test"
2
+
3
+ describe Metrics::Integration::WEBrick do
4
+
5
+ it "should start the WEBrick thread" do
6
+ Thread.stub!(:new).and_return do |block|
7
+ block.call
8
+ end
9
+
10
+ mock_server = mock(WEBrick::HTTPServer)
11
+ WEBrick::HTTPServer.should_receive(:new).and_return mock_server
12
+ mock_server.should_receive(:mount)
13
+ mock_server.should_receive(:start)
14
+
15
+ Metrics::Integration::WEBrick.start
16
+ end
17
+
18
+ end
data/spec/spec_helper.rb CHANGED
@@ -10,6 +10,9 @@ end
10
10
  $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
11
11
  require 'ruby-metrics'
12
12
 
13
+ Metrics.logger = Logger.new(STDERR)
14
+ Metrics.logger.level = Logger::INFO
15
+
13
16
  RSpec.configure do |config|
14
17
  config.mock_with :rspec
15
18
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 7
7
+ - 8
8
8
  - 0
9
- version: 0.7.0
9
+ version: 0.8.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - John Ewart
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-04-18 00:00:00 -07:00
17
+ date: 2011-04-26 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -71,6 +71,19 @@ dependencies:
71
71
  type: :development
72
72
  prerelease: false
73
73
  version_requirements: *id004
74
+ - !ruby/object:Gem::Dependency
75
+ name: rack-test
76
+ requirement: &id005 !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ type: :development
85
+ prerelease: false
86
+ version_requirements: *id005
74
87
  description: A Ruby implementation of metrics inspired by @coda's JVM metrics for those of us in Ruby land
75
88
  email:
76
89
  - john@johnewart.net
@@ -92,7 +105,11 @@ files:
92
105
  - examples/counter.rb
93
106
  - examples/gauge.rb
94
107
  - examples/histogram.rb
108
+ - examples/integration/rack_endpoint.ru
109
+ - examples/integration/rack_middleware.ru
110
+ - examples/integration/webrick.rb
95
111
  - examples/meter.rb
112
+ - examples/timer.rb
96
113
  - lib/ruby-metrics.rb
97
114
  - lib/ruby-metrics/agent.rb
98
115
  - lib/ruby-metrics/instruments.rb
@@ -102,6 +119,10 @@ files:
102
119
  - lib/ruby-metrics/instruments/histogram.rb
103
120
  - lib/ruby-metrics/instruments/meter.rb
104
121
  - lib/ruby-metrics/instruments/timer.rb
122
+ - lib/ruby-metrics/integration.rb
123
+ - lib/ruby-metrics/integration/rack_endpoint.rb
124
+ - lib/ruby-metrics/integration/rack_middleware.rb
125
+ - lib/ruby-metrics/integration/webrick.rb
105
126
  - lib/ruby-metrics/logging.rb
106
127
  - lib/ruby-metrics/statistics/exponential_sample.rb
107
128
  - lib/ruby-metrics/statistics/sample.rb
@@ -117,6 +138,9 @@ files:
117
138
  - spec/instruments/meter_spec.rb
118
139
  - spec/instruments/timer_spec.rb
119
140
  - spec/instruments_spec.rb
141
+ - spec/integration/rack_endpoint_spec.rb
142
+ - spec/integration/rack_middleware_spec.rb
143
+ - spec/integration/webrick_spec.rb
120
144
  - spec/spec_helper.rb
121
145
  - spec/statistics/exponential_sample_spec.rb
122
146
  - spec/statistics/sample_spec.rb
@@ -136,7 +160,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
160
  requirements:
137
161
  - - ">="
138
162
  - !ruby/object:Gem::Version
139
- hash: -24047821763014180
163
+ hash: 4032034234176091931
140
164
  segments:
141
165
  - 0
142
166
  version: "0"
@@ -145,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
169
  requirements:
146
170
  - - ">="
147
171
  - !ruby/object:Gem::Version
148
- hash: -24047821763014180
172
+ hash: 4032034234176091931
149
173
  segments:
150
174
  - 0
151
175
  version: "0"
@@ -165,6 +189,9 @@ test_files:
165
189
  - spec/instruments/meter_spec.rb
166
190
  - spec/instruments/timer_spec.rb
167
191
  - spec/instruments_spec.rb
192
+ - spec/integration/rack_endpoint_spec.rb
193
+ - spec/integration/rack_middleware_spec.rb
194
+ - spec/integration/webrick_spec.rb
168
195
  - spec/spec_helper.rb
169
196
  - spec/statistics/exponential_sample_spec.rb
170
197
  - spec/statistics/sample_spec.rb