Skip to content

Commit 3c904b4

Browse files
authored
Use dogstatsd single threaded mode (#1576)
1 parent edb8b89 commit 3c904b4

File tree

6 files changed

+73
-65
lines changed

6 files changed

+73
-65
lines changed

Gemfile

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ gem 'redcarpet', '~> 3.4' if RUBY_PLATFORM != 'java'
3030
gem 'rspec', '~> 3.10'
3131
gem 'rspec-collection_matchers', '~> 1.1'
3232
gem 'rspec_junit_formatter', '>= 0.4.1'
33-
gem 'rspec_n', '~> 1.3' if RUBY_VERSION >= '2.3.0'
33+
gem 'rspec_n', '~> 1.3' if RUBY_VERSION >= '2.4.0'
3434
gem 'ruby-prof', '~> 1.4' if RUBY_PLATFORM != 'java' && RUBY_VERSION >= '2.4.0'
3535
if RUBY_VERSION >= '2.5.0'
3636
# Merging branch coverage results does not work for old, unsupported rubies.
@@ -58,7 +58,9 @@ end
5858

5959
# Optional extensions
6060
# TODO: Move this to Appraisals?
61-
gem 'dogstatsd-ruby', '< 5.0'
61+
# dogstatsd v5, but lower than 5.2, has possible memory leak with ddtrace.
62+
# @see https://github.com/DataDog/dogstatsd-ruby/issues/182
63+
gem 'dogstatsd-ruby', '>= 3.3.0', '!= 5.0.0', '!= 5.0.1', '!= 5.1.0'
6264
gem 'opentracing', '>= 0.4.1'
6365

6466
# Profiler optional dependencies

docs/GettingStarted.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2410,7 +2410,7 @@ The tracer and its integrations can produce some additional metrics that can pro
24102410
To configure your application for metrics collection:
24112411

24122412
1. [Configure your Datadog agent for StatsD](https://docs.datadoghq.com/developers/dogstatsd/#setup)
2413-
2. Add `gem 'dogstatsd-ruby', '~> 4.0'` to your Gemfile (note that it is advised to pin v4.x [until this issue is solved](https://github.com/DataDog/dogstatsd-ruby/issues/182))
2413+
2. Add `gem 'dogstatsd-ruby', '~> 5.2'` to your Gemfile
24142414

24152415
#### For application runtime
24162416

lib/ddtrace/metrics.rb

+32-27
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,12 @@ def initialize(statsd: nil, enabled: true, **_)
2525
end
2626

2727
def supported?
28-
version = (
29-
defined?(Datadog::Statsd::VERSION) &&
30-
Datadog::Statsd::VERSION &&
31-
Gem::Version.new(Datadog::Statsd::VERSION)
32-
) || (
33-
Gem.loaded_specs['dogstatsd-ruby'] &&
34-
Gem.loaded_specs['dogstatsd-ruby'].version
35-
)
28+
version = dogstatsd_version
3629

37-
!version.nil? && (version >= Gem::Version.new('3.3.0'))
30+
!version.nil? && version >= Gem::Version.new('3.3.0') &&
31+
# dogstatsd-ruby >= 5.0 & < 5.2.0 has known issues with process forks
32+
# and do not support the single thread mode we use to avoid this problem.
33+
!(version >= Gem::Version.new('5.0') && version < Gem::Version.new('5.2'))
3834
end
3935

4036
def enabled?
@@ -56,10 +52,23 @@ def default_port
5652
def default_statsd_client
5753
require 'datadog/statsd'
5854

59-
incompatible_statsd_warning
60-
6155
# Create a StatsD client that points to the agent.
62-
Datadog::Statsd.new(default_hostname, default_port)
56+
#
57+
# We use `single_thread: true`, as dogstatsd-ruby >= 5.0 creates a background thread
58+
# by default, but does not handle forks correctly, causing resource leaks.
59+
#
60+
# Using dogstatsd-ruby >= 5.0 is still valuable, as it supports
61+
# transparent batch metric submission, which reduces submission
62+
# overhead.
63+
#
64+
# Versions < 5.0 are always single-threaded, but do not have the kwarg option.
65+
options = if dogstatsd_version >= Gem::Version.new('5.2')
66+
{ single_thread: true }
67+
else
68+
{}
69+
end
70+
71+
Datadog::Statsd.new(default_hostname, default_port, **options)
6372
end
6473

6574
def configure(options = {})
@@ -244,21 +253,17 @@ def gauge(stat, value, options = nil)
244253

245254
private
246255

247-
INCOMPATIBLE_STATSD_ONLY_ONCE = Datadog::Utils::OnlyOnce.new
248-
private_constant :INCOMPATIBLE_STATSD_ONLY_ONCE
249-
250-
# IMPORTANT: Once you delete this, you probably want to update the `#ignored_statsd_warning` below where we
251-
# recommend that customers add `gem 'dogstatsd-ruby', '~> 4.0'` to their Gemfile to perhaps state something
252-
# different.
253-
def incompatible_statsd_warning
254-
return if Gem.loaded_specs['dogstatsd-ruby'].version < Gem::Version.new('5.0')
256+
def dogstatsd_version
257+
return @dogstatsd_version if instance_variable_defined?(:@dogstatsd_version)
255258

256-
INCOMPATIBLE_STATSD_ONLY_ONCE.run do
257-
Datadog.logger.warn(
258-
'This version of `ddtrace` is incompatible with `dogstastd-ruby` version >= 5.0 and can ' \
259-
'cause unbounded memory usage. Please use `dogstastd-ruby` version < 5.0 instead.'
260-
)
261-
end
259+
@dogstatsd_version = (
260+
defined?(Datadog::Statsd::VERSION) &&
261+
Datadog::Statsd::VERSION &&
262+
Gem::Version.new(Datadog::Statsd::VERSION)
263+
) || (
264+
Gem.loaded_specs['dogstatsd-ruby'] &&
265+
Gem.loaded_specs['dogstatsd-ruby'].version
266+
)
262267
end
263268

264269
IGNORED_STATSD_ONLY_ONCE = Datadog::Utils::OnlyOnce.new
@@ -268,7 +273,7 @@ def ignored_statsd_warning
268273
IGNORED_STATSD_ONLY_ONCE.run do
269274
Datadog.logger.warn(
270275
'Ignoring user-supplied statsd instance as currently-installed version of dogstastd-ruby is incompatible. ' \
271-
"To fix this, ensure that you have `gem 'dogstatsd-ruby', '~> 4.0'` on your Gemfile or gems.rb file."
276+
"To fix this, ensure that you have `gem 'dogstatsd-ruby', '~> 5.2'` on your Gemfile or gems.rb file."
272277
)
273278
end
274279
end

spec/ddtrace/metrics_spec.rb

+33-28
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,18 @@
118118

119119
it { is_expected.to be true }
120120
end
121+
122+
context 'with incompatible 5.x version' do
123+
let(:version) { Gem::Version.new('5.0.0') }
124+
125+
it { is_expected.to be false }
126+
end
127+
128+
context 'with compatible 5.x version' do
129+
let(:version) { Gem::Version.new('5.2.0') }
130+
131+
it { is_expected.to be true }
132+
end
121133
end
122134

123135
context 'is loaded but ruby is not using rubygems' do
@@ -138,6 +150,18 @@
138150

139151
it { is_expected.to be true }
140152
end
153+
154+
context 'with incompatible 5.x version' do
155+
let(:gem_version_number) { '5.0.0' }
156+
157+
it { is_expected.to be false }
158+
end
159+
160+
context 'with compatible 5.x version' do
161+
let(:gem_version_number) { '5.2.0' }
162+
163+
it { is_expected.to be true }
164+
end
141165
end
142166
end
143167
end
@@ -246,10 +270,18 @@
246270
subject(:default_statsd_client) { metrics.default_statsd_client }
247271

248272
let(:statsd_client) { instance_double(Datadog::Statsd) }
273+
let(:options) do
274+
# This tests is run with both ~> 4.0 and latest dogstatsd-ruby.
275+
if Gem::Version.new(Datadog::Statsd::VERSION) >= Gem::Version.new('5.2.0')
276+
{ single_thread: true }
277+
else
278+
{}
279+
end
280+
end
249281

250282
before do
251283
expect(Datadog::Statsd).to receive(:new)
252-
.with(metrics.default_hostname, metrics.default_port)
284+
.with(metrics.default_hostname, metrics.default_port, **options)
253285
.and_return(statsd_client)
254286
end
255287

@@ -786,33 +818,6 @@
786818
end
787819
end
788820
end
789-
790-
describe '#incompatible_statsd_warning' do
791-
let(:options) { {} }
792-
793-
before { described_class.const_get('INCOMPATIBLE_STATSD_ONLY_ONCE').send(:reset_ran_once_state_for_tests) }
794-
795-
context 'with an incompatible dogstastd-ruby version' do
796-
before { skip unless Gem.loaded_specs['dogstatsd-ruby'].version >= Gem::Version.new('5.0') }
797-
798-
it 'emits deprecation warning once' do
799-
expect(Datadog.logger).to receive(:warn)
800-
.with(/This version of `ddtrace` is incompatible with `dogstastd-ruby`/).once
801-
802-
metrics
803-
end
804-
end
805-
806-
context 'with a compatible dogstastd-ruby version' do
807-
before { skip unless Gem.loaded_specs['dogstatsd-ruby'].version < Gem::Version.new('5.0') }
808-
809-
it 'emits no warnings' do
810-
expect(Datadog.logger).to_not receive(:warn)
811-
812-
metrics
813-
end
814-
end
815-
end
816821
end
817822

818823
RSpec.describe Datadog::Metrics::Options do

spec/ddtrace_integration_spec.rb

+2-6
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,8 @@ def open_file_descriptors
113113
expect(Datadog.configuration.runtime_metrics.enabled).to be(true).or be(false)
114114
end
115115

116-
it 'does not error on reporting health metrics', if: Datadog::Statsd::VERSION >= '5.0.0' do
117-
expect(Datadog.health_metrics.queue_accepted(1)).to be_truthy
118-
end
119-
120-
it 'does not error on reporting health metrics', if: Datadog::Statsd::VERSION < '5.0.0' do
121-
expect(Datadog.health_metrics.queue_accepted(1)).to be_a(Integer)
116+
it 'does not error on reporting health metrics' do
117+
expect { Datadog.health_metrics.queue_accepted(1) }.to_not raise_error
122118
end
123119

124120
context 'with OpenTracer' do

spec/support/configuration_helpers.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require 'bundler/stub_specification'
1+
require 'bundler'
22

33
module ConfigurationHelpers
44
shared_context 'loaded gems' do |gems = {}|

0 commit comments

Comments
 (0)