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 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
- #sh "ls pkg/*.gem | xargs -n 1 gem push"
24
+ sh "ls pkg/*.gem | xargs -n 1 gem push"
25
25
  end
26
26
 
27
27
  RSpec::Core::RakeTask.new do |t|
@@ -67,6 +67,10 @@ module Metrics
67
67
  @reporter = Reporter.new({:agent => self, :delay => delay})
68
68
  end
69
69
 
70
+ def stop_reporting
71
+ @reporter.stop
72
+ end
73
+
70
74
  def as_json(*_)
71
75
  @instruments
72
76
  end
@@ -1,11 +1,14 @@
1
+ require_relative 'instrument'
2
+
1
3
  module Metrics
2
4
  module Instruments
3
- class Counter
5
+ class Counter < Instrument
4
6
 
5
7
  attr_reader :units
6
8
 
7
9
  def initialize(options = {})
8
10
  @value = 0
11
+ @units = options[:units]
9
12
  end
10
13
 
11
14
  def inc(value = 1)
@@ -1,6 +1,8 @@
1
+ require_relative 'instrument'
2
+
1
3
  module Metrics
2
4
  module Instruments
3
- class Gauge
5
+ class Gauge < Instrument
4
6
  attr_reader :units
5
7
 
6
8
  def initialize(options = {}, &block)
@@ -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
- @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
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 Ecxel use
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] = 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]
50
+ scores[pct] =
51
+ if @count == 0
52
+ 0.0
61
53
  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
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 (@min == nil || value < @min)
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 (@max == nil || value > @max)
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
- 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))
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 = @count
96
- variance_s = @variance_s
97
-
98
- if count <= 1
99
- return 0.0
90
+ if @count <= 1
91
+ 0.0
100
92
  else
101
- return variance_s.to_f / (count - 1).to_i
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 = @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 = @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 = @count
131
- sum = @sum
132
-
133
- if count > 0
134
- return sum / count
106
+ if @count > 0
107
+ @sum / @count
135
108
  else
136
- return 0.0
109
+ 0.0
137
110
  end
138
111
  end
139
112
 
140
113
  def std_dev
141
- count = @count
142
- variance = self.variance()
143
-
144
- if count > 0
145
- return Math.sqrt(variance)
114
+ if @count > 0
115
+ Math.sqrt(variance)
146
116
  else
147
- return 0.0
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 => self.min,
158
- :max => self.max,
159
- :mean => self.mean,
160
- :variance => self.variance,
161
- :percentiles => self.quantiles([0.25, 0.50, 0.75, 0.95, 0.97, 0.98, 0.99])
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
 
@@ -0,0 +1,13 @@
1
+ module Metrics
2
+ module Instruments
3
+ class Instrument
4
+ def tags
5
+ @tags ||= {}
6
+ end
7
+
8
+ def tag(key, value)
9
+ tags[key] = value
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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
- 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
- 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
- @one_minute_rate = @five_minute_rate = @fifteen_minute_rate = 0.0
21
- @count = 0
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
- self.tick
30
- sleep(INTERVAL)
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 # begin
35
- end # thread new
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 = rate.to_f + (factor.to_f * (count.to_f - rate.to_f))
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 / Seconds.to_nsec(INTERVAL).to_f
50
+ count = @count.to_f / Seconds.to_nsec(INTERVAL_SECONDS).to_f
53
51
 
54
- if (@initialized)
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 = (count)
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 = @count
80
- if count == 0
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
- mult = scale_time_units(:seconds, rate_unit)
85
- count.to_f / (mult * elapsed.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 => self.one_minute_rate,
92
- :five_minute_rate => self.five_minute_rate,
93
- :fifteen_minute_rate => self.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
- require File.join(File.dirname(__FILE__), '..', 'time_units')
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
- mult = convert_to_ns(1, unit)
27
- self.update_timer(duration * mult)
30
+ update_timer(convert_to_ns(duration, unit))
28
31
  end
29
32
 
30
- def time(&block)
33
+ def time
31
34
  start_time = Time.now.to_f
32
- result = block.call
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 = [0.99,0.97,0.95,0.75,0.5,0.25])
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
- result = []
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([0.25, 0.50, 0.75, 0.95, 0.97, 0.98, 0.99]),
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).to_f
123
+ value.to_f / convert_to_ns(1.0, unit)
128
124
  end
129
125
  end
130
126
  end
@@ -12,7 +12,7 @@ module Metrics
12
12
  :requests, :uncaught_exceptions,
13
13
  :status_codes
14
14
 
15
- def initialize(options ={})
15
+ def initialize(options = {})
16
16
  @options = options
17
17
  @agent = @options.delete(:agent) || Agent.new
18
18
  end
@@ -15,54 +15,41 @@ module Metrics
15
15
  module Rack
16
16
  class Middleware
17
17
 
18
- attr_accessor :app, :options, :agent,
19
- # integration metrics
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 = self.requests.time{ @app.call(env) }
48
-
49
- if status_counter = self.status_codes[status / 100]
50
- status_counter.incr
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
- self.uncaught_exceptions.incr
38
+ incr_uncaught_exceptions
57
39
  raise
58
40
  end
59
-
60
- def show?(env, test = self.options[:show])
41
+
42
+ private
43
+ def show?(env, test = options[:show])
61
44
  case
62
- when String === test; env['PATH_INFO'] == test
63
- when Regexp === test; env['PATH_INFO'] =~ test
64
- when test.respond_to?(:call); test.call(env)
65
- else test
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(true)
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
- }.join
29
+ }
25
30
  end
26
31
 
27
32
  end