rack-statsd_batch 0.0.1
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/README.md +29 -0
- data/lib/rack/statsd_batch.rb +89 -0
- data/rack-statsd_batch.gemspec +23 -0
- data/test/application_interface_test.rb +46 -0
- data/test/connection_test.rb +43 -0
- data/test/middleware_test.rb +29 -0
- metadata +70 -0
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Rack::StatsdBatch
|
2
|
+
|
3
|
+
Throw your metrics data into your request env, and on egress it will get pushed
|
4
|
+
to statsd in as few requests as possible.
|
5
|
+
|
6
|
+
# Setup
|
7
|
+
|
8
|
+
Add to your gemfile
|
9
|
+
|
10
|
+
gem 'rack-statsd_batch', '~> 0.0.1', require: 'rack/statsd_batch'
|
11
|
+
|
12
|
+
Add to your middleware in config/application.rb
|
13
|
+
|
14
|
+
config.middleware.use Rack::StatsdBatch, 'statsd-server.myhost.com', 1234
|
15
|
+
|
16
|
+
# Use
|
17
|
+
|
18
|
+
The stats collector is included in your request environment. Access it from your
|
19
|
+
controller:
|
20
|
+
|
21
|
+
metrics = request.env['metrics']
|
22
|
+
|
23
|
+
There are five message types that map to the statsd backend
|
24
|
+
|
25
|
+
metrics.gauge_diff 'registered-users', +1
|
26
|
+
metrics.gauge 'total_things', 2037
|
27
|
+
metrics.timing 'render_time_ms', 237
|
28
|
+
metrics.count 'things_done', 1
|
29
|
+
metrics.sets 'user_ids', 27
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class StatsdBatch
|
5
|
+
def initialize rack_app, hostname, port, mtu=1432
|
6
|
+
@app = rack_app
|
7
|
+
@hostname = hostname
|
8
|
+
@port = port
|
9
|
+
@mtu = mtu
|
10
|
+
end
|
11
|
+
|
12
|
+
def call env
|
13
|
+
env['metrics'] = Rack::StatsdBatch::Recorder.new
|
14
|
+
status, headers, response = @app.call(env)
|
15
|
+
env['metrics'].publish(@hostname, @port, @mtu)
|
16
|
+
[status, headers, response]
|
17
|
+
end
|
18
|
+
|
19
|
+
class Recorder
|
20
|
+
attr_reader :data
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@data = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def count key, diff
|
27
|
+
@data << "#{key}:#{diff}|c"
|
28
|
+
end
|
29
|
+
|
30
|
+
def timing key, ms
|
31
|
+
@data << "#{key}:#{ms}|ms"
|
32
|
+
end
|
33
|
+
|
34
|
+
def gauge key, value
|
35
|
+
@data << "#{key}:#{value}|g"
|
36
|
+
end
|
37
|
+
|
38
|
+
def gauge_diff key, diff
|
39
|
+
value = "#{diff}"
|
40
|
+
value = "+#{value}" if diff > 0
|
41
|
+
@data << "#{key}:#{value}|g"
|
42
|
+
end
|
43
|
+
|
44
|
+
def sets key, set_entry
|
45
|
+
@data << "#{key}:#{set_entry}|s"
|
46
|
+
end
|
47
|
+
|
48
|
+
def publish host, port, mtu
|
49
|
+
return if @data.empty?
|
50
|
+
c = connection(host, port)
|
51
|
+
build_packets(mtu).each {|p| c.send(p) }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def connection host, port
|
57
|
+
Rack::StatsdBatch::Connection.new(UDPSocket.new, host, port)
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_packets mtu
|
61
|
+
rv = []
|
62
|
+
packet = ''
|
63
|
+
while !data.empty?
|
64
|
+
message = data.shift
|
65
|
+
newline_length = packet.empty? ? 0 : 1
|
66
|
+
if packet.length + message.length + newline_length > mtu
|
67
|
+
rv << packet
|
68
|
+
packet = ''
|
69
|
+
end
|
70
|
+
message = "\n#{message}" unless packet.empty?
|
71
|
+
packet << message
|
72
|
+
end
|
73
|
+
(rv << packet) unless packet.empty?
|
74
|
+
rv
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Connection
|
79
|
+
def initialize socket, *extra_params
|
80
|
+
@socket = socket
|
81
|
+
@extras = extra_params
|
82
|
+
end
|
83
|
+
|
84
|
+
def send data
|
85
|
+
@socket.send(data, flags=0, *@extras)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0")
|
4
|
+
|
5
|
+
s.name = 'rack-statsd_batch'
|
6
|
+
s.version = '0.0.1'
|
7
|
+
s.date = '2013-12-13'
|
8
|
+
|
9
|
+
s.summary = 'Send metrics data to statsd after your request'
|
10
|
+
s.description = 'Exposes a metrics collector to the request environment and flushes the data to statsd when the request is finished. Batches the data to send as few packets as possible.'
|
11
|
+
|
12
|
+
s.authors = ['xtoddx (Todd Willey)']
|
13
|
+
s.email = 'xtoddx@gmail.com'
|
14
|
+
s.homepage = 'http://github.com/CirrusMio/rack-statsd_batch'
|
15
|
+
s.license = 'MIT'
|
16
|
+
|
17
|
+
s.require_paths = %w[lib]
|
18
|
+
|
19
|
+
s.add_development_dependency('minitest')
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- tests/*`.split("\n")
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
gem 'minitest'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require_relative '../lib/rack/statsd_batch'
|
5
|
+
|
6
|
+
class ApplicationInterfaceTest < MiniTest::Test
|
7
|
+
def setup
|
8
|
+
@recorder = Rack::StatsdBatch::Recorder.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_count
|
12
|
+
@recorder.count('mykey', 1)
|
13
|
+
assert_equal 1, @recorder.data.length
|
14
|
+
assert_equal 'mykey:1|c', @recorder.data.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_timing
|
18
|
+
@recorder.timing('mykey', 1)
|
19
|
+
assert_equal 1, @recorder.data.length
|
20
|
+
assert_equal 'mykey:1|ms', @recorder.data.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_gauge_diff_negative
|
24
|
+
@recorder.gauge_diff('mykey', 1)
|
25
|
+
assert_equal 1, @recorder.data.length
|
26
|
+
assert_equal 'mykey:+1|g', @recorder.data.first
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_gauge_diff_negative
|
30
|
+
@recorder.gauge_diff('mykey', -1)
|
31
|
+
assert_equal 1, @recorder.data.length
|
32
|
+
assert_equal 'mykey:-1|g', @recorder.data.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_gauge_set
|
36
|
+
@recorder.gauge('mykey', 10)
|
37
|
+
assert_equal 1, @recorder.data.length
|
38
|
+
assert_equal 'mykey:10|g', @recorder.data.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_set_multiple_things
|
42
|
+
@recorder.gauge('mykey', 1)
|
43
|
+
@recorder.gauge_diff('myotherkey', 10)
|
44
|
+
assert_equal 2, @recorder.data.length
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
gem 'minitest'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require_relative '../lib/rack/statsd_batch'
|
5
|
+
|
6
|
+
$statsd_connection = nil
|
7
|
+
class TestConnection
|
8
|
+
attr_reader :packets
|
9
|
+
|
10
|
+
def initialize socket, host, port
|
11
|
+
$statsd_connection = self
|
12
|
+
end
|
13
|
+
|
14
|
+
def send packet
|
15
|
+
(@packets ||= []) << packet
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ConnectionTest < MiniTest::Test
|
20
|
+
def setup
|
21
|
+
@old_const = Rack::StatsdBatch::Connection
|
22
|
+
Rack::StatsdBatch.const_set :Connection, TestConnection
|
23
|
+
@recorder = Rack::StatsdBatch::Recorder.new
|
24
|
+
@recorder.gauge 'mygauge', 1
|
25
|
+
@recorder.gauge_diff 'myothergauge', 3
|
26
|
+
end
|
27
|
+
|
28
|
+
def teardown
|
29
|
+
Rack::StatsdBatch.const_set :Connection, @old_const
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_sends_packet
|
33
|
+
@recorder.publish('hostname', port=1234, mtu=1024)
|
34
|
+
assert_equal 1, $statsd_connection.packets.length
|
35
|
+
assert_equal "mygauge:1|g\nmyothergauge:+3|g",
|
36
|
+
$statsd_connection.packets.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_splits_packets
|
40
|
+
@recorder.publish('hostname', port=1234, mtu=@recorder.data.first.length)
|
41
|
+
assert_equal 2, $statsd_connection.packets.length
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
gem 'minitest'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
require_relative '../lib/rack/statsd_batch'
|
5
|
+
|
6
|
+
class MiddlewareTestApp
|
7
|
+
attr_accessor :callback
|
8
|
+
|
9
|
+
def call env
|
10
|
+
if @callback
|
11
|
+
@callback.call(env)
|
12
|
+
end
|
13
|
+
return [{}, 200, '']
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class MiddlewareTest < MiniTest::Test
|
18
|
+
def setup
|
19
|
+
@tester = MiddlewareTestApp.new
|
20
|
+
@app = Rack::StatsdBatch.new(@tester, 'localhost', 1234)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_env_contains_recorder
|
24
|
+
passed = nil
|
25
|
+
@tester.callback = ->(env){ passed = env.include?('metrics') }
|
26
|
+
@app.call({})
|
27
|
+
assert passed
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-statsd_batch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- xtoddx (Todd Willey)
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-12-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Exposes a metrics collector to the request environment and flushes the
|
31
|
+
data to statsd when the request is finished. Batches the data to send as few packets
|
32
|
+
as possible.
|
33
|
+
email: xtoddx@gmail.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- README.md
|
39
|
+
- lib/rack/statsd_batch.rb
|
40
|
+
- rack-statsd_batch.gemspec
|
41
|
+
- test/application_interface_test.rb
|
42
|
+
- test/connection_test.rb
|
43
|
+
- test/middleware_test.rb
|
44
|
+
homepage: http://github.com/CirrusMio/rack-statsd_batch
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.25
|
66
|
+
signing_key:
|
67
|
+
specification_version: 2
|
68
|
+
summary: Send metrics data to statsd after your request
|
69
|
+
test_files: []
|
70
|
+
has_rdoc:
|