@@ -4,10 +4,13 @@ module Datadog
4
4
module Profiling
5
5
# Responsible for wiring up the Profiler for execution
6
6
module Component
7
+ ALLOCATION_WITH_RACTORS_ONLY_ONCE = Datadog ::Core ::Utils ::OnlyOnce . new
8
+ private_constant :ALLOCATION_WITH_RACTORS_ONLY_ONCE
9
+
7
10
# Passing in a `nil` tracer is supported and will disable the following profiling features:
8
- # * Code Hotspots panel in the trace viewer, as well as scoping a profile down to a span
11
+ # * Profiling in the trace viewer, as well as scoping a profile down to a span
9
12
# * Endpoint aggregation in the profiler UX, including normalization (resource per endpoint call)
10
- def self . build_profiler_component ( settings :, agent_settings :, optional_tracer :) # rubocop:disable Metrics/MethodLength
13
+ def self . build_profiler_component ( settings :, agent_settings :, optional_tracer :, logger : ) # rubocop:disable Metrics/MethodLength
11
14
return [ nil , { profiling_enabled : false } ] unless settings . profiling . enabled
12
15
13
16
# Workaround for weird dependency direction: the Core::Configuration::Components class currently has a
@@ -36,14 +39,14 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
36
39
37
40
# NOTE: Please update the Initialization section of ProfilingDevelopment.md with any changes to this method
38
41
39
- no_signals_workaround_enabled = no_signals_workaround_enabled? ( settings )
42
+ no_signals_workaround_enabled = no_signals_workaround_enabled? ( settings , logger )
40
43
timeline_enabled = settings . profiling . advanced . timeline_enabled
41
- allocation_profiling_enabled = enable_allocation_profiling? ( settings )
44
+ allocation_profiling_enabled = enable_allocation_profiling? ( settings , logger )
42
45
heap_sample_every = get_heap_sample_every ( settings )
43
- heap_profiling_enabled = enable_heap_profiling? ( settings , allocation_profiling_enabled , heap_sample_every )
44
- heap_size_profiling_enabled = enable_heap_size_profiling? ( settings , heap_profiling_enabled )
46
+ heap_profiling_enabled = enable_heap_profiling? ( settings , allocation_profiling_enabled , heap_sample_every , logger )
47
+ heap_size_profiling_enabled = enable_heap_size_profiling? ( settings , heap_profiling_enabled , logger )
45
48
46
- overhead_target_percentage = valid_overhead_target ( settings . profiling . advanced . overhead_target_percentage )
49
+ overhead_target_percentage = valid_overhead_target ( settings . profiling . advanced . overhead_target_percentage , logger )
47
50
upload_period_seconds = [ 60 , settings . profiling . advanced . upload_period_seconds ] . max
48
51
49
52
recorder = Datadog ::Profiling ::StackRecorder . new (
@@ -57,13 +60,13 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
57
60
)
58
61
thread_context_collector = build_thread_context_collector ( settings , recorder , optional_tracer , timeline_enabled )
59
62
worker = Datadog ::Profiling ::Collectors ::CpuAndWallTimeWorker . new (
60
- gc_profiling_enabled : enable_gc_profiling? ( settings ) ,
63
+ gc_profiling_enabled : enable_gc_profiling? ( settings , logger ) ,
61
64
no_signals_workaround_enabled : no_signals_workaround_enabled ,
62
65
thread_context_collector : thread_context_collector ,
63
66
dynamic_sampling_rate_overhead_target_percentage : overhead_target_percentage ,
64
67
allocation_profiling_enabled : allocation_profiling_enabled ,
65
68
allocation_counting_enabled : settings . profiling . advanced . allocation_counting_enabled ,
66
- gvl_profiling_enabled : enable_gvl_profiling? ( settings ) ,
69
+ gvl_profiling_enabled : enable_gvl_profiling? ( settings , logger ) ,
67
70
)
68
71
69
72
internal_metadata = {
@@ -120,7 +123,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
120
123
)
121
124
end
122
125
123
- private_class_method def self . enable_gc_profiling? ( settings )
126
+ private_class_method def self . enable_gc_profiling? ( settings , logger )
124
127
return false unless settings . profiling . advanced . gc_enabled
125
128
126
129
# SEVERE - Only with Ractors
@@ -131,14 +134,14 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
131
134
if RUBY_VERSION . start_with? ( "3.0." ) ||
132
135
( RUBY_VERSION . start_with? ( "3.1." ) && RUBY_VERSION < "3.1.4" ) ||
133
136
( RUBY_VERSION . start_with? ( "3.2." ) && RUBY_VERSION < "3.2.3" )
134
- Datadog . logger . warn (
137
+ logger . warn (
135
138
"Current Ruby version (#{ RUBY_VERSION } ) has a VM bug where enabling GC profiling would cause " \
136
139
"crashes (https://bugs.ruby-lang.org/issues/18464). GC profiling has been disabled."
137
140
)
138
141
return false
139
142
elsif RUBY_VERSION . start_with? ( "3." )
140
- Datadog . logger . debug (
141
- "In all known versions of Ruby 3.x, using Ractors may result in GC profiling unexpectedly " \
143
+ logger . debug (
144
+ "Using Ractors may result in GC profiling unexpectedly " \
142
145
"stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your " \
143
146
"application stability or performance. This does not happen if Ractors are not used."
144
147
)
@@ -155,7 +158,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
155
158
heap_sample_rate
156
159
end
157
160
158
- private_class_method def self . enable_allocation_profiling? ( settings )
161
+ private_class_method def self . enable_allocation_profiling? ( settings , logger )
159
162
return false unless settings . profiling . allocation_enabled
160
163
161
164
# Allocation sampling is safe and supported on Ruby 2.x, but has a few caveats on Ruby 3.x.
@@ -165,7 +168,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
165
168
# https://github.com/ruby/ruby/pull/7464) that makes this crash in any configuration. This bug is
166
169
# fixed on Ruby versions 3.2.3 and 3.3.0.
167
170
if RUBY_VERSION . start_with? ( "3.2." ) && RUBY_VERSION < "3.2.3"
168
- Datadog . logger . warn (
171
+ logger . warn (
169
172
"Allocation profiling is not supported in Ruby versions 3.2.0, 3.2.1 and 3.2.2 and will be forcibly " \
170
173
"disabled. This is due to a VM bug that can lead to crashes (https://bugs.ruby-lang.org/issues/19482). " \
171
174
"Other Ruby versions do not suffer from this issue."
@@ -181,7 +184,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
181
184
if RUBY_VERSION . start_with? ( "3.0." ) ||
182
185
( RUBY_VERSION . start_with? ( "3.1." ) && RUBY_VERSION < "3.1.4" ) ||
183
186
( RUBY_VERSION . start_with? ( "3.2." ) && RUBY_VERSION < "3.2.3" )
184
- Datadog . logger . warn (
187
+ logger . warn (
185
188
"Current Ruby version (#{ RUBY_VERSION } ) has a VM bug where enabling allocation profiling while using " \
186
189
"Ractors may cause unexpected issues, including crashes (https://bugs.ruby-lang.org/issues/18464). " \
187
190
"This does not happen if Ractors are not used."
@@ -190,25 +193,27 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
190
193
# On all known versions of Ruby 3.x, due to https://bugs.ruby-lang.org/issues/19112, when a ractor gets
191
194
# garbage collected, Ruby will disable all active tracepoints, which this feature internally relies on.
192
195
elsif RUBY_VERSION . start_with? ( "3." )
193
- Datadog . logger . warn (
194
- "In all known versions of Ruby 3.x, using Ractors may result in allocation profiling unexpectedly " \
195
- "stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your " \
196
- "application stability or performance. This does not happen if Ractors are not used."
197
- )
196
+ ALLOCATION_WITH_RACTORS_ONLY_ONCE . run do
197
+ logger . info (
198
+ "Using Ractors may result in allocation profiling " \
199
+ "stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your " \
200
+ "application stability or performance. This does not happen if Ractors are not used."
201
+ )
202
+ end
198
203
end
199
204
200
- Datadog . logger . debug ( "Enabled allocation profiling" )
205
+ logger . debug ( "Enabled allocation profiling" )
201
206
202
207
true
203
208
end
204
209
205
- private_class_method def self . enable_heap_profiling? ( settings , allocation_profiling_enabled , heap_sample_rate )
210
+ private_class_method def self . enable_heap_profiling? ( settings , allocation_profiling_enabled , heap_sample_rate , logger )
206
211
heap_profiling_enabled = settings . profiling . advanced . experimental_heap_enabled
207
212
208
213
return false unless heap_profiling_enabled
209
214
210
215
if RUBY_VERSION < "3.1"
211
- Datadog . logger . warn (
216
+ logger . warn (
212
217
"Current Ruby version (#{ RUBY_VERSION } ) cannot support heap profiling due to VM limitations. " \
213
218
"Please upgrade to Ruby >= 3.1 in order to use this feature. Heap profiling has been disabled."
214
219
)
@@ -219,33 +224,31 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
219
224
raise ArgumentError , "Heap profiling requires allocation profiling to be enabled"
220
225
end
221
226
222
- Datadog . logger . warn (
227
+ logger . warn (
223
228
"Enabled experimental heap profiling: heap_sample_rate=#{ heap_sample_rate } . This is experimental, not " \
224
229
"recommended, and will increase overhead!"
225
230
)
226
231
227
232
true
228
233
end
229
234
230
- private_class_method def self . enable_heap_size_profiling? ( settings , heap_profiling_enabled )
235
+ private_class_method def self . enable_heap_size_profiling? ( settings , heap_profiling_enabled , logger )
231
236
heap_size_profiling_enabled = settings . profiling . advanced . experimental_heap_size_enabled
232
237
233
238
return false unless heap_profiling_enabled && heap_size_profiling_enabled
234
239
235
- Datadog . logger . warn (
240
+ logger . warn (
236
241
"Enabled experimental heap size profiling. This is experimental, not recommended, and will increase overhead!"
237
242
)
238
243
239
244
true
240
245
end
241
246
242
- private_class_method def self . no_signals_workaround_enabled? ( settings ) # rubocop:disable Metrics/MethodLength
247
+ private_class_method def self . no_signals_workaround_enabled? ( settings , logger ) # rubocop:disable Metrics/MethodLength
243
248
setting_value = settings . profiling . advanced . no_signals_workaround_enabled
244
- legacy_ruby_that_should_use_workaround = RUBY_VERSION . start_with? ( "2.5." )
245
249
246
250
unless [ true , false , :auto ] . include? ( setting_value )
247
- # TODO: Replace with a warning instead.
248
- Datadog . logger . error (
251
+ logger . warn (
249
252
"Ignoring invalid value for profiling no_signals_workaround_enabled setting: #{ setting_value . inspect } . " \
250
253
"Valid options are `true`, `false` or (default) `:auto`."
251
254
)
@@ -254,23 +257,23 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
254
257
end
255
258
256
259
if setting_value == false
257
- if legacy_ruby_that_should_use_workaround
258
- Datadog . logger . warn (
259
- 'The profiling "no signals" workaround has been disabled via configuration on a legacy Ruby version ' \
260
- "(< 2.6). This is not recommended " \
261
- "in production environments, as due to limitations in Ruby APIs, we suspect it may lead to crashes " \
262
- "in very rare situations. Please report any issues you run into to Datadog support or " \
260
+ if RUBY_VERSION . start_with? ( "2.5." )
261
+ logger . warn (
262
+ 'The profiling "no signals" workaround has been disabled via configuration on Ruby 2.5. ' \
263
+ "This is not recommended " \
264
+ "in production environments, as due to limitations in Ruby APIs, we suspect it may lead to rare crashes " \
265
+ "Please report any issues you run into to Datadog support or " \
263
266
"via <https://github.com/datadog/dd-trace-rb/issues/new>!"
264
267
)
265
268
else
266
- Datadog . logger . warn ( 'Profiling "no signals" workaround disabled via configuration' )
269
+ logger . warn ( 'Profiling "no signals" workaround disabled via configuration' )
267
270
end
268
271
269
272
return false
270
273
end
271
274
272
275
if setting_value == true
273
- Datadog . logger . warn (
276
+ logger . warn (
274
277
'Profiling "no signals" workaround enabled via configuration. Profiling data will have lower quality.'
275
278
)
276
279
@@ -280,10 +283,10 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
280
283
# Setting is in auto mode. Let's probe to see if we should enable it:
281
284
282
285
# We don't warn users in this situation because "upgrade your Ruby" is not a great warning
283
- return true if legacy_ruby_that_should_use_workaround
286
+ return true if RUBY_VERSION . start_with? ( "2.5." )
284
287
285
- if Gem . loaded_specs [ "mysql2" ] && incompatible_libmysqlclient_version? ( settings )
286
- Datadog . logger . warn (
288
+ if Gem . loaded_specs [ "mysql2" ] && incompatible_libmysqlclient_version? ( settings , logger )
289
+ logger . warn (
287
290
'Enabling the profiling "no signals" workaround because an incompatible version of the mysql2 gem is ' \
288
291
"installed. Profiling data will have lower quality. " \
289
292
"To fix this, upgrade the libmysqlclient in your OS image to version 8.0.0 or above."
@@ -292,7 +295,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
292
295
end
293
296
294
297
if Gem . loaded_specs [ "rugged" ]
295
- Datadog . logger . warn (
298
+ logger . warn (
296
299
'Enabling the profiling "no signals" workaround because the rugged gem is installed. ' \
297
300
"This is needed because some operations on this gem are currently incompatible with the normal working mode " \
298
301
"of the profiler, as detailed in <https://github.com/datadog/dd-trace-rb/issues/2721>. " \
@@ -302,7 +305,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
302
305
end
303
306
304
307
if ( defined? ( ::PhusionPassenger ) || Gem . loaded_specs [ "passenger" ] ) && incompatible_passenger_version?
305
- Datadog . logger . warn (
308
+ logger . warn (
306
309
'Enabling the profiling "no signals" workaround because an incompatible version of the passenger gem is ' \
307
310
"installed. Profiling data will have lower quality." \
308
311
"To fix this, upgrade the passenger gem to version 6.0.19 or above."
@@ -322,10 +325,10 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
322
325
#
323
326
# The `mysql2` gem's `info` method can be used to determine which `libmysqlclient` version is in use, and thus to
324
327
# detect if it's safe for the profiler to use signals or if we need to employ a fallback.
325
- private_class_method def self . incompatible_libmysqlclient_version? ( settings )
328
+ private_class_method def self . incompatible_libmysqlclient_version? ( settings , logger )
326
329
return true if settings . profiling . advanced . skip_mysql2_check
327
330
328
- Datadog . logger . debug (
331
+ logger . debug (
329
332
"Requiring `mysql2` to check if the `libmysqlclient` version it uses is compatible with profiling"
330
333
)
331
334
@@ -354,14 +357,14 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
354
357
libmysqlclient_version >= Gem ::Version . new ( "8.0.0" ) ||
355
358
looks_like_mariadb? ( info , libmysqlclient_version )
356
359
357
- Datadog . logger . debug (
360
+ logger . debug (
358
361
"The `mysql2` gem is using #{ compatible ? "a compatible" : "an incompatible" } version of " \
359
362
"the `libmysqlclient` library (#{ libmysqlclient_version } )"
360
363
)
361
364
362
365
!compatible
363
366
rescue StandardError , LoadError => e
364
- Datadog . logger . warn (
367
+ logger . warn (
365
368
"Failed to probe `mysql2` gem information. " \
366
369
"Cause: #{ e . class . name } #{ e . message } Location: #{ Array ( e . backtrace ) . first } "
367
370
)
@@ -383,12 +386,11 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
383
386
end
384
387
end
385
388
386
- private_class_method def self . valid_overhead_target ( overhead_target_percentage )
389
+ private_class_method def self . valid_overhead_target ( overhead_target_percentage , logger )
387
390
if overhead_target_percentage > 0 && overhead_target_percentage <= 20
388
391
overhead_target_percentage
389
392
else
390
- # TODO: Replace with a warning instead.
391
- Datadog . logger . error (
393
+ logger . warn (
392
394
"Ignoring invalid value for profiling overhead_target_percentage setting: " \
393
395
"#{ overhead_target_percentage . inspect } . Falling back to default value."
394
396
)
@@ -432,10 +434,10 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
432
434
settings . profiling . advanced . dir_interruption_workaround_enabled
433
435
end
434
436
435
- private_class_method def self . enable_gvl_profiling? ( settings )
437
+ private_class_method def self . enable_gvl_profiling? ( settings , logger )
436
438
if RUBY_VERSION < "3.2"
437
439
if settings . profiling . advanced . preview_gvl_enabled
438
- Datadog . logger . warn ( "GVL profiling is currently not supported in Ruby < 3.2 and will not be enabled." )
440
+ logger . warn ( "GVL profiling is currently not supported in Ruby < 3.2 and will not be enabled." )
439
441
end
440
442
441
443
return false
0 commit comments