statsd-instrument 2.5.1 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1 -1
  3. data/.rubocop.yml +11 -6
  4. data/.yardopts +5 -0
  5. data/CHANGELOG.md +75 -6
  6. data/README.md +54 -46
  7. data/benchmark/datagram-client +41 -0
  8. data/lib/statsd/instrument/assertions.rb +168 -57
  9. data/lib/statsd/instrument/backends/udp_backend.rb +20 -29
  10. data/lib/statsd/instrument/capture_sink.rb +27 -0
  11. data/lib/statsd/instrument/client.rb +313 -0
  12. data/lib/statsd/instrument/datagram.rb +75 -0
  13. data/lib/statsd/instrument/datagram_builder.rb +101 -0
  14. data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +71 -0
  15. data/lib/statsd/instrument/environment.rb +106 -29
  16. data/lib/statsd/instrument/log_sink.rb +24 -0
  17. data/lib/statsd/instrument/null_sink.rb +13 -0
  18. data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +39 -0
  19. data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +6 -10
  20. data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +37 -0
  21. data/lib/statsd/instrument/rubocop/metric_return_value.rb +7 -6
  22. data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +11 -20
  23. data/lib/statsd/instrument/rubocop/positional_arguments.rb +13 -13
  24. data/lib/statsd/instrument/rubocop/splat_arguments.rb +8 -14
  25. data/lib/statsd/instrument/rubocop.rb +64 -0
  26. data/lib/statsd/instrument/statsd_datagram_builder.rb +14 -0
  27. data/lib/statsd/instrument/strict.rb +112 -22
  28. data/lib/statsd/instrument/udp_sink.rb +62 -0
  29. data/lib/statsd/instrument/version.rb +1 -1
  30. data/lib/statsd/instrument.rb +191 -100
  31. data/test/assertions_test.rb +139 -176
  32. data/test/capture_sink_test.rb +44 -0
  33. data/test/client_test.rb +164 -0
  34. data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +162 -0
  35. data/test/datagram_builder_test.rb +120 -0
  36. data/test/deprecations_test.rb +56 -10
  37. data/test/dogstatsd_datagram_builder_test.rb +32 -0
  38. data/test/environment_test.rb +73 -7
  39. data/test/log_sink_test.rb +37 -0
  40. data/test/null_sink_test.rb +13 -0
  41. data/test/rubocop/measure_as_dist_argument_test.rb +44 -0
  42. data/test/rubocop/metaprogramming_positional_arguments_test.rb +1 -1
  43. data/test/rubocop/metric_prefix_argument_test.rb +38 -0
  44. data/test/rubocop/metric_return_value_test.rb +1 -1
  45. data/test/rubocop/metric_value_keyword_argument_test.rb +1 -1
  46. data/test/rubocop/positional_arguments_test.rb +1 -1
  47. data/test/rubocop/splat_arguments_test.rb +1 -1
  48. data/test/statsd_datagram_builder_test.rb +22 -0
  49. data/test/statsd_instrumentation_test.rb +0 -24
  50. data/test/statsd_test.rb +0 -23
  51. data/test/test_helper.rb +0 -2
  52. data/test/udp_backend_test.rb +25 -8
  53. data/test/udp_sink_test.rb +85 -0
  54. metadata +38 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 921e9b4ecf3a6a74cfb1460767ba4374121723d9ef2a7bd76b747904a25a7b48
4
- data.tar.gz: b62df176782d7aca1f24608015db7b68b610af386c7c30309517e39de3551dfe
3
+ metadata.gz: 0e0581a0deec7cbdfae1ca0b03d336b3e08cf1d14599087678f1a6a6ec35b321
4
+ data.tar.gz: f10c0f102d13bb71a8365a1cc9707c848af954adb02eea3b8bbb95a9bbc2f0b7
5
5
  SHA512:
