ddtrace 1.18.0 → 1.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -1
- data/ext/ddtrace_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +67 -52
- data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.c +22 -14
- data/ext/ddtrace_profiling_native_extension/collectors_dynamic_sampling_rate.h +4 -0
- data/ext/ddtrace_profiling_native_extension/collectors_gc_profiling_helper.c +156 -0
- data/ext/ddtrace_profiling_native_extension/collectors_gc_profiling_helper.h +5 -0
- data/ext/ddtrace_profiling_native_extension/collectors_stack.c +43 -102
- data/ext/ddtrace_profiling_native_extension/collectors_stack.h +10 -3
- data/ext/ddtrace_profiling_native_extension/collectors_thread_context.c +159 -124
- data/ext/ddtrace_profiling_native_extension/collectors_thread_context.h +2 -1
- data/ext/ddtrace_profiling_native_extension/extconf.rb +16 -0
- data/ext/ddtrace_profiling_native_extension/heap_recorder.c +970 -0
- data/ext/ddtrace_profiling_native_extension/heap_recorder.h +155 -0
- data/ext/ddtrace_profiling_native_extension/helpers.h +2 -0
- data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.c +20 -0
- data/ext/ddtrace_profiling_native_extension/libdatadog_helpers.h +11 -0
- data/ext/ddtrace_profiling_native_extension/private_vm_api_access.c +5 -0
- data/ext/ddtrace_profiling_native_extension/profiling.c +1 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.c +147 -0
- data/ext/ddtrace_profiling_native_extension/ruby_helpers.h +28 -0
- data/ext/ddtrace_profiling_native_extension/stack_recorder.c +329 -10
- data/ext/ddtrace_profiling_native_extension/stack_recorder.h +3 -0
- data/lib/datadog/core/configuration/settings.rb +139 -22
- data/lib/datadog/core/telemetry/collector.rb +10 -0
- data/lib/datadog/core/telemetry/event.rb +2 -1
- data/lib/datadog/core/telemetry/ext.rb +3 -0
- data/lib/datadog/core/telemetry/v1/app_event.rb +8 -1
- data/lib/datadog/core/telemetry/v1/install_signature.rb +38 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +6 -11
- data/lib/datadog/profiling/component.rb +197 -13
- data/lib/datadog/profiling/scheduler.rb +4 -6
- data/lib/datadog/profiling/stack_recorder.rb +13 -2
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +4 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +2 -1
- data/lib/datadog/tracing/contrib/rails/auto_instrument_railtie.rb +0 -2
- data/lib/ddtrace/version.rb +1 -1
- metadata +12 -7
@@ -314,32 +314,85 @@ module Datadog
|
|
314
314
|
|
315
315
|
# Can be used to enable/disable the Datadog::Profiling.allocation_count feature.
|
316
316
|
#
|
317
|
-
# This feature is
|
317
|
+
# This feature is now controlled via {:experimental_allocation_enabled}
|
318
|
+
option :allocation_counting_enabled do |o|
|
319
|
+
o.after_set do
|
320
|
+
Datadog.logger.warn(
|
321
|
+
'The profiling.advanced.allocation_counting_enabled setting has been deprecated for removal and no ' \
|
322
|
+
'longer does anything. Please remove it from your Datadog.configure block. ' \
|
323
|
+
'Allocation counting is now controlled by the `experimental_allocation_enabled` setting instead.'
|
324
|
+
)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# Can be used to enable/disable collection of allocation profiles.
|
329
|
+
#
|
330
|
+
# This feature is alpha and disabled by default
|
318
331
|
#
|
319
|
-
#
|
320
|
-
#
|
321
|
-
#
|
322
|
-
# (https://bugs.ruby-lang.org/issues/18464). We don't recommend using this feature on such Rubies.
|
323
|
-
# This bug is fixed on Ruby versions 3.1.4, 3.2.3 and 3.3.0.
|
332
|
+
# @warn This feature is not supported/safe in all Rubies. Details in {Datadog::Profiling::Component} but
|
333
|
+
# in summary, this should be supported on Ruby 2.x, 3.1.4+, 3.2.3+ and 3.3.0+. Enabling it on
|
334
|
+
# unsupported Rubies may result in unexpected behaviour, including crashes.
|
324
335
|
#
|
325
|
-
#
|
326
|
-
|
327
|
-
|
328
|
-
|
336
|
+
# @default `DD_PROFILING_EXPERIMENTAL_ALLOCATION_ENABLED` environment variable as a boolean, otherwise `false`
|
337
|
+
option :experimental_allocation_enabled do |o|
|
338
|
+
o.type :bool
|
339
|
+
o.env 'DD_PROFILING_EXPERIMENTAL_ALLOCATION_ENABLED'
|
340
|
+
o.default false
|
341
|
+
end
|
342
|
+
|
343
|
+
# Can be used to enable/disable the collection of heap profiles.
|
329
344
|
#
|
330
|
-
#
|
331
|
-
# Ruby 3.2.0 to 3.2.2 have a bug in the newobj tracepoint (https://bugs.ruby-lang.org/issues/19482,
|
332
|
-
# https://github.com/ruby/ruby/pull/7464) so that's an extra reason why it's not safe on those Rubies.
|
333
|
-
# This bug is fixed on Ruby versions 3.2.3 and 3.3.0.
|
345
|
+
# This feature is alpha and disabled by default
|
334
346
|
#
|
335
|
-
# @
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
347
|
+
# @warn To enable heap profiling you are required to also enable allocation profiling.
|
348
|
+
#
|
349
|
+
# @default `DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED` environment variable as a boolean, otherwise `false`
|
350
|
+
option :experimental_heap_enabled do |o|
|
351
|
+
o.type :bool
|
352
|
+
o.env 'DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED'
|
353
|
+
o.default false
|
354
|
+
end
|
355
|
+
|
356
|
+
# Can be used to enable/disable the collection of heap size profiles.
|
357
|
+
#
|
358
|
+
# This feature is alpha and enabled by default when heap profiling is enabled.
|
359
|
+
#
|
360
|
+
# @warn To enable heap size profiling you are required to also enable allocation and heap profiling.
|
361
|
+
#
|
362
|
+
# @default `DD_PROFILING_EXPERIMENTAL_HEAP_SIZE_ENABLED` environment variable as a boolean, otherwise
|
363
|
+
# whatever the value of DD_PROFILING_EXPERIMENTAL_HEAP_ENABLED is.
|
364
|
+
option :experimental_heap_size_enabled do |o|
|
365
|
+
o.type :bool
|
366
|
+
o.env 'DD_PROFILING_EXPERIMENTAL_HEAP_SIZE_ENABLED'
|
367
|
+
o.default true # This gets ANDed with experimental_heap_enabled in the profiler component.
|
368
|
+
end
|
369
|
+
|
370
|
+
# Can be used to configure the allocation sampling rate: a sample will be collected every x allocations.
|
371
|
+
#
|
372
|
+
# The lower the value, the more accuracy in allocation and heap tracking but the bigger the overhead. In
|
373
|
+
# particular, a value of 1 will sample ALL allocations.
|
374
|
+
#
|
375
|
+
# @default `DD_PROFILING_EXPERIMENTAL_ALLOCATION_SAMPLE_RATE` environment variable, otherwise `50`.
|
376
|
+
option :experimental_allocation_sample_rate do |o|
|
377
|
+
o.type :int
|
378
|
+
o.env 'DD_PROFILING_EXPERIMENTAL_ALLOCATION_SAMPLE_RATE'
|
379
|
+
o.default 50
|
380
|
+
end
|
381
|
+
|
382
|
+
# Can be used to configure the heap sampling rate: a heap sample will be collected for every x allocation
|
383
|
+
# samples.
|
384
|
+
#
|
385
|
+
# The lower the value, the more accuracy in heap tracking but the bigger the overhead. In particular, a
|
386
|
+
# value of 1 will track ALL allocations samples for heap profiles.
|
387
|
+
#
|
388
|
+
# The effective heap sampling rate in terms of allocations (not allocation samples) can be calculated via
|
389
|
+
# effective_heap_sample_rate = allocation_sample_rate * heap_sample_rate.
|
390
|
+
#
|
391
|
+
# @default `DD_PROFILING_EXPERIMENTAL_HEAP_SAMPLE_RATE` environment variable, otherwise `10`.
|
392
|
+
option :experimental_heap_sample_rate do |o|
|
393
|
+
o.type :int
|
394
|
+
o.env 'DD_PROFILING_EXPERIMENTAL_HEAP_SAMPLE_RATE'
|
395
|
+
o.default 10
|
343
396
|
end
|
344
397
|
|
345
398
|
# Can be used to disable checking which version of `libmysqlclient` is being used by the `mysql2` gem.
|
@@ -391,6 +444,34 @@ module Datadog
|
|
391
444
|
end
|
392
445
|
end
|
393
446
|
end
|
447
|
+
|
448
|
+
# Configures how much wall-time overhead the profiler targets. The profiler will dynamically adjust the
|
449
|
+
# interval between samples it takes so as to try and maintain the property that it spends no longer than
|
450
|
+
# this amount of wall-clock time profiling. For example, with the default value of 2%, the profiler will
|
451
|
+
# try and cause no more than 1.2 seconds per minute of overhead. Decreasing this value will reduce the
|
452
|
+
# accuracy of the data collected. Increasing will impact the application.
|
453
|
+
#
|
454
|
+
# We do not recommend tweaking this value.
|
455
|
+
#
|
456
|
+
# This value should be a percentage i.e. a number between 0 and 100, not 0 and 1.
|
457
|
+
#
|
458
|
+
# @default `DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE` as a float, otherwise 2.0
|
459
|
+
option :overhead_target_percentage do |o|
|
460
|
+
o.type :float
|
461
|
+
o.env 'DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE'
|
462
|
+
o.default 2.0
|
463
|
+
end
|
464
|
+
|
465
|
+
# Controls how often the profiler reports data, in seconds. Cannot be lower than 60 seconds.
|
466
|
+
#
|
467
|
+
# We do not recommend tweaking this value.
|
468
|
+
#
|
469
|
+
# @default `DD_PROFILING_UPLOAD_PERIOD` environment variable, otherwise 60
|
470
|
+
option :upload_period_seconds do |o|
|
471
|
+
o.type :int
|
472
|
+
o.env 'DD_PROFILING_UPLOAD_PERIOD'
|
473
|
+
o.default 60
|
474
|
+
end
|
394
475
|
end
|
395
476
|
|
396
477
|
# @public_api
|
@@ -592,6 +673,42 @@ module Datadog
|
|
592
673
|
o.env Core::Telemetry::Ext::ENV_HEARTBEAT_INTERVAL
|
593
674
|
o.default 60.0
|
594
675
|
end
|
676
|
+
|
677
|
+
# The install id of the application.
|
678
|
+
#
|
679
|
+
# This method is used internally, by library injection.
|
680
|
+
#
|
681
|
+
# @default `DD_INSTRUMENTATION_INSTALL_ID` environment variable, otherwise `nil`.
|
682
|
+
# @return [String,nil]
|
683
|
+
# @!visibility private
|
684
|
+
option :install_id do |o|
|
685
|
+
o.type :string, nilable: true
|
686
|
+
o.env Core::Telemetry::Ext::ENV_INSTALL_ID
|
687
|
+
end
|
688
|
+
|
689
|
+
# The install type of the application.
|
690
|
+
#
|
691
|
+
# This method is used internally, by library injection.
|
692
|
+
#
|
693
|
+
# @default `DD_INSTRUMENTATION_INSTALL_TYPE` environment variable, otherwise `nil`.
|
694
|
+
# @return [String,nil]
|
695
|
+
# @!visibility private
|
696
|
+
option :install_type do |o|
|
697
|
+
o.type :string, nilable: true
|
698
|
+
o.env Core::Telemetry::Ext::ENV_INSTALL_TYPE
|
699
|
+
end
|
700
|
+
|
701
|
+
# The install time of the application.
|
702
|
+
#
|
703
|
+
# This method is used internally, by library injection.
|
704
|
+
#
|
705
|
+
# @default `DD_INSTRUMENTATION_INSTALL_TIME` environment variable, otherwise `nil`.
|
706
|
+
# @return [String,nil]
|
707
|
+
# @!visibility private
|
708
|
+
option :install_time do |o|
|
709
|
+
o.type :string, nilable: true
|
710
|
+
o.env Core::Telemetry::Ext::ENV_INSTALL_TIME
|
711
|
+
end
|
595
712
|
end
|
596
713
|
|
597
714
|
# Remote configuration
|
@@ -9,6 +9,7 @@ require_relative '../utils/hash'
|
|
9
9
|
require_relative 'v1/application'
|
10
10
|
require_relative 'v1/dependency'
|
11
11
|
require_relative 'v1/host'
|
12
|
+
require_relative 'v1/install_signature'
|
12
13
|
require_relative 'v1/integration'
|
13
14
|
require_relative 'v1/product'
|
14
15
|
require_relative '../transport/ext'
|
@@ -81,6 +82,15 @@ module Datadog
|
|
81
82
|
)
|
82
83
|
end
|
83
84
|
|
85
|
+
# Forms a telemetry app-started install_signature object
|
86
|
+
def install_signature
|
87
|
+
Telemetry::V1::InstallSignature.new(
|
88
|
+
install_id: Datadog.configuration.dig('telemetry', 'install_id'),
|
89
|
+
install_type: Datadog.configuration.dig('telemetry', 'install_type'),
|
90
|
+
install_time: Datadog.configuration.dig('telemetry', 'install_time'),
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
84
94
|
# Forms a telemetry app-started integrations object
|
85
95
|
def integrations
|
86
96
|
Datadog.registry.map do |integration|
|
@@ -6,6 +6,9 @@ module Datadog
|
|
6
6
|
module Ext
|
7
7
|
ENV_ENABLED = 'DD_INSTRUMENTATION_TELEMETRY_ENABLED'
|
8
8
|
ENV_HEARTBEAT_INTERVAL = 'DD_TELEMETRY_HEARTBEAT_INTERVAL'
|
9
|
+
ENV_INSTALL_ID = 'DD_INSTRUMENTATION_INSTALL_ID'
|
10
|
+
ENV_INSTALL_TYPE = 'DD_INSTRUMENTATION_INSTALL_TYPE'
|
11
|
+
ENV_INSTALL_TIME = 'DD_INSTRUMENTATION_INSTALL_TIME'
|
9
12
|
end
|
10
13
|
end
|
11
14
|
end
|
@@ -10,18 +10,24 @@ module Datadog
|
|
10
10
|
:additional_payload,
|
11
11
|
:configuration,
|
12
12
|
:dependencies,
|
13
|
+
:install_signature,
|
13
14
|
:integrations
|
14
15
|
|
15
16
|
# @param additional_payload [Array<Telemetry::V1::Configuration>] List of Additional payload to track (any key
|
16
17
|
# value not mentioned and doesn't fit under a metric)
|
17
18
|
# @param configuration [Array<Telemetry::V1::Configuration>] List of Tracer related configuration data
|
18
19
|
# @param dependencies [Array<Telemetry::V1::Dependency>] List of all loaded modules requested by the app
|
20
|
+
# @param install_signature [Telemetry::V1::InstallSignature] Install signature data
|
19
21
|
# @param integrations [Array<Telemetry::V1::Integration>] List of integrations that are available within the app
|
20
22
|
# and applicable to be traced
|
21
|
-
def initialize(
|
23
|
+
def initialize(
|
24
|
+
additional_payload: nil, configuration: nil, dependencies: nil, install_signature: nil,
|
25
|
+
integrations: nil
|
26
|
+
)
|
22
27
|
@additional_payload = additional_payload
|
23
28
|
@configuration = configuration
|
24
29
|
@dependencies = dependencies
|
30
|
+
@install_signature = install_signature
|
25
31
|
@integrations = integrations
|
26
32
|
end
|
27
33
|
|
@@ -30,6 +36,7 @@ module Datadog
|
|
30
36
|
hash[:additional_payload] = map_hash(@additional_payload) if @additional_payload
|
31
37
|
hash[:configuration] = map_hash(@configuration) if @configuration
|
32
38
|
hash[:dependencies] = map_array(@dependencies) if @dependencies
|
39
|
+
hash[:install_signature] = @install_signature.to_h if @install_signature
|
33
40
|
hash[:integrations] = map_array(@integrations) if @integrations
|
34
41
|
end
|
35
42
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module Core
|
5
|
+
module Telemetry
|
6
|
+
module V1
|
7
|
+
# Describes attributes for install signature
|
8
|
+
class InstallSignature
|
9
|
+
using Core::Utils::Hash::Refinement
|
10
|
+
|
11
|
+
attr_reader \
|
12
|
+
:install_id,
|
13
|
+
:install_type,
|
14
|
+
:install_time
|
15
|
+
|
16
|
+
# @param id [String,nil] Install ID
|
17
|
+
# @param type [String,nil] Install type
|
18
|
+
# @param type [String,nil] Install time
|
19
|
+
def initialize(install_id:, install_type:, install_time:)
|
20
|
+
@install_id = install_id
|
21
|
+
@install_type = install_type
|
22
|
+
@install_time = install_time
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
hash = {
|
27
|
+
install_id: @install_id,
|
28
|
+
install_type: @install_type,
|
29
|
+
install_time: @install_time
|
30
|
+
}
|
31
|
+
hash.compact!
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -15,14 +15,15 @@ module Datadog
|
|
15
15
|
|
16
16
|
def initialize(
|
17
17
|
gc_profiling_enabled:,
|
18
|
-
allocation_counting_enabled:,
|
19
18
|
no_signals_workaround_enabled:,
|
20
19
|
thread_context_collector:,
|
21
|
-
|
20
|
+
dynamic_sampling_rate_overhead_target_percentage:,
|
21
|
+
allocation_sample_every:,
|
22
|
+
allocation_profiling_enabled:,
|
22
23
|
# **NOTE**: This should only be used for testing; disabling the dynamic sampling rate will increase the
|
23
24
|
# profiler overhead!
|
24
25
|
dynamic_sampling_rate_enabled: true,
|
25
|
-
|
26
|
+
idle_sampling_helper: IdleSamplingHelper.new
|
26
27
|
)
|
27
28
|
unless dynamic_sampling_rate_enabled
|
28
29
|
Datadog.logger.warn(
|
@@ -30,22 +31,16 @@ module Datadog
|
|
30
31
|
)
|
31
32
|
end
|
32
33
|
|
33
|
-
if allocation_counting_enabled && allocation_sample_every > 0
|
34
|
-
Datadog.logger.warn(
|
35
|
-
"Enabled experimental allocation profiling: allocation_sample_every=#{allocation_sample_every}. This is " \
|
36
|
-
'experimental, not recommended, and will increase overhead!'
|
37
|
-
)
|
38
|
-
end
|
39
|
-
|
40
34
|
self.class._native_initialize(
|
41
35
|
self,
|
42
36
|
thread_context_collector,
|
43
37
|
gc_profiling_enabled,
|
44
38
|
idle_sampling_helper,
|
45
|
-
allocation_counting_enabled,
|
46
39
|
no_signals_workaround_enabled,
|
47
40
|
dynamic_sampling_rate_enabled,
|
41
|
+
dynamic_sampling_rate_overhead_target_percentage,
|
48
42
|
allocation_sample_every,
|
43
|
+
allocation_profiling_enabled,
|
49
44
|
)
|
50
45
|
@worker_thread = nil
|
51
46
|
@failure_exception = nil
|
@@ -7,7 +7,7 @@ module Datadog
|
|
7
7
|
# Passing in a `nil` tracer is supported and will disable the following profiling features:
|
8
8
|
# * Code Hotspots panel in the trace viewer, as well as scoping a profile down to a span
|
9
9
|
# * Endpoint aggregation in the profiler UX, including normalization (resource per endpoint call)
|
10
|
-
def self.build_profiler_component(settings:, agent_settings:, optional_tracer:)
|
10
|
+
def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # rubocop:disable Metrics/MethodLength
|
11
11
|
require_relative '../profiling/diagnostics/environment_logger'
|
12
12
|
|
13
13
|
Profiling::Diagnostics::EnvironmentLogger.collect_and_log!
|
@@ -41,38 +41,57 @@ module Datadog
|
|
41
41
|
|
42
42
|
no_signals_workaround_enabled = no_signals_workaround_enabled?(settings)
|
43
43
|
timeline_enabled = settings.profiling.advanced.experimental_timeline_enabled
|
44
|
+
allocation_sample_every = get_allocation_sample_every(settings)
|
45
|
+
allocation_profiling_enabled = enable_allocation_profiling?(settings, allocation_sample_every)
|
46
|
+
heap_sample_every = get_heap_sample_every(settings)
|
47
|
+
heap_profiling_enabled = enable_heap_profiling?(settings, allocation_profiling_enabled, heap_sample_every)
|
48
|
+
heap_size_profiling_enabled = enable_heap_size_profiling?(settings, heap_profiling_enabled)
|
49
|
+
|
50
|
+
overhead_target_percentage = valid_overhead_target(settings.profiling.advanced.overhead_target_percentage)
|
51
|
+
upload_period_seconds = [60, settings.profiling.advanced.upload_period_seconds].max
|
44
52
|
|
45
53
|
recorder = Datadog::Profiling::StackRecorder.new(
|
46
54
|
cpu_time_enabled: RUBY_PLATFORM.include?('linux'), # Only supported on Linux currently
|
47
|
-
alloc_samples_enabled:
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
max_frames: settings.profiling.advanced.max_frames,
|
52
|
-
tracer: optional_tracer,
|
53
|
-
endpoint_collection_enabled: settings.profiling.advanced.endpoint.collection.enabled,
|
55
|
+
alloc_samples_enabled: allocation_profiling_enabled,
|
56
|
+
heap_samples_enabled: heap_profiling_enabled,
|
57
|
+
heap_size_enabled: heap_size_profiling_enabled,
|
58
|
+
heap_sample_every: heap_sample_every,
|
54
59
|
timeline_enabled: timeline_enabled,
|
55
60
|
)
|
61
|
+
thread_context_collector = build_thread_context_collector(settings, recorder, optional_tracer, timeline_enabled)
|
56
62
|
worker = Datadog::Profiling::Collectors::CpuAndWallTimeWorker.new(
|
57
63
|
gc_profiling_enabled: enable_gc_profiling?(settings),
|
58
|
-
allocation_counting_enabled: settings.profiling.advanced.allocation_counting_enabled,
|
59
64
|
no_signals_workaround_enabled: no_signals_workaround_enabled,
|
60
65
|
thread_context_collector: thread_context_collector,
|
61
|
-
|
66
|
+
dynamic_sampling_rate_overhead_target_percentage: overhead_target_percentage,
|
67
|
+
allocation_sample_every: allocation_sample_every,
|
68
|
+
allocation_profiling_enabled: allocation_profiling_enabled,
|
62
69
|
)
|
63
70
|
|
64
71
|
internal_metadata = {
|
65
72
|
no_signals_workaround_enabled: no_signals_workaround_enabled,
|
66
73
|
timeline_enabled: timeline_enabled,
|
74
|
+
allocation_sample_every: allocation_sample_every,
|
75
|
+
heap_sample_every: heap_sample_every,
|
67
76
|
}.freeze
|
68
77
|
|
69
78
|
exporter = build_profiler_exporter(settings, recorder, internal_metadata: internal_metadata)
|
70
79
|
transport = build_profiler_transport(settings, agent_settings)
|
71
|
-
scheduler = Profiling::Scheduler.new(exporter: exporter, transport: transport)
|
80
|
+
scheduler = Profiling::Scheduler.new(exporter: exporter, transport: transport, interval: upload_period_seconds)
|
72
81
|
|
73
82
|
Profiling::Profiler.new(worker: worker, scheduler: scheduler)
|
74
83
|
end
|
75
84
|
|
85
|
+
private_class_method def self.build_thread_context_collector(settings, recorder, optional_tracer, timeline_enabled)
|
86
|
+
Datadog::Profiling::Collectors::ThreadContext.new(
|
87
|
+
recorder: recorder,
|
88
|
+
max_frames: settings.profiling.advanced.max_frames,
|
89
|
+
tracer: optional_tracer,
|
90
|
+
endpoint_collection_enabled: settings.profiling.advanced.endpoint.collection.enabled,
|
91
|
+
timeline_enabled: timeline_enabled,
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
76
95
|
private_class_method def self.build_profiler_exporter(settings, recorder, internal_metadata:)
|
77
96
|
code_provenance_collector =
|
78
97
|
(Profiling::Collectors::CodeProvenance.new if settings.profiling.advanced.code_provenance_enabled)
|
@@ -110,6 +129,126 @@ module Datadog
|
|
110
129
|
end
|
111
130
|
end
|
112
131
|
|
132
|
+
private_class_method def self.get_allocation_sample_every(settings)
|
133
|
+
allocation_sample_rate = settings.profiling.advanced.experimental_allocation_sample_rate
|
134
|
+
|
135
|
+
if allocation_sample_rate <= 0
|
136
|
+
raise ArgumentError, "Allocation sample rate must be a positive integer. Was #{allocation_sample_rate}"
|
137
|
+
end
|
138
|
+
|
139
|
+
allocation_sample_rate
|
140
|
+
end
|
141
|
+
|
142
|
+
private_class_method def self.get_heap_sample_every(settings)
|
143
|
+
heap_sample_rate = settings.profiling.advanced.experimental_heap_sample_rate
|
144
|
+
|
145
|
+
raise ArgumentError, "Heap sample rate must be a positive integer. Was #{heap_sample_rate}" if heap_sample_rate <= 0
|
146
|
+
|
147
|
+
heap_sample_rate
|
148
|
+
end
|
149
|
+
|
150
|
+
private_class_method def self.enable_allocation_profiling?(settings, allocation_sample_every)
|
151
|
+
unless settings.profiling.advanced.experimental_allocation_enabled
|
152
|
+
# Allocation profiling disabled, short-circuit out
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
|
156
|
+
# Allocation sampling is safe and supported on Ruby 2.x, but has a few caveats on Ruby 3.x.
|
157
|
+
|
158
|
+
# SEVERE - All configurations
|
159
|
+
# Ruby 3.2.0 to 3.2.2 have a bug in the newobj tracepoint (https://bugs.ruby-lang.org/issues/19482,
|
160
|
+
# https://github.com/ruby/ruby/pull/7464) that makes this crash in any configuration. This bug is
|
161
|
+
# fixed on Ruby versions 3.2.3 and 3.3.0.
|
162
|
+
if RUBY_VERSION.start_with?('3.2.') && RUBY_VERSION < '3.2.3'
|
163
|
+
Datadog.logger.warn(
|
164
|
+
'Allocation profiling is not supported in Ruby versions 3.2.0, 3.2.1 and 3.2.2 and will be forcibly '\
|
165
|
+
'disabled. This is due to a VM bug that can lead to crashes (https://bugs.ruby-lang.org/issues/19482). '\
|
166
|
+
'Other Ruby versions do not suffer from this issue.'
|
167
|
+
)
|
168
|
+
return false
|
169
|
+
end
|
170
|
+
|
171
|
+
# SEVERE - Only with Ractors
|
172
|
+
# On Ruby versions 3.0 (all), 3.1.0 to 3.1.3, and 3.2.0 to 3.2.2 allocation profiling can trigger a VM bug
|
173
|
+
# that causes a segmentation fault during garbage collection of Ractors
|
174
|
+
# (https://bugs.ruby-lang.org/issues/18464). We don't recommend using this feature on such Rubies.
|
175
|
+
# This bug is fixed on Ruby versions 3.1.4, 3.2.3 and 3.3.0.
|
176
|
+
if RUBY_VERSION.start_with?('3.0.') ||
|
177
|
+
(RUBY_VERSION.start_with?('3.1.') && RUBY_VERSION < '3.1.4') ||
|
178
|
+
(RUBY_VERSION.start_with?('3.2.') && RUBY_VERSION < '3.2.3')
|
179
|
+
Datadog.logger.warn(
|
180
|
+
"Current Ruby version (#{RUBY_VERSION}) has a VM bug where enabling allocation profiling while using "\
|
181
|
+
'Ractors may cause unexpected issues, including crashes (https://bugs.ruby-lang.org/issues/18464). '\
|
182
|
+
'This does not happen if Ractors are not used.'
|
183
|
+
)
|
184
|
+
# ANNOYANCE - Only with Ractors
|
185
|
+
# On all known versions of Ruby 3.x, due to https://bugs.ruby-lang.org/issues/19112, when a ractor gets
|
186
|
+
# garbage collected, Ruby will disable all active tracepoints, which this feature internally relies on.
|
187
|
+
elsif RUBY_VERSION.start_with?('3.')
|
188
|
+
Datadog.logger.warn(
|
189
|
+
'In all known versions of Ruby 3.x, using Ractors may result in allocation profiling unexpectedly ' \
|
190
|
+
'stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your ' \
|
191
|
+
'application stability or performance. This does not happen if Ractors are not used.'
|
192
|
+
)
|
193
|
+
end
|
194
|
+
|
195
|
+
Datadog.logger.warn(
|
196
|
+
"Enabled experimental allocation profiling: allocation_sample_rate=#{allocation_sample_every}. This is " \
|
197
|
+
'experimental, not recommended, and will increase overhead!'
|
198
|
+
)
|
199
|
+
|
200
|
+
true
|
201
|
+
end
|
202
|
+
|
203
|
+
private_class_method def self.enable_heap_profiling?(settings, allocation_profiling_enabled, heap_sample_rate)
|
204
|
+
heap_profiling_enabled = settings.profiling.advanced.experimental_heap_enabled
|
205
|
+
|
206
|
+
return false unless heap_profiling_enabled
|
207
|
+
|
208
|
+
if RUBY_VERSION.start_with?('2.') && RUBY_VERSION < '2.7'
|
209
|
+
Datadog.logger.warn(
|
210
|
+
'Heap profiling currently relies on features introduced in Ruby 2.7 and will be forcibly disabled. '\
|
211
|
+
'Please upgrade to Ruby >= 2.7 in order to use this feature.'
|
212
|
+
)
|
213
|
+
return false
|
214
|
+
end
|
215
|
+
|
216
|
+
if RUBY_VERSION < '3.1'
|
217
|
+
Datadog.logger.debug(
|
218
|
+
"Current Ruby version (#{RUBY_VERSION}) supports forced object recycling which has a bug that the " \
|
219
|
+
'heap profiler is forced to work around to remain accurate. This workaround requires force-setting '\
|
220
|
+
"the SEEN_OBJ_ID flag on objects that should have it but don't. Full details can be found in " \
|
221
|
+
'https://github.com/DataDog/dd-trace-rb/pull/3360. This workaround should be safe but can be ' \
|
222
|
+
'bypassed by disabling the heap profiler or upgrading to Ruby >= 3.1 where forced object recycling ' \
|
223
|
+
'was completely removed (https://bugs.ruby-lang.org/issues/18290).'
|
224
|
+
)
|
225
|
+
end
|
226
|
+
|
227
|
+
unless allocation_profiling_enabled
|
228
|
+
raise ArgumentError,
|
229
|
+
'Heap profiling requires allocation profiling to be enabled'
|
230
|
+
end
|
231
|
+
|
232
|
+
Datadog.logger.warn(
|
233
|
+
"Enabled experimental heap profiling: heap_sample_rate=#{heap_sample_rate}. This is experimental, not " \
|
234
|
+
'recommended, and will increase overhead!'
|
235
|
+
)
|
236
|
+
|
237
|
+
true
|
238
|
+
end
|
239
|
+
|
240
|
+
private_class_method def self.enable_heap_size_profiling?(settings, heap_profiling_enabled)
|
241
|
+
heap_size_profiling_enabled = settings.profiling.advanced.experimental_heap_size_enabled
|
242
|
+
|
243
|
+
return false unless heap_profiling_enabled && heap_size_profiling_enabled
|
244
|
+
|
245
|
+
Datadog.logger.warn(
|
246
|
+
'Enabled experimental heap size profiling. This is experimental, not recommended, and will increase overhead!'
|
247
|
+
)
|
248
|
+
|
249
|
+
true
|
250
|
+
end
|
251
|
+
|
113
252
|
private_class_method def self.no_signals_workaround_enabled?(settings) # rubocop:disable Metrics/MethodLength
|
114
253
|
setting_value = settings.profiling.advanced.no_signals_workaround_enabled
|
115
254
|
legacy_ruby_that_should_use_workaround = RUBY_VERSION.start_with?('2.3.', '2.4.', '2.5.')
|
@@ -217,9 +356,12 @@ module Datadog
|
|
217
356
|
|
218
357
|
return true unless mysql2_client_class && mysql2_client_class.respond_to?(:info)
|
219
358
|
|
220
|
-
|
359
|
+
info = mysql2_client_class.info
|
360
|
+
libmysqlclient_version = Gem::Version.new(info[:version])
|
221
361
|
|
222
|
-
compatible =
|
362
|
+
compatible =
|
363
|
+
libmysqlclient_version >= Gem::Version.new('8.0.0') ||
|
364
|
+
looks_like_mariadb?(info, libmysqlclient_version)
|
223
365
|
|
224
366
|
Datadog.logger.debug(
|
225
367
|
"The `mysql2` gem is using #{compatible ? 'a compatible' : 'an incompatible'} version of " \
|
@@ -245,6 +387,48 @@ module Datadog
|
|
245
387
|
true
|
246
388
|
end
|
247
389
|
end
|
390
|
+
|
391
|
+
private_class_method def self.valid_overhead_target(overhead_target_percentage)
|
392
|
+
if overhead_target_percentage > 0 && overhead_target_percentage <= 20
|
393
|
+
overhead_target_percentage
|
394
|
+
else
|
395
|
+
Datadog.logger.error(
|
396
|
+
'Ignoring invalid value for profiling overhead_target_percentage setting: ' \
|
397
|
+
"#{overhead_target_percentage.inspect}. Falling back to default value."
|
398
|
+
)
|
399
|
+
|
400
|
+
2.0
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# To add just a bit more complexity to our detection code, in https://github.com/DataDog/dd-trace-rb/issues/3334
|
405
|
+
# a user reported that our code was incorrectly flagging the mariadb variant of libmysqlclient as being
|
406
|
+
# incompatible. In fact we have no reports of the mariadb variant needing the "no signals" workaround,
|
407
|
+
# so we flag it as compatible when it's in use.
|
408
|
+
#
|
409
|
+
# A problem is that there doesn't seem to be an obvious way to query the mysql2 gem on which kind of
|
410
|
+
# libmysqlclient it's using, so we detect it by looking at the version.
|
411
|
+
#
|
412
|
+
# The info method for mysql2 with mariadb looks something like this:
|
413
|
+
# `{:id=>30308, :version=>"3.3.8", :header_version=>"11.2.2"}`
|
414
|
+
#
|
415
|
+
# * The version seems to come from https://github.com/mariadb-corporation/mariadb-connector-c and the latest
|
416
|
+
# one is 3.x.
|
417
|
+
# * The header_version is what people usually see as the "mariadb version"
|
418
|
+
#
|
419
|
+
# As a comparison, for libmysql the info looks like:
|
420
|
+
# * `{:id=>80035, :version=>"8.0.35", :header_version=>"8.0.35"}`
|
421
|
+
#
|
422
|
+
# Thus our detection is version 4 or older, because libmysqlclient 4 is almost 20 years old so it's most probably
|
423
|
+
# not that one + header_version being 10 or newer, since according to https://endoflife.date/mariadb that's a
|
424
|
+
# sane range for modern mariadb releases.
|
425
|
+
private_class_method def self.looks_like_mariadb?(info, libmysqlclient_version)
|
426
|
+
header_version = Gem::Version.new(info[:header_version]) if info[:header_version]
|
427
|
+
|
428
|
+
!!(header_version &&
|
429
|
+
libmysqlclient_version < Gem::Version.new('5.0.0') &&
|
430
|
+
header_version >= Gem::Version.new('10.0.0'))
|
431
|
+
end
|
248
432
|
end
|
249
433
|
end
|
250
434
|
end
|
@@ -5,12 +5,11 @@ require_relative '../core/workers/polling'
|
|
5
5
|
|
6
6
|
module Datadog
|
7
7
|
module Profiling
|
8
|
-
# Periodically (every
|
8
|
+
# Periodically (every interval, 60 seconds by default) takes a profile from the `Exporter` and reports it using the
|
9
9
|
# configured transport. Runs on its own background thread.
|
10
10
|
class Scheduler < Core::Worker
|
11
11
|
include Core::Workers::Polling
|
12
12
|
|
13
|
-
DEFAULT_INTERVAL_SECONDS = 60
|
14
13
|
MINIMUM_INTERVAL_SECONDS = 0
|
15
14
|
|
16
15
|
# We sleep for at most this duration seconds before reporting data to avoid multi-process applications all
|
@@ -28,8 +27,7 @@ module Datadog
|
|
28
27
|
def initialize(
|
29
28
|
exporter:,
|
30
29
|
transport:,
|
31
|
-
fork_policy: Core::Workers::Async::Thread::FORK_POLICY_RESTART, # Restart in forks by default
|
32
|
-
interval: DEFAULT_INTERVAL_SECONDS,
|
30
|
+
interval:, fork_policy: Core::Workers::Async::Thread::FORK_POLICY_RESTART, # Restart in forks by default, # seconds
|
33
31
|
enabled: true
|
34
32
|
)
|
35
33
|
@exporter = exporter
|
@@ -115,8 +113,8 @@ module Datadog
|
|
115
113
|
#
|
116
114
|
# During PR review (https://github.com/DataDog/dd-trace-rb/pull/1807) we discussed the possible alternative of
|
117
115
|
# just sleeping before starting the scheduler loop. We ended up not going with that option to avoid the first
|
118
|
-
# profile containing up to
|
119
|
-
# usual
|
116
|
+
# profile containing up to interval + DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS instead of the
|
117
|
+
# usual interval seconds.
|
120
118
|
if run_loop?
|
121
119
|
jitter_seconds = rand * DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS # floating point number between (0.0...maximum)
|
122
120
|
sleep(jitter_seconds)
|