qubole-statsd-instrument 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +12 -0
  4. data/CHANGELOG.md +89 -0
  5. data/CONTRIBUTING.md +34 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +20 -0
  8. data/README.md +319 -0
  9. data/Rakefile +10 -0
  10. data/lib/statsd/instrument/assertions.rb +88 -0
  11. data/lib/statsd/instrument/backend.rb +17 -0
  12. data/lib/statsd/instrument/backends/capture_backend.rb +29 -0
  13. data/lib/statsd/instrument/backends/logger_backend.rb +20 -0
  14. data/lib/statsd/instrument/backends/null_backend.rb +7 -0
  15. data/lib/statsd/instrument/backends/udp_backend.rb +106 -0
  16. data/lib/statsd/instrument/environment.rb +54 -0
  17. data/lib/statsd/instrument/helpers.rb +14 -0
  18. data/lib/statsd/instrument/matchers.rb +96 -0
  19. data/lib/statsd/instrument/metric.rb +117 -0
  20. data/lib/statsd/instrument/metric_expectation.rb +67 -0
  21. data/lib/statsd/instrument/railtie.rb +14 -0
  22. data/lib/statsd/instrument/version.rb +5 -0
  23. data/lib/statsd/instrument.rb +407 -0
  24. data/lib/statsd-instrument.rb +1 -0
  25. data/shipit.rubygems.yml +1 -0
  26. data/statsd-instrument.gemspec +27 -0
  27. data/test/assertions_test.rb +329 -0
  28. data/test/benchmark/tags.rb +34 -0
  29. data/test/capture_backend_test.rb +24 -0
  30. data/test/environment_test.rb +46 -0
  31. data/test/helpers_test.rb +24 -0
  32. data/test/integration_test.rb +20 -0
  33. data/test/logger_backend_test.rb +20 -0
  34. data/test/matchers_test.rb +102 -0
  35. data/test/metric_test.rb +45 -0
  36. data/test/statsd_instrumentation_test.rb +328 -0
  37. data/test/statsd_test.rb +136 -0
  38. data/test/test_helper.rb +10 -0
  39. data/test/udp_backend_test.rb +167 -0
  40. metadata +182 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0694d4ff0f7d5f512163a9b3bfc818a91bdda979
