ddtrace 1.18.0 → 1.19.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/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)
|