statsd-instrument 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +32 -0
  3. data/.github/workflows/ci.yml +24 -8
  4. data/.rubocop.yml +24 -0
  5. data/CHANGELOG.md +116 -3
  6. data/CONTRIBUTING.md +8 -6
  7. data/Gemfile +3 -0
  8. data/Rakefile +1 -1
  9. data/benchmark/README.md +29 -0
  10. data/benchmark/send-metrics-to-dev-null-log +47 -0
  11. data/benchmark/send-metrics-to-local-udp-receiver +57 -0
  12. data/lib/statsd/instrument.rb +126 -94
  13. data/lib/statsd/instrument/assertions.rb +69 -37
  14. data/lib/statsd/instrument/backends/capture_backend.rb +2 -0
  15. data/lib/statsd/instrument/helpers.rb +12 -8
  16. data/lib/statsd/instrument/metric.rb +56 -42
  17. data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +46 -0
  18. data/lib/statsd/instrument/rubocop/metric_return_value.rb +31 -0
  19. data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +45 -0
  20. data/lib/statsd/instrument/rubocop/positional_arguments.rb +99 -0
  21. data/lib/statsd/instrument/rubocop/splat_arguments.rb +37 -0
  22. data/lib/statsd/instrument/strict.rb +145 -0
  23. data/lib/statsd/instrument/version.rb +1 -1
  24. data/test/assertions_test.rb +37 -0
  25. data/test/benchmark/clock_gettime.rb +27 -0
  26. data/test/benchmark/default_tags.rb +1 -1
  27. data/test/deprecations_test.rb +86 -0
  28. data/test/helpers/rubocop_helper.rb +47 -0
  29. data/test/integration_test.rb +6 -2
  30. data/test/matchers_test.rb +9 -9
  31. data/test/metric_test.rb +3 -18
  32. data/test/rubocop/metaprogramming_positional_arguments_test.rb +58 -0
  33. data/test/rubocop/metric_return_value_test.rb +78 -0
  34. data/test/rubocop/metric_value_keyword_argument_test.rb +39 -0
  35. data/test/rubocop/positional_arguments_test.rb +110 -0
  36. data/test/rubocop/splat_arguments_test.rb +27 -0
  37. data/test/statsd_instrumentation_test.rb +77 -86
  38. data/test/statsd_test.rb +32 -65
  39. data/test/test_helper.rb +12 -1
  40. data/test/udp_backend_test.rb +8 -0
  41. metadata +28 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c681ce1c0c9bd2755808d3b89ed7a56a924deaf384bf842e5e0a2b600a06c251
4
- data.tar.gz: 8d4fbba3f23be9603861fff38a0dd604d00faa9d0105051a3e08d83e464fdf71
3
+ metadata.gz: 19c798557b1db75029e1efded949cee656fe0b33b77e3c1c13edde73be5e4957
4
+ data.tar.gz: 46b43c2589e9d0628cab7d070a4163e6bfa6d54e7b03b8d85681e0bf29d2c733
5
5
  SHA512:
6
- metadata.gz: 15cbe3f85876c3e47658c8c15b65840e1bf97d4423199beedaef54e7843e469fd0958cf95a3960b11c5651e0fb8b3be3cf75543c22f98a5f0942e84ed6c4140f
7
- data.tar.gz: '0590c1766ab84f54901568e98d273ddb3d6a7b59494223904cb5bfe9625473bfea40724f4f3b2d854cd4496a7f3b50d042fce082719bfab6596e5cbe467b4e51'
6
+ metadata.gz: 6b09c458ef308d1afbf8264a44f1399a00863e607afd0ad3bd43b297976130e50663e9c4cd9fa17490ad4f283faef9f15b41fc9283bac7500b87dbd21af89a5a
7
+ data.tar.gz: d52f9d40110e812ece11a3d87d52013df1f18e96f0a88ea8c1411950d17926a7936954839cb5e6d374c63745499e3f9e379607bf5432880643bb03b32b7eabb2
@@ -0,0 +1,32 @@
1
+ name: Benchmarks
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ test:
7
+ name: Send metric over UDP
8
+ runs-on: ubuntu-18.04
9
+
10
+ steps:
11
+ - uses: actions/checkout@v1
12
+
13
+ - name: Setup Ruby
14
+ uses: actions/setup-ruby@v1
15
+ with:
16
+ ruby-version: 2.6
17
+
18
+ - name: Install dependencies
19
+ run: gem install bundler && bundle install --jobs 4 --retry 3
20
+
21
+ - name: Run benchmark on branch
22
+ run: benchmark/send-metrics-to-local-udp-receiver
23
+
24
+ - uses: actions/checkout@v1
25
+ with:
26
+ ref: 'master'
27
+
28
+ - name: Install dependencies if needed
29
+ run: bundle check || bundle install --jobs 4 --retry 3
30
+
31
+ - name: Run benchmark on master
32
+ run: benchmark/send-metrics-to-local-udp-receiver
@@ -4,11 +4,30 @@ on: push
4
4
 
