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 +5 -1
- data/README.md +49 -7
- data/examples/counter.rb +0 -1
- data/examples/gauge.rb +0 -1
- data/examples/histogram.rb +2 -2
- data/examples/integration/rack_endpoint.ru +24 -0
- data/examples/integration/rack_middleware.ru +21 -0
- data/examples/integration/webrick.rb +17 -0
- data/examples/meter.rb +4 -5
- data/examples/timer.rb +31 -0
- data/lib/ruby-metrics/agent.rb +4 -38
- data/lib/ruby-metrics/instruments.rb +29 -16
- data/lib/ruby-metrics/instruments/counter.rb +2 -0
- data/lib/ruby-metrics/instruments/gauge.rb +2 -0
- data/lib/ruby-metrics/instruments/histogram.rb +5 -2
- data/lib/ruby-metrics/instruments/meter.rb +13 -24
- data/lib/ruby-metrics/instruments/timer.rb +27 -5
- data/lib/ruby-metrics/integration.rb +12 -0
- data/lib/ruby-metrics/integration/rack_endpoint.rb +33 -0
- data/lib/ruby-metrics/integration/rack_middleware.rb +82 -0
- data/lib/ruby-metrics/integration/webrick.rb +47 -0
- data/lib/ruby-metrics/version.rb +1 -1
- data/ruby-metrics.gemspec +1 -0
- data/spec/agent_spec.rb +1 -13
- data/spec/instruments/timer_spec.rb +10 -6
- data/spec/instruments_spec.rb +15 -1
- data/spec/integration/rack_endpoint_spec.rb +60 -0
- data/spec/integration/rack_middleware_spec.rb +129 -0
- data/spec/integration/webrick_spec.rb +18 -0
- data/spec/spec_helper.rb +3 -0
- metadata +32 -5
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ruby-metrics (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
|
-
|
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
|
-
|
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
data/examples/gauge.rb
CHANGED
data/examples/histogram.rb
CHANGED
@@ -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/
|
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
|
-
|
5
|
-
@metrics
|
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/
|
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
|
data/lib/ruby-metrics/agent.rb
CHANGED
@@ -1,26 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'logging')
|
2
2
|
require File.join(File.dirname(__FILE__), 'instruments')
|
3
|
-
require '
|
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
|
12
|
+
def initialize
|
33
13
|
logger.debug "Initializing Metrics..."
|
34
14
|
@instruments = Metrics::Instruments
|
35
|
-
@port = port
|
36
15
|
end
|
37
16
|
|
38
|
-
def
|
39
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
+
|
@@ -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
|
-
|
66
|
+
convert_to_ns @one_minute_rate, rate_unit
|
63
67
|
end
|
64
68
|
|
65
69
|
def five_minute_rate(rate_unit = :seconds)
|
66
|
-
|
70
|
+
convert_to_ns @five_minute_rate, rate_unit
|
67
71
|
end
|
68
72
|
|
69
73
|
def fifteen_minute_rate(rate_unit = :seconds)
|
70
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
data/lib/ruby-metrics/version.rb
CHANGED
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
|
-
|
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(
|
107
|
+
sleep(0.25)
|
104
108
|
"narf"
|
105
109
|
end
|
106
110
|
|
107
111
|
result.should == "narf"
|
108
112
|
|
109
|
-
@timer.max.should >=
|
110
|
-
@timer.max.should <=
|
113
|
+
@timer.max.should >= 250000000
|
114
|
+
@timer.max.should <= 300000000
|
111
115
|
|
112
116
|
end
|
113
117
|
end
|
data/spec/instruments_spec.rb
CHANGED
@@ -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
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 8
|
8
8
|
- 0
|
9
|
-
version: 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-
|
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:
|
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:
|
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
|