jashmenn-statsd-instrument 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.travis.yml +6 -0
- data/Gemfile +10 -0
- data/LICENSE +20 -0
- data/README.md +156 -0
- data/Rakefile +10 -0
- data/lib/statsd-instrument.rb +1 -0
- data/lib/statsd/instrument.rb +157 -0
- data/statsd-instrument.gemspec +15 -0
- data/test/statsd-instrument_test.rb +230 -0
- metadata +83 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Shopify
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# StatsD client for Ruby apps
|
2
|
+
|
3
|
+
![Built on Travis](https://secure.travis-ci.org/Shopify/statsd-instrument.png?branch=master)
|
4
|
+
|
5
|
+
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.
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
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.
|
10
|
+
|
11
|
+
For Shopify, our retention periods are:
|
12
|
+
|
13
|
+
1. 10 seconds of granularity for the last 6 hours
|
14
|
+
2. 60 seconds of granularity for the last week
|
15
|
+
3. 10 minutes of granularity for the last 5 years
|
16
|
+
|
17
|
+
This is the same as what Etsy uses (mentioned in the README for http://github.com/etsy/statsd).
|
18
|
+
|
19
|
+
## Configuration
|
20
|
+
|
21
|
+
``` ruby
|
22
|
+
StatsD.server = 'statsd.myservice.com:8125'
|
23
|
+
StatsD.logger = Rails.logger
|
24
|
+
StatsD.mode = :production
|
25
|
+
StatsD.prefix = 'my_app' # An optional prefix to be added to each stat.
|
26
|
+
StatsD.default_sample_rate = 0.1 # Sample 10% of events. By default all events are reported.
|
27
|
+
```
|
28
|
+
|
29
|
+
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.
|
30
|
+
|
31
|
+
## StatsD keys
|
32
|
+
|
33
|
+
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.
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
### StatsD.measure
|
38
|
+
|
39
|
+
Lets you benchmark how long the execution of a specific method takes.
|
40
|
+
|
41
|
+
``` ruby
|
42
|
+
# You can pass a key and a ms value
|
43
|
+
StatsD.measure('GoogleBase.insert', 2.55)
|
44
|
+
|
45
|
+
# or more commonly pass a block that calls your code
|
46
|
+
StatsD.measure('GoogleBase.insert') do
|
47
|
+
GoogleBase.insert(product)
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
Rather than using this method directly it's more common to use the metaprogramming methods made available.
|
52
|
+
|
53
|
+
``` ruby
|
54
|
+
GoogleBase.extend StatsD::Instrument
|
55
|
+
GoogleBase.statsd_measure :insert, 'GoogleBase.insert'
|
56
|
+
```
|
57
|
+
|
58
|
+
### StatsD.increment
|
59
|
+
|
60
|
+
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.
|
61
|
+
|
62
|
+
``` ruby
|
63
|
+
# increments default to +1
|
64
|
+
StatsD.increment('GoogleBase.insert')
|
65
|
+
# you can also specify how much to increment the key by
|
66
|
+
StatsD.increment('GoogleBase.insert', 10)
|
67
|
+
# you can also specify a sample rate, so only 1/10 of events
|
68
|
+
# actually get to statsd. Useful for very high volume data
|
69
|
+
StatsD.increment('GoogleBase.insert', 1, 0.1)
|
70
|
+
```
|
71
|
+
|
72
|
+
Again it's more common to use the metaprogramming methods.
|
73
|
+
|
74
|
+
## Metaprogramming Methods
|
75
|
+
|
76
|
+
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.
|
77
|
+
|
78
|
+
``` ruby
|
79
|
+
GoogleBase.extend StatsD::Instrument
|
80
|
+
```
|
81
|
+
|
82
|
+
Then use the methods provided below to instrument methods in your class.
|
83
|
+
|
84
|
+
### statsd\_count
|
85
|
+
|
86
|
+
This will increment the given key even if the method doesn't finish (ie. raises).
|
87
|
+
|
88
|
+
``` ruby
|
89
|
+
GoogleBase.statsd_count :insert, 'GoogleBase.insert'
|
90
|
+
```
|
91
|
+
|
92
|
+
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.
|
93
|
+
|
94
|
+
### statsd\_count\_if
|
95
|
+
|
96
|
+
This will only increment the given key if the method executes successfully.
|
97
|
+
|
98
|
+
``` ruby
|
99
|
+
GoogleBase.statsd_count_if :insert, 'GoogleBase.insert'
|
100
|
+
```
|
101
|
+
|
102
|
+
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.
|
103
|
+
|
104
|
+
``` ruby
|
105
|
+
GoogleBase.statsd_count_if :insert, 'GoogleBase.insert' do |response|
|
106
|
+
response.code == 200
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
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.
|
111
|
+
|
112
|
+
### statsd\_count\_success
|
113
|
+
|
114
|
+
Similar to statsd_count_if, except this will increment one key in the case of success and another key in the case of failure.
|
115
|
+
|
116
|
+
``` ruby
|
117
|
+
GoogleBase.statsd_count_success :insert, 'GoogleBase.insert'
|
118
|
+
```
|
119
|
+
|
120
|
+
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.
|
121
|
+
|
122
|
+
Again you can pass a block to define what success means.
|
123
|
+
|
124
|
+
``` ruby
|
125
|
+
GoogleBase.statsd_count_success :insert, 'GoogleBase.insert' do |response|
|
126
|
+
response.code == 200
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
### Instrumenting Class Methods
|
131
|
+
|
132
|
+
You can instrument class methods, just like instance methods, using the metaprogramming methods. You simply have to configure the instrumentation on the singleton class of the Class you want to instrument.
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
AWS::S3::Base.singleton_class.extend StatsD::Instrument
|
136
|
+
AWS::S3::Base.singleton_class.statsd_measure :request, 'S3.request'
|
137
|
+
```
|
138
|
+
|
139
|
+
## Reliance on DNS
|
140
|
+
Out of the box StatsD is set up to be unidirectional fire-and-forget over UDP. Configuring the StatsD host to be a non-ip will trigger a DNS lookup (ie synchronous round trip network call) for each metric sent. This can be particularly problematic in clouds that have a shared DNS infrastructure such as AWS.
|
141
|
+
|
142
|
+
### Common Workarounds
|
143
|
+
1. Using an IP avoids the DNS lookup but generally requires an application deploy to change.
|
144
|
+
2. Hardcoding the DNS/IP pair in /etc/hosts allows the IP to change without redeploying your application but fails to scale as the number of servers increases.
|
145
|
+
3. Installing caching software such as nscd that uses the DNS TTL avoids most DNS lookups but makes the exact moment of change indeterminate.
|
146
|
+
|
147
|
+
## Compatibility
|
148
|
+
|
149
|
+
Tested on:
|
150
|
+
|
151
|
+
* Ruby 1.8.7
|
152
|
+
* Ruby Enterprise Edition 1.8.7
|
153
|
+
* Ruby 1.9.2
|
154
|
+
* Ruby 1.9.3
|
155
|
+
|
156
|
+
Ruby 1.9 compatibility is planned for the long term. Your mileage may vary with other Ruby environments.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'statsd/instrument'
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'benchmark'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
class << Benchmark
|
6
|
+
def ms
|
7
|
+
1000 * realtime { yield }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
module StatsD
|
13
|
+
class << self
|
14
|
+
attr_accessor :host, :port, :mode, :logger, :enabled, :default_sample_rate,
|
15
|
+
:prefix
|
16
|
+
end
|
17
|
+
self.enabled = true
|
18
|
+
self.default_sample_rate = 1
|
19
|
+
|
20
|
+
TimeoutClass = defined?(::SystemTimer) ? ::SystemTimer : ::Timeout
|
21
|
+
|
22
|
+
# StatsD.server = 'localhost:1234'
|
23
|
+
def self.server=(conn)
|
24
|
+
self.host, port = conn.split(':')
|
25
|
+
self.port = port.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
module Instrument
|
29
|
+
def statsd_measure(method, name)
|
30
|
+
add_to_method(method, name, :measure) do |old_method, new_method, metric_name, *args|
|
31
|
+
define_method(new_method) do |*args|
|
32
|
+
StatsD.measure(metric_name) { send(old_method, *args) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def statsd_count_success(method, name)
|
38
|
+
add_to_method(method, name, :count_success) do |old_method, new_method, metric_name|
|
39
|
+
define_method(new_method) do |*args|
|
40
|
+
begin
|
41
|
+
truthiness = result = send(old_method, *args)
|
42
|
+
rescue
|
43
|
+
truthiness = false
|
44
|
+
raise
|
45
|
+
else
|
46
|
+
truthiness = (yield(result) rescue false) if block_given?
|
47
|
+
result
|
48
|
+
ensure
|
49
|
+
StatsD.increment("#{metric_name}." + (truthiness == false ? 'failure' : 'success'))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def statsd_count_if(method, name)
|
56
|
+
add_to_method(method, name, :count_if) do |old_method, new_method, metric_name|
|
57
|
+
define_method(new_method) do |*args|
|
58
|
+
begin
|
59
|
+
truthiness = result = send(old_method, *args)
|
60
|
+
rescue
|
61
|
+
truthiness = false
|
62
|
+
raise
|
63
|
+
else
|
64
|
+
truthiness = (yield(result) rescue false) if block_given?
|
65
|
+
result
|
66
|
+
ensure
|
67
|
+
StatsD.increment(metric_name) if truthiness
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def statsd_count(method, name)
|
74
|
+
add_to_method(method, name, :count) do |old_method, new_method, metric_name|
|
75
|
+
define_method(new_method) do |*args|
|
76
|
+
StatsD.increment(metric_name)
|
77
|
+
send(old_method, *args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def add_to_method(method, name, action, &block)
|
84
|
+
metric_name = name
|
85
|
+
|
86
|
+
method_name_without_statsd = :"#{method}_for_#{action}_on_#{self.name}_without_#{name}"
|
87
|
+
# raw_ssl_request_for_measure_on_FedEx_without_ActiveMerchant.Shipping.#{self.class.name}.ssl_request
|
88
|
+
|
89
|
+
method_name_with_statsd = :"#{method}_for_#{action}_on_#{self.name}_with_#{name}"
|
90
|
+
# raw_ssl_request_for_measure_on_FedEx_with_ActiveMerchant.Shipping.#{self.class.name}.ssl_request
|
91
|
+
|
92
|
+
raise ArgumentError, "already instrumented #{method} for #{self.name}" if method_defined? method_name_without_statsd
|
93
|
+
raise ArgumentError, "could not find method #{method} for #{self.name}" unless method_defined?(method) || private_method_defined?(method)
|
94
|
+
|
95
|
+
alias_method method_name_without_statsd, method
|
96
|
+
yield method_name_without_statsd, method_name_with_statsd, metric_name
|
97
|
+
alias_method method, method_name_with_statsd
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# glork:320|ms
|
102
|
+
def self.measure(key, milli = nil)
|
103
|
+
result = nil
|
104
|
+
ms = milli || Benchmark.ms do
|
105
|
+
result = yield
|
106
|
+
end
|
107
|
+
|
108
|
+
write(key, ms, :ms)
|
109
|
+
result
|
110
|
+
end
|
111
|
+
|
112
|
+
# gorets:1|c
|
113
|
+
def self.increment(key, delta = 1, sample_rate = default_sample_rate)
|
114
|
+
write(key, delta, :incr, sample_rate)
|
115
|
+
end
|
116
|
+
|
117
|
+
# gerbals:30|g
|
118
|
+
def self.gauge(key, amount)
|
119
|
+
write(key, amount, :gauge)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def self.socket
|
125
|
+
@socket ||= UDPSocket.new
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.write(k,v,op, sample_rate = default_sample_rate)
|
129
|
+
return unless enabled
|
130
|
+
return if sample_rate < 1 && rand > sample_rate
|
131
|
+
|
132
|
+
command = "#{self.prefix + '.' if self.prefix}#{k}:#{v}"
|
133
|
+
case op
|
134
|
+
when :incr
|
135
|
+
command << '|c'
|
136
|
+
when :ms
|
137
|
+
command << '|ms'
|
138
|
+
when :gauge
|
139
|
+
command << '|g'
|
140
|
+
end
|
141
|
+
|
142
|
+
command << "|@#{sample_rate}" if sample_rate < 1
|
143
|
+
|
144
|
+
if mode.to_s == 'production'
|
145
|
+
socket_wrapper { socket.send(command, 0, host, port) }
|
146
|
+
else
|
147
|
+
logger.info "[StatsD] #{command}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.socket_wrapper(options = {})
|
152
|
+
TimeoutClass.timeout(options.fetch(:timeout, 0.1)) { yield }
|
153
|
+
rescue Timeout::Error, SocketError, IOError, SystemCallError => e
|
154
|
+
logger.error e
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "jashmenn-statsd-instrument"
|
3
|
+
s.version = '1.1.3'
|
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,230 @@
|
|
1
|
+
# Allow testing with the SystemTimer gem on 1.8
|
2
|
+
if RUBY_VERSION =~ /^1.8/
|
3
|
+
puts "Loading SystemTimer gem"
|
4
|
+
require 'system_timer'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'statsd-instrument'
|
8
|
+
require 'test/unit'
|
9
|
+
require 'mocha'
|
10
|
+
require 'logger'
|
11
|
+
|
12
|
+
StatsD.logger = Logger.new('/dev/null')
|
13
|
+
|
14
|
+
module ActiveMerchant; end
|
15
|
+
class ActiveMerchant::Base
|
16
|
+
def ssl_post(arg)
|
17
|
+
if arg
|
18
|
+
'OK'
|
19
|
+
else
|
20
|
+
raise 'Not OK'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ActiveMerchant::Gateway < ActiveMerchant::Base
|
26
|
+
def purchase(arg)
|
27
|
+
ssl_post(arg)
|
28
|
+
true
|
29
|
+
rescue
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.sync
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.singleton_class
|
38
|
+
class << self; self; end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ActiveMerchant::UniqueGateway < ActiveMerchant::Base
|
43
|
+
def ssl_post(arg)
|
44
|
+
{:success => arg}
|
45
|
+
end
|
46
|
+
|
47
|
+
def purchase(arg)
|
48
|
+
ssl_post(arg)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ActiveMerchant::Base.extend StatsD::Instrument
|
53
|
+
|
54
|
+
class StatsDTest < Test::Unit::TestCase
|
55
|
+
def setup
|
56
|
+
StatsD.mode = nil
|
57
|
+
StatsD.stubs(:increment)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_statsd_count_if
|
61
|
+
ActiveMerchant::Gateway.statsd_count_if :ssl_post, 'ActiveMerchant.Gateway.if'
|
62
|
+
|
63
|
+
StatsD.expects(:increment).with(includes('if')).once
|
64
|
+
ActiveMerchant::Gateway.new.purchase(true)
|
65
|
+
ActiveMerchant::Gateway.new.purchase(false)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_statsd_count_if_with_block
|
69
|
+
ActiveMerchant::UniqueGateway.statsd_count_if :ssl_post, 'ActiveMerchant.Gateway.block' do |result|
|
70
|
+
result[:success]
|
71
|
+
end
|
72
|
+
|
73
|
+
StatsD.expects(:increment).with(includes('block')).once
|
74
|
+
ActiveMerchant::UniqueGateway.new.purchase(true)
|
75
|
+
ActiveMerchant::UniqueGateway.new.purchase(false)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_statsd_count_success
|
79
|
+
ActiveMerchant::Gateway.statsd_count_success :ssl_post, 'ActiveMerchant.Gateway'
|
80
|
+
|
81
|
+
StatsD.expects(:increment).with(includes('success'))
|
82
|
+
ActiveMerchant::Gateway.new.purchase(true)
|
83
|
+
|
84
|
+
StatsD.expects(:increment).with(includes('failure'))
|
85
|
+
ActiveMerchant::Gateway.new.purchase(false)
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_statsd_count_success_with_block
|
89
|
+
ActiveMerchant::UniqueGateway.statsd_count_success :ssl_post, 'ActiveMerchant.Gateway' do |result|
|
90
|
+
result[:success]
|
91
|
+
end
|
92
|
+
|
93
|
+
StatsD.expects(:increment).with(includes('success'))
|
94
|
+
ActiveMerchant::UniqueGateway.new.purchase(true)
|
95
|
+
|
96
|
+
StatsD.expects(:increment).with(includes('failure'))
|
97
|
+
ActiveMerchant::UniqueGateway.new.purchase(false)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_statsd_count
|
101
|
+
ActiveMerchant::Gateway.statsd_count :ssl_post, 'ActiveMerchant.Gateway.ssl_post'
|
102
|
+
|
103
|
+
StatsD.expects(:increment).with(includes('ssl_post'))
|
104
|
+
ActiveMerchant::Gateway.new.purchase(true)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_statsd_measure
|
108
|
+
ActiveMerchant::UniqueGateway.statsd_measure :ssl_post, 'ActiveMerchant.Gateway.ssl_post'
|
109
|
+
|
110
|
+
StatsD.expects(:write).with('ActiveMerchant.Gateway.ssl_post', is_a(Float), :ms).returns({:success => true})
|
111
|
+
ActiveMerchant::UniqueGateway.new.purchase(true)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_instrumenting_class_method
|
115
|
+
ActiveMerchant::Gateway.singleton_class.extend StatsD::Instrument
|
116
|
+
ActiveMerchant::Gateway.singleton_class.statsd_count :sync, 'ActiveMerchant.Gateway.sync'
|
117
|
+
|
118
|
+
StatsD.expects(:increment).with(includes('sync'))
|
119
|
+
ActiveMerchant::Gateway.sync
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_count_with_sampling
|
123
|
+
StatsD.unstub(:increment)
|
124
|
+
StatsD.stubs(:rand).returns(0.6)
|
125
|
+
StatsD.logger.expects(:info).never
|
126
|
+
|
127
|
+
StatsD.increment('sampling.foo.bar', 1, 0.1)
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_count_with_successful_sample
|
131
|
+
StatsD.unstub(:increment)
|
132
|
+
StatsD.stubs(:rand).returns(0.01)
|
133
|
+
StatsD.logger.expects(:info).once.with do |string|
|
134
|
+
string.include?('@0.1')
|
135
|
+
end
|
136
|
+
|
137
|
+
StatsD.increment('sampling.foo.bar', 1, 0.1)
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_production_mode_should_use_udp_socket
|
141
|
+
StatsD.unstub(:increment)
|
142
|
+
|
143
|
+
StatsD.mode = :production
|
144
|
+
StatsD.server = 'localhost:123'
|
145
|
+
UDPSocket.any_instance.expects(:send)
|
146
|
+
|
147
|
+
StatsD.increment('fooz')
|
148
|
+
StatsD.mode = :test
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_should_not_write_when_disabled
|
152
|
+
StatsD.enabled = false
|
153
|
+
StatsD.expects(:logger).never
|
154
|
+
StatsD.increment('fooz')
|
155
|
+
StatsD.enabled = true
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_statsd_mode
|
159
|
+
StatsD.unstub(:increment)
|
160
|
+
StatsD.logger.expects(:info).once
|
161
|
+
StatsD.expects(:socket_wrapper).twice
|
162
|
+
StatsD.mode = :foo
|
163
|
+
StatsD.increment('foo')
|
164
|
+
StatsD.mode = :production
|
165
|
+
StatsD.increment('foo')
|
166
|
+
StatsD.mode = 'production'
|
167
|
+
StatsD.increment('foo')
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_statsd_prefix
|
171
|
+
StatsD.unstub(:increment)
|
172
|
+
StatsD.prefix = 'my_app'
|
173
|
+
StatsD.logger.expects(:info).once.with do |string|
|
174
|
+
string.include?('my_app.foo')
|
175
|
+
end
|
176
|
+
StatsD.logger.expects(:info).once.with do |string|
|
177
|
+
string.include?('food')
|
178
|
+
end
|
179
|
+
StatsD.increment('foo')
|
180
|
+
StatsD.prefix = nil
|
181
|
+
StatsD.increment('food')
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_statsd_measure_with_explicit_value
|
185
|
+
StatsD.expects(:write).with('values.foobar', 42, :ms)
|
186
|
+
|
187
|
+
StatsD.measure('values.foobar', 42)
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_socket_error_should_not_raise
|
191
|
+
StatsD.mode = :production
|
192
|
+
UDPSocket.any_instance.expects(:send).raises(SocketError)
|
193
|
+
StatsD.measure('values.foobar', 42)
|
194
|
+
StatsD.mode = :test
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_timeout_error_should_not_raise
|
198
|
+
StatsD.mode = :production
|
199
|
+
UDPSocket.any_instance.expects(:send).raises(Timeout::Error)
|
200
|
+
StatsD.measure('values.foobar', 42)
|
201
|
+
StatsD.mode = :test
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_system_call_error_should_not_raise
|
205
|
+
StatsD.mode = :production
|
206
|
+
UDPSocket.any_instance.expects(:send).raises(Errno::ETIMEDOUT)
|
207
|
+
StatsD.measure('values.foobar', 42)
|
208
|
+
StatsD.mode = :test
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_io_error_should_not_raise
|
212
|
+
StatsD.mode = :production
|
213
|
+
UDPSocket.any_instance.expects(:send).raises(IOError)
|
214
|
+
StatsD.measure('values.foobar', 42)
|
215
|
+
StatsD.mode = :test
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_long_request_should_timeout
|
219
|
+
StatsD.mode = :production
|
220
|
+
UDPSocket.any_instance.expects(:send).yields do
|
221
|
+
begin
|
222
|
+
Timeout.timeout(0.5) { sleep 1 }
|
223
|
+
rescue Timeout::Error
|
224
|
+
raise "Allowed long running request"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
StatsD.measure('values.foobar', 42)
|
228
|
+
StatsD.mode = :test
|
229
|
+
end
|
230
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jashmenn-statsd-instrument
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 1
|
8
|
+
- 3
|
9
|
+
version: 1.1.3
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jesse Storimer
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-04-05 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: mocha
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
description: A StatsD client for Ruby apps. Provides metaprogramming methods to inject StatsD instrumentation into your code.
|
33
|
+
email:
|
34
|
+
- jesse@shopify.com
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files: []
|
40
|
+
|
41
|
+
files:
|
42
|
+
- .gitignore
|
43
|
+
- .travis.yml
|
44
|
+
- Gemfile
|
45
|
+
- LICENSE
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- lib/statsd-instrument.rb
|
49
|
+
- lib/statsd/instrument.rb
|
50
|
+
- statsd-instrument.gemspec
|
51
|
+
- test/statsd-instrument_test.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/shopify/statsd-instrument
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.6
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: A StatsD client for Ruby apps
|
82
|
+
test_files:
|
83
|
+
- test/statsd-instrument_test.rb
|