yam-ruby-metrics 0.8.6

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.
Files changed (49) hide show
  1. data/.bundle/config +2 -0
  2. data/.gitignore +4 -0
  3. data/.rvmrc +1 -0
  4. data/CHANGELOG.md +48 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +36 -0
  7. data/LICENSE +21 -0
  8. data/README.md +86 -0
  9. data/Rakefile +19 -0
  10. data/autotest/discover.rb +3 -0
  11. data/examples/counter.rb +10 -0
  12. data/examples/gauge.rb +25 -0
  13. data/examples/histogram.rb +32 -0
  14. data/examples/integration/rack_endpoint.ru +24 -0
  15. data/examples/integration/rack_middleware.ru +21 -0
  16. data/examples/integration/webrick.rb +17 -0
  17. data/examples/meter.rb +25 -0
  18. data/examples/timer.rb +31 -0
  19. data/lib/ruby-metrics.rb +15 -0
  20. data/lib/ruby-metrics/agent.rb +62 -0
  21. data/lib/ruby-metrics/instruments/counter.rb +39 -0
  22. data/lib/ruby-metrics/instruments/gauge.rb +23 -0
  23. data/lib/ruby-metrics/instruments/histogram.rb +188 -0
  24. data/lib/ruby-metrics/instruments/meter.rb +99 -0
  25. data/lib/ruby-metrics/instruments/timer.rb +138 -0
  26. data/lib/ruby-metrics/integration.rb +11 -0
  27. data/lib/ruby-metrics/integration/rack_endpoint.rb +33 -0
  28. data/lib/ruby-metrics/integration/rack_middleware.rb +82 -0
  29. data/lib/ruby-metrics/integration/webrick.rb +47 -0
  30. data/lib/ruby-metrics/logging.rb +19 -0
  31. data/lib/ruby-metrics/statistics/exponential_sample.rb +91 -0
  32. data/lib/ruby-metrics/statistics/uniform_sample.rb +37 -0
  33. data/lib/ruby-metrics/time_units.rb +61 -0
  34. data/lib/ruby-metrics/version.rb +3 -0
  35. data/ruby-metrics.gemspec +27 -0
  36. data/spec/agent_spec.rb +48 -0
  37. data/spec/instruments/counter_spec.rb +79 -0
  38. data/spec/instruments/gauge_spec.rb +42 -0
  39. data/spec/instruments/histogram_spec.rb +115 -0
  40. data/spec/instruments/meter_spec.rb +99 -0
  41. data/spec/instruments/timer_spec.rb +146 -0
  42. data/spec/integration/rack_endpoint_spec.rb +60 -0
  43. data/spec/integration/rack_middleware_spec.rb +129 -0
  44. data/spec/integration/webrick_spec.rb +18 -0
  45. data/spec/spec_helper.rb +18 -0
  46. data/spec/statistics/exponential_sample_spec.rb +138 -0
  47. data/spec/statistics/uniform_sample_spec.rb +59 -0
  48. data/spec/time_units_spec.rb +13 -0
  49. metadata +184 -0
