ruby-metrics 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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