6
- metadata.gz: 3d1ff5390d8cd5dc611e42dc4667894a0c82e406d92cd51d6a71ff4cd7ffa1e747e56f8f19d483c28335a4b5a616848356fc23f50787276883aafe17b043316d
7
- data.tar.gz: 542ad17d646c28b691287db6debc18a6386a332aa148d2549c60602aaab0427bb836bd755e701495aec9db856a7fa387ddbea5de3e85f7d5c973ec9d52d3c008
6
+ metadata.gz: 0fc4c808a257a72bf0d86edb1e9ec9ae6c700f80408f83d574e466e1ce7cf2d627aee5e71dc5fdbb998850395881421cba97172db446fc102d04314546d55f60
7
+ data.tar.gz: 748ffe0481def020345c33029a630b266f033d84dcf80429316e0ec95a7394b82e20f82e9ee568efa3f455ac853fcbb3fec99243da482f8af3967069dffae13e
@@ -892,7 +892,7 @@ Lint/FormatParameterMismatch:
892
892
  Enabled: true
893
893
 
894
894
  Lint/HandleExceptions:
895
- Enabled: true
895
+ AllowComments: true
896
896
 
897
897
  Lint/ImplicitStringConcatenation:
898
898
  Description: Checks for adjacent string literals on the same line, which could
data/.rubocop.yml CHANGED
@@ -2,11 +2,7 @@ inherit_from:
2
2
  - https://shopify.github.io/ruby-style-guide/rubocop.yml
3
3
 
4
4
  require:
5
- - ./lib/statsd/instrument/rubocop/metric_return_value.rb
6
- - ./lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb
7
- - ./lib/statsd/instrument/rubocop/positional_arguments.rb
8
- - ./lib/statsd/instrument/rubocop/splat_arguments.rb
9
- - ./lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb
5
+ - ./lib/statsd/instrument/rubocop.rb
10
6
 
11
7
  AllCops:
12
8
  TargetRubyVersion: 2.3
@@ -27,7 +23,10 @@ Style/ClassAndModuleChildren:
27
23
  Style/MethodCallWithArgsParentheses:
28
24
  Enabled: false # TODO: enable later
29
25
 
30
- # Enable our own cops on our own repos
26
+ Lint/UnusedMethodArgument:
27
+ AllowUnusedKeywordArguments: true
28
+
29
+ # Enable our own cops on our own repo
31
30
 
32
31
  StatsD/MetricReturnValue:
33
32
  Enabled: true
@@ -43,3 +42,9 @@ StatsD/SplatArguments:
43
42
 
44
43
  StatsD/MetaprogrammingPositionalArguments:
45
44
  Enabled: true
45
+
46
+ StatsD/MeasureAsDistArgument:
47
+ Enabled: true
48
+
49
+ StatsD/MetricPrefixArgument:
50
+ Enabled: true
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --markup=markdown
2
+ -
3
+ README.md
4
+ CHANGELOG.md
5
+ CONTRIBUTING.md
data/CHANGELOG.md CHANGED
@@ -6,6 +6,75 @@ section below.
6
6
 
7
7
  ### Unreleased changes
8
8
 
