fozzie 0.0.15 → 0.0.16
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.
- data/Guardfile +3 -0
- data/doc/lib/core_ext/hash.html +79 -0
- data/doc/lib/fozzie.html +148 -0
- data/doc/lib/fozzie/configuration.html +174 -0
- data/doc/lib/fozzie/interface.html +288 -0
- data/doc/lib/fozzie/rack/middleware.html +97 -0
- data/doc/lib/fozzie/rails/middleware.html +97 -0
- data/doc/lib/fozzie/socket.html +123 -0
- data/doc/lib/fozzie/version.html +36 -0
- data/fozzie.gemspec +6 -0
- data/lib/fozzie.rb +52 -10
- data/lib/fozzie/configuration.rb +16 -32
- data/lib/fozzie/interface.rb +121 -0
- data/lib/fozzie/rack/middleware.rb +3 -0
- data/lib/fozzie/rails/middleware.rb +4 -0
- data/lib/fozzie/socket.rb +44 -0
- data/lib/fozzie/version.rb +1 -1
- data/spec/lib/fozzie/configuration_spec.rb +9 -42
- data/spec/lib/fozzie/interface_spec.rb +136 -0
- data/spec/lib/fozzie/rack/middleware_spec.rb +3 -5
- data/spec/lib/fozzie/rails/middleware_spec.rb +35 -29
- data/spec/lib/fozzie_spec.rb +6 -138
- metadata +69 -28
- data/lib/fozzie/classes.rb +0 -146
- data/lib/fozzie/config.rb +0 -19
- data/spec/lib/fozzie/config_spec.rb +0 -24
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'fozzie/socket'
|
3
|
+
|
4
|
+
module Fozzie
|
5
|
+
class Interface
|
6
|
+
include Fozzie::Socket, Singleton
|
7
|
+
|
8
|
+
# Increments the given stat by one, with an optional sample rate
|
9
|
+
#
|
10
|
+
# `Stats.increment 'wat'`
|
11
|
+
def increment(stat, sample_rate=1)
|
12
|
+
count(stat, 1, sample_rate)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Decrements the given stat by one, with an optional sample rate
|
16
|
+
#
|
17
|
+
# `Stats.decrement 'wat'`
|
18
|
+
def decrement(stat, sample_rate=1)
|
19
|
+
count(stat, -1, sample_rate)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Registers a count for the given stat, with an optional sample rate
|
23
|
+
#
|
24
|
+
# `Stats.count 'wat', 500`
|
25
|
+
def count(stat, count, sample_rate=1)
|
26
|
+
send(stat, count, 'c', sample_rate)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Registers a timing (in ms) for the given stat, with an optional sample rate
|
30
|
+
#
|
31
|
+
# `Stats.timing 'wat', 500`
|
32
|
+
def timing(stat, ms, sample_rate=1)
|
33
|
+
send(stat, ms, 'ms', sample_rate)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Registers the time taken to complete a given block (in ms), with an optional sample rate
|
37
|
+
#
|
38
|
+
# `Stats.time 'wat' { # Do something... }`
|
39
|
+
def time(stat, sample_rate=1)
|
40
|
+
stat = stat.flatten.join('.') if stat.kind_of?(Array)
|
41
|
+
start = Time.now
|
42
|
+
result = yield
|
43
|
+
timing(stat, ((Time.now - start) * 1000).round, sample_rate)
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
# Registers the time taken to complete a given block (in ms), with an optional sample rate
|
48
|
+
#
|
49
|
+
# `Stats.time_to_do 'wat' { # Do something, again... }`
|
50
|
+
def time_to_do(stat, sample_rate=1, &block)
|
51
|
+
time_for(stat, sample_rate, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Registers the time taken to complete a given block (in ms), with an optional sample rate
|
55
|
+
#
|
56
|
+
# `Stats.time_for 'wat' { # Do something, grrr... }`
|
57
|
+
def time_for(stat, sample_rate=1, &block)
|
58
|
+
time(stat, sample_rate, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Registers a commit
|
62
|
+
#
|
63
|
+
# `Stats.commit`
|
64
|
+
def commit
|
65
|
+
event :commit
|
66
|
+
end
|
67
|
+
|
68
|
+
# Registers a commit
|
69
|
+
#
|
70
|
+
# `Stats.commit`
|
71
|
+
def committed
|
72
|
+
commit
|
73
|
+
end
|
74
|
+
|
75
|
+
# Registers that the app has been built
|
76
|
+
#
|
77
|
+
# `Stats.built`
|
78
|
+
def built
|
79
|
+
event :build
|
80
|
+
end
|
81
|
+
|
82
|
+
# Registers a build for the app
|
83
|
+
#
|
84
|
+
# `Stats.build`
|
85
|
+
def build
|
86
|
+
built
|
87
|
+
end
|
88
|
+
|
89
|
+
# Registers a deployed status for the given app
|
90
|
+
#
|
91
|
+
# `Stats.deployed 'watapp'`
|
92
|
+
def deployed(app = nil)
|
93
|
+
event :deploy, app
|
94
|
+
end
|
95
|
+
|
96
|
+
# Registers a deployment for the given app
|
97
|
+
#
|
98
|
+
# `Stats.deploy 'watapp'`
|
99
|
+
def deploy(app = nil)
|
100
|
+
deployed(app)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Registers an increment on the result of the given boolean
|
104
|
+
#
|
105
|
+
# `Stats.increment_on 'wat', wat.random?`
|
106
|
+
def increment_on(stat, perf, sample_rate=1)
|
107
|
+
key = "#{stat}.%s" % (perf ? "success" : "fail")
|
108
|
+
increment(key, sample_rate)
|
109
|
+
perf
|
110
|
+
end
|
111
|
+
|
112
|
+
# Register an event of any type
|
113
|
+
#
|
114
|
+
# `Stats.event 'wat', 'app'`
|
115
|
+
def event(type, app = nil)
|
116
|
+
stat = ["event", type.to_s, app].compact.join('.')
|
117
|
+
timing stat, Time.now.usec
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -2,8 +2,12 @@ require 'fozzie/rack/middleware'
|
|
2
2
|
|
3
3
|
module Fozzie
|
4
4
|
module Rails
|
5
|
+
|
6
|
+
# Time and record each request through a given Rails app
|
7
|
+
# This middlewware times server processing for a resource, not view render.
|
5
8
|
class Middleware < Fozzie::Rack::Middleware
|
6
9
|
|
10
|
+
# Generates the statistics key for the current path
|
7
11
|
def generate_key(env)
|
8
12
|
path_str = env['PATH_INFO']
|
9
13
|
return nil unless path_str
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Fozzie
|
2
|
+
module Socket
|
3
|
+
|
4
|
+
RESERVED_CHARS_REGEX = /[\:\|\@]/
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# Send the statistic to the server
|
9
|
+
def send(stat, delta, type, sample_rate)
|
10
|
+
prefix = "%s." % Fozzie.c.data_prefix unless Fozzie.c.data_prefix.nil?
|
11
|
+
stat = stat.to_s.gsub('::', '.').gsub(RESERVED_CHARS_REGEX, '_')
|
12
|
+
|
13
|
+
k = "%s%s:%s|%s" % [prefix, stat, delta, type]
|
14
|
+
k << '|@%s' % sample_rate.to_s if sample_rate < 1
|
15
|
+
|
16
|
+
sampled(sample_rate) { send_to_socket(k.strip) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# If the statistic is sampled, generate a condition to check if it's good to send
|
20
|
+
def sampled(sample_rate)
|
21
|
+
yield unless sample_rate < 1 and rand > sample_rate
|
22
|
+
end
|
23
|
+
|
24
|
+
# Send data to the server via the socket
|
25
|
+
def send_to_socket(message)
|
26
|
+
begin
|
27
|
+
Fozzie.logger.debug {"Statsd: #{message}"} if Fozzie.logger
|
28
|
+
Timeout.timeout(Fozzie.c.timeout) {
|
29
|
+
socket.send(message, 0, Fozzie.c.host, Fozzie.c.port)
|
30
|
+
true
|
31
|
+
}
|
32
|
+
rescue => exc
|
33
|
+
Fozzie.ogger.debug {"Statsd Failure: #{exc.message}\n#{exc.backtrace}"} if Fozzie.logger
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# The Socket we want to use to send data
|
39
|
+
def socket
|
40
|
+
@socket ||= UDPSocket.new
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/lib/fozzie/version.rb
CHANGED
@@ -13,32 +13,32 @@ describe Fozzie::Configuration do
|
|
13
13
|
|
14
14
|
it "attempts to load configuration from yaml" do
|
15
15
|
c = Fozzie::Configuration.new({:env => 'test', :config_path => 'spec/'})
|
16
|
-
c.stubs(:origin_name).returns
|
17
|
-
c.host.should
|
18
|
-
c.port.should
|
19
|
-
c.appname.should
|
20
|
-
c.data_prefix.should
|
16
|
+
c.stubs(:origin_name).returns ""
|
17
|
+
c.host.should eq '1.1.1.1'
|
18
|
+
c.port.should eq 9876
|
19
|
+
c.appname.should eq 'fozzie'
|
20
|
+
c.data_prefix.should eq 'fozzie.test'
|
21
21
|
end
|
22
22
|
|
23
23
|
it "defaults env" do
|
24
|
-
subject.env.should
|
24
|
+
subject.env.should eq 'development'
|
25
25
|
end
|
26
26
|
|
27
27
|
it "creates a data prefix" do
|
28
28
|
subject.stubs(:origin_name).returns("")
|
29
|
-
subject.data_prefix.should
|
29
|
+
subject.data_prefix.should eq 'development'
|
30
30
|
end
|
31
31
|
|
32
32
|
it "creates a data prefix with appname when set" do
|
33
33
|
subject.stubs(:origin_name).returns("")
|
34
34
|
subject.appname = 'astoria'
|
35
|
-
subject.data_prefix.should
|
35
|
+
subject.data_prefix.should eq 'astoria.development'
|
36
36
|
end
|
37
37
|
|
38
38
|
it "creates a prefix with origin" do
|
39
39
|
subject.stubs(:origin_name).returns("app.server.local")
|
40
40
|
subject.appname = 'astoria'
|
41
|
-
subject.data_prefix.should
|
41
|
+
subject.data_prefix.should eq 'astoria.app-server-local.development'
|
42
42
|
end
|
43
43
|
|
44
44
|
it "handles missing configuration namespace" do
|
@@ -51,37 +51,4 @@ describe Fozzie::Configuration do
|
|
51
51
|
subject.namespaces.should include("S")
|
52
52
|
end
|
53
53
|
|
54
|
-
describe "#ip_from_host" do
|
55
|
-
|
56
|
-
it "returns host if host an ip" do
|
57
|
-
c = Fozzie::Configuration.new({:env => 'test', :config_path => nil, :host => '127.0.0.1'})
|
58
|
-
c.ip_from_host.should == '127.0.0.1'
|
59
|
-
end
|
60
|
-
|
61
|
-
it "assigns nil on miss" do
|
62
|
-
Resolv.expects(:getaddresses).with('some.awesome.log.server').returns([])
|
63
|
-
c = Fozzie::Configuration.new({:env => 'test', :config_path => nil, :host => 'some.awesome.log.server'})
|
64
|
-
c.ip_from_host.should == ""
|
65
|
-
c.ip_from_host.should == ""
|
66
|
-
end
|
67
|
-
|
68
|
-
it "looks up ip from host" do
|
69
|
-
c = Fozzie::Configuration.new({:env => 'test', :config_path => nil, :host => 'lonelyplanet.com'})
|
70
|
-
c.ip_from_host.should match(/^(?:\d{1,3}\.){3}\d{1,3}$/)
|
71
|
-
end
|
72
|
-
|
73
|
-
it "caches the ip once it is retrieved" do
|
74
|
-
Resolv.expects(:getaddresses).with('lonelyplanet.com').returns(["1.1.1.1"])
|
75
|
-
c = Fozzie::Configuration.new({:env => 'test', :config_path => nil, :host => 'lonelyplanet.com'})
|
76
|
-
c.ip_from_host.should == "1.1.1.1"
|
77
|
-
end
|
78
|
-
|
79
|
-
it "raises Timeout on slow lookup" do
|
80
|
-
Resolv.stubs(:getaddresses).with('lonelyplanet.com') {|val| sleep 0.6; [] }
|
81
|
-
c = Fozzie::Configuration.new(:env => 'test', :config_path => nil, :host => 'lonelyplanet.com', :timeout => 0.5)
|
82
|
-
c.ip_from_host.should == ""
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
54
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fozzie/interface'
|
3
|
+
|
4
|
+
describe Fozzie::Interface do
|
5
|
+
|
6
|
+
subject { Fozzie::Interface.instance }
|
7
|
+
|
8
|
+
it "acts an a singleton" do
|
9
|
+
Fozzie.c.namespaces.each do |k|
|
10
|
+
Kernel.const_get(k).should eq Fozzie::Interface.instance
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "times a given block" do
|
15
|
+
subject.expects(:timing).with() {|b, val, timing| b == 'data.bin' && (1000..1200).include?(val) }.twice
|
16
|
+
subject.time_for('data.bin') { sleep 1 }
|
17
|
+
subject.time_to_do('data.bin') { sleep 1 }
|
18
|
+
end
|
19
|
+
|
20
|
+
it "registers a commit" do
|
21
|
+
subject.expects(:timing).with('event.commit', anything).twice
|
22
|
+
subject.commit
|
23
|
+
subject.committed
|
24
|
+
end
|
25
|
+
|
26
|
+
it "registers a build" do
|
27
|
+
subject.expects(:timing).with('event.build', anything).twice
|
28
|
+
subject.build
|
29
|
+
subject.built
|
30
|
+
end
|
31
|
+
|
32
|
+
it "registers a deploy" do
|
33
|
+
subject.expects(:timing).with('event.deploy', anything).twice
|
34
|
+
subject.deploy
|
35
|
+
subject.deployed
|
36
|
+
end
|
37
|
+
|
38
|
+
it "ensures block is called on socket error" do
|
39
|
+
UDPSocket.any_instance.stubs(:send).raises(SocketError)
|
40
|
+
proc { subject.time_for('data.bin') { sleep 1 } }.should_not raise_error
|
41
|
+
proc { subject.time_to_do('data.bin') { sleep 1 } }.should_not raise_error
|
42
|
+
end
|
43
|
+
|
44
|
+
it "raises exception if natural exception from block" do
|
45
|
+
proc { subject.time_for('data.bin') { raise ArgumentError, "testing" } }.should raise_error(ArgumentError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "only calls the block once on SocketError" do
|
49
|
+
UDPSocket.any_instance.stubs(:send).raises(SocketError)
|
50
|
+
i = 0
|
51
|
+
p = proc {|n| (n + 1) }
|
52
|
+
val = subject.time_for('data.bin') { i+= p.call(i) }
|
53
|
+
val.should == 1
|
54
|
+
end
|
55
|
+
|
56
|
+
it "raises Timeout on slow lookup" do
|
57
|
+
UDPSocket.any_instance.stubs(:send).with(any_parameters) { sleep 0.6 }
|
58
|
+
subject.increment('data.bin').should eq false
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#increment_on" do
|
62
|
+
|
63
|
+
it "registers success" do
|
64
|
+
subject.expects(:increment).with("event.increment.success", 1)
|
65
|
+
subject.increment_on('event.increment', true).should == true
|
66
|
+
end
|
67
|
+
|
68
|
+
it "registers failure" do
|
69
|
+
subject.expects(:increment).with("event.increment.fail", 1)
|
70
|
+
subject.increment_on('event.increment', false).should == false
|
71
|
+
end
|
72
|
+
|
73
|
+
it "simply questions the passed val with if" do
|
74
|
+
a = mock()
|
75
|
+
a.expects(:save).returns({})
|
76
|
+
subject.expects(:increment).with("event.increment.success", 1)
|
77
|
+
subject.increment_on('event.increment', a.save).should == {}
|
78
|
+
end
|
79
|
+
|
80
|
+
it "registers fail on nil return" do
|
81
|
+
a = mock()
|
82
|
+
a.expects(:save).returns(nil)
|
83
|
+
subject.expects(:increment).with("event.increment.fail", 1)
|
84
|
+
subject.increment_on('event.increment', a.save).should == nil
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "performing actions" do
|
88
|
+
|
89
|
+
it "registers success" do
|
90
|
+
a = mock()
|
91
|
+
a.expects(:save).returns(true)
|
92
|
+
subject.expects(:increment).with("event.increment.success", 1)
|
93
|
+
subject.increment_on('event.increment', a.save).should == true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "registers failure" do
|
97
|
+
a = mock()
|
98
|
+
a.expects(:save).returns(false)
|
99
|
+
subject.expects(:increment).with("event.increment.fail", 1)
|
100
|
+
subject.increment_on('event.increment', a.save).should == false
|
101
|
+
end
|
102
|
+
|
103
|
+
it "registers positive even when nested" do
|
104
|
+
a = mock()
|
105
|
+
a.expects(:save).returns(true)
|
106
|
+
subject.expects(:timing).with('event.run', any_parameters)
|
107
|
+
subject.expects(:increment).with("event.increment.success", 1)
|
108
|
+
|
109
|
+
res = subject.time_to_do "event.run" do
|
110
|
+
subject.increment_on('event.increment', a.save)
|
111
|
+
end
|
112
|
+
res.should == true
|
113
|
+
end
|
114
|
+
|
115
|
+
it "registers negative even when nested" do
|
116
|
+
a = mock()
|
117
|
+
a.expects(:save).returns(false)
|
118
|
+
subject.expects(:timing).with('event.run', any_parameters)
|
119
|
+
subject.expects(:increment).with("event.increment.fail", 1)
|
120
|
+
|
121
|
+
res = subject.time_to_do "event.run" do
|
122
|
+
subject.increment_on('event.increment', a.save)
|
123
|
+
end
|
124
|
+
res.should == false
|
125
|
+
end
|
126
|
+
|
127
|
+
it "allows passing of arrays for stat key" do
|
128
|
+
subject.expects(:timing).with('event.commit', any_parameters)
|
129
|
+
subject.time_to_do %w{event commit} do; end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -5,11 +5,9 @@ require 'rack/test'
|
|
5
5
|
describe Fozzie::Rack::Middleware do
|
6
6
|
|
7
7
|
subject do
|
8
|
-
RackApp
|
9
|
-
def call(env)
|
10
|
-
|
11
|
-
end
|
12
|
-
end unless defined?(RackApp)
|
8
|
+
unless defined?(RackApp)
|
9
|
+
RackApp = Class.new { def call(env); env end }
|
10
|
+
end
|
13
11
|
Fozzie::Rack::Middleware.new RackApp.new
|
14
12
|
end
|
15
13
|
|
@@ -2,9 +2,13 @@ require 'spec_helper'
|
|
2
2
|
require 'action_controller'
|
3
3
|
|
4
4
|
describe Fozzie::Rails::Middleware do
|
5
|
+
let(:path_info) { '/somewhere/railsy' }
|
6
|
+
let(:fake_env) { ({ 'PATH_INFO' => path_info }) }
|
5
7
|
|
6
8
|
before :each do
|
7
|
-
|
9
|
+
unless defined?(ActionController::RoutingError)
|
10
|
+
ActionController::RoutingError = Class.new(StandardError)
|
11
|
+
end
|
8
12
|
end
|
9
13
|
|
10
14
|
subject do
|
@@ -24,55 +28,57 @@ describe Fozzie::Rails::Middleware do
|
|
24
28
|
end
|
25
29
|
|
26
30
|
describe "rails 2" do
|
27
|
-
|
28
|
-
before :each do
|
29
|
-
RailsApp.stubs(:version).returns("2.3.1")
|
30
|
-
end
|
31
|
+
let!(:version) { RailsApp.stubs(:version).returns("2.3.1") }
|
31
32
|
|
32
33
|
it "#generate_key" do
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
ActionController::Routing::Routes.expects(:recognize_path)
|
35
|
+
.with(path_info)
|
36
|
+
.returns({:controller => 'somewhere', :action => 'railsy'})
|
37
|
+
|
36
38
|
subject.generate_key(fake_env).should == 'somewhere.railsy.render'
|
37
39
|
end
|
38
40
|
|
39
41
|
it "returns nil on routing error" do
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
ActionController::Routing::Routes.expects(:recognize_path)
|
43
|
+
.with(path_info)
|
44
|
+
.raises(ArgumentError)
|
45
|
+
|
43
46
|
subject.generate_key(fake_env).should == nil
|
44
47
|
end
|
45
48
|
|
46
49
|
end
|
47
50
|
|
48
51
|
describe "rails 3" do
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
RailsApp.stubs(:application).returns(@app)
|
55
|
-
end
|
52
|
+
let(:app) { Class.new }
|
53
|
+
let(:routing) { Class.new }
|
54
|
+
let!(:rails) { RailsApp.stubs(:application).returns(app) }
|
55
|
+
let!(:version) { RailsApp.stubs(:version).returns("3.1.1") }
|
56
|
+
let!(:routes) { app.stubs(:routes).returns(routing)}
|
56
57
|
|
57
58
|
it "#generate_key" do
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
routing.expects(:recognize_path)
|
60
|
+
.with(path_info)
|
61
|
+
.returns({:controller => 'somewhere', :action => 'railsy'})
|
62
|
+
|
61
63
|
subject.generate_key(fake_env).should == 'somewhere.railsy.render'
|
62
64
|
end
|
63
65
|
|
64
66
|
it "returns nil on error" do
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
routing.expects(:recognize_path)
|
68
|
+
.with(path_info)
|
69
|
+
.raises(ArgumentError)
|
70
|
+
|
68
71
|
subject.generate_key(fake_env).should == nil
|
69
72
|
end
|
70
|
-
|
73
|
+
|
71
74
|
it "returns nil on routing error" do
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
routing.expects(:recognize_path)
|
76
|
+
.with(path_info)
|
77
|
+
.raises(ActionController::RoutingError)
|
78
|
+
|
79
|
+
S.expects(:increment)
|
80
|
+
.with('routing.error')
|
81
|
+
|
76
82
|
subject.generate_key(fake_env).should == nil
|
77
83
|
end
|
78
84
|
|