ruby-metrics 0.7.0 → 0.8.0

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