9
+ _Nothing yet!_
10
+
11
+ ## Version 2.6.0
12
+
13
+ This release contains a new `StatsD::Instrument::Client` class, which is
14
+ slated to replace the current implementation in the next major release.
15
+
16
+ The main reasons for this rewrite are two folds:
17
+ - Improved performance.
18
+ - Being able to instantiate multiple clients.
19
+
20
+ We have worked hard to make the new client as compatible as possible. However,
21
+ to accomplish some of our goals we have deprecated some stuff that we think
22
+ is unlikely to be used. See the rest of the release notes of this version, and
23
+ version 2.5.0 to see what is deprecated.
24
+
25
+ You can test compatibility with the new client by replacing `StatsD` with
26
+ `StatsD.client`, which points to a client that will be instantiated using
27
+ the same environment variables that you can already use for this library. You
28
+ can also use strict mode, and rubocop rules to check whether you are using any
29
+ deprecated patterns. See below for more info.
30
+
31
+ - **⚠️ DEPRECATION**: Using the `prefix: "foo"` argument for `StatsD.metric`
32
+ calls (and the metaprogramming macros) is deprecated.
33
+
34
+ - You can include the prefix in the metric name.
35
+ - If you want to override the global prefix, set `no_prefix: true` and
36
+ include the desired prefix in the metric name
37
+
38
+ This library ships with a Rubocop rule to detect uses of this keyword
39
+ argument in your codebase:
40
+
41
+ ``` sh
42
+ # Check for the prefix arguments on your StatsD.metric calls
43
+ rubocop --only StatsD/MetricPrefixArgument \
44
+ -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb
45
+ ```
46
+
47
+ Strict mode has also been updated to no longer allow this argument.
48
+
49
+ - **⚠️ DEPRECATION**: Using the `as_dist: true` argument for `StatsD.measure`
50
+ and `statsd_measure` methods is deprecated. This argument was only available
51
+ for internal use, but was exposed in the public API. It is unlikely that you
52
+ are usijng this argumenr, but you can check to make sure using this Rubocop
53
+ invocation:
54
+
55
+ ``` sh
56
+ # Check for the as_dist arguments on your StatsD.measure calls
57
+ rubocop --only StatsD/MeasureAsDistArgument \
58
+ -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop.rb
59
+ ```
60
+
61
+ Strict mode has also been updated to no longer allow this argument.
62
+
63
+ - You can now enable strict mode by setting the `STATSD_STRICT_MODE`
64
+ environment variable. No more need to change your Gemfile! Note that it is
65
+ still not recommended to enable strict mode in production due to the
66
+ performance penalty, but is recommended for development and test. E.g. use
67
+ `STATSD_STRICT_MODE=1 rails test` to run your test suite with strict mode
68
+ enabled to expose any deprecations in your codebase.
69
+
70
+ - Add support for `STATSD_PREFIX` and `STATSD_DEFAULT_TAGS` environment variables
71
+ to configure the prefix to use for metrics and the comma-separated list of tags
72
+ to apply to every metric, respectively.
73
+
74
+ These environment variables are preferred over using `StatsD.prefix` and
75
+ `StatsD.default_tags`: it's best practice to configure the StatsD library
76
+ using environment variables.
77
+
9
78
  - Several improvements to `StatsD.event` and `StatsD.service_check` (both are
10
79
  Datadog-only). The previous implementation would sometimes construct invalid
11
80
  datagrams based on the input. The method signatures have been made more
@@ -19,7 +88,7 @@ section below.
19
88
  Consider the following example:
20
89
 
21
90
  ``` ruby
22
- assert_raises(RuntimeError)
91
+ assert_raises(RuntimeError) do
23
92
  assert_statsd_increment('foo') do
24
93
  raise 'something unexpected'
25
94
  end
@@ -39,7 +108,7 @@ section below.
39
108
  This means that the following test will fail:
40
109
 
41
110
  ``` ruby
42
- assert_raises(RuntimeError)
111
+ assert_raises(RuntimeError) do
43
112
  assert_statsd_increment('foo') do
44
113
  StatsD.increment('foo')
45
114
  raise 'something unexpected'
@@ -53,7 +122,7 @@ section below.
53
122
 
54
123
  ``` ruby
55
124
  assert_statsd_increment('foo') do
56
- assert_raises(RuntimeError)
125
+ assert_raises(RuntimeError) do
57
126
  StatsD.increment('foo')
58
127
  raise 'something unexpected'
59
128
  end
@@ -62,7 +131,7 @@ section below.
62
131
 
63
132
  See #193, #184, and #166 for more information.
64
133
 
65
- ## Version 2.5.1
134
+ ## Verison 2.5.1
66
135
 
67
136
  - **Bugfix:** when using metaprogramming methods, changes to `StatsD.prefix` after
68
137
  the metaprogramming method was evaluated would not be respected. This
@@ -73,7 +142,7 @@ section below.
73
142
 
74
143
  ## Version 2.5.0
75
144
 
76
- - **DEPRECATION**: Providing a sample rate and tags to your metrics and method
145
+ - **⚠️ DEPRECATION**: Providing a sample rate and tags to your metrics and method
77
146
  instrumentation macros should be done using keyword arguments rather than
78
147
  positional arguments. Also, previously you could provide `value` as a keyword
79
148
  argument, but it should be provided as the second positional argument.
@@ -115,7 +184,7 @@ section below.
115
184
 
116
185
  ```
