ruby-metrics 0.9.0 → 0.9.4
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 +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
|