ruby-metrics 0.5.0

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.
@@ -0,0 +1,155 @@
1
+ module Metrics
2
+ module Instruments
3
+ class Histogram < Base
4
+
5
+ def initialize
6
+ @count = 0
7
+ @sample = Metrics::Statistics::UniformSample.new
8
+ @min = nil
9
+ @max = nil
10
+ @sum = 0
11
+ @variance_s = 0
12
+ @variance_m = -1
13
+ end
14
+
15
+ def update(value)
16
+ @count += 1
17
+ @sum += value
18
+ @sample.update(value)
19
+ update_max(value)
20
+ update_min(value)
21
+ update_variance(value);
22
+ end
23
+
24
+ def clear
25
+ @sample.clear
26
+ @min = nil
27
+ @max = nil
28
+ @sum = 0
29
+ @count = 0
30
+ @variance_m = -1
31
+ @variance_s = 0
32
+ end
33
+
34
+ def quantiles(percentiles)
35
+ # Calculated using the same logic as R and Ecxel use
36
+ # as outlined by the NIST here: http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm
37
+ count = @count
38
+ scores = {}
39
+ values = @sample.values[0..count-1]
40
+
41
+ percentiles.each do |pct|
42
+ scores[pct] = 0.0
43
+ end
44
+
45
+ if count > 0
46
+ values.sort!
47
+ percentiles.each do |pct|
48
+ idx = pct * (values.length - 1) + 1.0
49
+ if idx <= 1
50
+ scores[pct] = values[0]
51
+ elsif idx >= values.length
52
+ scores[pct] = values[values.length-1]
53
+ else
54
+ lower = values[idx.to_i - 1]
55
+ upper = values[idx.to_i]
56
+ scores[pct] = lower + (idx - idx.floor) * (upper - lower)
57
+ end
58
+ end
59
+ end
60
+
61
+ return scores
62
+ end
63
+
64
+ def update_min(value)
65
+ if (@min == nil || value < @min)
66
+ @min = value
67
+ end
68
+ end
69
+
70
+ def update_max(value)
71
+ if (@max == nil || value > @max)
72
+ @max = value
73
+ end
74
+ end
75
+
76
+ def update_variance(value)
77
+ count = @count
78
+ old_m = @variance_m
79
+ new_m = @variance_m + ((value - old_m) / count)
80
+ new_s = @variance_s + ((value - old_m) * (value - new_m))
81
+
82
+ @variance_m = new_m
83
+ @variance_s = new_s
84
+ end
85
+
86
+ def variance
87
+ count = @count
88
+ variance_s = @variance_s
89
+
90
+ if count <= 1
91
+ return 0.0
92
+ else
93
+ return variance_s.to_f / (count - 1).to_i
94
+ end
95
+ end
96
+
97
+ def count
98
+ count = @count
99
+ return count
100
+ end
101
+
102
+
103
+ def max
104
+ max = @max
105
+ if max != nil
106
+ return max
107
+ else
108
+ return 0.0
109
+ end
110
+ end
111
+
112
+ def min
113
+ min = @min
114
+ if min != nil
115
+ return min
116
+ else
117
+ return 0.0
118
+ end
119
+ end
120
+
121
+ def mean
122
+ count = @count
123
+ sum = @sum
124
+
125
+ if count > 0
126
+ return sum / count
127
+ else
128
+ return 0.0
129
+ end
130
+ end
131
+
132
+ def std_dev
133
+ count = @count
134
+ variance = self.variance()
135
+
136
+ if count > 0
137
+ return Math.sqrt(variance)
138
+ else
139
+ return 0.0
140
+ end
141
+ end
142
+
143
+ def to_s
144
+ {
145
+ :min => self.min,
146
+ :max => self.max,
147
+ :mean => self.mean,
148
+ :variance => self.variance,
149
+ :percentiles => self.quantiles([0.25, 0.50, 0.75, 0.95, 0.97, 0.98, 0.99])
150
+ }.to_json
151
+ end
152
+
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,99 @@
1
+ require 'ruby-units'
2
+
3
+ module Metrics
4
+ module Instruments
5
+ class Meter < Base
6
+ # From http://www.teamquest.com/pdfs/whitepaper/ldavg2.pdf
7
+ INTERVAL = 5.0
8
+ INTERVAL_IN_NS = 5000000000.0
9
+ ONE_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL / 60.0)
10
+ FIVE_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL / (60.0 * 5.0))
11
+ FIFTEEN_MINUTE_FACTOR = 1 - Math.exp(-INTERVAL / (60.0 * 15.0))
12
+
13
+ attr_reader :counted, :uncounted
14
+
15
+ def initialize(options = {})
16
+ @one_minute_rate = @five_minute_rate = @fifteen_minute_rate = 0.0
17
+ @counted = @uncounted = 0
18
+ @initialized = false
19
+
20
+ if options[:interval]
21
+ interval = options[:interval]
22
+ else
23
+ interval = "5 seconds"
24
+ end
25
+
26
+ if options[:rateunit]
27
+ @rateunit = options[:rateunit]
28
+ else
29
+ @rateunit = interval
30
+ end
31
+
32
+ # HACK: this is here because ruby-units thinks 1s in ns is 999,999,999.9999999 not 1bn
33
+ # TODO: either fix ruby-units, or remove it?
34
+ @ratemultiplier = @rateunit.to("nanoseconds").scalar.ceil
35
+
36
+ @timer_thread = Thread.new do
37
+ sleep_time = interval.to("seconds").scalar
38
+ begin
39
+ loop do
40
+ self.tick
41
+ sleep(sleep_time)
42
+ end
43
+ rescue Exception => e
44
+ logger.error "Error in timer thread: #{e.class.name}: #{e}\n #{e.backtrace.join("\n ")}"
45
+ end # begin
46
+ end # thread new
47
+
48
+ end
49
+
50
+ def clear
51
+ end
52
+
53
+ def mark(count = 1)
54
+ @uncounted += count
55
+ @counted += count
56
+ end
57
+
58
+ def calc_rate(rate, factor, count)
59
+ rate = rate + (factor * (count - rate))
60
+ rate
61
+ end
62
+
63
+ def tick
64
+ count = @uncounted.to_f / INTERVAL_IN_NS.to_f
65
+
66
+ if (@initialized)
67
+ @one_minute_rate = calc_rate(@one_minute_rate, ONE_MINUTE_FACTOR, count)
68
+ @five_minute_rate = calc_rate(@five_minute_rate, FIVE_MINUTE_FACTOR, count)
69
+ @fifteen_minute_rate = calc_rate(@fifteen_minute_rate, FIFTEEN_MINUTE_FACTOR, count)
70
+ else
71
+ @one_minute_rate = @five_minute_rate = @fifteen_minute_rate = (count)
72
+ @initialized = true
73
+ end
74
+
75
+ @uncounted = 0
76
+ end
77
+
78
+ def one_minute_rate
79
+ @one_minute_rate * @ratemultiplier
80
+ end
81
+
82
+ def five_minute_rate
83
+ @five_minute_rate * @ratemultiplier
84
+ end
85
+
86
+ def fifteen_minute_rate
87
+ @fifteen_minute_rate * @ratemultiplier
88
+ end
89
+
90
+ def to_s
91
+ {
92
+ :one_minute_rate => self.one_minute_rate,
93
+ :five_minute_rate => self.five_minute_rate,
94
+ :fifteen_minute_rate => self.fifteen_minute_rate
95
+ }.to_json
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,21 @@
1
+ require 'logger'
2
+
3
+ module Metrics
4
+ module Logging
5
+
6
+ def self.included(target)
7
+ target.extend ClassMethods
8
+ end
9
+
10
+ def logger
11
+ self.class.logger
12
+ end
13
+
14
+ module ClassMethods
15
+ def logger
16
+ Metrics.logger
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Metrics
2
+ module Statistics
3
+ class Sample
4
+ def clear
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def size
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def update(value)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def values
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ module Metrics
2
+ module Statistics
3
+ class UniformSample < Sample
4
+
5
+ def initialize(size = 1028)
6
+ @values = Array.new(size)
7
+ @count = 0
8
+ @size = size
9
+ self.clear
10
+ end
11
+
12
+ def clear
13
+ (0..@values.length-1).each do |i|
14
+ @values[i] = 0
15
+ end
16
+ @count = 0
17
+ end
18
+
19
+ def size
20
+ @values.length
21
+ end
22
+
23
+ def update(value)
24
+ if @count < @values.length
25
+ @values[@count] = value
26
+ @count += 1
27
+ else
28
+ index = rand(@size) % @count
29
+ @values[index] = value
30
+ end
31
+ end
32
+
33
+ def values
34
+ @values.dup
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module Metrics
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ruby-metrics/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ruby-metrics"
7
+ s.version = Metrics::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["John Ewart"]
10
+ s.email = ["john@johnewart.net"]
11
+ s.homepage = "https://github.com/johnewart/ruby-metrics"
12
+ s.summary = %q{Metrics for Ruby}
13
+ s.description = %q{A Ruby implementation of metrics inspired by @coda's JVM metrics for those of us in Ruby land}
14
+
15
+ s.rubyforge_project = "ruby-metrics"
16
+
17
+ s.add_dependency "json"
18
+ s.add_dependency "ruby-units"
19
+
20
+ s.add_development_dependency "rspec"
21
+ s.add_development_dependency "simplecov", [">= 0.3.8"] #, :require => false
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Metrics::Agent do
4
+ before :each do
5
+ @agent = Metrics::Agent.new
6
+ end
7
+
8
+ it "should create a new agent" do
9
+ end
10
+
11
+ it "should add a counter instrument correctly" do
12
+ @counter = Metrics::Instruments::Counter.new
13
+ Metrics::Instruments::Counter.stub!(:new).and_return @counter
14
+ @agent.counter(:test_counter).should == @counter
15
+ end
16
+
17
+ it "should allow for creating a gauge with a block via #gauge" do
18
+ @agent.gauge :test_gauge do
19
+ "result"
20
+ end
21
+ end
22
+
23
+ it "should add a meter instrument correctly" do
24
+ @meter = Metrics::Instruments::Meter.new
25
+ Metrics::Instruments::Meter.stub!(:new).and_return @meter
26
+
27
+ @agent.meter(:test_meter).should == @meter
28
+ end
29
+
30
+ it "should start the WEBrick daemon" do
31
+ Thread.stub!(:new).and_return do |block|
32
+ block.call
33
+ end
34
+
35
+ mock_server = mock(WEBrick::HTTPServer)
36
+ WEBrick::HTTPServer.should_receive(:new).and_return mock_server
37
+ mock_server.should_receive(:mount)
38
+ mock_server.should_receive(:start)
39
+ @agent.start
40
+ end
41
+
42
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Metrics::Instruments::Base do
4
+ before(:each) do
5
+ @base = Metrics::Instruments::Base.new
6
+ end
7
+
8
+ %w( to_i to_f to_s ).each do |method|
9
+ it "should raise a NotImplementedError for ##{method}" do
10
+ lambda do
11
+ @base.send(method)
12
+ end.should raise_exception(NotImplementedError)
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Metrics::Instruments::Counter do
4
+ before(:each) do
5
+ @counter = Metrics::Instruments::Counter.new
6
+ end
7
+
8
+ it "should create a new entity with zero as its value" do
9
+ @counter.to_i.should == 0
10
+ end
11
+
12
+ it "should increment its counter by the value specified" do
13
+ value = 1
14
+ lambda do
15
+ @counter.inc(value)
16
+ end.should change{ @counter.to_i }.by(value)
17
+ end
18
+
19
+ it "should increment its counter by one by default" do
20
+ lambda do
21
+ @counter.inc
22
+ end.should change{ @counter.to_i }.by(1)
23
+ end
24
+
25
+ it "should decrement its counter by the value specified" do
26
+ value = 1
27
+ lambda do
28
+ @counter.dec(value)
29
+ end.should change{ @counter.to_i }.by(-value)
30
+ end
31
+
32
+ it "should decrement its counter by one by default" do
33
+ lambda do
34
+ @counter.dec
35
+ end.should change{ @counter.to_i }.by(-1)
36
+ end
37
+
38
+ it "should alias #incr to #inc" do
39
+ lambda do
40
+ @counter.incr
41
+ end.should change{ @counter.to_i }.by(1)
42
+ end
43
+
44
+ it "should alias #decr to #dec" do
45
+ lambda do
46
+ @counter.decr
47
+ end.should change{ @counter.to_i }.by(-1)
48
+ end
49
+
50
+ it "should clear the counter correctly" do
51
+ @counter.clear
52
+ @counter.to_i.should == 0
53
+ end
54
+
55
+ it "should correctly represent the value as a string" do
56
+ @counter.clear
57
+ @counter.to_i.should == 0
58
+ @counter.to_s.should == "0"
59
+ end
60
+
61
+ it "should return the new count when incrementing" do
62
+ count = @counter.to_i
63
+ @counter.inc(value = 1).should == count + value
64
+ end
65
+
66
+ it "should return the new count when decrementing" do
67
+ lambda do
68
+ @counter.dec(1)
69
+ end.should change{ @counter.to_i }.by(-1)
70
+ end
71
+
72
+ end