117
186
 
118
- - **DEPRECATION**: Relying on the return value of the StatsD metric methods
187
+ - **⚠️ DEPRECATION**: Relying on the return value of the StatsD metric methods
119
188
  (e.g. `StatsD.increment`) is deprecated. StatsD is a fire-and-forget
120
189
  protocol, so your code should not depend on the return value of these methods.
121
190
 
data/README.md CHANGED
@@ -1,57 +1,51 @@
1
1
  # StatsD client for Ruby apps
2
2
 
3
- [![Built on Travis](https://secure.travis-ci.org/Shopify/statsd-instrument.svg?branch=master)](https://secure.travis-ci.org/Shopify/statsd-instrument)
3
+ This is a ruby client for statsd (http://github.com/etsy/statsd). It provides
4
+ a lightweight way to track and measure metrics in your application.
4
5
 
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
+ We call out to statsd by sending data over a UDP socket. UDP sockets are fast,
7
+ but unreliable, there is no guarantee that your data will ever arrive at its
8
+ location. In other words, fire and forget. This is perfect for this use case
9
+ because it means your code doesn't get bogged down trying to log statistics.
10
+ We send data to statsd several times per request and haven't noticed a
11
+ performance hit.
6
12
 
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).
13
+ For more information about StatsD, see the [README of the Etsy
14
+ project](http://github.com/etsy/statsd).
10
15
 
11
16
  ## Configuration
12
17
 
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
- ```
18
+ It's recommended to configure this librray by setting environment variables.
19
+ The following environment variables are supported:
20
+
21
+ - `STATSD_ADDR`: (default `localhost:8125`) The address to send the StatsD UDP
22
+ datagrams to.
23
+ - `STATSD_IMPLEMENTATION`: (default: `statsd`). The StatsD implementation you
24
+ are using. `statsd`, `statsite` and `datadog` are supported. Some features
25
+ are only available on certain implementations,
26
+ - `STATSD_ENV`: The environment StatsD will run in. If this is not set
27
+ explicitly, this will be determined based on other environment variables,
28
+ like `RAILS_ENV` or `ENV`. The library will behave differently:
29
+
30
+ - In the **production** and **staging** environment, thre librray will
31
+ actually send UDP packets.
32
+ - In the **test** environment, it will swallow all calls, but allows you to
33
+ capture them for testing purposes. See below for notes on writing tests.
34
+ - In **development** and all other environments, it will write all calls to
35
+ the log (`StatsD.logger`, which by default writes to STDOUT).
36
+
37
+ - `STATSD_SAMPLE_RATE`: (default: `1.0`) The default sample rate to use for all
38
+ metrics. This can be used to reduce the amount of network traffic and CPU
39
+ overhead the usage of this library generates. This can be overridden in a
40
+ metric method call.
41
+ - `STATSD_PREFIX`: The prefix to apply to all metric names. This can be
42
+ overridden in a metric method call.
43
+ - `STATSD_DEFAULT_TAGS`: A comma-separated list of tags to apply to all metrics.
44
+ (Note: tags are not supported by all iomplementations.)
50
45
 
51
46
  ## StatsD keys
52
47
 
53
48
  StatsD keys look like 'admin.logins.api.success'. Dots are used as namespace separators.
54
- In Graphite, they will show up as folders.
55
49
 
56
50
  ## Usage
57
51
 
@@ -82,7 +76,7 @@ StatsD.increment('GoogleBase.insert')
82
76
  StatsD.increment('GoogleBase.insert', 10)
83
77
  # you can also specify a sample rate, so only 1/10 of events
84
78
  # actually get to statsd. Useful for very high volume data
85
- StatsD.increment('GoogleBase.insert', 1, sample_rate: 0.1)
79
+ StatsD.increment('GoogleBase.insert', sample_rate: 0.1)
86
80
  ```
87
81
 
88
82
  #### StatsD.gauge
@@ -106,6 +100,18 @@ StatsD.set('GoogleBase.customers', "12345", sample_rate: 1.0)
106
100
 
107
101
  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
102
 
103
+ #### StatsD.histogram
104
+
105
+ Builds a histogram of numeric values.
106
+ ``` ruby
107
+
108
+ StatsD.histogram('Order.value', order.value_in_usd.to_f tags: { source: 'POS' })
109
+ ```
110
+
111
+ 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.
112
+
113
+ *Note: This is only supported by the beta datadog implementatation.*
114
+
109
115
  #### StatsD.distribution
110
116
 
111
117
  A modified gauge that submits a distribution of values over a sample period. Arithmetic and statistical calculations (percetiles, average, etc.) on the data set are peformed server side rather than client side like a histogram.
@@ -333,7 +339,9 @@ end
333
339
 
334
340
  ### Compatibility
335
341
 
336
- Tested using Travis CI against Ruby 2.0, 2.1, 2.2, Rubinius, and JRuby.
342
+ The library is tested against Ruby 2.3 and higher. We are not testing on
343
+ different Ruby implementations besides MRI, but we expect it to work on other
344
+ implementations as well.
337
345
 
338
346
  ### Reliance on DNS
339
347
 
@@ -350,6 +358,6 @@ This can be particularly problematic in clouds that have a shared DNS infrastruc
350
358
 
351
359
  This library was developed for shopify.com and is MIT licensed.
352
360
 
353
- - [API documentation](http://www.rubydoc.info/gems/statsd-instrument/frames)
361
+ - [API documentation](http://www.rubydoc.info/gems/statsd-instrument)
354
362
  - [The changelog](./CHANGELOG.md) covers the changes between releases.
355
363
  - [Contributing notes](./CONTRIBUTING.md) if you are interested in contributing to this library.
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'benchmark/ips'
6
+ require 'socket'
7
+
8
+ # Set up an UDP listener to which we can send StatsD packets
9
+ legacy_receiver = UDPSocket.new
10
+ legacy_receiver.bind('localhost', 0)
11
+
12
+ ENV['ENV'] = "production"
13
+ ENV['STATSD_ADDR'] = "#{legacy_receiver.addr[2]}:#{legacy_receiver.addr[1]}"
14
+ ENV['STATSD_IMPLEMENTATION'] ||= 'datadog'
15
+
16
+ require 'statsd-instrument'
17
+ require 'statsd/instrument/client'
18
+
19
+ legacy_client = StatsD
20
+
21
+ # Set up an UDP listener to which we can send StatsD packets
22
+ new_client_receiver = UDPSocket.new
23
+ new_client_receiver.bind('localhost', 0)
24
+
25
+ udp_sink = StatsD::Instrument::UDPSink.new(new_client_receiver.addr[2], new_client_receiver.addr[1])
26
+ new_client = StatsD::Instrument::Client.new(sink: udp_sink, default_sample_rate: StatsD.default_sample_rate)
27
+
28
+ Benchmark.ips do |bench|
29
+ bench.report("Legacy client (sample rate: #{StatsD.default_sample_rate})") do
30
+ legacy_client.increment('StatsD.increment')
31
+ end
32
+
33
+ bench.report("New client (sample rate: #{StatsD.default_sample_rate})") do
34
+ new_client.increment('StatsD.increment')
35
+ end
36
+
37
+ bench.compare!
38
+ end
39
+
40
+ legacy_receiver.close
41
+ new_client_receiver.close
@@ -1,121 +1,232 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This module defines several assertion methods that can be used to verify that
4
+ # your application is emitting the right StatsD metrics.
5
+ #
6
+ # Every metric type has its own assertion method, like {#assert_statsd_increment}
7
+ # to assert `StatsD.increment` calls. You can also assert other properties of the
8
+ # metric that was emitted, lioke the sample rate or presence of tags.
9
+ # To check for the absence of metrics, use {#assert_no_statsd_calls}.
10
+ #
11
+ # @example Check for metric properties:
12
+ # assert_statsd_measure('foo', sample_rate: 0.1, tags: ["bar"]) do
13
+ # StatsD.measure('foo', sample_rate: 0.5, tags: ['bar','baz']) do
14
+ # some_code_to_measure
15
+ # end
16
+ # end
17
+ #
18
+ # @example Check for multiple occurrences:
19
+ # assert_statsd_increment('foo', times: 2) do
20
+ # StatsD.increment('foo')
21
+ # StatsD.increment('foo')
22
+ # end
23
+ #
24
+ # @example Absence of metrics
25
+ # assert_no_statsd_calls do
26
+ # foo
27
+ # end
28
+ #
29
+ # @example Handling exceptions
30
+ # assert_statsd_increment('foo.error') do
31
+ # # If we expect exceptions to occur, we have to handle them inside
32
+ # # the block we pass to assert_statsd_increment.
33
+ # assert_raises(RuntimeError) do
34
+ # begin
35
+ # attempt_foo
36
+ # rescue
37
+ # StatsD.increment('foo.error')
38
+ # raise 'foo failed'
39
+ # end
40
+ # end
41
+ # end
3
42
  module StatsD::Instrument::Assertions
4
43
  include StatsD::Instrument::Helpers
5
44
 
45
+ # Asserts no metric occurred during the execution of the provided block.
46
+ #
47
+ # @param [String] metric_name (default: nil) The metric name that is not allowed
48
+ # to happen inside the block. If this is set to `nil`, the assertion will fail
49
+ # if any metric occurs.
50
+ # @yield A block in which the specified metric should not occur. This block
51
+ # should not raise any exceptions.
52
+ # @return [void]
53
+ # @raise [Minitest::Assertion] If an exception occurs, or if any metric (with the
54
+ # provided name, or any), occurred during the execution of the provided block.
6
55
  def assert_no_statsd_calls(metric_name = nil, &block)
7
56
  metrics = capture_statsd_calls(&block)
8
57
  metrics.select! { |m| m.name == metric_name } if metric_name
9
58
  assert(metrics.empty?, "No StatsD calls for metric #{metrics.map(&:name).join(', ')} expected.")
59
+ rescue => exception
60
+ flunk(<<~MESSAGE)
61
+ An exception occurred in the block provided to the StatsD assertion.
62
+
63
+ #{exception.class.name}: #{exception.message}
64
+ \t#{exception.backtrace.join("\n\t")}
65
+
66
+ If this exception is expected, make sure to handle it using `assert_raises`
67
+ inside the block provided to the StatsD assertion.
68
+ MESSAGE
10
69
  end
11
70
 
71
+ # Asserts that a given counter metric occurred inside the provided block.
72
+ #
73
+ # @param [String] metric_name The name of the metric that should occur.
74
+ # @param [Hash] options (see StatsD::Instrument::MetricExpectation.new)
75
+ # @yield A block in which the specified metric should occur. This block
76
+ # should not raise any exceptions.
77
+ # @return [void]
78
+ # @raise [Minitest::Assertion] If an exception occurs, or if the metric did
79
+ # not occur as specified during the execution the block.
12
80
  def assert_statsd_increment(metric_name, options = {}, &block)
13
81
  assert_statsd_call(:c, metric_name, options, &block)
14
82
  end
15
83
 
84
+ # Asserts that a given timing metric occurred inside the provided block.
85
+ #
86
+ # @param metric_name (see #assert_statsd_increment)
87
+ # @param options (see #assert_statsd_increment)
88
+ # @yield (see #assert_statsd_increment)
89
+ # @return [void]
90
+ # @raise (see #assert_statsd_increment)
16
91
  def assert_statsd_measure(metric_name, options = {}, &block)
17
92
  assert_statsd_call(:ms, metric_name, options, &block)
18
93
  end
19
94
 
95
+ # Asserts that a given gauge metric occurred inside the provided block.
96
+ #
97
+ # @param metric_name (see #assert_statsd_increment)
98
+ # @param options (see #assert_statsd_increment)
99
+ # @yield (see #assert_statsd_increment)
100
+ # @return [void]
101
+ # @raise (see #assert_statsd_increment)
20
102
  def assert_statsd_gauge(metric_name, options = {}, &block)
21
103
  assert_statsd_call(:g, metric_name, options, &block)
22
104
  end
23
105
 
106
+ # Asserts that a given histogram metric occurred inside the provided block.
107
+ #
108
+ # @param metric_name (see #assert_statsd_increment)
109
+ # @param options (see #assert_statsd_increment)
110
+ # @yield (see #assert_statsd_increment)
111
+ # @return [void]
112
+ # @raise (see #assert_statsd_increment)
24
113
  def assert_statsd_histogram(metric_name, options = {}, &block)
25
114
  assert_statsd_call(:h, metric_name, options, &block)
26
115
  end
27
116
 
117
+ # Asserts that a given distribution metric occurred inside the provided block.
118
+ #
119
+ # @param metric_name (see #assert_statsd_increment)
120
+ # @param options (see #assert_statsd_increment)
121
+ # @yield (see #assert_statsd_increment)
122
+ # @return [void]
123
+ # @raise (see #assert_statsd_increment)
28
124
  def assert_statsd_distribution(metric_name, options = {}, &block)
29
125
  assert_statsd_call(:d, metric_name, options, &block)
30
126
  end
31
127
 
128
+ # Asserts that a given set metric occurred inside the provided block.
129
+ #
130
+ # @param metric_name (see #assert_statsd_increment)
131
+ # @param options (see #assert_statsd_increment)
132
+ # @yield (see #assert_statsd_increment)
133
+ # @return [void]
134
+ # @raise (see #assert_statsd_increment)
32
135
  def assert_statsd_set(metric_name, options = {}, &block)
33
136
  assert_statsd_call(:s, metric_name, options, &block)
34
137
  end
35
138
 
139
+ # Asserts that a given key/value metric occurred inside the provided block.
140
+ #
141
+ # @param metric_name (see #assert_statsd_increment)
142
+ # @param options (see #assert_statsd_increment)
143
+ # @yield (see #assert_statsd_increment)
144
+ # @return [void]
145
+ # @raise (see #assert_statsd_increment)
36
146
  def assert_statsd_key_value(metric_name, options = {}, &block)
37
147
  assert_statsd_call(:kv, metric_name, options, &block)
38
148
  end
39
149
 
150
+ # Asserts that the set of provided metric expectations came true.
151
+ #
152
+ # Generally, it's recommended to use more specific assertion methods, like
153
+ # {#assert_statsd_increment} and others.
154
+ #
40
155
  # @private
41
- def assert_statsd_calls(expected_metrics, &block)
156
+ # @param [Array<StatsD::Instrument::MetricExpectation>] expected_metrics The set of
157
+ # metric expectations to verify.
158
+ # @yield (see #assert_statsd_increment)
159
+ # @return [void]
160
+ # @raise (see #assert_statsd_increment)
161
+ def assert_statsd_calls(expected_metrics)
42
162
  raise ArgumentError, "block must be given" unless block_given?
43
163
 
44
164
  capture_backend = StatsD::Instrument::Backends::CaptureBackend.new
45
165
  with_capture_backend(capture_backend) do
46
- exception_occurred = nil
47
166
  begin
48
- block.call
167
+ yield
49
168
  rescue => exception
50
- exception_occurred = exception
51
- raise
52
- ensure
53
- metrics = capture_backend.collected_metrics
54
- matched_expected_metrics = []
55
- expected_metrics.each do |expected_metric|
56
- expected_metric_times = expected_metric.times
57
- expected_metric_times_remaining = expected_metric.times
58
- filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
59
-
60
- if filtered_metrics.empty?
61
- flunk_with_exception_info(exception_occurred, "No StatsD calls for metric #{expected_metric.name} " \
62
- "of type #{expected_metric.type} were made.")
63
- end
169
+ flunk(<<~MESSAGE)
170
+ An exception occurred in the block provided to the StatsD assertion.
64
171
 
65
- filtered_metrics.each do |metric|
66
- next unless expected_metric.matches(metric)
172
+ #{exception.class.name}: #{exception.message}
173
+ \t#{exception.backtrace.join("\n\t")}
67
174
 
68
- assert(within_numeric_range?(metric.sample_rate),
69
- "Unexpected sample rate type for metric #{metric.name}, must be numeric")
175
+ If this exception is expected, make sure to handle it using `assert_raises`
176
+ inside the block provided to the StatsD assertion.
177
+ MESSAGE
178
+ end
70
179
 
71
- if expected_metric_times_remaining == 0
72
- flunk_with_exception_info(exception_occurred, "Unexpected StatsD call; number of times this metric " \
73
- "was expected exceeded: #{expected_metric.inspect}")
74
- end
180
+ metrics = capture_backend.collected_metrics
181
+ matched_expected_metrics = []
182
+ expected_metrics.each do |expected_metric|
183
+ expected_metric_times = expected_metric.times
184
+ expected_metric_times_remaining = expected_metric.times
185
+ filtered_metrics = metrics.select { |m| m.type == expected_metric.type && m.name == expected_metric.name }
75
186
 
76
- expected_metric_times_remaining -= 1
77
- metrics.delete(metric)
78
- if expected_metric_times_remaining == 0
79
- matched_expected_metrics << expected_metric
80
- end
81
- end
187
+ if filtered_metrics.empty?
188
+ flunk("No StatsD calls for metric #{expected_metric.name} of type #{expected_metric.type} were made.")
189
+ end
82
190
 
83
- next if expected_metric_times_remaining == 0
191
+ filtered_metrics.each do |metric|
192
+ next unless expected_metric.matches(metric)
84
193
 
85
- msg = +"Metric expected #{expected_metric_times} times but seen " \
86
- "#{expected_metric_times - expected_metric_times_remaining} " \
87
- "times: #{expected_metric.inspect}."
88
- msg << "\nCaptured metrics with the same key: #{filtered_metrics}" if filtered_metrics.any?
89
- flunk_with_exception_info(exception_occurred, msg)
90
- end
91
- expected_metrics -= matched_expected_metrics
194
+ assert(within_numeric_range?(metric.sample_rate),
195
+ "Unexpected sample rate type for metric #{metric.name}, must be numeric")
196
+
197
+ if expected_metric_times_remaining == 0
198
+ flunk("Unexpected StatsD call; number of times this metric " \
199
+ "was expected exceeded: #{expected_metric.inspect}")
200
+ end
92
201
 
93
- unless expected_metrics.empty?
94
- flunk_with_exception_info(exception_occurred, "Unexpected StatsD calls; the following metric expectations " \
95
- "were not satisfied: #{expected_metrics.inspect}")
202
+ expected_metric_times_remaining -= 1
203
+ metrics.delete(metric)
204
+ if expected_metric_times_remaining == 0
205
+ matched_expected_metrics << expected_metric
206
+ end
96
207
  end
97
208
 
98
- pass
209
+ next if expected_metric_times_remaining == 0
210
+
211
+ msg = +"Metric expected #{expected_metric_times} times but seen " \
212
+ "#{expected_metric_times - expected_metric_times_remaining} " \
213
+ "times: #{expected_metric.inspect}."
214
+ msg << "\nCaptured metrics with the same key: #{filtered_metrics}" if filtered_metrics.any?
215
+ flunk(msg)
99
216
  end
100
- end
101
- end
217
+ expected_metrics -= matched_expected_metrics
102
218
 
103
- private
219
+ unless expected_metrics.empty?
220
+ flunk("Unexpected StatsD calls; the following metric expectations " \
221
+ "were not satisfied: #{expected_metrics.inspect}")
222
+ end
104
223
 
105
- def flunk_with_exception_info(exception, message)
106
- if exception
107
- flunk(<<~EXCEPTION)
108
- #{message}
109
-
110
- This could be due to the exception that occurred inside the block:
111
- #{exception.class.name}: #{exception.message}
112
- \t#{exception.backtrace.join("\n\t")}
113
- EXCEPTION
114
- else
115
- flunk(message)
224
+ pass
116
225
  end
117
226
  end
118
227
 
228
+ private
229
+
119
230
  def assert_statsd_call(metric_type, metric_name, options = {}, &block)
120
231
  options[:name] = metric_name
121
232
  options[:type] = metric_type