5
5
  jobs:
6
6
  test:
7
- name: Ruby ${{ matrix.ruby }}
8
- runs-on: ubuntu-18.04
7
+ name: Ruby ${{ matrix.ruby }} on ${{ matrix.platform }}
8
+ runs-on: ${{ matrix.platform }}
9
9
  strategy:
10
+ fail-fast: false
10
11
  matrix:
11
- ruby: ['2.3', '2.4', '2.5', '2.6']
12
+ platform: [ubuntu-18.04, windows-2019, macOS-10.14]
13
+ ruby: [2.3, 2.4, 2.5, 2.6]
14
+
15
+ exclude:
16
+ # The Windows environment does not support older Ruby versions. We only test against the latest version
17
+ - platform: windows-2019
18
+ ruby: 2.3
19
+ - platform: windows-2019
20
+ ruby: 2.4
21
+ - platform: windows-2019
22
+ ruby: 2.5
23
+
24
+ # On macOS, we only test against the Ruby version macOS ships with (2.3)
25
+ - platform: macOS-10.14
26
+ ruby: 2.4
27
+ - platform: macOS-10.14
28
+ ruby: 2.5
29
+ - platform: macOS-10.14
30
+ ruby: 2.6
12
31
 
13
32
  steps:
14
33
  - uses: actions/checkout@v1
@@ -16,13 +35,10 @@ jobs:
16
35
  - name: Setup Ruby
17
36
  uses: actions/setup-ruby@v1
18
37
  with:
19
- version: ${{ matrix.ruby }}
20
- architecture: 'x64'
38
+ ruby-version: ${{ matrix.ruby }}
21
39
 
22
40
  - name: Install dependencies
23
- run: |
24
- gem install bundler
25
- bundle install --jobs 4 --retry 3
41
+ run: gem install bundler && bundle install --jobs 4 --retry 3
26
42
 
27
43
  - name: Run test suite
28
44
  run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,6 +1,13 @@
1
1
  inherit_from:
2
2
  - https://shopify.github.io/ruby-style-guide/rubocop.yml
3
3
 
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
10
+
4
11
  AllCops:
5
12
  TargetRubyVersion: 2.3
6
13
  UseCache: true
@@ -19,3 +26,20 @@ Style/ClassAndModuleChildren:
19
26
 
20
27
  Style/MethodCallWithArgsParentheses:
21
28
  Enabled: false # TODO: enable later
29
+
30
+ # Enable our own cops on our own repos
31
+
32
+ StatsD/MetricReturnValue:
33
+ Enabled: true
34
+
35
+ StatsD/MetricValueKeywordArgument:
36
+ Enabled: true
37
+
38
+ StatsD/PositionalArguments:
39
+ Enabled: true
40
+
41
+ StatsD/SplatArguments:
42
+ Enabled: true
43
+
44
+ StatsD/MetaprogrammingPositionalArguments:
45
+ Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,14 +1,127 @@
1
1
  # Changelog
2
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.
3
+ This file documents the changes between releases of this library. When
4
+ creating a pull request, please add an entry to the "unreleased changes"
5
+ section below.
5
6
 
6
7
  ### Unreleased changes
7
8
 