4
+ data.tar.gz: 7b589905601377964502e99086d82015af0ff333
5
+ SHA512:
6
+ metadata.gz: eb4fc7372e4d8c70bb5a0431ca1c5621837e804eb2411dcc7b0719b703b1babae02a3fa8926af7550135a512692531e68ca7a6e8c366e5a793d3334fe7ed72ac
7
+ data.tar.gz: 18f7e78a40037fe3a410ecb7652fe2af190c6151e9b18b7af6229ceac11b080c66cce8e140bf630c294f31f49c56135f41ea39c4b3c097839cf6ca8d2f5f861f
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .yardoc
2
+ doc
3
+ *.gem
4
+ .bundle
5
+ Gemfile.lock
6
+ pkg/*
7
+ vendor/
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.10
4
+ - 2.2.6
5
+ - 2.3.3
6
+ - ruby-head
7
+
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
11
+
12
+ sudo: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,89 @@
1
+ # Changelog
2
+
3
+ This file documents the changes between releases of this library. When creating a pull request,
4
+ please at an entry to the "unreleased changes" section below.
5
+
6
+ ### Unreleased changes
7
+
8
+ ### Version 2.1.3
9
+
10
+ - The `assert_statsd_calls` test helper will now raise an exception whenever a block isn't passed.
11
+ - Sending stats inside an exit handler will no longer cause programs to exit abruptly.
12
+
13
+ ### Version 2.1.2
14
+
15
+ - Use `prepend` instead of rewriting classes for metaprogramming methods.
16
+ - RSpec: make matchers more flexible.
17
+ - Bugfix: Only ask Rails for the environment when it's actually loaded.
18
+
19
+ ### Version 2.1.1
20
+
21
+ - Add `assert_statsd_calls` to from validating cases where one has multiple metrics with the same name and type being recorded, but with different options.
22
+
23
+ ### Version 2.1.0
24
+
25
+ - Fix rspec-rails compatibility
26
+ - Add `value` keyword argument to all metric types.
27
+
28
+ ### Version 2.0.12
29
+
30
+ - Make StatsD client thread-safe
31
+ - Assertions: Ensure sample rates have proper values.
32
+ - Assertions: Make tag assertions work more intuitively
33
+ - RSpec: Add backwards compatibility for RSpec 2
34
+
35
+ ### Version 2.0.11
36
+
37
+ - Don't change method visibility when adding instrumentation to methods using metaprogramming
38
+ - RSpec: add support for Compound expectations
39
+
40
+ ### Version 2.0.10
41
+
42
+ - Assertions: allow ignoring certain tags when asserting for other tags to be present.
43
+
44
+ ### Version 2.0.9
45
+
46
+ - Better error message for `assert_no_statsd_calls`
47
+
48
+ ### Version 2.0.8
49
+
50
+ - More tag handling performance improvements.
51
+ - RSpec matchers documentation improvements
52
+
53
+ ### Version 2.0.7
54
+
55
+ - Tag handling performance improvements.
56
+ - Test against Ruby 2.2.
57
+ - Drop support for Ruby 1.9.3.
58
+
59
+ ### Version 2.0.6
60
+
61
+ - Fix some loading order issues in Rails environments.
62
+ - Default behavior: in a **staging** environment, the defaults are now the same as in a **production environment**.
63
+ - Documentation overhaul
64
+
65
+ ### Version 2.0.5
66
+
67
+ - Allow for nested assertions using the `assert_statsd_*` assertion methods.
68
+
69
+ ### Version 2.0.4
70
+
71
+ - Add a Railtie to fix some initialization issues.
72
+
73
+ ### Version 2.0.3
74
+
75
+ - Assertion method bugfixes
76
+
77
+ ### Version 2.0.2
78
+
79
+ - Documentation fixes
80
+
81
+ ### Version 2.0.1
82
+
83
+ - Add assertion methods `assert_statsd_histogram`, `assert_statsd_set`, and `assert_statsd_key_value`.
84
+
85
+ ### Version 2.0.0
86
+
87
+ - Complete rewrite using pluggable backends.
88
+ - Add assertion methods in `StatsD::Instrument::Assertions` to make testing easier and less brittle.
89
+ - Drop support for Ruby 1.8
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,34 @@
1
+ # Contributing
2
+
3
+ This project is MIT licensed and welcomes outside contributions.
4
+
5
+ ## Reporting issues
6
+
7
+ Report issues using the [Github issues tracker](https://github.com/Shopify/statsd-instrument/issues/new).
8
+
9
+ When reporting issues, please incldue the following information:
10
+
11
+ - Your Ruby interpreter version.
12
+ - The statsd-instrument version. **Note:** only the latest version is supported.
13
+ - The StatsD backend you are using.
14
+
15
+ ## Pull request
16
+
17
+ 1. Fork the repository, and create a branch.
18
+ 2. Implement the feature or bugfix, and add tests that cover the changed functionality.
19
+ 3. Create a pull request. Make sure that you get Travis CI passes.
20
+ 4. Ping **@jstorimer** and/or **@wvanbergen** for a code review.
21
+
22
+ Some notes:
23
+
24
+ - Make sure to follow to coding style.
25
+ - Make sure your changes are properly documented using [yardoc syntax](http://www.rubydoc.info/gems/yard/file/docs/GettingStarted.md).
26
+ - Add an entry to the "unreleased changes" section of [CHANGELOG.md](./CHANGELOG.md).
27
+ - **Do not** update `StatsD::Instrument::VERSION`. This will be done during the release prodecure.
28
+
29
+ ## Release procedure
30
+
31
+ 1. Update the version number in `lib/statsd/instrument/version.rb`.
32
+ 2. Move the "Unreleased changes" items in [CHANGELOG.md](./CHANGELOG.md) to a new section for the release.
33
+ 3. Commit these changes.
34
+ 4. Run `bundle exec rake release`.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
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,319 @@
1
+ # StatsD client for Ruby apps
2
+
3
+ [![Built on Travis](https://secure.travis-ci.org/Shopify/statsd-instrument.png?branch=master)](https://secure.travis-ci.org/Shopify/statsd-instrument)
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 its 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
+ For more information about StatsD, see the [README of the Etsy project](http://github.com/etsy/statsd).
10
+
11
+ ## Configuration
12
+
13
+ The library comes with different backends. Based on your environment (detected using environment
14
+ variables), it will select one of the following backends by default:
15
+
16
+ - **Production** and **staging** environment: `StatsD::Instrument::Backends::UDPBackend` will actually send UDP packets.
17
+ It will configure itself using environment variables: it uses `STATSD_ADDR` for the address to connect
18
+ to (default `"localhost:8125"`), and `STATSD_IMPLEMENTATION` to set the protocol variant. (See below)
19
+ - **Test** environment: `StatsD::Instrument::Backends::NullBackend` will swallow all calls. See below for
20
+ notes on writing tests.
21
+ - **Development**, and all other, environments: `StatsD::Instrument::Backends::LoggerBackend` will log all
22
+ calls to stdout.
23
+
24
+ You can override the currently active backend by setting `StatsD.backend`:
25
+
26
+ ``` ruby
27
+ # Sets up a UDP backend. First argument is the UDP address to send StatsD packets to,
28
+ # second argument specifies the protocol variant (i.e. `:statsd`, `:statsite`, or `:datadog`).
29
+ StatsD.backend = StatsD::Instrument::Backends::UDPBackend.new("1.2.3.4:8125", :statsite)
30
+
31
+ # Sets up a logger backend
32
+ StatsD.backend = StatsD::Instrument::Backends::LoggerBackend.new(Rails.logger)
33
+ ```
34
+
35
+ The other available settings, with their default, are
36
+
37
+ ``` ruby
38
+ # Logger to which commands are logged when using the LoggerBackend, which is
39
+ # the default in development environment. Also, any errors or warnings will
40
+ # be logged here.
41
+ StatsD.logger = defined?(Rails) ? Rails.logger : Logger.new($stderr)
42
+
43
+ # An optional prefix to be added to each metric.
44
+ StatsD.prefix = nil # but can be set to any string
45
+
46
+ # Sample 10% of events. By default all events are reported, which may overload your network or server.
47
+ # You can, and should vary this on a per metric basis, depending on frequency and accuracy requirements
48
+ StatsD.default_sample_rate = (ENV['STATSD_SAMPLE_RATE'] || 0.1 ).to_f
49
+ ```
50
+
51
+ ## StatsD keys
52
+
53
+ StatsD keys look like 'admin.logins.api.success'. Dots are used as namespace separators.
54
+ In Graphite, they will show up as folders.
55
+
56
+ ## Usage
57
+
58
+ You can either use the basic methods to submit stats over StatsD, or you can use the metaprogramming methods to instrument your methods with some basic stats (call counts, successes & failures, and timings).
59
+
60
+ #### StatsD.measure
61
+
62
+ Lets you benchmark how long the execution of a specific method takes.
63
+
64
+ ``` ruby
65
+ # You can pass a key and a ms value
66
+ StatsD.measure('GoogleBase.insert', 2.55)
67
+
68
+ # or more commonly pass a block that calls your code
69
+ StatsD.measure('GoogleBase.insert') do
70
+ GoogleBase.insert(product)
71
+ end
72
+ ```
73
+
74
+ #### StatsD.increment
75
+
76
+ 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.
77
+
78
+ ``` ruby
79
+ # increments default to +1
80
+ StatsD.increment('GoogleBase.insert')
81
+ # you can also specify how much to increment the key by
82
+ StatsD.increment('GoogleBase.insert', 10)
83
+ # you can also specify a sample rate, so only 1/10 of events
84
+ # actually get to statsd. Useful for very high volume data
85
+ StatsD.increment('GoogleBase.insert', 1, sample_rate: 0.1)
86
+ ```
87
+
88
+ #### StatsD.gauge
89
+
90
+ A gauge is a single numerical value that tells you the state of the system at a point in time. A good example would be the number of messages in a queue.
91
+
92
+ ``` ruby
93
+ StatsD.gauge('GoogleBase.queued', 12, sample_rate: 1.0)
94
+ ```
95
+
96
+ Normally, you shouldn't update this value too often, and therefore there is no need to sample this kind metric.
97
+
98
+ #### StatsD.set
99
+
100
+ A set keeps track of the number of unique values that have been seen. This is a good fit for keeping track of the number of unique visitors. The value can be a string.
101
+
102
+ ``` ruby
103
+ # Submit the customer ID to the set. It will only be counted if it hasn't been seen before.
104
+ StatsD.set('GoogleBase.customers', "12345", sample_rate: 1.0)
105
+ ```
106
+
107
+ Because you are counting unique values, the results of using a sampling value less than 1.0 can lead to unexpected, hard to interpret results.
108
+
109
+ ### Metaprogramming Methods
110
+
111
+ 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.
112
+
113
+ ``` ruby
114
+ GoogleBase.extend StatsD::Instrument
115
+ ```
116
+
117
+ Then use the methods provided below to instrument methods in your class.
118
+
119
+ #### statsd\_measure
120
+
121
+ This will measure how long a method takes to run, and submits the result to the given key.
122
+
123
+ ``` ruby
124
+ GoogleBase.statsd_measure :insert, 'GoogleBase.insert'
125
+ ```
126
+
127
+ #### statsd\_count
128
+
129
+ This will increment the given key even if the method doesn't finish (ie. raises).
130
+
131
+ ``` ruby
132
+ GoogleBase.statsd_count :insert, 'GoogleBase.insert'
133
+ ```
134
+
135
+ 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.
136
+
137
+ #### statsd\_count\_if
138
+
139
+ This will only increment the given key if the method executes successfully.
140
+
141
+ ``` ruby
142
+ GoogleBase.statsd_count_if :insert, 'GoogleBase.insert'
143
+ ```
144
+
145
+ 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.
146
+
147
+ ``` ruby
148
+ GoogleBase.statsd_count_if :insert, 'GoogleBase.insert' do |response|
149
+ response.code == 200
150
+ end
151
+ ```
152
+
153
+ 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.
154
+
155
+ #### statsd\_count\_success
156
+
157
+ Similar to statsd_count_if, except this will increment one key in the case of success and another key in the case of failure.
158
+
159
+ ``` ruby
160
+ GoogleBase.statsd_count_success :insert, 'GoogleBase.insert'
161
+ ```
162
+
163
+ 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.
164
+
165
+ Again you can pass a block to define what success means.
166
+
167
+ ``` ruby
168
+ GoogleBase.statsd_count_success :insert, 'GoogleBase.insert' do |response|
169
+ response.code == 200
170
+ end
171
+ ```
172
+
173
+ ### Instrumenting Class Methods
174
+
175
+ 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.
176
+
177
+ ```ruby
178
+ AWS::S3::Base.singleton_class.statsd_measure :request, 'S3.request'
179
+ ```
180
+
181
+ ### Dynamic Metric Names
182
+
183
+ You can use a lambda function instead of a string dynamically set
184
+ the name of the metric. The lambda function must accept two arguments:
185
+ the object the function is being called on and the array of arguments
186
+ passed.
187
+
188
+ ```ruby
189
+ GoogleBase.statsd_count :insert, lambda{|object, args| object.class.to_s.downcase + "." + args.first.to_s + ".insert" }
190
+ ```
191
+
192
+ ### Tags
193
+
194
+ The Datadog implementation support tags, which you can use to slice and dice metrics in their UI. You can specify a list of tags as an option, either standalone tag (e.g. `"mytag"`), or key value based, separated by a colon: `"env:production"`.
195
+
196
+ ``` ruby
197
+ StatsD.increment('my.counter', tags: ['env:production', 'unicorn'])
198
+ GoogleBase.statsd_count :insert, 'GoogleBase.insert', tags: ['env:production']
199
+ ```
200
+
201
+ If implementation is not set to `:datadog`, tags will not be included in the UDP packets, and a
202
+ warning is logged to `StatsD.logger`.
203
+
204
+ ## Testing
205
+
206
+ This library comes with a module called `StatsD::Instrument::Assertions` and `StatsD::Instrument::Matchers` to help you write tests
207
+ to verify StatsD is called properly.
208
+
209
+ ### minitest
210
+
211
+ ``` ruby
212
+ class MyTestcase < Minitest::Test
213
+ include StatsD::Instrument::Assertions
214
+
215
+ def test_some_metrics
216
+ # This will pass if there is exactly one matching StatsD call
217
+ # it will ignore any other, non matching calls.
218
+ assert_statsd_increment('counter.name', sample_rate: 1.0) do
219
+ StatsD.increment('unrelated') # doesn't match
220
+ StatsD.increment('counter.name', sample_rate: 1.0) # matches
221
+ StatsD.increment('counter.name', sample_rate: 0.1) # doesn't match
222
+ end
223
+
224
+ # Set `times` if there will be multiple matches:
225
+ assert_statsd_increment('counter.name', times: 2) do
226
+ StatsD.increment('unrelated') # doesn't match
227
+ StatsD.increment('counter.name', sample_rate: 1.0) # matches
228
+ StatsD.increment('counter.name', sample_rate: 0.1) # matches too
229
+ end
230
+ end
231
+
232
+ def test_no_udp_traffic
233
+ # Verifies no StatsD calls occured at all.
234
+ assert_no_statsd_calls do
235
+ do_some_work
236
+ end
237
+
238
+ # Verifies no StatsD calls occured for the given metric.
239
+ assert_no_statsd_calls('metric_name') do
240
+ do_some_work
241
+ end
242
+ end
243
+
244
+ def test_more_complicated_stuff
245
+ # capture_statsd_calls will capture all the StatsD calls in the
246
+ # given block, and returns them as an array. You can then run your
247
+ # own assertions on it.
248
+ metrics = capture_statsd_calls do
249
+ StatsD.increment('mycounter', sample_rate: 0.01)
250
+ end
251
+
252
+ assert_equal 1, metrics.length
253
+ assert_equal 'mycounter', metrics[0].name
254
+ assert_equal :c, metrics[0].type
255
+ assert_equal 1, metrics[0].value
256
+ assert_equal 0.01, metrics[0].sample_rate
257
+ end
258
+ end
259
+
260
+ ```
261
+
262
+ ### RSpec
263
+
264
+ ```ruby
265
+ RSpec.configure do |config|
266
+ config.include StatsD::Instrument::Matchers
267
+ end
268
+
269
+ RSpec.describe 'Matchers' do
270
+ context 'trigger_statsd_increment' do
271
+ it 'will pass if there is exactly one matching StatsD call' do
272
+ expect { StatsD.increment('counter') }.to trigger_statsd_increment('counter')
273
+ end
274
+
275
+ it 'will pass if it matches the correct number of times' do
276
+ expect {
277
+ 2.times do
278
+ StatsD.increment('counter')
279
+ end
280
+ }.to trigger_statsd_increment('counter', times: 2)
281
+ end
282
+
283
+ it 'will pass if it matches argument' do
284
+ expect {
285
+ StatsD.measure('counter', 0.3001)
286
+ }.to trigger_statsd_measure('counter', value: be_between(0.29, 0.31))
287
+ end
288
+
289
+ it 'will pass if there is no matching StatsD call on negative expectation' do
290
+ expect { StatsD.increment('other_counter') }.not_to trigger_statsd_increment('counter')
291
+ end
292
+ end
293
+ end
294
+ ```
295
+
296
+ ## Notes
297
+
298
+ ### Compatibility
299
+
300
+ Tested using Travis CI against Ruby 2.0, 2.1, 2.2, Rubinius, and JRuby.
301
+
302
+ ### Reliance on DNS
303
+
304
+ Out of the box StatsD is set up to be unidirectional fire-and-forget over UDP. Configuring
305
+ the StatsD host to be a non-ip will trigger a DNS lookup (i.e. a synchronous TCP round trip).
306
+ This can be particularly problematic in clouds that have a shared DNS infrastructure such as AWS.
307
+
308
+ 1. Using a hardcoded IP avoids the DNS lookup but generally requires an application deploy to change.
309
+ 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.
310
+ 3. Installing caching software such as nscd that uses the DNS TTL avoids most DNS lookups but makes the exact moment of change indeterminate.
311
+
312
+
313
+ ## Links
314
+
315
+ This library was developed for shopify.com and is MIT licensed.
316
+
317
+ - [API documentation](http://www.rubydoc.info/gems/statsd-instrument/frames)
318
+ - [The changelog](./CHANGELOG.md) covers the changes between releases.
319
+ - [Contributing notes](./CONTRIBUTING.md) if you are interested in contributing to this library.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new('test') do |t|
5
+ t.ruby_opts << '-rubygems'
6
+ t.libs << 'lib' << 'test'
7
+ t.test_files = FileList['test/*.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,88 @@
1
+ module StatsD::Instrument::Assertions
2
+ include StatsD::Instrument::Helpers
3
+
4
+ def assert_no_statsd_calls(metric_name = nil, &block)
5
+ metrics = capture_statsd_calls(&block)
6
+ metrics.select! { |m| m.name == metric_name } if metric_name
7
+ assert metrics.empty?, "No StatsD calls for metric #{metrics.map(&:name).join(', ')} expected."
8
+ end
9
+
10
+ def assert_statsd_increment(metric_name, options = {}, &block)
11
+ assert_statsd_call(:c, metric_name, options, &block)
12
+ end
13
+
14
+ def assert_statsd_measure(metric_name, options = {}, &block)
15
+ assert_statsd_call(:ms, metric_name, options, &block)
16
+ end
17
+
18
+ def assert_statsd_gauge(metric_name, options = {}, &block)
19
+ assert_statsd_call(:g, metric_name, options, &block)
20
+ end
21
+
22
+ def assert_statsd_histogram(metric_name, options = {}, &block)
23
+ assert_statsd_call(:h, metric_name, options, &block)
24
+ end
25
+
26
+ def assert_statsd_set(metric_name, options = {}, &block)
27
+ assert_statsd_call(:s, metric_name, options, &block)
28
+ end
29
+
30
+ def assert_statsd_key_value(metric_name, options = {}, &block)
31
+ assert_statsd_call(:kv, metric_name, options, &block)
32
+ end
33
+
34
+ # @private
35
+ def assert_statsd_calls(expected_metrics, &block)
36
+ unless block
37
+ raise ArgumentError, "block must be given"
38
+ end
39
+
40
+ metrics = capture_statsd_calls(&block)
41
+ matched_expected_metrics = []
42
+
43
+ expected_metrics.each do |expected_metric|
44
+ expected_metric_times = expected_metric.times
45
+ expected_metric_times_remaining = expected_metric.times
46
+ filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
47
+ assert filtered_metrics.length > 0,
48
+ "No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made."
49
+
50
+ filtered_metrics.each do |metric|
51
+ assert within_numeric_range?(metric.sample_rate),
52
+ "Unexpected sample rate type for metric #{metric.name}, must be numeric"
53
+ if expected_metric.matches(metric)
54
+ assert expected_metric_times_remaining > 0,
55
+ "Unexpected StatsD call; number of times this metric was expected exceeded: #{expected_metric.inspect}"
56
+ expected_metric_times_remaining -= 1
57
+ metrics.delete(metric)
58
+ if expected_metric_times_remaining == 0
59
+ matched_expected_metrics << expected_metric
60
+ end
61
+ end
62
+ end
63
+
64
+ assert expected_metric_times_remaining == 0,
65
+ "Metric expected #{expected_metric_times} times but seen"\
66
+ " #{expected_metric_times-expected_metric_times_remaining}"\
67
+ " times: #{expected_metric.inspect}"
68
+ end
69
+ expected_metrics -= matched_expected_metrics
70
+
71
+ assert expected_metrics.empty?,
72
+ "Unexpected StatsD calls; the following metric expectations were not satisfied: #{expected_metrics.inspect}"
73
+ end
74
+
75
+ private
76
+
77
+ def assert_statsd_call(metric_type, metric_name, options = {}, &block)
78
+ options[:name] = metric_name
79
+ options[:type] = metric_type
80
+ options[:times] ||= 1
81
+ expected_metric = StatsD::Instrument::MetricExpectation.new(options)
82
+ assert_statsd_calls([expected_metric], &block)
83
+ end
84
+
85
+ def within_numeric_range?(object)
86
+ object.kind_of?(Numeric) && (0.0..1.0).cover?(object)
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ # This abstract class specifies the interface a backend implementation should conform to.
2
+ # @abstract
3
+ class StatsD::Instrument::Backend
4
+
5
+ # Collects a metric.
6
+ #
7
+ # @param metric [StatsD::Instrument::Metric] The metric to collect
8
+ # @return [void]
9
+ def collect_metric(metric)
10
+ raise NotImplementedError, "Use a concerete backend implementation"
11
+ end
12
+ end
13
+
14
+ require 'statsd/instrument/backends/logger_backend'
15
+ require 'statsd/instrument/backends/null_backend'
16
+ require 'statsd/instrument/backends/capture_backend'
17
+ require 'statsd/instrument/backends/udp_backend'
@@ -0,0 +1,29 @@
1
+ module StatsD::Instrument::Backends
2
+
3
+ # The capture backend is used to capture the metrics that are collected, so you can
4
+ # run assertions on them.
5
+ #
6
+ # @!attribute collected_metrics [r]
7
+ # @return [Array<StatsD::Instrument::Metric>] The list of metrics that were collected.
8
+ # @see StatsD::Instrument::Assertions
9
+ class CaptureBackend < StatsD::Instrument::Backend
10
+ attr_reader :collected_metrics
11
+
12
+ def initialize
13
+ reset
14
+ end
15
+
16
+ # Adds a metric to the ist of collected metrics.
17
+ # @param metric [StatsD::Instrument::Metric] The metric to collect.
18
+ # @return [void]
19
+ def collect_metric(metric)
20
+ @collected_metrics << metric
21
+ end
22
+
23
+ # Resets the list of collected metrics to an empty list.
24
+ # @return [void]
25
+ def reset
26
+ @collected_metrics = []
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module StatsD::Instrument::Backends
2
+
3
+ # The logger backend simply logs every metric to a logger
4
+ # @!attribute logger
5
+ # @return [Logger]
6
+ class LoggerBackend < StatsD::Instrument::Backend
7
+
8
+ attr_accessor :logger
9
+
10
+ def initialize(logger)
11
+ @logger = logger
12
+ end
13
+
14
+ # @param metric [StatsD::Instrument::Metric]
15
+ # @return [void]
16
+ def collect_metric(metric)
17
+ logger.info "[StatsD] #{metric}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module StatsD::Instrument::Backends
2
+ # The null backend does nothing when receiving a metric, effectively disabling the gem completely.
3
+ class NullBackend < StatsD::Instrument::Backend
4
+ def collect_metric(metric)
5
+ end
6
+ end
7
+ end