unicorn_metrics 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/unicorn_metrics/counter.rb +41 -0
- data/lib/unicorn_metrics/default_http_metrics.rb +16 -0
- data/lib/unicorn_metrics/middleware.rb +116 -0
- data/lib/unicorn_metrics/registry.rb +75 -0
- data/lib/unicorn_metrics/request_counter.rb +42 -0
- data/lib/unicorn_metrics/request_timer.rb +43 -0
- data/lib/unicorn_metrics/response_counter.rb +43 -0
- data/lib/unicorn_metrics/timer.rb +57 -0
- data/lib/unicorn_metrics/version.rb +3 -0
- data/lib/unicorn_metrics.rb +62 -0
- data/test/test_counter.rb +65 -0
- data/test/test_helper.rb +5 -0
- data/test/test_middleware.rb +61 -0
- data/test/test_registry.rb +34 -0
- data/test/test_request_counter.rb +56 -0
- data/test/test_request_timer.rb +56 -0
- data/test/test_response_counter.rb +56 -0
- data/test/test_timer.rb +76 -0
- data/test/test_unicorn_metrics.rb +43 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MzM4NmFhYWM4ZjU5YjAxMGE0NjA5YWQ0ZTUzMTEwNjI0OGE5YmRmOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MmYxZDljYTIyZTRkZjAyZjkyYTMzMGZhNzc2ZmUxOTA4NWI3MzM5OA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZjZiNzRjODE2MmIzNzY0YmI1MWRjNjdkMjMwNGUzOWFmY2NlZTIyMGI3MTM1
|
10
|
+
MTA3ODU4MTM5ZTZjYTY4OTMzNThiYzFlYzc5MzZmMmU3Mzg2ZWQwMzhhNGQx
|
11
|
+
YTY3ZDE2MGFmNDJhYmU2ZDZjYmI1NTQyZTFlYzU2NTFkYTc1NDU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZjhiMDNkZjczNjJmNzQ2Y2JmMDIwYTliODk1MWZhNTU5MTE5ZGVhMDFlMzBk
|
14
|
+
MzZmZDc0MTE0YzNkNGQ2ZWI3M2FjYmU5YWNmNmVhODkwMDNlOTA5MjVjZWUx
|
15
|
+
MTljNTJlOGM2MGY1NzEyNDg3YTlhOTYxYTg4Mzk1NjU1ODRiZTc=
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# UnicornMetrics::Counter is an atomic counter that conveniently wraps the Raindrops::Struct
|
2
|
+
#
|
3
|
+
class UnicornMetrics::Counter
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
class Stats < Raindrops::Struct.new(:value) ; end
|
9
|
+
|
10
|
+
# Delegate getter and setter to @stats
|
11
|
+
def_instance_delegator :@stats, :value
|
12
|
+
|
13
|
+
# Provide #increment and #decrement by delegating to @stats
|
14
|
+
def_instance_delegator :@stats, :incr_value, :increment
|
15
|
+
def_instance_delegator :@stats, :decr_value, :decrement
|
16
|
+
|
17
|
+
# @param name [String] user-defined name
|
18
|
+
def initialize(name)
|
19
|
+
@name = name
|
20
|
+
@stats = Stats.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Reset the counter
|
24
|
+
def reset
|
25
|
+
@stats.value = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
"counter"
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Hash] JSON representation of the object
|
33
|
+
def as_json(*)
|
34
|
+
{
|
35
|
+
name => {
|
36
|
+
"type" => type,
|
37
|
+
"value" => value
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module UnicornMetrics::DefaultHttpMetrics
|
2
|
+
def register_default_http_counters
|
3
|
+
[
|
4
|
+
["responses.2xx", /[2]\d{2}/], ["responses.3xx", /[3]\d{2}/],
|
5
|
+
["responses.4xx", /[4]\d{2}/], ["responses.5xx", /[5]\d{2}/]
|
6
|
+
].each { |c| register(:response_counter, *c) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def register_default_http_timers
|
10
|
+
[
|
11
|
+
['requests.GET', 'GET'], ['requests.POST', 'POST'],
|
12
|
+
['requests.DELETE', 'DELETE'], ['requests.HEAD', 'HEAD'],
|
13
|
+
['requests.PUT', 'PUT']
|
14
|
+
].each { |c| register(:request_timer, *c) }
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# UnicornMetrics::Middleware extends the existing Raindrops::Middleware class
|
2
|
+
#
|
3
|
+
require 'unicorn_metrics' unless defined?(UnicornMetrics)
|
4
|
+
require 'raindrops'
|
5
|
+
require 'benchmark'
|
6
|
+
|
7
|
+
class UnicornMetrics::Middleware < Raindrops::Middleware
|
8
|
+
|
9
|
+
# @param opts [Hash] options hash
|
10
|
+
# @option opts [String] :metrics the HTTP endpoint that exposes the application metrics
|
11
|
+
def initialize(app, opts = {})
|
12
|
+
@registry = UnicornMetrics::Registry
|
13
|
+
@metrics_path = opts[:metrics] || "/metrics"
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
return metrics_response if env['PATH_INFO'] == @metrics_path
|
19
|
+
|
20
|
+
response = nil
|
21
|
+
time = Benchmark.realtime do
|
22
|
+
response = super
|
23
|
+
#=> [ status, headers, <#Raindrops::Middleware::Proxy> ]
|
24
|
+
# Proxy is a wrapper around the response body
|
25
|
+
end
|
26
|
+
collect_http_metrics(env, response, time) if UnicornMetrics.http_metrics?
|
27
|
+
response
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def metrics_response
|
32
|
+
body = @registry.as_json.merge(raindrops).to_json
|
33
|
+
|
34
|
+
headers = {
|
35
|
+
"Content-Type" => "application/json",
|
36
|
+
"Content-Length" => body.size.to_s,
|
37
|
+
}
|
38
|
+
[ 200, headers, [ body ] ]
|
39
|
+
end
|
40
|
+
|
41
|
+
def collect_http_metrics(env, response, elapsed_time)
|
42
|
+
method, path = env['REQUEST_METHOD'], env['PATH_INFO']
|
43
|
+
status = response[0]
|
44
|
+
|
45
|
+
UnicornMetrics::ResponseCounter.notify(status, path)
|
46
|
+
UnicornMetrics::RequestTimer.notify(method, path, elapsed_time)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Provide Raindrops::Middleware statistics in the metrics JSON response
|
50
|
+
# `@stats` is defined in the Raindrops::Middleware class
|
51
|
+
|
52
|
+
# * calling - the number of application dispatchers on your machine
|
53
|
+
# * writing - the number of clients being written to on your machine
|
54
|
+
def raindrops
|
55
|
+
{
|
56
|
+
"raindrops.calling" => {
|
57
|
+
"type" => "gauge",
|
58
|
+
"value" => @stats.calling
|
59
|
+
},
|
60
|
+
"raindrops.writing" => {
|
61
|
+
"type" => "gauge",
|
62
|
+
"value" => @stats.writing
|
63
|
+
}
|
64
|
+
}.merge(total_listener_stats)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Supporting additional stats collected by Raindrops for Linux platforms
|
68
|
+
# `@tcp` and `@unix` are defined in Raindrops::Middleware
|
69
|
+
def total_listener_stats(listeners={})
|
70
|
+
if defined?(Raindrops::Linux.tcp_listener_stats)
|
71
|
+
listeners.merge!(raindrops_tcp_listener_stats) if @tcp
|
72
|
+
listeners.merge!(raindrops_unix_listener_stats) if @unix
|
73
|
+
end
|
74
|
+
listeners
|
75
|
+
end
|
76
|
+
|
77
|
+
def raindrops_tcp_listener_stats
|
78
|
+
hash = {
|
79
|
+
"raindrops.tcp.active" => { type: :gauge, value: 0 },
|
80
|
+
"raindrops.tcp.queued" => { type: :gauge, value: 0 }
|
81
|
+
}
|
82
|
+
Raindrops::Linux.tcp_listener_stats(@tcp).each do |_, stats|
|
83
|
+
hash["raindrops.tcp.active"][:value] += stats.active.to_i
|
84
|
+
hash["raindrops.tcp.queued"][:value] += stats.queued.to_i
|
85
|
+
end
|
86
|
+
hash
|
87
|
+
end
|
88
|
+
|
89
|
+
def raindrops_unix_listener_stats
|
90
|
+
hash = {
|
91
|
+
"raindrops.unix.active" => { type: :gauge, value: 0 },
|
92
|
+
"raindrops.unix.queued" => { type: :gauge, value: 0 }
|
93
|
+
}
|
94
|
+
Raindrops::Linux.unix_listener_stats(@unix).each do |_, stats|
|
95
|
+
hash["raindrops.unix.active"][:value] += stats.active.to_i
|
96
|
+
hash["raindrops.unix.queued"][:value] += stats.queued.to_i
|
97
|
+
end
|
98
|
+
hash
|
99
|
+
end
|
100
|
+
|
101
|
+
# NOTE: The 'total' is being used in favor of returning stats for \
|
102
|
+
# each listening address, which was the default in Raindrops
|
103
|
+
def listener_stats(listeners={})
|
104
|
+
if defined?(Raindrops::Linux.tcp_listener_stats)
|
105
|
+
Raindrops::Linux.tcp_listener_stats(@tcp).each do |addr,stats|
|
106
|
+
listeners["raindrops.#{addr}.active"] = "#{stats.active}"
|
107
|
+
listeners["raindrops.#{addr}.queued"] = "#{stats.queued}"
|
108
|
+
end if @tcp
|
109
|
+
Raindrops::Linux.unix_listener_stats(@unix).each do |addr,stats|
|
110
|
+
listeners["raindrops.#{addr}.active"] = "#{stats.active}"
|
111
|
+
listeners["raindrops.#{addr}.queued"] = "#{stats.queued}"
|
112
|
+
end if @unix
|
113
|
+
end
|
114
|
+
listeners
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Borrowing nomenclature from http://metrics.codahale.com/
|
2
|
+
#
|
3
|
+
# To support a cleaner interface, the UnicornMetrics module delegates to the Registry
|
4
|
+
# for supported methods. Methods should not be called directly on this module
|
5
|
+
|
6
|
+
# UnicornMetrics::Registry is a container for Metrics
|
7
|
+
# @private
|
8
|
+
module UnicornMetrics::Registry
|
9
|
+
|
10
|
+
# Map metrics types to class names
|
11
|
+
METRIC_TYPES = {
|
12
|
+
:counter => 'Counter',
|
13
|
+
:timer => 'Timer',
|
14
|
+
:response_counter => 'ResponseCounter',
|
15
|
+
:request_counter => 'RequestCounter',
|
16
|
+
:request_timer => 'RequestTimer'
|
17
|
+
}
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
# Return a hash of metrics that have been defined
|
22
|
+
#
|
23
|
+
# @return [Hash] a metric name to metric object
|
24
|
+
def metrics
|
25
|
+
@metrics ||= {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Register a new metric. Arguments are optional. See metric class definitions.
|
29
|
+
#
|
30
|
+
# @param type [Symbol] underscored metric name
|
31
|
+
# @param name [String] string representing the metric name
|
32
|
+
# @return [Counter, Timer, ResponseCounter, RequestCounter, RequestTimer]
|
33
|
+
def register(type, name, *args)
|
34
|
+
type = METRIC_TYPES.fetch(type) { raise "Invalid type: #{type}" }
|
35
|
+
validate_name!(name)
|
36
|
+
metric = UnicornMetrics.const_get(type).new(name,*args)
|
37
|
+
metrics[name] = metric
|
38
|
+
define_getter(name)
|
39
|
+
|
40
|
+
metric
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Hash] default JSON representation of metrics
|
44
|
+
def as_json(*)
|
45
|
+
metrics.inject({}) do |hash, (name, metric)|
|
46
|
+
hash.merge(metric.as_json)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
# Convenience methods to return the stored metrics.
|
52
|
+
# Allows the use of names with spaces, dots, and dashes, which are \
|
53
|
+
# replaced by an underscore:
|
54
|
+
#
|
55
|
+
# def UnicornMetrics::Registry.stat_name
|
56
|
+
# metrics.fetch('stat_name')
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
def define_getter(name)
|
60
|
+
define_singleton_method(format_name(name)) { metrics.fetch(name) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Replace non-word characters with '_'
|
64
|
+
def format_name(name)
|
65
|
+
name.gsub(/\W/, '_')
|
66
|
+
end
|
67
|
+
|
68
|
+
# @raise [ArgumentError] if the metric name is in use
|
69
|
+
def validate_name!(name)
|
70
|
+
if metrics.fetch(name,false)
|
71
|
+
raise ArgumentError, "The name, '#{name}', is in use."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Counter defined to keep count of method types of http requests
|
2
|
+
# Requires the UnicornMetrics::Middleware
|
3
|
+
|
4
|
+
class UnicornMetrics::RequestCounter < UnicornMetrics::Counter
|
5
|
+
attr_reader :path, :method_name
|
6
|
+
|
7
|
+
METHOD_COUNTERS = []
|
8
|
+
|
9
|
+
# @param name [String] user-defined name
|
10
|
+
# @param method_name [String] name of the HTTP method
|
11
|
+
# @param path [Regex] optional regex that is used to match to a specific URI
|
12
|
+
def initialize(name, method_name, path=nil)
|
13
|
+
@path = path
|
14
|
+
@method_name = method_name.to_s.upcase
|
15
|
+
METHOD_COUNTERS << self
|
16
|
+
super(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<UnicornMetrics::RequestCounter>]
|
20
|
+
def self.counters ; METHOD_COUNTERS ; end
|
21
|
+
|
22
|
+
# @param meth_val [String] is the HTTP method of the request
|
23
|
+
# @param path [String] is the URI of the request
|
24
|
+
def self.notify(meth_val, path)
|
25
|
+
counters.each { |c| c.increment if c.path_method_match?(meth_val, path) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param (see #notify)
|
29
|
+
# @return [Boolean]
|
30
|
+
def path_method_match?(meth_val, path_val)
|
31
|
+
path_matches?(path_val) && method_matches?(meth_val)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def path_matches?(val)
|
36
|
+
!!(path =~ val) || path.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_matches?(val)
|
40
|
+
method_name.upcase == val.to_s
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Timer defined to keep track of total elapsed request time
|
2
|
+
# Requires the UnicornMetrics::Middleware
|
3
|
+
|
4
|
+
class UnicornMetrics::RequestTimer < UnicornMetrics::Timer
|
5
|
+
attr_reader :path, :method_name
|
6
|
+
|
7
|
+
REQUEST_TIMERS = []
|
8
|
+
|
9
|
+
# @param name [String] user-defined name
|
10
|
+
# @param method_name [String] name of the HTTP method
|
11
|
+
# @param path [Regex] optional regex that is used to match to a specific URI
|
12
|
+
def initialize(name, method_name, path=nil)
|
13
|
+
@path = path
|
14
|
+
@method_name = method_name.to_s
|
15
|
+
REQUEST_TIMERS << self
|
16
|
+
super(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<UnicornMetrics::RequestTimer>]
|
20
|
+
def self.timers ; REQUEST_TIMERS ; end
|
21
|
+
|
22
|
+
# @param meth_val [String] is the HTTP method of the request
|
23
|
+
# @param path [String] is the URI of the request
|
24
|
+
def self.notify(meth_val, path, elapsed_time)
|
25
|
+
timers.each { |c| c.tick(elapsed_time) if c.path_method_match?(meth_val, path) }
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# @param (see #notify)
|
30
|
+
# @return [Boolean]
|
31
|
+
def path_method_match?(meth_val, path_val)
|
32
|
+
path_matches?(path_val) && method_matches?(meth_val)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def path_matches?(val)
|
37
|
+
!!(path =~ val) || path.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_matches?(val)
|
41
|
+
method_name.upcase == val.to_s
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Counter defined to keep count of status codes of http responses
|
2
|
+
# Requires the UnicornMetrics::Middleware
|
3
|
+
|
4
|
+
class UnicornMetrics::ResponseCounter < UnicornMetrics::Counter
|
5
|
+
attr_reader :path, :status_code
|
6
|
+
|
7
|
+
STATUS_COUNTERS = []
|
8
|
+
|
9
|
+
# @param name [String] user-defined name
|
10
|
+
# @param status_code [Regex] the HTTP status code (e.g., `/[2]\d{2}/`)
|
11
|
+
# @param path [Regex] optional regex that is used to match to a specific URI
|
12
|
+
def initialize(name, status_code, path=nil)
|
13
|
+
@path = path
|
14
|
+
@status_code = status_code
|
15
|
+
STATUS_COUNTERS << self
|
16
|
+
super(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array<UnicornMetrics::ResponseCounter>]
|
20
|
+
def self.counters ; STATUS_COUNTERS ; end
|
21
|
+
|
22
|
+
|
23
|
+
# @param status [String] is the HTTP status code of the request
|
24
|
+
# @param path [String] is the URI of the request
|
25
|
+
def self.notify(status, path)
|
26
|
+
counters.each { |c| c.increment if c.path_status_match?(status, path) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param (see #notify)
|
30
|
+
# @return [Boolean]
|
31
|
+
def path_status_match?(status,path)
|
32
|
+
status_matches?(status) && path_matches?(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def path_matches?(val)
|
37
|
+
path.nil? || !!(path =~ val)
|
38
|
+
end
|
39
|
+
|
40
|
+
def status_matches?(val)
|
41
|
+
!!(status_code =~ val.to_s)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# UnicornMetrics::Timer keeps track of total time and the count of 'ticks'
|
2
|
+
# A simple rate of average of ticks over time elapsed can be calculated this way.
|
3
|
+
# For more advanced metrics (e.g., 1/5/15min moving averages) this data should be reported to an intelligent metric store (i.e. Graphite)
|
4
|
+
#
|
5
|
+
class UnicornMetrics::Timer
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# The Raindrops::Struct can only hold unsigned long ints (0 -> 4,294,967,295)
|
11
|
+
# Since we usually care about ms in a web application, \
|
12
|
+
# let's store 3 significant digits after the decimal
|
13
|
+
EXPONENT = -3
|
14
|
+
|
15
|
+
class Stats < Raindrops::Struct.new(:count, :mantissa) ; end
|
16
|
+
|
17
|
+
def_instance_delegators :@stats, :mantissa, :count
|
18
|
+
|
19
|
+
# @param name [String] user-defined name
|
20
|
+
def initialize(name)
|
21
|
+
@name = name
|
22
|
+
@stats = Stats.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
"timer"
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param elapsed_time [Numeric] in seconds
|
30
|
+
def tick(elapsed_time)
|
31
|
+
elapsed_time = (elapsed_time * 10**-EXPONENT).to_i
|
32
|
+
|
33
|
+
@stats.mantissa = mantissa + elapsed_time
|
34
|
+
@stats.incr_count
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reset the timer
|
38
|
+
def reset
|
39
|
+
@stats.mantissa = 0 and @stats.count = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Numeric] total elapsed time
|
43
|
+
def sum
|
44
|
+
(mantissa * 10**EXPONENT).to_f.round(-EXPONENT)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Hash] JSON representation of the object
|
48
|
+
def as_json(*)
|
49
|
+
{
|
50
|
+
name => {
|
51
|
+
"type" => type,
|
52
|
+
"sum" => sum,
|
53
|
+
"count" => count
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module UnicornMetrics
|
2
|
+
class << self
|
3
|
+
|
4
|
+
# Returns the UnicornMetrics::Registry object
|
5
|
+
#
|
6
|
+
# @return [UnicornMetrics::Registry]
|
7
|
+
def registry
|
8
|
+
UnicornMetrics::Registry
|
9
|
+
end
|
10
|
+
|
11
|
+
# Make this class 'configurable'
|
12
|
+
#
|
13
|
+
# @yieldparam self [UnicornMetrics]
|
14
|
+
def configure
|
15
|
+
yield self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Enable/disable HTTP metrics. Includes defaults
|
19
|
+
#
|
20
|
+
# @param boolean [Boolean] to enable or disable default HTTP metrics
|
21
|
+
def http_metrics=(boolean=false)
|
22
|
+
return if @_assigned
|
23
|
+
|
24
|
+
if @http_metrics = boolean
|
25
|
+
registry.extend(UnicornMetrics::DefaultHttpMetrics)
|
26
|
+
registry.register_default_http_counters
|
27
|
+
registry.register_default_http_timers
|
28
|
+
end
|
29
|
+
@_assigned = true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Used by the middleware to determine whether any HTTP metrics have been defined
|
33
|
+
#
|
34
|
+
# @return [Boolean] if HTTP metrics have been defined
|
35
|
+
def http_metrics? ; @http_metrics ; end
|
36
|
+
|
37
|
+
private
|
38
|
+
# Delegate methods to UnicornMetrics::Registry
|
39
|
+
#
|
40
|
+
# http://robots.thoughtbot.com/post/28335346416/always-define-respond-to-missing-when-overriding
|
41
|
+
def respond_to_missing?(method_name, include_private=false)
|
42
|
+
registry.respond_to?(method_name, include_private)
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing(method_name, *args, &block)
|
46
|
+
return super unless registry.respond_to?(method_name)
|
47
|
+
registry.send(method_name, *args, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
require 'raindrops'
|
53
|
+
require 'unicorn_metrics/registry'
|
54
|
+
require 'unicorn_metrics/version'
|
55
|
+
require 'unicorn_metrics/counter'
|
56
|
+
require 'unicorn_metrics/timer'
|
57
|
+
require 'unicorn_metrics/default_http_metrics'
|
58
|
+
require 'unicorn_metrics/request_counter'
|
59
|
+
require 'unicorn_metrics/request_timer'
|
60
|
+
require 'unicorn_metrics/response_counter'
|
61
|
+
require 'forwardable'
|
62
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe UnicornMetrics::Counter do
|
4
|
+
before do
|
5
|
+
@counter = UnicornMetrics::Counter.new("test_counter")
|
6
|
+
@counter.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#type" do
|
10
|
+
it "returns 'counter'" do
|
11
|
+
@counter.type.must_equal 'counter'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#value" do
|
16
|
+
it "returns the internal count" do
|
17
|
+
@counter.value.must_equal 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#increment" do
|
22
|
+
it "increments the counter value" do
|
23
|
+
5.times { @counter.increment }
|
24
|
+
@counter.value.must_equal 5
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#decrement" do
|
29
|
+
it "decrements the counter value" do
|
30
|
+
5.times { @counter.increment }
|
31
|
+
5.times { @counter.decrement }
|
32
|
+
@counter.value.must_equal 0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#reset" do
|
37
|
+
it "resets the counter value" do
|
38
|
+
5.times { @counter.increment }
|
39
|
+
@counter.reset
|
40
|
+
@counter.value.must_equal 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#as_json" do
|
45
|
+
it "returns the JSON representation of the object as a hash" do
|
46
|
+
hash = {
|
47
|
+
@counter.name => {
|
48
|
+
"type" => @counter.type,
|
49
|
+
"value" => @counter.value
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
@counter.as_json.must_equal hash
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# REFACTOR: This test is very slow
|
58
|
+
describe "forking" do
|
59
|
+
it "can be shared across processes" do
|
60
|
+
2.times { fork { @counter.increment ; exit } }
|
61
|
+
Process.waitall
|
62
|
+
@counter.value.must_equal 2
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'unicorn_metrics/middleware'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# Stubbing Raindrops::Linux to support testing listener statistics
|
6
|
+
# See Raindrops::Middleware and Raindrops::Linux
|
7
|
+
module Raindrops::Linux
|
8
|
+
Stats = Struct.new(:active, :queued)
|
9
|
+
def self.tcp_listener_stats(*) ; [['123', Stats.new(1,5)]] ; end
|
10
|
+
def self.unix_listener_stats(*) ; [['456', Stats.new(1,5)]] ; end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe UnicornMetrics::Middleware do
|
14
|
+
before do
|
15
|
+
@resp_headers = { 'Content-Type' => 'text/plain', 'Content-Length' => '0' }
|
16
|
+
@response = [ 200, @resp_headers, ["test_body"] ]
|
17
|
+
@app = ->(env){ @response }
|
18
|
+
|
19
|
+
# Remove any metrics lingering from previous tests
|
20
|
+
UnicornMetrics.metrics.delete_if{true}
|
21
|
+
|
22
|
+
@counter = UnicornMetrics.register(:counter, "test_counter")
|
23
|
+
options = { metrics: '/metrics', listeners: %w(0.0.0.0:80) }
|
24
|
+
@middleware = UnicornMetrics::Middleware.new(@app, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
after { UnicornMetrics.metrics.delete("test_counter")}
|
28
|
+
|
29
|
+
describe "#call" do
|
30
|
+
context "when path matches the defined metrics path" do
|
31
|
+
before do
|
32
|
+
response = @middleware.call({'PATH_INFO' => '/metrics'})
|
33
|
+
@hash = JSON response[2][0]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns the metrics response JSON body" do
|
37
|
+
@hash.fetch("test_counter").must_equal @counter.as_json.fetch("test_counter")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "includes raindrops middleware metrics" do
|
41
|
+
@hash.must_include "raindrops.calling"
|
42
|
+
@hash.must_include "raindrops.writing"
|
43
|
+
@hash.must_include "raindrops.tcp.active"
|
44
|
+
@hash.must_include "raindrops.tcp.queued"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when the path does not match the defined metrics path" do
|
49
|
+
it "returns the expected response" do
|
50
|
+
response = @middleware.call({'PATH_INFO' => '/'})
|
51
|
+
|
52
|
+
# The Raindrops::Middleware wraps the response body in a Proxy
|
53
|
+
# Write the response body to a string to match the expectation
|
54
|
+
response[2] = [ response[2].inject(""){ |str, v| str << v } ]
|
55
|
+
|
56
|
+
response.must_equal @response
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe UnicornMetrics::Registry do
|
4
|
+
describe "METRIC_TYPES" do
|
5
|
+
it "returns a hash that maps type symbols to class names" do
|
6
|
+
hash = {
|
7
|
+
:counter => 'Counter',
|
8
|
+
:timer => 'Timer',
|
9
|
+
:response_counter => 'ResponseCounter',
|
10
|
+
:request_counter => 'RequestCounter',
|
11
|
+
:request_timer => 'RequestTimer'
|
12
|
+
}
|
13
|
+
UnicornMetrics::Registry::METRIC_TYPES.must_equal hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".register" do
|
18
|
+
before { UnicornMetrics.register(:counter,"test-counter") }
|
19
|
+
after { UnicornMetrics.metrics.delete("test-counter")}
|
20
|
+
|
21
|
+
it "initializes and stores a new metric object" do
|
22
|
+
UnicornMetrics.metrics.fetch("test-counter").must_be_instance_of UnicornMetrics::Counter
|
23
|
+
end
|
24
|
+
|
25
|
+
it "defines getter method from the name of the metric with non-word chars replaced by '_'" do
|
26
|
+
UnicornMetrics.metrics.fetch("test-counter").must_be_same_as UnicornMetrics.test_counter
|
27
|
+
end
|
28
|
+
|
29
|
+
it "raises an error if a name is used twice" do
|
30
|
+
->{UnicornMetrics.register(:counter, "test-counter")}.must_raise ArgumentError
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe UnicornMetrics::RequestCounter do
|
4
|
+
before do
|
5
|
+
@counter = UnicornMetrics::RequestCounter.new("test_counter", 'POST')
|
6
|
+
@counter.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".counters" do
|
10
|
+
it "returns a collection of current RequestCounter instances" do
|
11
|
+
UnicornMetrics::RequestCounter.counters.must_include @counter
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".notify" do
|
16
|
+
it "increments all existing counters that match an http method and path" do
|
17
|
+
UnicornMetrics::RequestCounter.notify('POST','/')
|
18
|
+
@counter.value.must_equal 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#path_method_match?" do
|
23
|
+
context "when path is nil (not specified)" do
|
24
|
+
context "when method name matches" do
|
25
|
+
it "returns true" do
|
26
|
+
@counter.path_method_match?('POST', '/anything').must_equal true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when method name does not match" do
|
31
|
+
it "returns false" do
|
32
|
+
@counter.path_method_match?('GET', '/anything').must_equal false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when path is not nil (it is set)" do
|
38
|
+
before { @counter.instance_variable_set(:@path, /\/something/) }
|
39
|
+
after { @counter.instance_variable_set(:@path, nil) }
|
40
|
+
|
41
|
+
context "when method matches" do
|
42
|
+
context "when patch matches" do
|
43
|
+
it "returns true" do
|
44
|
+
@counter.path_method_match?('POST', '/something').must_equal true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when patch does not match" do
|
49
|
+
it "returns false" do
|
50
|
+
@counter.path_method_match?('POST', '/bla').must_equal false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe UnicornMetrics::RequestTimer do
|
4
|
+
before do
|
5
|
+
@timer = UnicornMetrics::RequestTimer.new("test_timer", 'POST')
|
6
|
+
@timer.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".timers" do
|
10
|
+
it "returns a collection of current RequestTimer instances" do
|
11
|
+
UnicornMetrics::RequestTimer.timers.must_include @timer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".notify" do
|
16
|
+
it "ticks all existing timers that match an http method and path" do
|
17
|
+
UnicornMetrics::RequestTimer.notify('POST','/', 10.0)
|
18
|
+
@timer.sum.must_equal 10.0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#path_method_match?" do
|
23
|
+
context "when path is nil (not specified)" do
|
24
|
+
context "when method name matches" do
|
25
|
+
it "returns true" do
|
26
|
+
@timer.path_method_match?('POST', '/anything').must_equal true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when method name does not match" do
|
31
|
+
it "returns false" do
|
32
|
+
@timer.path_method_match?('GET', '/anything').must_equal false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when path is not nil (it is set)" do
|
38
|
+
before { @timer.instance_variable_set(:@path, /\/something/) }
|
39
|
+
after { @timer.instance_variable_set(:@path, nil) }
|
40
|
+
|
41
|
+
context "when method matches" do
|
42
|
+
context "when patch matches" do
|
43
|
+
it "returns true" do
|
44
|
+
@timer.path_method_match?('POST', '/something').must_equal true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when patch does not match" do
|
49
|
+
it "returns false" do
|
50
|
+
@timer.path_method_match?('POST', '/bla').must_equal false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe UnicornMetrics::ResponseCounter do
|
4
|
+
before do
|
5
|
+
@counter = UnicornMetrics::ResponseCounter.new("test_counter", /[2]\d{2}/)
|
6
|
+
@counter.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".counters" do
|
10
|
+
it "returns a collection of current ResponseCounter instances" do
|
11
|
+
UnicornMetrics::ResponseCounter.counters.must_include @counter
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".notify" do
|
16
|
+
it "increments all existing counters that match a status code and path" do
|
17
|
+
UnicornMetrics::ResponseCounter.notify('200','/')
|
18
|
+
@counter.value.must_equal 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#path_status_match?" do
|
23
|
+
context "when path is nil (not specified)" do
|
24
|
+
context "when status name matches" do
|
25
|
+
it "returns true" do
|
26
|
+
@counter.path_status_match?('200', '/anything').must_equal true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when status name does not match" do
|
31
|
+
it "returns false" do
|
32
|
+
@counter.path_status_match?('400', '/anything').must_equal false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when path is not nil (it is set)" do
|
38
|
+
before { @counter.instance_variable_set(:@path, /\/something/) }
|
39
|
+
after { @counter.instance_variable_set(:@path, nil) }
|
40
|
+
|
41
|
+
context "when status matches" do
|
42
|
+
context "when patch matches" do
|
43
|
+
it "returns true" do
|
44
|
+
@counter.path_status_match?('200', '/something').must_equal true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when patch does not match" do
|
49
|
+
it "returns false" do
|
50
|
+
@counter.path_status_match?('200', '/bla').must_equal false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/test/test_timer.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe UnicornMetrics::Timer do
|
4
|
+
before do
|
5
|
+
@timer = UnicornMetrics::Timer.new("test_timer")
|
6
|
+
@timer.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#type" do
|
10
|
+
it "returns 'timer'" do
|
11
|
+
@timer.type.must_equal 'timer'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when initialized" do
|
16
|
+
describe "#sum" do
|
17
|
+
it "must be zero" do
|
18
|
+
@timer.sum.must_equal 0.0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#count" do
|
23
|
+
it "must be zero" do
|
24
|
+
@timer.count.must_equal 0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when ticked" do
|
30
|
+
describe "#sum" do
|
31
|
+
it "returns sum + elapsed time" do
|
32
|
+
@timer.tick(5)
|
33
|
+
@timer.sum.must_equal 5.0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#count" do
|
38
|
+
it "returns the count of ticks" do
|
39
|
+
@timer.tick(5)
|
40
|
+
@timer.count.must_equal 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#reset" do
|
46
|
+
it "resets count and sum" do
|
47
|
+
5.times { @timer.tick(5) }
|
48
|
+
@timer.reset
|
49
|
+
@timer.sum.must_equal 0
|
50
|
+
@timer.count.must_equal 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#as_json" do
|
55
|
+
it "returns the JSON representation of the object as a hash" do
|
56
|
+
hash = {
|
57
|
+
@timer.name => {
|
58
|
+
"type" => @timer.type,
|
59
|
+
"sum" => @timer.sum,
|
60
|
+
"count" => @timer.count
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
@timer.as_json.must_equal hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "forking" do
|
69
|
+
it "can be shared across processes" do
|
70
|
+
2.times { fork { @timer.tick(5) ; exit } }
|
71
|
+
Process.waitall
|
72
|
+
@timer.sum.must_equal 10.0
|
73
|
+
@timer.count.must_equal 2
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe UnicornMetrics do
|
4
|
+
describe "::registry" do
|
5
|
+
it "returns the UnicornMetrics::Registry object" do
|
6
|
+
UnicornMetrics.registry.must_equal UnicornMetrics::Registry
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "::configure" do
|
11
|
+
it "yields self" do
|
12
|
+
->{ UnicornMetrics.configure {|u| print u}}.must_output 'UnicornMetrics'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "::http_metrics=" do
|
17
|
+
context "when arg is false" do
|
18
|
+
it "should not extend Registry with DefaultHttpCounters module" do
|
19
|
+
UnicornMetrics.registry.wont_respond_to :register_default_http_counters
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when arg is true" do
|
24
|
+
before { UnicornMetrics.http_metrics = true }
|
25
|
+
|
26
|
+
it "extends Registry with DefaultHttpMetrics module" do
|
27
|
+
UnicornMetrics.registry.must_respond_to :register_default_http_counters
|
28
|
+
UnicornMetrics.registry.must_respond_to :register_default_http_timers
|
29
|
+
end
|
30
|
+
|
31
|
+
it "registers the default http counters" do
|
32
|
+
UnicornMetrics.registry.metrics.keys.size.must_equal 9
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "delegates unknown methods to Registry" do
|
38
|
+
methods = UnicornMetrics.registry.methods(false)
|
39
|
+
respond_count = 0
|
40
|
+
methods.each { |m| respond_count+=1 if UnicornMetrics.respond_to?(m) }
|
41
|
+
respond_count.must_equal methods.size
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unicorn_metrics
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alan Cohen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 10.1.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 10.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: raindrops
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.11.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.11.0
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- acohen@climate.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/unicorn_metrics/counter.rb
|
49
|
+
- lib/unicorn_metrics/default_http_metrics.rb
|
50
|
+
- lib/unicorn_metrics/middleware.rb
|
51
|
+
- lib/unicorn_metrics/registry.rb
|
52
|
+
- lib/unicorn_metrics/request_counter.rb
|
53
|
+
- lib/unicorn_metrics/request_timer.rb
|
54
|
+
- lib/unicorn_metrics/response_counter.rb
|
55
|
+
- lib/unicorn_metrics/timer.rb
|
56
|
+
- lib/unicorn_metrics/version.rb
|
57
|
+
- lib/unicorn_metrics.rb
|
58
|
+
- test/test_counter.rb
|
59
|
+
- test/test_helper.rb
|
60
|
+
- test/test_middleware.rb
|
61
|
+
- test/test_registry.rb
|
62
|
+
- test/test_request_counter.rb
|
63
|
+
- test/test_request_timer.rb
|
64
|
+
- test/test_response_counter.rb
|
65
|
+
- test/test_timer.rb
|
66
|
+
- test/test_unicorn_metrics.rb
|
67
|
+
homepage: http://www.climate.com
|
68
|
+
licenses: []
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 1.9.3
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements:
|
85
|
+
- Preforking http server (i.e., Unicorn).
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 2.0.6
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: Metrics library for Rack applications using a preforking http server (i.e.,
|
91
|
+
Unicorn)
|
92
|
+
test_files:
|
93
|
+
- test/test_counter.rb
|
94
|
+
- test/test_helper.rb
|
95
|
+
- test/test_middleware.rb
|
96
|
+
- test/test_registry.rb
|
97
|
+
- test/test_request_counter.rb
|
98
|
+
- test/test_request_timer.rb
|
99
|
+
- test/test_response_counter.rb
|
100
|
+
- test/test_timer.rb
|
101
|
+
- test/test_unicorn_metrics.rb
|
102
|
+
has_rdoc:
|