9
+ _Nothing yet_
10
+
11
+ ## Version 2.5.0
12
+
13
+ - **DEPRECATION**: Providing a sample rate and tags to your metrics and method
14
+ instrumentation macros should be done using keyword arguments rather than
15
+ positional arguments. Also, previously you could provide `value` as a keyword
16
+ argument, but it should be provided as the second positional argument.
17
+
18
+ ``` ruby
19
+ # DEPRECATED
20
+ StatsD.increment 'counter', 1, 0.1, ['tag']
21
+ StatsD.increment 'counter', value: 123, tags: { foo: 'bar' }
22
+ StatsD.measure('duration', nil, 1.0) { foo }
23
+ statsd_count_success :method, 'metric-name', 0.1
24
+
25
+ # SUPPORTED
26
+ StatsD.increment 'counter', sample_rate: 0.1, tags: ['tag']
27
+ StatsD.increment 'counter', 123, tags: { foo: 'bar' }
28
+ StatsD.measure('duration', sample_rate: 1.0) { foo }
29
+ statsd_count_success :method, 'metric-name', sample_rate: 0.1
30
+ ```
31
+
32
+ The documentation of the methods has been updated to reflect this change.
33
+ The behavior of the library is not changed for the time being, so you can
34
+ safely upgrade to this version. However, in a future major release, we will
35
+ remove support for the positional arguments.
36
+
37
+ The library includes some cops to help with finding issues in your existing
38
+ codebase, and fixing them:
39
+
40
+ ``` sh
41
+ # Check for positional arguments on your StatsD.metric calls
42
+ rubocop --only StatsD/PositionalArguments \
43
+ -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/positional_arguments.rb
44
+
45
+ # Check for positional arguments on your statsd_instrumentation macros
46
+ rubocop --only StatsD/MetaprogrammingPositionalArguments \
47
+ -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb
48
+
49
+ # Check for value as keyword argument
50
+ rubocop --only StatsD/MetricValueKeywordArgument \
51
+ -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb
52
+
53
+ ```
54
+
55
+ - **DEPRECATION**: Relying on the return value of the StatsD metric methods
56
+ (e.g. `StatsD.increment`) is deprecated. StatsD is a fire-and-forget
57
+ protocol, so your code should not depend on the return value of these methods.
58
+
59
+ The documentation of the methods has been updated to reflect this change.
60
+ The behavior of the library is not changed for the time being, so you can
61
+ safely upgrade to this version. However, in a future major release, we will
62
+ start to explicitly return `nil`.
63
+
64
+ This gem comes with a Rubocop rule that can help verify that your
65
+ application is not relying on the return value of the metric methods. To use
66
+ this cop on your codebase, invoke Rubocop with the following arguments:
67
+
68
+ ``` sh
69
+ rubocop --only StatsD/MetricReturnValue \
70
+ -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/metric_return_value.rb
71
+ ```
72
+
73
+ - **Strict mode**: These custom Rubocop rules will give you a quick indication
74
+ of the issues in your codebase, but are not airtight. This library now also
75
+ ships with strict mode, a mixin module that already disables this deprecated
76
+ behavior so it will raise exceptions if you are depending on deprecated
77
+ behavior. It will also do additional input validation, and make sure the
78
+ `StatsD` metric methods return `nil`.
79
+
80
+ You enable strict mode by requiring `statsd/instrument/strict`:
81
+
82
+ ``` ruby
83
+ # In your Gemfile
84
+ gem 'statd-instrument', require: 'statsd/instrument/strict'
85
+
86
+ # Or, in your test helper:
87
+ require 'statsd/instrument/strict'
88
+ ```
89
+
90
+ It is recommended to enable this in CI to find deprecation issues, but not
91
+ in production because enabling it comes with a performance penalty.
92
+
93
+ - **Performance improvements 🎉**: Several internal changes have made the
94
+ library run singificantly faster. The changes:
95
+
96
+ - Improve performance of duration calculations. (#168)
97
+ - Early exit when no changes are needed to bring tags and metric names to
98
+ normalized form. (#173)
99
+ - Refactor method argument handling to reduce object allocations and
100
+ processing. (#174)
101
+
102
+ A benchmark suite was added (#169) and it now runs as part of CI (#170) so we
103
+ can more easily spot performance regressions before they get merged into the
104
+ library.
105
+
106
+ The result of this work:
107
+
108
+ ```
109
+ Comparison:
110
+ StatsD metrics to local UDP receiver (branch: master, sha: 2f98046): 10344.9 i/s
111
+ StatsD metrics to local UDP receiver (branch: v2.4.0, sha: 371d22a): 8556.5 i/s - 1.21x (± 0.00) slower
112
+ ```
113
+
114
+ The deprecations mentioned above will allows us to provide an even greater
115
+ performance improvement, so update your code base to not use those
116
+ deprecations anymore, and keep your eyes open for future releases of the
117
+ library!
118
+
119
+ - _Bugfix:_ avoid deadlock when an error occurs in the integration test suite (#175)
120
+
8
121
  ## Version 2.4.0
9
122
 
10
123
  - Add `StatsD.default_tags` to specify tags that should be included in all metrics. (#159)
11
- - Improve assertion message when assertying metrics whose tags do not match. (#100)
124
+ - Improve assertion message when asserting metrics whose tags do not match. (#100)
12
125
  - Enforce the Shopify Ruby style guide. (#164)
13
126
  - Migrate CI to Github actions. (#158)
14
127
  - Make the library frozen string literal-compatible. (#161, #163)
data/CONTRIBUTING.md CHANGED
@@ -36,12 +36,13 @@ This gem is used in production at Shopify, and is used to instrument some of
36
36
  our hottest code paths. This means that we are very careful about not
37
37
  introducing performance regressions in this library.
38
38
 
39
- **Important:** Whenever you make changes to the metric emission code path in this library,
40
- you **must** include benchmark results to show the impact of your changes.
39
+ **Important:** Whenever you make changes to the metric emission code path in
40
+ this library, you **must** include benchmark results to show the impact of
41
+ your changes.
41
42
 
42
- The `test/benchmark/` folder contains some example benchmark script that you
43
- can use, or can serve as a starting point. Please run your benchmark on your
44
- pull request revision, as well as the latest revision on `master`.
43
+ The `benchmark/` folder contains some example benchmark script that you can
44
+ use, or can serve as a starting point. The [benchmark README](benchmark/README.md)
45
+ has instructions on how to benchmark your changes.
45
46
 
46
47
  ### On backwards compatibility
47
48
 
@@ -51,7 +52,8 @@ accept changes that are backwards incompatible:
51
52
  - Changes that will require us to update our codebases.
52
53
  - Changes that will cause metrics emitted by this library to change in form or shape.
53
54
 
54
- This means that we may not be able to accept fixes for what you consider a bug.
55
+ This means that we may not be able to accept fixes for what you consider a bug, because
56
+ we are depending on the current behavior of the library.
55
57
 
56
58
  ## Release procedure
57
59
 
data/Gemfile CHANGED
@@ -2,3 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
  gemspec
5
+
6
+ # benchmark-ips save! method is not part of a released version yet.
7
+ gem 'benchmark-ips', git: 'https://github.com/evanphx/benchmark-ips', branch: 'master'
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rake/testtask'
6
6
  Rake::TestTask.new('test') do |t|
7
7
  t.ruby_opts << '-r rubygems'
8
8
  t.libs << 'lib' << 'test'
9
- t.test_files = FileList['test/*.rb']
9
+ t.test_files = FileList['test/**/*_test.rb']
10
10
  end
11
11
 
12
12
  task default: :test
@@ -0,0 +1,29 @@
1
+ # Benchmark scripts
2
+
3
+ This directory contains benchmark scripts that can be used to gauge the
4
+ performance impact of changes.
5
+
6
+ As mentioned in the contributing guidelines, this library is used heavily in
7
+ production at Shopify in many of our hot code paths. This means that we care a
8
+ lot about changes not introducing performance regressions. Every pull request
9
+ that changes the code path to send metrics should include benchmarks
10
+ demonstrating the performance impact of the changes.
11
+
12
+ This directory contains two scripts to help with benchmarking.
13
+
14
+ - `send-metrics-to-dev-null-log` exercises the code path to construct metrics.
15
+ - `send-metrics-to-local-udp-listener` will also exercise the code path to
16
+ actually send a StatsD packet over UDP.
17
+
18
+ To benchmark your changes:
19
+
20
+ 1. Make sure the benchmark script will actually cover your changes.
21
+ - If not, please create a new benchmark script that does.
22
+ - Do not commit this script to the repository (yet), so it will continue to
23
+ be available if you check out another branch.
24
+ 2. Run these scripts on your pull request branch. The results will be stored in
25
+ a temporary file.
26
+ 3. Checkout the latest version of `master`.
27
+ 4. Run the benchmark again. The benchmark script will now print a comparison
28
+ between your branch and master.
29
+ 5. Include the output in your pull request description.
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'benchmark/ips'
6
+ require 'logger'
7
+
8
+ revision = %x(git rev-parse HEAD).rstrip
9
+ master_revision = %x(git rev-parse origin/master).rstrip
10
+ branch = if revision == master_revision
11
+ 'master'
12
+ else
13
+ %x(git rev-parse --abbrev-ref HEAD).rstrip
14
+ end
15
+
16
+ intermediate_results_filename = "#{Dir.tmpdir}/statsd-instrument-benchmarks/#{File.basename($PROGRAM_NAME)}"
17
+ FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
18
+
19
+ ENV['ENV'] = "development"
20
+ require 'statsd-instrument'
21
+ StatsD.logger = Logger.new(File::NULL)
22
+
23
+ report = Benchmark.ips do |bench|
24
+ bench.report("StatsD metrics to /dev/null log (branch: #{branch}, sha: #{revision[0, 7]})") do
25
+ StatsD.increment('StatsD.increment', 10, sample_rate: 15)
26
+ StatsD.measure('StatsD.measure') { 1 + 1 }
27
+ StatsD.gauge('StatsD.gauge', 12.0, tags: ["foo:bar", "quc"])
28
+ StatsD.set('StatsD.set', 'value', tags: { foo: 'bar', baz: 'quc' })
29
+ StatsD.event('StasD.event', "12345")
30
+ StatsD.service_check("StatsD.service_check", "ok")
31
+ end
32
+
33
+ # Store the results in between runs
34
+ bench.save!(intermediate_results_filename)
35
+ bench.compare!
36
+ end
37
+
38
+ if report.entries.length == 1
39
+ puts
40
+ puts "To compare the performance of this revision against another revision (e.g. master),"
41
+ puts "check out a different branch and run this benchmark script again."
42
+ elsif ENV['KEEP_RESULTS']
43
+ puts
44
+ puts "The intermediate results have been stored in #{intermediate_results_filename}"
45
+ else
46
+ File.unlink(intermediate_results_filename)
47
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'benchmark/ips'
6
+ require 'socket'
7
+
8
+ revision = %x(git rev-parse HEAD).rstrip
9
+ master_revision = %x(git rev-parse origin/master).rstrip
10
+ branch = if revision == master_revision
11
+ 'master'
12
+ else
13
+ %x(git rev-parse --abbrev-ref HEAD).rstrip
14
+ end
15
+
16
+ intermediate_results_filename = "#{Dir.tmpdir}/statsd-instrument-benchmarks/#{File.basename($PROGRAM_NAME)}"
17
+ FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
18
+
19
+ # Set up an UDP listener to which we can send StatsD packets
20
+ receiver = UDPSocket.new
21
+ receiver.bind('localhost', 0)
22
+
23
+ ENV['ENV'] = "production"
24
+ ENV['STATSD_ADDR'] = "#{receiver.addr[2]}:#{receiver.addr[1]}"
25
+ ENV['STATSD_IMPLEMENTATION'] ||= 'datadog'
26
+
27
+ require 'statsd-instrument'
28
+
29
+ report = Benchmark.ips do |bench|
30
+ bench.report("StatsD metrics to local UDP receiver (branch: #{branch}, sha: #{revision[0, 7]})") do
31
+ StatsD.increment('StatsD.increment', 10)
32
+ StatsD.measure('StatsD.measure') { 1 + 1 }
33
+ StatsD.gauge('StatsD.gauge', 12.0, tags: ["foo:bar", "quc"])
34
+ StatsD.set('StatsD.set', 'value', tags: { foo: 'bar', baz: 'quc' })
35
+ if StatsD.backend.implementation == :datadog
36
+ StatsD.event('StasD.event', "12345")
37
+ StatsD.service_check("StatsD.service_check", "ok")
38
+ end
39
+ end
40
+
41
+ # Store the results in between runs
42
+ bench.save!(intermediate_results_filename)
43
+ bench.compare!
44
+ end
45
+
46
+ receiver.close
47
+
48
+ if report.entries.length == 1
49
+ puts
50
+ puts "To compare the performance of this revision against another revision (e.g. master),"
51
+ puts "check out a different branch and run this benchmark script again."
52
+ elsif ENV['KEEP_RESULTS']
53
+ puts
54
+ puts "The intermediate results have been stored in #{intermediate_results_filename}"
55
+ else
56
+ File.unlink(intermediate_results_filename)
57
+ end