unicorn_metrics 0.1.1
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.
- 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:
|