statsd-instrument 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +125 -0
- data/Rakefile +8 -0
- data/lib/statsd-instrument.rb +1 -0
- data/lib/statsd/instrument.rb +146 -0
- data/statsd-instrument.gemspec +15 -0
- data/test/statsd-instrument_test.rb +148 -0
- metadata +88 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# StatsD client for Ruby apps
|
2
|
+
|
3
|
+
This is a ruby client for statsd (http://github.com/etsy/statsd). It provides a lightweight way to track and measure metrics in your application.
|
4
|
+
|
5
|
+
We call out to statsd by sending data over a UDP socket. UDP sockets are fast, but unreliable, there is no guarantee that your data will ever arrive at it's location. In other words, fire and forget. This is perfect for this use case because it means your code doesn't get bogged down trying to log statistics. We send data to statsd several times per request and haven't noticed a performance hit.
|
6
|
+
|
7
|
+
The fact that all of your stats data may not make it into statsd is no issue. Graphite (the graph database that statsd is built on) will only show you trends in your data. Internally it only keeps enough data to satisfy the levels of granularity we specify. As well as satisfying it's requirement as a fixed size database. We can throw as much data at it as we want it and it will do it's best to show us the trends over time and get rid of the fluff.
|
8
|
+
|
9
|
+
For Shopify, our retention periods are:
|
10
|
+
|
11
|
+
1. 10 seconds of granularity for the last 6 hours
|
12
|
+
2. 60 seconds of granularity for the last week
|
13
|
+
3. 10 minutes of granularity for the last 5 years
|
14
|
+
|
15
|
+
This is the same as what Etsy uses (mentioned in the README for [http://github.com/etsy/statd](http://github.com/etsy/statd])).
|
16
|
+
|
17
|
+
## Configuration
|
18
|
+
|
19
|
+
``` ruby
|
20
|
+
StatsD.server = 'statsd.myservice.com:8125'
|
21
|
+
StatsD.logger = Rails.logger
|
22
|
+
StatsD.mode = :production
|
23
|
+
```
|
24
|
+
|
25
|
+
If you set the mode to anything besides production then the library will print its calls to the logger, rather than sending them over the wire.
|
26
|
+
|
27
|
+
## StatsD keys
|
28
|
+
|
29
|
+
StatsD keys look like 'admin.logins.api.success'. Each dot in the key represents a 'folder' in the graphite interface. You can include any data you want in the keys.
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### StatsD.measure
|
34
|
+
|
35
|
+
Lets you benchmark how long the execution of a specific method takes.
|
36
|
+
|
37
|
+
``` ruby
|
38
|
+
# You can pass a key and a ms value
|
39
|
+
StatsD.measure('GoogleBase.insert', 2.55)
|
40
|
+
|
41
|
+
# or more commonly pass a block that calls your code
|
42
|
+
StatsD.measure('GoogleBase.insert') do
|
43
|
+
GoogleBase.insert(product)
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
Rather than using this method directly it's more common to use the metaprogramming methods made available.
|
48
|
+
|
49
|
+
``` ruby
|
50
|
+
GoogleBase.extend StatsD::Instrument
|
51
|
+
GoogleBase.statsd_measure :insert, 'GoogleBase.insert'
|
52
|
+
```
|
53
|
+
|
54
|
+
### StatsD.increment
|
55
|
+
|
56
|
+
Lets you increment a key in statsd to keep a count of something. If the specified key doesn't exist it will create it for you.
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
# increments default to +1
|
60
|
+
StatsD.increment('GoogleBase.insert')
|
61
|
+
# you can also specify how much to increment the key by
|
62
|
+
StatsD.increment('GoogleBase.insert', 10)
|
63
|
+
# you can also specify a sample rate, so only 1/10 of events
|
64
|
+
# actually get to statsd. Useful for very high volume data
|
65
|
+
StatsD.increment('GoogleBase.insert', 1, 0.1)
|
66
|
+
```
|
67
|
+
|
68
|
+
Again it's more common to use the metaprogramming methods.
|
69
|
+
|
70
|
+
## Metaprogramming Methods
|
71
|
+
|
72
|
+
As mentioned, it's most common to use the provided metaprogramming methods. This lets you define all of your instrumentation in one file and not litter your code with instrumentation details. You should enable a class for instrumentation by extending it with the `StatsD::Instrument` class.
|
73
|
+
|
74
|
+
``` ruby
|
75
|
+
GoogleBase.extend StatsD::Instrument
|
76
|
+
```
|
77
|
+
|
78
|
+
Then use the methods provided below to instrument methods in your class.
|
79
|
+
|
80
|
+
### statsd\_count
|
81
|
+
|
82
|
+
This will increment the given key even if the method doesn't finish (ie. raises).
|
83
|
+
|
84
|
+
``` ruby
|
85
|
+
GoogleBase.statsd_count :insert, 'GoogleBase.insert'
|
86
|
+
```
|
87
|
+
|
88
|
+
Note how I used the 'GoogleBase.insert' key above when measuring this method, and I reused here when counting the method calls. StatsD automatically separates these two kinds of stats into namespaces so there won't be a key collision here.
|
89
|
+
|
90
|
+
### statsd\_count\_if
|
91
|
+
|
92
|
+
This will only increment the given key if the method executes successfully.
|
93
|
+
|
94
|
+
``` ruby
|
95
|
+
GoogleBase.statsd_count_if :insert, 'GoogleBase.insert'
|
96
|
+
```
|
97
|
+
|
98
|
+
So now, if GoogleBase#insert raises an exception or returns false (ie. result == false), we won't increment the key. If you want to define what success means for a given method you can pass a block that takes the result of the method.
|
99
|
+
|
100
|
+
``` ruby
|
101
|
+
GoogleBase.statsd_count_if :insert, 'GoogleBase.insert' do |response|
|
102
|
+
result.code == 200
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
In the above example we will only increment the key in statsd if the result of the block returns true. So the method is returning a Net::HTTP response and we're checking the status code.
|
107
|
+
|
108
|
+
### statsd\_count\_success
|
109
|
+
|
110
|
+
Similar to statsd_count_if, except this will increment one key in the case of success and another key in the case of failure.
|
111
|
+
|
112
|
+
``` ruby
|
113
|
+
GoogleBase.statsd_count_success :insert, 'GoogleBase.insert'
|
114
|
+
```
|
115
|
+
|
116
|
+
So if this method fails execution (raises or returns false) we'll increment the failure key ('GoogleBase.insert.failure'), otherwise we'll increment the success key ('GoogleBase.insert.success'). Notice that we're modifying the given key before sending it to statsd.
|
117
|
+
|
118
|
+
Again you can pass a block to define what success means.
|
119
|
+
|
120
|
+
``` ruby
|
121
|
+
GoogleBase.statsd_count_if :insert, 'GoogleBase.insert' do |response|
|
122
|
+
result.code == 200
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'statsd/instrument'
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module StatsD
|
4
|
+
class << self
|
5
|
+
attr_accessor :host, :port, :mode, :logger, :enabled
|
6
|
+
end
|
7
|
+
self.enabled = true
|
8
|
+
|
9
|
+
trap("TTOU") { self.enabled = false }
|
10
|
+
trap("TTIN") { self.enabled = true }
|
11
|
+
|
12
|
+
# StatsD.server = 'localhost:1234'
|
13
|
+
def self.server=(conn)
|
14
|
+
self.host, port = conn.split(':')
|
15
|
+
self.port = port.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
module Instrument
|
19
|
+
def statsd_measure(method, name)
|
20
|
+
add_to_method(method, name, :measure) do |old_method, new_method, metric_name, *args|
|
21
|
+
define_method(new_method) do |*args|
|
22
|
+
StatsD.measure(send(metric_name)) { send(old_method, *args) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def statsd_count_success(method, name)
|
28
|
+
add_to_method(method, name, :count_success) do |old_method, new_method, metric_name|
|
29
|
+
define_method(new_method) do |*args|
|
30
|
+
begin
|
31
|
+
truthiness = result = send(old_method, *args)
|
32
|
+
rescue
|
33
|
+
truthiness = false
|
34
|
+
raise
|
35
|
+
else
|
36
|
+
truthiness = (yield(result) rescue false) if block_given?
|
37
|
+
result
|
38
|
+
ensure
|
39
|
+
StatsD.increment("#{send(metric_name)}." + (truthiness == false ? 'failure' : 'success'))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def statsd_count_if(method, name)
|
46
|
+
add_to_method(method, name, :count_if) do |old_method, new_method, metric_name|
|
47
|
+
define_method(new_method) do |*args|
|
48
|
+
begin
|
49
|
+
truthiness = result = send(old_method, *args)
|
50
|
+
rescue
|
51
|
+
truthiness = false
|
52
|
+
raise
|
53
|
+
else
|
54
|
+
truthiness = (yield(result) rescue false) if block_given?
|
55
|
+
result
|
56
|
+
ensure
|
57
|
+
StatsD.increment(send(metric_name)) if truthiness
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def statsd_count(method, name)
|
64
|
+
add_to_method(method, name, :count) do |old_method, new_method, metric_name|
|
65
|
+
define_method(new_method) do |*args|
|
66
|
+
StatsD.increment(send(metric_name))
|
67
|
+
send(old_method, *args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def statsd_memoize(metric_name, name)
|
74
|
+
define_method(metric_name) do
|
75
|
+
name = eval("\"#{name}\"", binding)
|
76
|
+
|
77
|
+
self.class.send(:define_method, metric_name) do
|
78
|
+
name
|
79
|
+
end
|
80
|
+
send(metric_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_to_method(method, name, action, &block)
|
85
|
+
metric_name = :"#{method}_#{name}_metric_name"
|
86
|
+
statsd_memoize(metric_name, name)
|
87
|
+
|
88
|
+
method_name_without_statsd = :"#{method}_for_#{action}_on_#{self.name}_without_#{name}"
|
89
|
+
# raw_ssl_request_for_measure_on_FedEx_without_ActiveMerchant.Shipping.#{self.class.name}.ssl_request
|
90
|
+
|
91
|
+
method_name_with_statsd = :"#{method}_for_#{action}_on_#{self.name}_with_#{name}"
|
92
|
+
# raw_ssl_request_for_measure_on_FedEx_with_ActiveMerchant.Shipping.#{self.class.name}.ssl_request
|
93
|
+
|
94
|
+
raise ArgumentError, "already instrumented #{method} for #{self.name}" if method_defined? method_name_without_statsd
|
95
|
+
raise ArgumentError, "could not find method #{method} for #{self.name}" unless method_defined?(method) || private_method_defined?(method)
|
96
|
+
|
97
|
+
alias_method method_name_without_statsd, method
|
98
|
+
yield method_name_without_statsd, method_name_with_statsd, metric_name
|
99
|
+
alias_method method, method_name_with_statsd
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# glork:320|ms
|
104
|
+
def self.measure(key, milli = nil)
|
105
|
+
result = nil
|
106
|
+
ms = Benchmark.ms do
|
107
|
+
result = yield
|
108
|
+
end if milli.nil?
|
109
|
+
|
110
|
+
write(key, ms, :ms)
|
111
|
+
result
|
112
|
+
end
|
113
|
+
|
114
|
+
# gorets:1|c
|
115
|
+
def self.increment(key, delta = 1, sample_rate = 1)
|
116
|
+
write(key, delta, :incr, sample_rate)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def self.socket
|
122
|
+
@socket ||= UDPSocket.new
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.write(k,v,op, sample_rate = 1)
|
126
|
+
return unless enabled
|
127
|
+
return if sample_rate < 1 && rand > sample_rate
|
128
|
+
|
129
|
+
command = "#{k}:#{v}"
|
130
|
+
case op
|
131
|
+
when :incr
|
132
|
+
command << '|c'
|
133
|
+
when :ms
|
134
|
+
command << '|ms'
|
135
|
+
end
|
136
|
+
|
137
|
+
command << "|@#{sample_rate}" if sample_rate < 1
|
138
|
+
|
139
|
+
if mode == :production
|
140
|
+
socket.send(command, 0, host, port)
|
141
|
+
else
|
142
|
+
logger.info "[StatsD] #{command}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "statsd-instrument"
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.authors = ["Jesse Storimer"]
|
5
|
+
s.email = ["jesse@shopify.com"]
|
6
|
+
s.homepage = "http://github.com/shopify/statsd-instrument"
|
7
|
+
|
8
|
+
s.summary = %q{A StatsD client for Ruby apps}
|
9
|
+
s.description = %q{A StatsD client for Ruby apps. Provides metaprogramming methods to inject StatsD instrumentation into your code.}
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
|
14
|
+
s.add_development_dependency 'mocha'
|
15
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'statsd-instrument'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'mocha'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
StatsD.logger = Logger.new('/dev/null')
|
7
|
+
|
8
|
+
module ActiveMerchant; end
|
9
|
+
class ActiveMerchant::Base
|
10
|
+
def ssl_post(arg)
|
11
|
+
if arg
|
12
|
+
'OK'
|
13
|
+
else
|
14
|
+
raise 'Not OK'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ActiveMerchant::Gateway < ActiveMerchant::Base
|
20
|
+
def purchase(arg)
|
21
|
+
ssl_post(arg)
|
22
|
+
true
|
23
|
+
rescue
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ActiveMerchant::UniqueGateway < ActiveMerchant::Base
|
29
|
+
def ssl_post(arg)
|
30
|
+
{:success => arg}
|
31
|
+
end
|
32
|
+
|
33
|
+
def purchase(arg)
|
34
|
+
ssl_post(arg)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ActiveMerchant::Base.extend StatsD::Instrument
|
39
|
+
|
40
|
+
class StatsDTest < Test::Unit::TestCase
|
41
|
+
def setup
|
42
|
+
StatsD.stubs(:increment)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_statsd_count_if
|
46
|
+
ActiveMerchant::Gateway.statsd_count_if :ssl_post, 'ActiveMerchant.Billing.#{self.class.name}.if'
|
47
|
+
|
48
|
+
StatsD.expects(:increment).with(includes('if')).once
|
49
|
+
ActiveMerchant::Gateway.new.purchase(true)
|
50
|
+
ActiveMerchant::Gateway.new.purchase(false)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_statsd_count_if_with_block
|
54
|
+
ActiveMerchant::UniqueGateway.statsd_count_if :ssl_post, 'ActiveMerchant.Billing.#{self.class.name}.block' do |result|
|
55
|
+
result[:success]
|
56
|
+
end
|
57
|
+
|
58
|
+
StatsD.expects(:increment).with(includes('block')).once
|
59
|
+
ActiveMerchant::UniqueGateway.new.purchase(true)
|
60
|
+
ActiveMerchant::UniqueGateway.new.purchase(false)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_statsd_count_success
|
64
|
+
ActiveMerchant::Gateway.statsd_count_success :ssl_post, 'ActiveMerchant.Billing.#{self.class.name}'
|
65
|
+
|
66
|
+
StatsD.expects(:increment).with(includes('success'))
|
67
|
+
ActiveMerchant::Gateway.new.purchase(true)
|
68
|
+
|
69
|
+
StatsD.expects(:increment).with(includes('failure'))
|
70
|
+
ActiveMerchant::Gateway.new.purchase(false)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_statsd_count_success_with_block
|
74
|
+
ActiveMerchant::UniqueGateway.statsd_count_success :ssl_post, 'ActiveMerchant.Billing.#{self.class.name}' do |result|
|
75
|
+
result[:success]
|
76
|
+
end
|
77
|
+
|
78
|
+
StatsD.expects(:increment).with(includes('success'))
|
79
|
+
ActiveMerchant::UniqueGateway.new.purchase(true)
|
80
|
+
|
81
|
+
StatsD.expects(:increment).with(includes('failure'))
|
82
|
+
ActiveMerchant::UniqueGateway.new.purchase(false)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_statsd_count
|
86
|
+
ActiveMerchant::Gateway.statsd_count :ssl_post, 'ActiveMerchant.Billing.#{self.class.name}.ssl_post'
|
87
|
+
|
88
|
+
StatsD.expects(:increment).with(includes('ssl_post'))
|
89
|
+
ActiveMerchant::Gateway.new.purchase(true)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_statsd_measure
|
93
|
+
ActiveMerchant::UniqueGateway.statsd_measure :ssl_post, 'ActiveMerchant.Billing.#{self.class.name}.ssl_post'
|
94
|
+
|
95
|
+
StatsD.expects(:measure).with(includes('ssl_post')).returns({:success => true})
|
96
|
+
ActiveMerchant::UniqueGateway.new.purchase(true)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_count_with_sampling
|
100
|
+
StatsD.unstub(:increment)
|
101
|
+
StatsD.stubs(:rand).returns(0.6)
|
102
|
+
StatsD.logger.expects(:info).never
|
103
|
+
|
104
|
+
StatsD.increment('sampling.foo.bar', 1, 0.1)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_count_with_successful_sample
|
108
|
+
StatsD.unstub(:increment)
|
109
|
+
StatsD.stubs(:rand).returns(0.01)
|
110
|
+
StatsD.logger.expects(:info).once.with do |string|
|
111
|
+
string.include?('@0.1')
|
112
|
+
end
|
113
|
+
|
114
|
+
StatsD.increment('sampling.foo.bar', 1, 0.1)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_production_mode_should_use_udp_socket
|
118
|
+
StatsD.unstub(:increment)
|
119
|
+
|
120
|
+
StatsD.mode = :production
|
121
|
+
StatsD.server = 'localhost:123'
|
122
|
+
UDPSocket.any_instance.expects(:send)
|
123
|
+
|
124
|
+
StatsD.increment('fooz')
|
125
|
+
StatsD.mode = :test
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_should_not_write_when_disabled
|
129
|
+
StatsD.enabled = false
|
130
|
+
StatsD.expects(:logger).never
|
131
|
+
StatsD.increment('fooz')
|
132
|
+
StatsD.enabled = true
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_receiving_TTOU_should_disable
|
136
|
+
Process.kill("TTOU", $$)
|
137
|
+
sleep 0.5
|
138
|
+
assert !StatsD.enabled
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_receiving_TTIN_should_disable
|
142
|
+
StatsD.enabled = false
|
143
|
+
|
144
|
+
Process.kill("TTIN", $$)
|
145
|
+
sleep 0.5
|
146
|
+
assert StatsD.enabled
|
147
|
+
end
|
148
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: statsd-instrument
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jesse Storimer
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-28 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: mocha
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: A StatsD client for Ruby apps. Provides metaprogramming methods to inject StatsD instrumentation into your code.
|
36
|
+
email:
|
37
|
+
- jesse@shopify.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- lib/statsd-instrument.rb
|
50
|
+
- lib/statsd/instrument.rb
|
51
|
+
- statsd-instrument.gemspec
|
52
|
+
- test/statsd-instrument_test.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/shopify/statsd-instrument
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.3.9.2
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: A StatsD client for Ruby apps
|
87
|
+
test_files:
|
88
|
+
- test/statsd-instrument_test.rb
|