ruby-metrics 0.9.0 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +1 -1
- data/lib/ruby-metrics/agent.rb +4 -0
- data/lib/ruby-metrics/instruments/counter.rb +4 -1
- data/lib/ruby-metrics/instruments/gauge.rb +3 -1
- data/lib/ruby-metrics/instruments/histogram.rb +52 -82
- data/lib/ruby-metrics/instruments/instrument.rb +13 -0
- data/lib/ruby-metrics/instruments/meter.rb +29 -32
- data/lib/ruby-metrics/instruments/timer.rb +16 -20
- data/lib/ruby-metrics/integration/rack_endpoint.rb +1 -1
- data/lib/ruby-metrics/integration/rack_middleware.rb +31 -33
- data/lib/ruby-metrics/reporter.rb +7 -2
- data/lib/ruby-metrics/reporters/ganglia.rb +80 -0
- data/lib/ruby-metrics/reporters/librato.rb +85 -0
- data/lib/ruby-metrics/statistics/exponential_sample.rb +36 -46
- data/lib/ruby-metrics/statistics/uniform_sample.rb +8 -9
- data/lib/ruby-metrics/time_units.rb +9 -15
- data/lib/ruby-metrics/version.rb +1 -1
- data/ruby-metrics-opentsdb.gemspec +1 -1
- data/ruby-metrics.gemspec +1 -1
- data/spec/instruments/counter_spec.rb +37 -27
- data/spec/instruments/histogram_spec.rb +1 -1
- data/spec/instruments/timer_spec.rb +2 -2
- data/spec/integration/rack_middleware_spec.rb +6 -6
- data/spec/reporter_spec.rb +27 -0
- data/spec/reporters/opentsdb_spec.rb +297 -0
- metadata +53 -49
- data/ruby-metrics-ganglia.gemspec +0 -21
- data/ruby-metrics-librato.gemspec +0 -20
- data/spec/time_units_spec.rb +0 -13
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '04971d78af7ca682008c7d66db2eac34111c089bb17778cc18ff434843707126'
|
4
|
+
data.tar.gz: e1ec094ef4a2082d12df838b211fac9cebaf207bf4fabb33fb3b30eaef1f97ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60b859a4037eb7f22d2bbaa0965cc1ed55845817a613701a9a3f19bba088c6886feb68aad020e421506d330fb87eafa612b0c364773d850dbcd92d9e95798e56
|
7
|
+
data.tar.gz: 1b2f48bee677fadfffb53335058aa036985f64f9ce2f6f7901bae3fe8b996dc26c36b83d0303d1ea93065e5eef6cf298ff8c4328c8c13df0182592b694c2d02a
|
data/Rakefile
CHANGED
@@ -21,7 +21,7 @@ task :release => :build do
|
|
21
21
|
sh 'git', 'tag', '-m', "Version #{Metrics::VERSION}", "v#{Metrics::VERSION}"
|
22
22
|
sh "git push origin master"
|
23
23
|
sh "git push origin v#{Metrics::VERSION}"
|
24
|
-
|
24
|
+
sh "ls pkg/*.gem | xargs -n 1 gem push"
|
25
25
|
end
|
26
26
|
|
27
27
|
RSpec::Core::RakeTask.new do |t|
|
data/lib/ruby-metrics/agent.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
+
require_relative 'instrument'
|
1
2
|
require 'ruby-metrics/statistics/uniform_sample'
|
2
3
|
require 'ruby-metrics/statistics/exponential_sample'
|
3
4
|
|
4
5
|
module Metrics
|
5
6
|
module Instruments
|
6
|
-
class Histogram
|
7
|
+
class Histogram < Instrument
|
8
|
+
attr_reader :count
|
7
9
|
|
8
10
|
def initialize(type = :uniform)
|
9
|
-
@
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@variance_m = -1
|
11
|
+
@sample =
|
12
|
+
case type
|
13
|
+
when :uniform
|
14
|
+
Metrics::Statistics::UniformSample.new
|
15
|
+
when :exponential
|
16
|
+
Metrics::Statistics::ExponentialSample.new
|
17
|
+
else
|
18
|
+
raise ArgumentError, "Unknown type #{type.inspect}"
|
19
|
+
end
|
20
|
+
|
21
|
+
clear
|
21
22
|
end
|
22
23
|
|
23
24
|
def update(value)
|
@@ -26,7 +27,7 @@ module Metrics
|
|
26
27
|
@sample.update(value)
|
27
28
|
update_max(value)
|
28
29
|
update_min(value)
|
29
|
-
update_variance(value)
|
30
|
+
update_variance(value)
|
30
31
|
end
|
31
32
|
|
32
33
|
def clear
|
@@ -40,111 +41,80 @@ module Metrics
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def quantiles(percentiles)
|
43
|
-
# Calculated using the same logic as R and
|
44
|
+
# Calculated using the same logic as R and Excel use
|
44
45
|
# as outlined by the NIST here: http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm
|
45
|
-
count = @count
|
46
|
-
scores = {}
|
47
|
-
values = @sample.values[0..count-1]
|
48
46
|
|
47
|
+
sorted_values = @sample.values[0...@count].sort
|
48
|
+
scores = { }
|
49
49
|
percentiles.each do |pct|
|
50
|
-
scores[pct] =
|
51
|
-
|
52
|
-
|
53
|
-
if count > 0
|
54
|
-
values.sort!
|
55
|
-
percentiles.each do |pct|
|
56
|
-
idx = pct * (values.length - 1) + 1.0
|
57
|
-
if idx <= 1
|
58
|
-
scores[pct] = values[0]
|
59
|
-
elsif idx >= values.length
|
60
|
-
scores[pct] = values[values.length-1]
|
50
|
+
scores[pct] =
|
51
|
+
if @count == 0
|
52
|
+
0.0
|
61
53
|
else
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
54
|
+
index = pct * (sorted_values.length - 1) + 1.0
|
55
|
+
if index <= 1
|
56
|
+
sorted_values.first
|
57
|
+
elsif index >= sorted_values.length
|
58
|
+
sorted_values.last
|
59
|
+
else
|
60
|
+
lower = sorted_values[index.to_i - 1]
|
61
|
+
upper = sorted_values[index.to_i]
|
62
|
+
lower + (index - index.floor) * (upper - lower)
|
63
|
+
end
|
66
64
|
end
|
67
65
|
end
|
68
|
-
|
69
|
-
return scores
|
66
|
+
scores
|
70
67
|
end
|
71
68
|
|
72
69
|
def update_min(value)
|
73
|
-
if
|
70
|
+
if @min.nil? || value < @min
|
74
71
|
@min = value
|
75
72
|
end
|
76
73
|
end
|
77
74
|
|
78
75
|
def update_max(value)
|
79
|
-
if
|
76
|
+
if @max.nil? || value > @max
|
80
77
|
@max = value
|
81
78
|
end
|
82
79
|
end
|
83
80
|
|
84
81
|
def update_variance(value)
|
85
|
-
|
86
|
-
|
87
|
-
new_m = @variance_m + ((value - old_m) / count)
|
88
|
-
new_s = @variance_s + ((value - old_m) * (value - new_m))
|
82
|
+
new_m = @variance_m + ((value - @variance_m) / @count)
|
83
|
+
new_s = @variance_s + ((value - @variance_m) * (value - new_m))
|
89
84
|
|
90
85
|
@variance_m = new_m
|
91
86
|
@variance_s = new_s
|
92
87
|
end
|
93
88
|
|
94
89
|
def variance
|
95
|
-
count
|
96
|
-
|
97
|
-
|
98
|
-
if count <= 1
|
99
|
-
return 0.0
|
90
|
+
if @count <= 1
|
91
|
+
0.0
|
100
92
|
else
|
101
|
-
|
93
|
+
@variance_s.to_f / (@count - 1)
|
102
94
|
end
|
103
95
|
end
|
104
96
|
|
105
|
-
def count
|
106
|
-
count = @count
|
107
|
-
return count
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
97
|
def max
|
112
|
-
max
|
113
|
-
if max != nil
|
114
|
-
return max
|
115
|
-
else
|
116
|
-
return 0.0
|
117
|
-
end
|
98
|
+
@max || 0.0
|
118
99
|
end
|
119
100
|
|
120
101
|
def min
|
121
|
-
min
|
122
|
-
if min != nil
|
123
|
-
return min
|
124
|
-
else
|
125
|
-
return 0.0
|
126
|
-
end
|
102
|
+
@min || 0.0
|
127
103
|
end
|
128
104
|
|
129
105
|
def mean
|
130
|
-
count
|
131
|
-
|
132
|
-
|
133
|
-
if count > 0
|
134
|
-
return sum / count
|
106
|
+
if @count > 0
|
107
|
+
@sum / @count
|
135
108
|
else
|
136
|
-
|
109
|
+
0.0
|
137
110
|
end
|
138
111
|
end
|
139
112
|
|
140
113
|
def std_dev
|
141
|
-
count
|
142
|
-
|
143
|
-
|
144
|
-
if count > 0
|
145
|
-
return Math.sqrt(variance)
|
114
|
+
if @count > 0
|
115
|
+
Math.sqrt(variance)
|
146
116
|
else
|
147
|
-
|
117
|
+
0.0
|
148
118
|
end
|
149
119
|
end
|
150
120
|
|
@@ -154,11 +124,11 @@ module Metrics
|
|
154
124
|
|
155
125
|
def as_json(*_)
|
156
126
|
{
|
157
|
-
:min
|
158
|
-
:max
|
159
|
-
:mean
|
160
|
-
:variance
|
161
|
-
:percentiles =>
|
127
|
+
:min => min,
|
128
|
+
:max => max,
|
129
|
+
:mean => mean,
|
130
|
+
:variance => variance,
|
131
|
+
:percentiles => quantiles(Timer::DEFAULT_PERCENTILES)
|
162
132
|
}
|
163
133
|
end
|
164
134
|
|
@@ -1,42 +1,41 @@
|
|
1
|
+
require_relative 'instrument'
|
1
2
|
require 'ruby-metrics/time_units'
|
2
3
|
|
3
4
|
module Metrics
|
4
5
|
module Instruments
|
5
|
-
class Meter
|
6
|
+
class Meter < Instrument
|
6
7
|
include Metrics::TimeConversion
|
7
8
|
|
8
9
|
# From http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
attr_reader :count
|
16
|
-
attr_reader :units
|
10
|
+
INTERVAL_SECONDS = 5.0
|
11
|
+
ONE_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL_SECONDS / 60.0)
|
12
|
+
FIVE_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL_SECONDS / (60.0 * 5.0))
|
13
|
+
FIFTEEN_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL_SECONDS / (60.0 * 15.0))
|
14
|
+
|
15
|
+
attr_reader :count, :units
|
17
16
|
alias_method :counted, :count
|
18
17
|
|
19
18
|
def initialize(options = {})
|
20
|
-
@
|
21
|
-
|
22
|
-
@initialized = false
|
23
|
-
@start_time = Time.now.to_f
|
24
|
-
@units = "#{options[:units]}"
|
19
|
+
@units = options[:units]
|
20
|
+
clear
|
25
21
|
|
26
22
|
@timer_thread = Thread.new do
|
27
23
|
begin
|
28
24
|
loop do
|
29
|
-
|
30
|
-
sleep(
|
25
|
+
tick
|
26
|
+
sleep(INTERVAL_SECONDS)
|
31
27
|
end
|
32
28
|
rescue Exception => e
|
33
29
|
logger.error "Error in timer thread: #{e.class.name}: #{e}\n #{e.backtrace.join("\n ")}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
30
|
+
end
|
31
|
+
end
|
37
32
|
end
|
38
33
|
|
39
34
|
def clear
|
35
|
+
@one_minute_rate = @five_minute_rate = @fifteen_minute_rate = 0.0
|
36
|
+
@count = 0
|
37
|
+
@initialized = false
|
38
|
+
@start_time = Time.now.to_f
|
40
39
|
end
|
41
40
|
|
42
41
|
def mark(count = 1)
|
@@ -44,19 +43,18 @@ module Metrics
|
|
44
43
|
end
|
45
44
|
|
46
45
|
def calc_rate(rate, factor, count)
|
47
|
-
rate
|
48
|
-
rate.to_f
|
46
|
+
rate.to_f + factor.to_f * (count.to_f - rate.to_f)
|
49
47
|
end
|
50
48
|
|
51
49
|
def tick
|
52
|
-
count = @count.to_f
|
50
|
+
count = @count.to_f / Seconds.to_nsec(INTERVAL_SECONDS).to_f
|
53
51
|
|
54
|
-
if
|
52
|
+
if @initialized
|
55
53
|
@one_minute_rate = calc_rate(@one_minute_rate, ONE_MINUTE_FACTOR, count)
|
56
54
|
@five_minute_rate = calc_rate(@five_minute_rate, FIVE_MINUTE_FACTOR, count)
|
57
55
|
@fifteen_minute_rate = calc_rate(@fifteen_minute_rate, FIFTEEN_MINUTE_FACTOR, count)
|
58
56
|
else
|
59
|
-
@one_minute_rate = @five_minute_rate = @fifteen_minute_rate =
|
57
|
+
@one_minute_rate = @five_minute_rate = @fifteen_minute_rate = count
|
60
58
|
@initialized = true
|
61
59
|
end
|
62
60
|
|
@@ -76,21 +74,20 @@ module Metrics
|
|
76
74
|
end
|
77
75
|
|
78
76
|
def mean_rate(rate_unit = :seconds)
|
79
|
-
count
|
80
|
-
|
81
|
-
return 0.0
|
77
|
+
if @count == 0
|
78
|
+
0.0
|
82
79
|
else
|
83
80
|
elapsed = Time.now.to_f - @start_time.to_f
|
84
|
-
|
85
|
-
count.to_f / (
|
81
|
+
scale_factor = scale_time_units(:seconds, rate_unit)
|
82
|
+
@count.to_f / (elapsed * scale_factor)
|
86
83
|
end
|
87
84
|
end
|
88
85
|
|
89
86
|
def as_json(*_)
|
90
87
|
{
|
91
|
-
:one_minute_rate
|
92
|
-
:five_minute_rate
|
93
|
-
:fifteen_minute_rate
|
88
|
+
:one_minute_rate => one_minute_rate,
|
89
|
+
:five_minute_rate => five_minute_rate,
|
90
|
+
:fifteen_minute_rate => fifteen_minute_rate
|
94
91
|
}
|
95
92
|
end
|
96
93
|
|
@@ -1,12 +1,15 @@
|
|
1
|
-
|
1
|
+
require_relative 'instrument'
|
2
|
+
require 'ruby-metrics/time_units'
|
2
3
|
|
3
4
|
module Metrics
|
4
5
|
module Instruments
|
5
|
-
class Timer
|
6
|
+
class Timer < Instrument
|
6
7
|
include Metrics::TimeConversion
|
7
8
|
|
8
9
|
attr_reader :duration_unit, :rate_unit, :units
|
9
10
|
|
11
|
+
DEFAULT_PERCENTILES = [0.25, 0.50, 0.75, 0.95, 0.97, 0.98, 0.99]
|
12
|
+
|
10
13
|
def initialize(options = {})
|
11
14
|
@meter = Meter.new
|
12
15
|
@histogram = ExponentialHistogram.new
|
@@ -19,17 +22,17 @@ module Metrics
|
|
19
22
|
end
|
20
23
|
|
21
24
|
def clear
|
25
|
+
@meter.clear
|
22
26
|
@histogram.clear
|
23
27
|
end
|
24
28
|
|
25
29
|
def update(duration, unit)
|
26
|
-
|
27
|
-
self.update_timer(duration * mult)
|
30
|
+
update_timer(convert_to_ns(duration, unit))
|
28
31
|
end
|
29
32
|
|
30
|
-
def time
|
33
|
+
def time
|
31
34
|
start_time = Time.now.to_f
|
32
|
-
result =
|
35
|
+
result = yield
|
33
36
|
time_diff = Time.now.to_f - start_time
|
34
37
|
time_in_ns = convert_to_ns time_diff, :seconds
|
35
38
|
update_timer(time_in_ns)
|
@@ -72,24 +75,17 @@ module Metrics
|
|
72
75
|
scale_duration_to_ns @histogram.std_dev, @duration_unit
|
73
76
|
end
|
74
77
|
|
75
|
-
def quantiles(percentiles =
|
76
|
-
result
|
77
|
-
|
78
|
-
@histogram.quantiles(percentiles).each do |k,v|
|
78
|
+
def quantiles(percentiles = DEFAULT_PERCENTILES)
|
79
|
+
@histogram.quantiles(percentiles).inject({}) do |result, (k, v)|
|
79
80
|
result[k] = scale_duration_to_ns v, @duration_unit
|
81
|
+
result
|
80
82
|
end
|
81
|
-
|
82
|
-
result
|
83
83
|
end
|
84
84
|
|
85
85
|
def values
|
86
|
-
|
87
|
-
|
88
|
-
@histogram.values.each do |value|
|
89
|
-
result << (scale_duration_to_ns value, @duration_unit)
|
86
|
+
@histogram.values.map do |value|
|
87
|
+
scale_duration_to_ns value, @duration_unit
|
90
88
|
end
|
91
|
-
|
92
|
-
result
|
93
89
|
end
|
94
90
|
|
95
91
|
def update_timer(duration)
|
@@ -112,7 +108,7 @@ module Metrics
|
|
112
108
|
:min => min,
|
113
109
|
:max => max,
|
114
110
|
:mean => mean,
|
115
|
-
:percentiles => quantiles
|
111
|
+
:percentiles => quantiles,
|
116
112
|
:unit => @duration_unit
|
117
113
|
}
|
118
114
|
}
|
@@ -124,7 +120,7 @@ module Metrics
|
|
124
120
|
|
125
121
|
private
|
126
122
|
def scale_duration_to_ns(value, unit)
|
127
|
-
value.to_f / convert_to_ns(1, unit)
|
123
|
+
value.to_f / convert_to_ns(1.0, unit)
|
128
124
|
end
|
129
125
|
end
|
130
126
|
end
|
@@ -15,54 +15,41 @@ module Metrics
|
|
15
15
|
module Rack
|
16
16
|
class Middleware
|
17
17
|
|
18
|
-
attr_accessor :app, :options, :agent
|
19
|
-
|
20
|
-
:requests, :uncaught_exceptions,
|
21
|
-
:status_codes
|
22
|
-
|
18
|
+
attr_accessor :app, :options, :agent
|
19
|
+
|
23
20
|
def initialize(app, options ={})
|
24
21
|
@app = app
|
25
22
|
@options = {:show => "/stats"}.merge(options)
|
26
23
|
@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
24
|
end
|
41
|
-
|
25
|
+
|
42
26
|
def call(env)
|
43
27
|
return show(env) if show?(env)
|
44
28
|
|
45
29
|
env['metrics.agent'] = @agent
|
46
30
|
|
47
|
-
status, headers, body =
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
|
31
|
+
status, headers, body = time_request { @app.call(env) }
|
32
|
+
|
33
|
+
incr_status_code_counter(status / 100)
|
34
|
+
|
53
35
|
[status, headers, body]
|
54
36
|
rescue Exception
|
55
37
|
# TODO: add "last_uncaught_exception" with string of error
|
56
|
-
|
38
|
+
incr_uncaught_exceptions
|
57
39
|
raise
|
58
40
|
end
|
59
|
-
|
60
|
-
|
41
|
+
|
42
|
+
private
|
43
|
+
def show?(env, test = options[:show])
|
61
44
|
case
|
62
|
-
when String === test
|
63
|
-
|
64
|
-
when
|
65
|
-
|
45
|
+
when String === test
|
46
|
+
env['PATH_INFO'] == test
|
47
|
+
when Regexp === test
|
48
|
+
env['PATH_INFO'] =~ test
|
49
|
+
when test.respond_to?(:call)
|
50
|
+
test.call(env)
|
51
|
+
else
|
52
|
+
test
|
66
53
|
end
|
67
54
|
end
|
68
55
|
|
@@ -75,7 +62,18 @@ module Metrics
|
|
75
62
|
[body]
|
76
63
|
]
|
77
64
|
end
|
78
|
-
|
65
|
+
|
66
|
+
def time_request(&block)
|
67
|
+
@agent.timer(:_requests).time(&block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def incr_uncaught_exceptions
|
71
|
+
@agent.counter(:_uncaught_exceptions).incr
|
72
|
+
end
|
73
|
+
|
74
|
+
def incr_status_code_counter(hundred)
|
75
|
+
@agent.counter(:"_status_#{hundred}xx").incr
|
76
|
+
end
|
79
77
|
end
|
80
78
|
end
|
81
79
|
end
|
@@ -5,7 +5,12 @@ module Metrics
|
|
5
5
|
|
6
6
|
include Logging
|
7
7
|
|
8
|
+
def stop
|
9
|
+
@running = false
|
10
|
+
end
|
11
|
+
|
8
12
|
def initialize(options = {})
|
13
|
+
@running = true
|
9
14
|
|
10
15
|
if options[:agent] == nil
|
11
16
|
raise "Need an agent to report data from"
|
@@ -15,13 +20,13 @@ module Metrics
|
|
15
20
|
agent = options[:agent]
|
16
21
|
|
17
22
|
Thread.new {
|
18
|
-
while
|
23
|
+
while @running
|
19
24
|
agent.reporters.each do |name, service|
|
20
25
|
service.report(agent)
|
21
26
|
end
|
22
27
|
sleep delay
|
23
28
|
end
|
24
|
-
}
|
29
|
+
}
|
25
30
|
end
|
26
31
|
|
27
32
|
end
|