statsd-instrument 3.0.1 → 3.1.2
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 +2 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile +8 -10
- data/README.md +7 -4
- data/Rakefile +6 -6
- data/benchmark/send-metrics-to-dev-null-log +14 -14
- data/benchmark/send-metrics-to-local-udp-receiver +18 -18
- data/lib/statsd/instrument/assertions.rb +7 -7
- data/lib/statsd/instrument/batched_udp_sink.rb +159 -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 -22
- data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +2 -2
- data/lib/statsd/instrument/environment.rb +19 -11
- data/lib/statsd/instrument/expectation.rb +6 -18
- data/lib/statsd/instrument/matchers.rb +8 -4
- data/lib/statsd/instrument/railtie.rb +1 -1
- 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 +3 -3
- 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/rubocop.rb +13 -34
- data/lib/statsd/instrument/strict.rb +1 -1
- data/lib/statsd/instrument/udp_sink.rb +11 -13
- data/lib/statsd/instrument/version.rb +1 -1
- data/lib/statsd/instrument.rb +56 -59
- data/lib/statsd-instrument.rb +1 -1
- data/statsd-instrument.gemspec +2 -0
- data/test/assertions_test.rb +200 -155
- data/test/benchmark/clock_gettime.rb +1 -1
- 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 +41 -41
- 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 +3 -3
- 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 +3 -3
- 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 +142 -44
- metadata +21 -7
- data/test/benchmark/default_tags.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85b73b161dac9bc8839c3b820e9ea038d05078519fca692e971a48be7077e799
|
4
|
+
data.tar.gz: e2d5bfa763a5d53537494c8a5006327a93967d4d2b63f74a685094c551815c81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6db90f921635692d1d19145e3b346029886022c679649fac3e6f5641411ddc557fcd2028baff7be4e0b47a23e53f97e4afe9af4ecb7e537dc0850b32352fbec
|
7
|
+
data.tar.gz: 6c8f12361a70596d49dc84b3feb6637bc0ff7e9d62f1e6e7876ec02111cc94a2b561947ac4bfda045f612f57951f9789e578447e67a0b8bda4beec4f0fc76576
|
@@ -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.7
|
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,24 @@ section below.
|
|
8
8
|
|
9
9
|
_Nothing yet_
|
10
10
|
|
11
|
+
## Version 3.1.2
|
12
|
+
|
13
|
+
- Fix bug when passing custom client to expectation.
|
14
|
+
|
15
|
+
## Version 3.1.1
|
16
|
+
|
17
|
+
- Improved flushing of buffered datagrams on process exit when using UDP batching.
|
18
|
+
|
19
|
+
## Version 3.1.0
|
20
|
+
|
21
|
+
- Introduced UDP batching using a dispatcher thread, and made it the
|
22
|
+
production default.
|
23
|
+
- Dropped support for Ruby 2.4 and 2.5.
|
24
|
+
|
25
|
+
## Version 3.0.2
|
26
|
+
|
27
|
+
- Properly handle no_prefix when using StatsD assertions.
|
28
|
+
|
11
29
|
## Version 3.0.1
|
12
30
|
|
13
31
|
- Fix metaprograming methods to not print keyword argument warnings on
|
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
@@ -1,6 +1,6 @@
|
|
1
1
|
# StatsD client for Ruby apps
|
2
2
|
|
3
|
-
This is a ruby client for statsd (
|
3
|
+
This is a ruby client for statsd (https://github.com/statsd/statsd). It provides
|
4
4
|
a lightweight way to track and measure metrics in your application.
|
5
5
|
|
6
6
|
We call out to statsd by sending data over a UDP socket. UDP sockets are fast,
|
@@ -10,8 +10,8 @@ because it means your code doesn't get bogged down trying to log statistics.
|
|
10
10
|
We send data to statsd several times per request and haven't noticed a
|
11
11
|
performance hit.
|
12
12
|
|
13
|
-
For more information about StatsD, see the [README of the
|
14
|
-
project](
|
13
|
+
For more information about StatsD, see the [README of the StatsD
|
14
|
+
project](https://github.com/statsd/statsd).
|
15
15
|
|
16
16
|
## Configuration
|
17
17
|
|
@@ -20,7 +20,7 @@ The following environment variables are supported:
|
|
20
20
|
|
21
21
|
- `STATSD_ADDR`: (default `localhost:8125`) The address to send the StatsD UDP
|
22
22
|
datagrams to.
|
23
|
-
- `STATSD_IMPLEMENTATION`: (default: `
|
23
|
+
- `STATSD_IMPLEMENTATION`: (default: `datadog`). The StatsD implementation you
|
24
24
|
are using. `statsd`, `statsite` and `datadog` are supported. Some features
|
25
25
|
are only available on certain implementations,
|
26
26
|
- `STATSD_ENV`: The environment StatsD will run in. If this is not set
|
@@ -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
|
-
|
10
|
-
branch = if revision ==
|
11
|
-
|
9
|
+
base_revision = %x(git rev-parse origin/master).rstrip
|
10
|
+
branch = if revision == base_revision
|
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
|
-
|
12
|
-
branch = if revision ==
|
13
|
-
|
11
|
+
base_revision = %x(git rev-parse origin/master).rstrip
|
12
|
+
branch = if revision == base_revision
|
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
|
@@ -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.
|
@@ -74,7 +74,7 @@ module StatsD
|
|
74
74
|
# @raise [Minitest::Assertion] If an exception occurs, or if the metric did
|
75
75
|
# not occur as specified during the execution the block.
|
76
76
|
def assert_statsd_increment(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
|
77
|
-
expectation = StatsD::Instrument::Expectation.increment(metric_name, value, **options)
|
77
|
+
expectation = StatsD::Instrument::Expectation.increment(metric_name, value, client: client, **options)
|
78
78
|
assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
|
79
79
|
end
|
80
80
|
|
@@ -86,7 +86,7 @@ module StatsD
|
|
86
86
|
# @return [void]
|
87
87
|
# @raise (see #assert_statsd_increment)
|
88
88
|
def assert_statsd_measure(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
|
89
|
-
expectation = StatsD::Instrument::Expectation.measure(metric_name, value, **options)
|
89
|
+
expectation = StatsD::Instrument::Expectation.measure(metric_name, value, client: client, **options)
|
90
90
|
assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
|
91
91
|
end
|
92
92
|
|
@@ -98,7 +98,7 @@ module StatsD
|
|
98
98
|
# @return [void]
|
99
99
|
# @raise (see #assert_statsd_increment)
|
100
100
|
def assert_statsd_gauge(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
|
101
|
-
expectation = StatsD::Instrument::Expectation.gauge(metric_name, value, **options)
|
101
|
+
expectation = StatsD::Instrument::Expectation.gauge(metric_name, value, client: client, **options)
|
102
102
|
assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
|
103
103
|
end
|
104
104
|
|
@@ -110,7 +110,7 @@ module StatsD
|
|
110
110
|
# @return [void]
|
111
111
|
# @raise (see #assert_statsd_increment)
|
112
112
|
def assert_statsd_histogram(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
|
113
|
-
expectation = StatsD::Instrument::Expectation.histogram(metric_name, value, **options)
|
113
|
+
expectation = StatsD::Instrument::Expectation.histogram(metric_name, value, client: client, **options)
|
114
114
|
assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
|
115
115
|
end
|
116
116
|
|
@@ -122,7 +122,7 @@ module StatsD
|
|
122
122
|
# @return [void]
|
123
123
|
# @raise (see #assert_statsd_increment)
|
124
124
|
def assert_statsd_distribution(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
|
125
|
-
expectation = StatsD::Instrument::Expectation.distribution(metric_name, value, **options)
|
125
|
+
expectation = StatsD::Instrument::Expectation.distribution(metric_name, value, client: client, **options)
|
126
126
|
assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
|
127
127
|
end
|
128
128
|
|
@@ -134,7 +134,7 @@ module StatsD
|
|
134
134
|
# @return [void]
|
135
135
|
# @raise (see #assert_statsd_increment)
|
136
136
|
def assert_statsd_set(metric_name, value = nil, datagrams: nil, client: nil, **options, &block)
|
137
|
-
expectation = StatsD::Instrument::Expectation.set(metric_name, value, **options)
|
137
|
+
expectation = StatsD::Instrument::Expectation.set(metric_name, value, client: client, **options)
|
138
138
|
assert_statsd_expectation(expectation, datagrams: datagrams, client: client, &block)
|
139
139
|
end
|
140
140
|
|
@@ -0,0 +1,159 @@
|
|
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.0 || 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(wait = @flush_interval * 2)
|
75
|
+
@interrupted = true
|
76
|
+
if @dispatcher_thread&.alive?
|
77
|
+
@dispatcher_thread.join(wait)
|
78
|
+
else
|
79
|
+
flush
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
NEWLINE = "\n".b.freeze
|
86
|
+
def flush
|
87
|
+
return if @buffer.empty?
|
88
|
+
|
89
|
+
datagrams = @buffer.shift(@buffer.size)
|
90
|
+
|
91
|
+
until datagrams.empty?
|
92
|
+
packet = String.new(datagrams.pop, encoding: Encoding::BINARY, capacity: MAX_PACKET_SIZE)
|
93
|
+
|
94
|
+
until datagrams.empty? || packet.bytesize + datagrams.first.bytesize + 1 > MAX_PACKET_SIZE
|
95
|
+
packet << NEWLINE << datagrams.shift
|
96
|
+
end
|
97
|
+
|
98
|
+
send_packet(packet)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def dispatch
|
103
|
+
until @interrupted
|
104
|
+
begin
|
105
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
106
|
+
flush
|
107
|
+
next_sleep_duration = @flush_interval - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
|
108
|
+
|
109
|
+
sleep(next_sleep_duration) if next_sleep_duration > 0
|
110
|
+
rescue => error
|
111
|
+
report_error(error)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
flush
|
116
|
+
invalidate_socket
|
117
|
+
end
|
118
|
+
|
119
|
+
def report_error(error)
|
120
|
+
StatsD.logger.error do
|
121
|
+
"[#{self.class.name}] The dispatcher thread encountered an error #{error.class}: #{error.message}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def send_packet(packet)
|
126
|
+
retried = false
|
127
|
+
socket.send(packet, 0)
|
128
|
+
rescue SocketError, IOError, SystemCallError => error
|
129
|
+
StatsD.logger.debug do
|
130
|
+
"[#{self.class.name}] Resetting connection because of #{error.class}: #{error.message}"
|
131
|
+
end
|
132
|
+
invalidate_socket
|
133
|
+
if retried
|
134
|
+
StatsD.logger.warning do
|
135
|
+
"[#{self.class.name}] Events were dropped because of #{error.class}: #{error.message}"
|
136
|
+
end
|
137
|
+
else
|
138
|
+
retried = true
|
139
|
+
retry
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def socket
|
144
|
+
@socket ||= begin
|
145
|
+
socket = UDPSocket.new
|
146
|
+
socket.connect(@host, @port)
|
147
|
+
socket
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def invalidate_socket
|
152
|
+
@socket&.close
|
153
|
+
ensure
|
154
|
+
@socket = nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|