@@ -0,0 +1,15 @@
1
+ # == Metrics Initialization
2
+ #
3
+
4
+ require 'logger'
5
+
6
+ module Metrics
7
+ class << self
8
+ attr_writer :logger
9
+ def logger
10
+ @logger ||= Logger.new(STDOUT)
11
+ end
12
+ end
13
+ end
14
+
15
+ require 'ruby-metrics/agent'
@@ -0,0 +1,62 @@
1
+ require 'ruby-metrics/logging'
2
+
3
+ require 'ruby-metrics/time_units'
4
+
5
+ require 'ruby-metrics/instruments/counter'
6
+ require 'ruby-metrics/instruments/meter'
7
+ require 'ruby-metrics/instruments/gauge'
8
+ require 'ruby-metrics/instruments/histogram'
9
+ require 'ruby-metrics/instruments/timer'
10
+
11
+ require 'ruby-metrics/integration'
12
+
13
+ require 'json'
14
+
15
+ module Metrics
16
+ class Agent
17
+ include Logging
18
+
19
+ attr_reader :instruments
20
+
21
+ def initialize(options = {})
22
+ @instruments = {}
23
+ end
24
+
25
+ alias_method :registered, :instruments
26
+
27
+ def counter(name)
28
+ @instruments[name] ||= Instruments::Counter.new
29
+ end
30
+
31
+ def meter(name)
32
+ @instruments[name] ||= Instruments::Meter.new
33
+ end
34
+
35
+ def gauge(name, &block)
36
+ @instruments[name] ||= Instruments::Gauge.new(&block)
37
+ end
38
+
39
+ def timer(name, options = {})
40
+ @instruments[name] ||= Instruments::Timer.new(options)
41
+ end
42
+
43
+ def uniform_histogram(name)
44
+ @instruments[name] ||= Instruments::UniformHistogram.new
45
+ end
46
+
47
+ # For backwards compatibility
48
+ alias_method :histogram, :uniform_histogram
49
+
50
+ def exponential_histogram(name)
51
+ @instruments[name] ||= Instruments::ExponentialHistogram.new
52
+ end
53
+
54
+ def as_json(*_)
55
+ @instruments
56
+ end
57
+
58
+ def to_json(*_)
59
+ as_json.to_json
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ module Metrics
2
+ module Instruments
3
+ class Counter
4
+ def initialize
5
+ @value = 0
6
+ end
7
+
8
+ def inc(value = 1)
9
+ @value += value
10
+ end
11
+ alias_method :incr, :inc
12
+
13
+ def dec(value = 1)
14
+ @value -= value
15
+ end
16
+ alias_method :decr, :dec
17
+
18
+ def clear
19
+ @value = 0
20
+ end
21
+
22
+ def to_i
23
+ @value.to_i
24
+ end
25
+
26
+ def to_s
27
+ @value.to_s
28
+ end
29
+
30
+ def as_json(*_)
31
+ @value
32
+ end
33
+
34
+ def to_json(*_)
35
+ as_json.to_json
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ module Metrics
2
+ module Instruments
3
+ class Gauge
4
+ def initialize(&block)
5
+ raise ArgumentError, "a block is required" unless block_given?
6
+ @block = block
7
+ end
8
+
9
+ def get
10
+ instance_exec(&@block)
11
+ end
12
+
13
+ def as_json(*_)
14
+ value = get
15
+ value.respond_to?(:as_json) ? value.as_json : value
16
+ end
17
+
18
+ def to_json(*_)
19
+ as_json.to_json
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,188 @@
1
+ require 'ruby-metrics/statistics/uniform_sample'
2
+ require 'ruby-metrics/statistics/exponential_sample'
3
+
4
+ module Metrics
5
+ module Instruments
6
+ class Histogram
7
+
8
+ def initialize(type = :uniform)
9
+ @count = 0
10
+ case type
11
+ when :uniform
12
+ @sample = Metrics::Statistics::UniformSample.new
13
+ when :exponential
14
+ @sample = Metrics::Statistics::ExponentialSample.new
15
+ end
16
+ @min = nil
17
+ @max = nil
18
+ @sum = 0
19
+ @variance_s = 0
20
+ @variance_m = -1
21
+ end
22
+
23
+ def update(value)
24
+ @count += 1
25
+ @sum += value
26
+ @sample.update(value)
27
+ update_max(value)
28
+ update_min(value)
29
+ update_variance(value);
30
+ end
31
+
32
+ def clear
33
+ @sample.clear
34
+ @min = nil
35
+ @max = nil
36
+ @sum = 0
37
+ @count = 0
38
+ @variance_m = -1
39
+ @variance_s = 0
40
+ end
41
+
42
+ def quantiles(percentiles)
43
+ # Calculated using the same logic as R and Excel use
44
+ # 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
+
49
+ percentiles.each do |pct|
50
+ scores[pct] = 0.0
51
+ end
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]
61
+ else
62
+ lower = values[idx.to_i - 1]
63
+ upper = values[idx.to_i]
64
+ scores[pct] = lower + (idx - idx.floor) * (upper - lower)
65
+ end
66
+ end
67
+ end
68
+
69
+ return scores
70
+ end
71
+
72
+ def update_min(value)
73
+ if (@min == nil || value < @min)
74
+ @min = value
75
+ end
76
+ end
77
+
78
+ def update_max(value)
79
+ if (@max == nil || value > @max)
80
+ @max = value
81
+ end
82
+ end
83
+
84
+ def update_variance(value)
85
+ count = @count
86
+ old_m = @variance_m
87
+ new_m = @variance_m + ((value - old_m) / count)
88
+ new_s = @variance_s + ((value - old_m) * (value - new_m))
89
+
90
+ @variance_m = new_m
91
+ @variance_s = new_s
92
+ end
93
+
94
+ def variance
95
+ count = @count
96
+ variance_s = @variance_s
97
+
98
+ if count <= 1
99
+ return 0.0
100
+ else
101
+ return variance_s.to_f / (count - 1).to_i
102
+ end
103
+ end
104
+
105
+ def count
106
+ count = @count
107
+ return count
108
+ end
109
+
110
+
111
+ def max
112
+ max = @max
113
+ if max != nil
114
+ return max
115
+ else
116
+ return 0.0
117
+ end
118
+ end
119
+
120
+ def min
121
+ min = @min
122
+ if min != nil
123
+ return min
124
+ else
125
+ return 0.0
126
+ end
127
+ end
128
+
129
+ def mean
130
+ count = @count
131
+ sum = @sum
132
+
133
+ if count > 0
134
+ return sum / count
135
+ else
136
+ return 0.0
137
+ end
138
+ end
139
+
140
+ def std_dev
141
+ count = @count
142
+ variance = self.variance()
143
+
144
+ if count > 0
145
+ return Math.sqrt(variance)
146
+ else
147
+ return 0.0
148
+ end
149
+ end
150
+
151
+ def values
152
+ @sample.values
153
+ end
154
+
155
+ def as_json(*_)
156
+ percentiles = self.quantiles([0.5, 0.75, 0.95, 0.98, 0.99, 0.999])
157
+ {
158
+ :min => self.min,
159
+ :max => self.max,
160
+ :mean => self.mean,
161
+ :median => percentiles['0.5'],
162
+ :std_dev => self.std_dev,
163
+ :p75 => percentiles['0.75'],
164
+ :p95 => percentiles['0.95'],
165
+ :p98 => percentiles['0.98'],
166
+ :p99 => percentiles['0.99'],
167
+ :p999 => percentiles['0.999']
168
+ }
169
+ end
170
+
171
+ def to_json(*_)
172
+ as_json.to_json
173
+ end
174
+ end
175
+
176
+ class ExponentialHistogram < Histogram
177
+ def initialize
178
+ super(:exponential)
179
+ end
180
+ end
181
+
182
+ class UniformHistogram < Histogram
183
+ def initialize
184
+ super(:uniform)
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,99 @@
1
+ require 'ruby-metrics/time_units'
2
+
3
+ module Metrics
4
+ module Instruments
5
+ class Meter
6
+ include Metrics::TimeConversion
7
+
8
+ # From http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf
9
+ INTERVAL = 5.0
10
+ INTERVAL_IN_NS = 5000000000.0
11
+ ONE_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL / 60.0)
12
+ FIVE_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL / (60.0 * 5.0))
13
+ FIFTEEN_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL / (60.0 * 15.0))
14
+
15
+ attr_reader :count
16
+ alias_method :counted, :count
17
+
18
+ def initialize(options = {})
19
+ @one_minute_rate = @five_minute_rate = @fifteen_minute_rate = 0.0
20
+ @count = 0
21
+ @initialized = false
22
+ @start_time = Time.now.to_f
23
+
24
+ @timer_thread = Thread.new do
25
+ begin
26
+ loop do
27
+ self.tick
28
+ sleep(INTERVAL)
29
+ end
30
+ rescue Exception => e
31
+ logger.error "Error in timer thread: #{e.class.name}: #{e}\n #{e.backtrace.join("\n ")}"
32
+ end # begin
33
+ end # thread new
34
+
35
+ end
36
+
37
+ def clear
38
+ end
39
+
40
+ def mark(count = 1)
41
+ @count += count
42
+ end
43
+
44
+ def calc_rate(rate, factor, count)
45
+ rate = rate.to_f + (factor.to_f * (count.to_f - rate.to_f))
46
+ rate.to_f
47
+ end
48
+
49
+ def tick
50
+ count = @count.to_f / Seconds.to_nsec(INTERVAL).to_f
51
+
52
+ if (@initialized)
53
+ @one_minute_rate = calc_rate(@one_minute_rate, ONE_MINUTE_FACTOR, count)
54
+ @five_minute_rate = calc_rate(@five_minute_rate, FIVE_MINUTE_FACTOR, count)
55
+ @fifteen_minute_rate = calc_rate(@fifteen_minute_rate, FIFTEEN_MINUTE_FACTOR, count)
56
+ else
57
+ @one_minute_rate = @five_minute_rate = @fifteen_minute_rate = (count)
58
+ @initialized = true
59
+ end
60
+
61
+ @count = 0
62
+ end
63
+
64
+ def one_minute_rate(rate_unit = :seconds)
65
+ convert_to_ns @one_minute_rate, rate_unit
66
+ end
67
+
68
+ def five_minute_rate(rate_unit = :seconds)
69
+ convert_to_ns @five_minute_rate, rate_unit
70
+ end
71
+
72
+ def fifteen_minute_rate(rate_unit = :seconds)
73
+ convert_to_ns @fifteen_minute_rate, rate_unit
74
+ end
75
+
76
+ def mean_rate(rate_unit = :seconds)
77
+ count = @count
78
+ if count == 0
79
+ return 0.0;
80
+ else
81
+ elapsed = Time.now.to_f - @start_time.to_f
82
+ convert_to_ns (count.to_f / elapsed.to_f), rate_unit
83
+ end
84
+ end
85
+
86
+ def as_json(*_)
87
+ {
88
+ :one_minute_rate => self.one_minute_rate,
89
+ :five_minute_rate => self.five_minute_rate,
90
+ :fifteen_minute_rate => self.fifteen_minute_rate
91
+ }
92
+ end
93
+
94
+ def to_json(*_)
95
+ as_json.to_json
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,138 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'time_units')
2
+
3
+ module Metrics
4
+ module Instruments
5
+ class Timer
6
+ include Metrics::TimeConversion
7
+
8
+ attr_reader :duration_unit, :rate_unit
9
+
10
+ def initialize(options = {})
11
+ @meter = Meter.new
12
+ @histogram = ExponentialHistogram.new
13
+
14
+ @duration_unit = options[:duration_unit] || :seconds
15
+ @rate_unit = options[:rate_unit] || :seconds
16
+
17
+ clear
18
+ end
19
+
20
+ def clear
21
+ @histogram.clear
22
+ end
23
+
24
+ def update(duration, unit)
25
+ mult = convert_to_ns(1, unit)
26
+ self.update_timer(duration * mult)
27
+ end
28
+
29
+ def time(&block)
30
+ start_time = Time.now.to_f
31
+ result = block.call
32
+ time_diff = Time.now.to_f - start_time
33
+ time_in_ns = convert_to_ns time_diff, :seconds
34
+ update_timer(time_in_ns)
35
+ result
36
+ end
37
+
38
+ def count
39
+ @histogram.count
40
+ end
41
+
42
+ def fifteen_minute_rate
43
+ @meter.fifteen_minute_rate(@rate_unit)
44
+ end
45
+
46
+ def five_minute_rate
47
+ @meter.five_minute_rate(@rate_unit)
48
+ end
49
+
50
+ def one_minute_rate
51
+ @meter.one_minute_rate(@rate_unit)
52
+ end
53
+
54
+ def mean_rate
55
+ @meter.mean_rate(@rate_unit)
56
+ end
57
+
58
+ def max
59
+ scale_duration_to_ns @histogram.max, @duration_unit
60
+ end
61
+
62
+ def min
63
+ scale_duration_to_ns @histogram.min, @duration_unit
64
+ end
65
+
66
+ def mean
67
+ scale_duration_to_ns @histogram.mean, @duration_unit
68
+ end
69
+
70
+ def std_dev
71
+ scale_duration_to_ns @histogram.std_dev, @duration_unit
72
+ end
73
+
74
+ def quantiles(percentiles = [0.99,0.97,0.95,0.75,0.5,0.25])
75
+ result = {}
76
+
77
+ @histogram.quantiles(percentiles).each do |k,v|
78
+ result[k] = scale_duration_to_ns v, @duration_unit
79
+ end
80
+
81
+ result
82
+ end
83
+
84
+ def values
85
+ result = []
86
+
87
+ @histogram.values.each do |value|
88
+ result << (scale_duration_to_ns value, @duration_unit)
89
+ end
90
+
91
+ result
92
+ end
93
+
94
+ def update_timer(duration)
95
+ if duration >= 0
96
+ @histogram.update(duration)
97
+ @meter.mark
98
+ end
99
+ end
100
+
101
+ def as_json(*_)
102
+ percentiles = self.quantiles([0.5, 0.75, 0.95, 0.98, 0.99, 0.999])
103
+ {
104
+ :rates => {
105
+ :count => count,
106
+ :m1 => one_minute_rate,
107
+ :m5 => five_minute_rate,
108
+ :m15 => fifteen_minute_rate,
109
+ :mean => mean_rate,
110
+ :unit => @rate_unit
111
+ },
112
+ :duration => {
113
+ :min => self.min,
114
+ :max => self.max,
115
+ :mean => self.mean,
116
+ :median => percentiles['0.5'],
117
+ :std_dev => self.std_dev,
118
+ :p75 => percentiles['0.75'],
119
+ :p95 => percentiles['0.95'],
120
+ :p98 => percentiles['0.98'],
121
+ :p99 => percentiles['0.99'],
122
+ :p999 => percentiles['0.999'],
123
+ :unit => @duration_unit
124
+ }
125
+ }
126
+ end
127
+
128
+ def to_json(*_)
129
+ as_json.to_json
130
+ end
131
+
132
+ private
133
+ def scale_duration_to_ns(value, unit)
134
+ value.to_f / convert_to_ns(1, unit).to_f
135
+ end
136
+ end
137
+ end
138
+ end