statsd-instrument 3.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/lint.yml +22 -0
- data/.github/workflows/{ci.yml → tests.yml} +3 -21
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile +8 -10
- data/README.md +3 -0
- data/Rakefile +6 -6
- data/benchmark/send-metrics-to-dev-null-log +12 -12
- data/benchmark/send-metrics-to-local-udp-receiver +16 -16
- data/lib/statsd-instrument.rb +1 -1
- data/lib/statsd/instrument.rb +56 -59
- data/lib/statsd/instrument/assertions.rb +1 -1
- data/lib/statsd/instrument/batched_udp_sink.rb +154 -0
- data/lib/statsd/instrument/client.rb +3 -3
- data/lib/statsd/instrument/datagram.rb +1 -1
- data/lib/statsd/instrument/datagram_builder.rb +10 -10
- data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +2 -2
- data/lib/statsd/instrument/environment.rb +19 -11
- data/lib/statsd/instrument/expectation.rb +3 -3
- data/lib/statsd/instrument/matchers.rb +8 -4
- data/lib/statsd/instrument/railtie.rb +1 -1
- data/lib/statsd/instrument/rubocop.rb +8 -8
- data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +1 -1
- data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +2 -2
- data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +1 -1
- data/lib/statsd/instrument/rubocop/metric_return_value.rb +2 -2
- data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +1 -1
- data/lib/statsd/instrument/rubocop/positional_arguments.rb +4 -4
- data/lib/statsd/instrument/rubocop/singleton_configuration.rb +1 -1
- data/lib/statsd/instrument/rubocop/splat_arguments.rb +2 -2
- data/lib/statsd/instrument/strict.rb +1 -1
- data/lib/statsd/instrument/udp_sink.rb +10 -12
- data/lib/statsd/instrument/version.rb +1 -1
- data/statsd-instrument.gemspec +2 -0
- data/test/assertions_test.rb +167 -169
- data/test/benchmark/clock_gettime.rb +1 -1
- data/test/benchmark/default_tags.rb +9 -9
- data/test/benchmark/metrics.rb +8 -8
- data/test/benchmark/tags.rb +4 -4
- data/test/capture_sink_test.rb +11 -11
- data/test/client_test.rb +64 -64
- data/test/datagram_builder_test.rb +40 -40
- data/test/datagram_test.rb +5 -5
- data/test/dogstatsd_datagram_builder_test.rb +22 -22
- data/test/environment_test.rb +26 -17
- data/test/helpers/rubocop_helper.rb +2 -2
- data/test/helpers_test.rb +12 -12
- data/test/integration_test.rb +6 -6
- data/test/log_sink_test.rb +2 -2
- data/test/matchers_test.rb +46 -46
- data/test/null_sink_test.rb +2 -2
- data/test/rubocop/measure_as_dist_argument_test.rb +2 -2
- data/test/rubocop/metaprogramming_positional_arguments_test.rb +2 -2
- data/test/rubocop/metric_prefix_argument_test.rb +2 -2
- data/test/rubocop/metric_return_value_test.rb +3 -3
- data/test/rubocop/metric_value_keyword_argument_test.rb +2 -2
- data/test/rubocop/positional_arguments_test.rb +2 -2
- data/test/rubocop/singleton_configuration_test.rb +8 -8
- data/test/rubocop/splat_arguments_test.rb +2 -2
- data/test/statsd_datagram_builder_test.rb +6 -6
- data/test/statsd_instrumentation_test.rb +104 -104
- data/test/statsd_test.rb +35 -35
- data/test/test_helper.rb +13 -6
- data/test/udp_sink_test.rb +117 -45
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a481f6cd0897b93e3e992fc5315b496c033a98a8cccd02948146cb638e0a6b2
|
4
|
+
data.tar.gz: ab454f2fecc89aa662ac2f16632fe2fc3c4e4b2b102d3c4058642e5706bf64c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3fae39281925135491d66805ff8dada34b0ea798d3403fdf0e6d3b9c1e2d3eda4a78295d7eea046ba7a0db55fb5d357d72e853558f12fdf81c71eff3713c816
|
7
|
+
data.tar.gz: 852e0838027594b8451d4fb617ca9aa5f614b570c50180bd513fb2986419adf3e9ea4f59ce86e63fbe799ccc3a07720954c87778c9a9dca63cd62fb339906d43
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: Lint
|
2
|
+
|
3
|
+
on: push
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
name: Rubocop
|
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 Rubocop
|
22
|
+
run: bin/rubocop
|
@@ -9,27 +9,12 @@ jobs:
|
|
9
9
|
strategy:
|
10
10
|
fail-fast: false
|
11
11
|
matrix:
|
12
|
-
|
12
|
+
ruby: ['2.6', '2.7', '3.0']
|
13
13
|
|
14
|
-
|
14
|
+
# Windows on macOS builds started failing, so they are disabled for noew
|
15
15
|
# platform: [windows-2019, macOS-10.14, ubuntu-18.04]
|
16
|
-
|
17
16
|
# exclude:
|
18
|
-
#
|
19
|
-
# - platform: windows-2019
|
20
|
-
# ruby: 2.3
|
21
|
-
# - platform: windows-2019
|
22
|
-
# ruby: 2.4
|
23
|
-
# - platform: windows-2019
|
24
|
-
# ruby: 2.5
|
25
|
-
|
26
|
-
# On macOS, we only test against the Ruby version macOS ships with (2.3)
|
27
|
-
# - platform: macOS-10.14
|
28
|
-
# ruby: 2.4
|
29
|
-
# - platform: macOS-10.14
|
30
|
-
# ruby: 2.5
|
31
|
-
# - platform: macOS-10.14
|
32
|
-
# ruby: 2.6
|
17
|
+
# ...
|
33
18
|
|
34
19
|
steps:
|
35
20
|
- uses: actions/checkout@v1
|
@@ -44,6 +29,3 @@ jobs:
|
|
44
29
|
|
45
30
|
- name: Run test suite
|
46
31
|
run: rake test
|
47
|
-
|
48
|
-
- name: Run Rubocop
|
49
|
-
run: bin/rubocop
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -8,6 +8,12 @@ section below.
|
|
8
8
|
|
9
9
|
_Nothing yet_
|
10
10
|
|
11
|
+
## Version 3.1.0
|
12
|
+
|
13
|
+
- Introduced UDP batching using a dispatcher thread, and made it the
|
14
|
+
production default.
|
15
|
+
- Dropped support for Ruby 2.4 and 2.5.
|
16
|
+
|
11
17
|
## Version 3.0.2
|
12
18
|
|
13
19
|
- Properly handle no_prefix when using StatsD assertions.
|
data/Gemfile
CHANGED
@@ -3,13 +3,11 @@
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
|
14
|
-
# benchmark-ips save! method is not part of a released version yet.
|
15
|
-
gem 'benchmark-ips', git: 'https://github.com/evanphx/benchmark-ips', branch: 'master'
|
6
|
+
gem "rake"
|
7
|
+
gem "minitest"
|
8
|
+
gem "rspec"
|
9
|
+
gem "mocha"
|
10
|
+
gem "yard"
|
11
|
+
gem "rubocop", ">= 1.0"
|
12
|
+
gem "rubocop-shopify", require: false
|
13
|
+
gem "benchmark-ips"
|
data/README.md
CHANGED
@@ -42,6 +42,9 @@ The following environment variables are supported:
|
|
42
42
|
overridden in a metric method call.
|
43
43
|
- `STATSD_DEFAULT_TAGS`: A comma-separated list of tags to apply to all metrics.
|
44
44
|
(Note: tags are not supported by all implementations.)
|
45
|
+
- `STATSD_FLUSH_INTERVAL`: (default: `1.0`) The interval in seconds at which
|
46
|
+
events are sent in batch. Only applicable to the UDP configuration. If set
|
47
|
+
to `0.0`, metrics are sent immediately.
|
45
48
|
|
46
49
|
## StatsD keys
|
47
50
|
|
data/Rakefile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
5
|
|
6
|
-
Rake::TestTask.new(
|
7
|
-
t.ruby_opts <<
|
8
|
-
t.libs <<
|
9
|
-
t.test_files = FileList[
|
6
|
+
Rake::TestTask.new("test") do |t|
|
7
|
+
t.ruby_opts << "-r rubygems"
|
8
|
+
t.libs << "lib" << "test"
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
10
10
|
end
|
11
11
|
|
12
12
|
task(default: :test)
|
@@ -1,14 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "tmpdir"
|
6
|
+
require "benchmark/ips"
|
7
7
|
|
8
8
|
revision = %x(git rev-parse HEAD).rstrip
|
9
9
|
master_revision = %x(git rev-parse origin/master).rstrip
|
10
10
|
branch = if revision == master_revision
|
11
|
-
|
11
|
+
"master"
|
12
12
|
else
|
13
13
|
%x(git rev-parse --abbrev-ref HEAD).rstrip
|
14
14
|
end
|
@@ -16,18 +16,18 @@ end
|
|
16
16
|
intermediate_results_filename = "#{Dir.tmpdir}/statsd-instrument-benchmarks/#{File.basename($PROGRAM_NAME)}"
|
17
17
|
FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
|
18
18
|
|
19
|
-
ENV[
|
20
|
-
require
|
19
|
+
ENV["ENV"] = "development"
|
20
|
+
require "statsd-instrument"
|
21
21
|
StatsD.logger = Logger.new(File::NULL)
|
22
22
|
|
23
23
|
report = Benchmark.ips do |bench|
|
24
24
|
bench.report("StatsD metrics to /dev/null log (branch: #{branch}, sha: #{revision[0, 7]})") do
|
25
|
-
StatsD.increment(
|
26
|
-
StatsD.measure(
|
27
|
-
StatsD.gauge(
|
28
|
-
StatsD.set(
|
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
29
|
if StatsD.singleton_client.datagram_builder_class == StatsD::Instrument::DogStatsDDatagramBuilder
|
30
|
-
StatsD.event(
|
30
|
+
StatsD.event("StasD.event", "12345")
|
31
31
|
StatsD.service_check("StatsD.service_check", "ok")
|
32
32
|
end
|
33
33
|
end
|
@@ -41,7 +41,7 @@ if report.entries.length == 1
|
|
41
41
|
puts
|
42
42
|
puts "To compare the performance of this revision against another revision (e.g. master),"
|
43
43
|
puts "check out a different branch and run this benchmark script again."
|
44
|
-
elsif ENV[
|
44
|
+
elsif ENV["KEEP_RESULTS"]
|
45
45
|
puts
|
46
46
|
puts "The intermediate results have been stored in #{intermediate_results_filename}"
|
47
47
|
else
|
@@ -1,16 +1,16 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "benchmark/ips"
|
6
|
+
require "tmpdir"
|
7
|
+
require "socket"
|
8
|
+
require "statsd-instrument"
|
9
9
|
|
10
10
|
revision = %x(git rev-parse HEAD).rstrip
|
11
11
|
master_revision = %x(git rev-parse origin/master).rstrip
|
12
12
|
branch = if revision == master_revision
|
13
|
-
|
13
|
+
"master"
|
14
14
|
else
|
15
15
|
%x(git rev-parse --abbrev-ref HEAD).rstrip
|
16
16
|
end
|
@@ -20,22 +20,22 @@ FileUtils.mkdir_p(File.dirname(intermediate_results_filename))
|
|
20
20
|
|
21
21
|
# Set up an UDP listener to which we can send StatsD packets
|
22
22
|
receiver = UDPSocket.new
|
23
|
-
receiver.bind(
|
23
|
+
receiver.bind("localhost", 0)
|
24
24
|
|
25
25
|
StatsD.singleton_client = StatsD::Instrument::Environment.new(
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
"STATSD_ADDR" => "#{receiver.addr[2]}:#{receiver.addr[1]}",
|
27
|
+
"STATSD_IMPLEMENTATION" => "dogstatsd",
|
28
|
+
"STATSD_ENV" => "production",
|
29
29
|
).client
|
30
30
|
|
31
31
|
report = Benchmark.ips do |bench|
|
32
32
|
bench.report("StatsD metrics to local UDP receiver (branch: #{branch}, sha: #{revision[0, 7]})") do
|
33
|
-
StatsD.increment(
|
34
|
-
StatsD.measure(
|
35
|
-
StatsD.gauge(
|
36
|
-
StatsD.set(
|
33
|
+
StatsD.increment("StatsD.increment", 10)
|
34
|
+
StatsD.measure("StatsD.measure") { 1 + 1 }
|
35
|
+
StatsD.gauge("StatsD.gauge", 12.0, tags: ["foo:bar", "quc"])
|
36
|
+
StatsD.set("StatsD.set", "value", tags: { foo: "bar", baz: "quc" })
|
37
37
|
if StatsD.singleton_client.datagram_builder_class == StatsD::Instrument::DogStatsDDatagramBuilder
|
38
|
-
StatsD.event(
|
38
|
+
StatsD.event("StasD.event", "12345")
|
39
39
|
StatsD.service_check("StatsD.service_check", "ok")
|
40
40
|
end
|
41
41
|
end
|
@@ -51,7 +51,7 @@ if report.entries.length == 1
|
|
51
51
|
puts
|
52
52
|
puts "To compare the performance of this revision against another revision (e.g. master),"
|
53
53
|
puts "check out a different branch and run this benchmark script again."
|
54
|
-
elsif ENV[
|
54
|
+
elsif ENV["KEEP_RESULTS"]
|
55
55
|
puts
|
56
56
|
puts "The intermediate results have been stored in #{intermediate_results_filename}"
|
57
57
|
else
|
data/lib/statsd-instrument.rb
CHANGED
data/lib/statsd/instrument.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "socket"
|
4
|
+
require "logger"
|
5
|
+
require "forwardable"
|
6
6
|
|
7
7
|
# The `StatsD` module contains low-level metrics for collecting metrics and
|
8
8
|
# sending them to the backend.
|
@@ -31,7 +31,7 @@ module StatsD
|
|
31
31
|
# @private
|
32
32
|
# @return [String]
|
33
33
|
def self.generate_metric_name(name, callee, *args)
|
34
|
-
name.respond_to?(:call) ? name.call(callee, args).gsub(
|
34
|
+
name.respond_to?(:call) ? name.call(callee, args).gsub("::", ".") : name.gsub("::", ".")
|
35
35
|
end
|
36
36
|
|
37
37
|
# Even though this method is considered private, and is no longer used internally,
|
@@ -113,26 +113,24 @@ module StatsD
|
|
113
113
|
def statsd_count_success(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
|
114
114
|
add_to_method(method, name, :count_success) do
|
115
115
|
define_method(method) do |*args, &block|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
truthiness = false
|
127
|
-
end
|
116
|
+
truthiness = result = super(*args, &block)
|
117
|
+
rescue
|
118
|
+
truthiness = false
|
119
|
+
raise
|
120
|
+
else
|
121
|
+
if block_given?
|
122
|
+
begin
|
123
|
+
truthiness = yield(result)
|
124
|
+
rescue
|
125
|
+
truthiness = false
|
128
126
|
end
|
129
|
-
result
|
130
|
-
ensure
|
131
|
-
client ||= StatsD.singleton_client
|
132
|
-
suffix = truthiness == false ? 'failure' : 'success'
|
133
|
-
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
134
|
-
client.increment("#{key}.#{suffix}", sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
|
135
127
|
end
|
128
|
+
result
|
129
|
+
ensure
|
130
|
+
client ||= StatsD.singleton_client
|
131
|
+
suffix = truthiness == false ? "failure" : "success"
|
132
|
+
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
133
|
+
client.increment("#{key}.#{suffix}", sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
|
136
134
|
end
|
137
135
|
end
|
138
136
|
end
|
@@ -152,27 +150,25 @@ module StatsD
|
|
152
150
|
def statsd_count_if(method, name, sample_rate: nil, tags: nil, no_prefix: false, client: nil)
|
153
151
|
add_to_method(method, name, :count_if) do
|
154
152
|
define_method(method) do |*args, &block|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
truthiness = false
|
166
|
-
end
|
167
|
-
end
|
168
|
-
result
|
169
|
-
ensure
|
170
|
-
if truthiness
|
171
|
-
client ||= StatsD.singleton_client
|
172
|
-
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
173
|
-
client.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
|
153
|
+
truthiness = result = super(*args, &block)
|
154
|
+
rescue
|
155
|
+
truthiness = false
|
156
|
+
raise
|
157
|
+
else
|
158
|
+
if block_given?
|
159
|
+
begin
|
160
|
+
truthiness = yield(result)
|
161
|
+
rescue
|
162
|
+
truthiness = false
|
174
163
|
end
|
175
164
|
end
|
165
|
+
result
|
166
|
+
ensure
|
167
|
+
if truthiness
|
168
|
+
client ||= StatsD.singleton_client
|
169
|
+
key = StatsD::Instrument.generate_metric_name(name, self, *args)
|
170
|
+
client.increment(key, sample_rate: sample_rate, tags: tags, no_prefix: no_prefix)
|
171
|
+
end
|
176
172
|
end
|
177
173
|
end
|
178
174
|
end
|
@@ -347,21 +343,22 @@ module StatsD
|
|
347
343
|
end
|
348
344
|
end
|
349
345
|
|
350
|
-
require
|
351
|
-
require
|
352
|
-
require
|
353
|
-
require
|
354
|
-
require
|
355
|
-
require
|
356
|
-
require
|
357
|
-
require
|
358
|
-
require
|
359
|
-
require
|
360
|
-
require
|
361
|
-
require
|
362
|
-
require
|
363
|
-
require
|
364
|
-
require
|
365
|
-
require
|
366
|
-
require
|
367
|
-
require
|
346
|
+
require "statsd/instrument/version"
|
347
|
+
require "statsd/instrument/client"
|
348
|
+
require "statsd/instrument/datagram"
|
349
|
+
require "statsd/instrument/dogstatsd_datagram"
|
350
|
+
require "statsd/instrument/datagram_builder"
|
351
|
+
require "statsd/instrument/statsd_datagram_builder"
|
352
|
+
require "statsd/instrument/dogstatsd_datagram_builder"
|
353
|
+
require "statsd/instrument/null_sink"
|
354
|
+
require "statsd/instrument/udp_sink"
|
355
|
+
require "statsd/instrument/batched_udp_sink"
|
356
|
+
require "statsd/instrument/capture_sink"
|
357
|
+
require "statsd/instrument/log_sink"
|
358
|
+
require "statsd/instrument/environment"
|
359
|
+
require "statsd/instrument/helpers"
|
360
|
+
require "statsd/instrument/assertions"
|
361
|
+
require "statsd/instrument/expectation"
|
362
|
+
require "statsd/instrument/matchers" if defined?(::RSpec)
|
363
|
+
require "statsd/instrument/railtie" if defined?(::Rails::Railtie)
|
364
|
+
require "statsd/instrument/strict" if ENV["STATSD_STRICT_MODE"]
|
@@ -61,7 +61,7 @@ module StatsD
|
|
61
61
|
end
|
62
62
|
|
63
63
|
datagrams.select! { |metric| metric_names.include?(metric.name) } unless metric_names.empty?
|
64
|
-
assert(datagrams.empty?, "No StatsD calls for metric #{datagrams.map(&:name).join(
|
64
|
+
assert(datagrams.empty?, "No StatsD calls for metric #{datagrams.map(&:name).join(", ")} expected.")
|
65
65
|
end
|
66
66
|
|
67
67
|
# Asserts that a given counter metric occurred inside the provided block.
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StatsD
|
4
|
+
module Instrument
|
5
|
+
# @note This class is part of the new Client implementation that is intended
|
6
|
+
# to become the new default in the next major release of this library.
|
7
|
+
class BatchedUDPSink
|
8
|
+
DEFAULT_FLUSH_INTERVAL = 1.0
|
9
|
+
MAX_PACKET_SIZE = 508
|
10
|
+
|
11
|
+
def self.for_addr(addr, flush_interval: DEFAULT_FLUSH_INTERVAL)
|
12
|
+
host, port_as_string = addr.split(":", 2)
|
13
|
+
new(host, Integer(port_as_string), flush_interval: flush_interval)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :host, :port
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def finalize(dispatcher)
|
20
|
+
proc { dispatcher.shutdown }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(host, port, flush_interval: DEFAULT_FLUSH_INTERVAL)
|
25
|
+
@host = host
|
26
|
+
@port = port
|
27
|
+
@dispatcher = Dispatcher.new(host, port, flush_interval)
|
28
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@dispatcher))
|
29
|
+
end
|
30
|
+
|
31
|
+
def sample?(sample_rate)
|
32
|
+
sample_rate == 1 || rand < sample_rate
|
33
|
+
end
|
34
|
+
|
35
|
+
def <<(datagram)
|
36
|
+
@dispatcher << datagram
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
class Dispatcher
|
41
|
+
BUFFER_CLASS = if !::Object.const_defined?(:RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
42
|
+
::Array
|
43
|
+
else
|
44
|
+
begin
|
45
|
+
gem("concurrent-ruby")
|
46
|
+
rescue Gem::MissingSpecError
|
47
|
+
raise Gem::MissingSpecError, "statsd-instrument depends on `concurrent-ruby` on #{RUBY_ENGINE}"
|
48
|
+
end
|
49
|
+
require "concurrent/array"
|
50
|
+
Concurrent::Array
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(host, port, flush_interval)
|
54
|
+
@host = host
|
55
|
+
@port = port
|
56
|
+
@interrupted = false
|
57
|
+
@flush_interval = flush_interval
|
58
|
+
@buffer = BUFFER_CLASS.new
|
59
|
+
@dispatcher_thread = Thread.new { dispatch }
|
60
|
+
end
|
61
|
+
|
62
|
+
def <<(datagram)
|
63
|
+
unless @dispatcher_thread&.alive?
|
64
|
+
# If the dispatcher thread is dead, we assume it is because
|
65
|
+
# the process was forked. So to avoid ending datagrams twice
|
66
|
+
# we clear the buffer.
|
67
|
+
@buffer.clear
|
68
|
+
@dispatcher_thread = Thread.new { dispatch }
|
69
|
+
end
|
70
|
+
@buffer << datagram
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def shutdown
|
75
|
+
@interrupted = true
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
NEWLINE = "\n".b.freeze
|
81
|
+
def flush
|
82
|
+
return if @buffer.empty?
|
83
|
+
|
84
|
+
datagrams = @buffer.shift(@buffer.size)
|
85
|
+
|
86
|
+
until datagrams.empty?
|
87
|
+
packet = String.new(datagrams.pop, encoding: Encoding::BINARY, capacity: MAX_PACKET_SIZE)
|
88
|
+
|
89
|
+
until datagrams.empty? || packet.bytesize + datagrams.first.bytesize + 1 > MAX_PACKET_SIZE
|
90
|
+
packet << NEWLINE << datagrams.shift
|
91
|
+
end
|
92
|
+
|
93
|
+
send_packet(packet)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def dispatch
|
98
|
+
until @interrupted
|
99
|
+
begin
|
100
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
101
|
+
flush
|
102
|
+
next_sleep_duration = @flush_interval - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
|
103
|
+
|
104
|
+
sleep(next_sleep_duration) if next_sleep_duration > 0
|
105
|
+
rescue => error
|
106
|
+
report_error(error)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
flush
|
111
|
+
invalidate_socket
|
112
|
+
end
|
113
|
+
|
114
|
+
def report_error(error)
|
115
|
+
StatsD.logger.error do
|
116
|
+
"[#{self.class.name}] The dispatcher thread encountered an error #{error.class}: #{error.message}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def send_packet(packet)
|
121
|
+
retried = false
|
122
|
+
socket.send(packet, 0)
|
123
|
+
rescue SocketError, IOError, SystemCallError => error
|
124
|
+
StatsD.logger.debug do
|
125
|
+
"[#{self.class.name}] Resetting connection because of #{error.class}: #{error.message}"
|
126
|
+
end
|
127
|
+
invalidate_socket
|
128
|
+
if retried
|
129
|
+
StatsD.logger.warning do
|
130
|
+
"[#{self.class.name}] Events were dropped because of #{error.class}: #{error.message}"
|
131
|
+
end
|
132
|
+
else
|
133
|
+
retried = true
|
134
|
+
retry
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def socket
|
139
|
+
@socket ||= begin
|
140
|
+
socket = UDPSocket.new
|
141
|
+
socket.connect(@host, @port)
|
142
|
+
socket
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def invalidate_socket
|
147
|
+
@socket&.close
|
148
|
+
ensure
|
149
|
+
@socket = nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|