datadog 2.26.0 → 2.28.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +7 -6
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
  6. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -5
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
  9. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +2 -2
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
  12. data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
  13. data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
  14. data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
  15. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
  16. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
  17. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +9 -8
  18. data/ext/datadog_profiling_native_extension/profiling.c +20 -15
  19. data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
  20. data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
  21. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
  22. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
  23. data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
  24. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
  25. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
  26. data/ext/libdatadog_api/crashtracker.c +5 -8
  27. data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
  28. data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
  29. data/ext/libdatadog_api/ddsketch.c +4 -8
  30. data/ext/libdatadog_api/feature_flags.c +5 -5
  31. data/ext/libdatadog_api/helpers.h +27 -0
  32. data/ext/libdatadog_api/init.c +4 -0
  33. data/lib/datadog/ai_guard/configuration/settings.rb +13 -1
  34. data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
  35. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +42 -0
  36. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
  37. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
  38. data/lib/datadog/ai_guard.rb +2 -0
  39. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
  40. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
  41. data/lib/datadog/appsec/component.rb +1 -1
  42. data/lib/datadog/appsec/context.rb +3 -3
  43. data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
  44. data/lib/datadog/appsec/contrib/active_record/patcher.rb +1 -1
  45. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  46. data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
  47. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
  48. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
  49. data/lib/datadog/appsec/contrib/rails/patcher.rb +10 -2
  50. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  51. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
  52. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
  53. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +5 -4
  54. data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
  55. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +4 -4
  56. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +1 -1
  57. data/lib/datadog/appsec/ext.rb +2 -0
  58. data/lib/datadog/appsec/metrics/collector.rb +8 -3
  59. data/lib/datadog/appsec/metrics/exporter.rb +7 -0
  60. data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
  61. data/lib/datadog/appsec/metrics.rb +5 -5
  62. data/lib/datadog/appsec/remote.rb +4 -4
  63. data/lib/datadog/appsec/utils/http/media_type.rb +37 -23
  64. data/lib/datadog/appsec.rb +7 -1
  65. data/lib/datadog/core/configuration/settings.rb +17 -0
  66. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  67. data/lib/datadog/core/crashtracking/tag_builder.rb +6 -0
  68. data/lib/datadog/core/knuth_sampler.rb +57 -0
  69. data/lib/datadog/core/telemetry/logger.rb +2 -0
  70. data/lib/datadog/core/telemetry/logging.rb +20 -2
  71. data/lib/datadog/di/configuration/settings.rb +22 -0
  72. data/lib/datadog/di/redactor.rb +8 -1
  73. data/lib/datadog/profiling/component.rb +13 -0
  74. data/lib/datadog/profiling/exporter.rb +4 -0
  75. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  76. data/lib/datadog/profiling/flush.rb +3 -0
  77. data/lib/datadog/profiling/profiler.rb +3 -5
  78. data/lib/datadog/profiling/scheduler.rb +8 -7
  79. data/lib/datadog/profiling/tag_builder.rb +1 -0
  80. data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
  81. data/lib/datadog/version.rb +1 -1
  82. metadata +13 -6
@@ -4,14 +4,13 @@ module Datadog
4
4
  module AppSec
5
5
  module Utils
6
6
  module HTTP
7
- # Implementation of media type for content negotiation
7
+ # Implementation of media type for HTTP headers
8
8
  #
9
9
  # See:
10
10
  # - https://www.rfc-editor.org/rfc/rfc7231#section-5.3.1
11
11
  # - https://www.rfc-editor.org/rfc/rfc7231#section-5.3.2
12
12
  class MediaType
13
- class ParseError < ::StandardError
14
- end
13
+ ParseError = Class.new(StandardError) # steep:ignore IncompatibleAssignment
15
14
 
16
15
  WILDCARD = '*'
17
16
 
@@ -19,7 +18,7 @@ module Datadog
19
18
  TOKEN_RE = /[-#$%&'*+.^_`|~A-Za-z0-9]+/.freeze
20
19
 
21
20
  # See: https://www.rfc-editor.org/rfc/rfc7231#section-3.1.1.1
22
- PARAMETER_RE = %r{ # rubocop:disable Style/RegexpLiteral
21
+ PARAMETER_RE = %r{
23
22
  (?:
24
23
  (?<parameter_name>#{TOKEN_RE})
25
24
  =
@@ -46,39 +45,54 @@ module Datadog
46
45
 
47
46
  attr_reader :type, :subtype, :parameters
48
47
 
49
- def initialize(media_type)
50
- media_type_match = MEDIA_TYPE_RE.match(media_type)
48
+ def self.json?(media_type)
49
+ return false if media_type.nil? || media_type.empty?
51
50
 
52
- raise ParseError, media_type.inspect if media_type_match.nil?
51
+ match = MEDIA_TYPE_RE.match(media_type)
52
+ return false if match.nil?
53
53
 
54
- @type = (media_type_match['type'] || WILDCARD).downcase
55
- @subtype = (media_type_match['subtype'] || WILDCARD).downcase
56
- @parameters = {}
54
+ subtype = match['subtype']
55
+ return false if subtype.nil? || subtype.empty?
57
56
 
58
- parameters = media_type_match['parameters']
57
+ subtype.downcase!
58
+ subtype == 'json' || subtype.end_with?('+json')
59
+ end
59
60
 
60
- return if parameters.nil?
61
+ def initialize(media_type)
62
+ match = MEDIA_TYPE_RE.match(media_type)
63
+ raise ParseError, media_type.inspect if match.nil?
61
64
 
62
- parameters.split(';').map(&:strip).each do |parameter|
63
- parameter_match = PARAMETER_RE.match(parameter)
65
+ @type = match['type'] || WILDCARD
66
+ @type.downcase!
64
67
 
65
- next if parameter_match.nil?
68
+ @subtype = match['subtype'] || WILDCARD
69
+ @subtype.downcase!
66
70
 
67
- parameter_name = parameter_match['parameter_name']
68
- parameter_value = parameter_match['parameter_value']
71
+ @parameters = {}
72
+
73
+ parameters = match['parameters']
74
+ return if parameters.nil? || parameters.empty?
69
75
 
70
- next if parameter_name.nil? || parameter_value.nil?
76
+ parameters.scan(PARAMETER_RE) do |name, unquoted_value, quoted_value|
77
+ # NOTE: Order of unquoted_value and quoted_value does not matter,
78
+ # as they are mutually exclusive by the regex.
79
+ # @type var value: ::String?
80
+ value = unquoted_value || quoted_value
81
+ next if name.nil? || value.nil?
71
82
 
72
- @parameters[parameter_name.downcase] = parameter_value.downcase
83
+ # See https://github.com/soutaro/steep/issues/2051
84
+ name.downcase! # steep:ignore NoMethod
85
+ value.downcase!
86
+
87
+ # See https://github.com/soutaro/steep/issues/2051
88
+ @parameters[name] = value # steep:ignore ArgumentTypeMismatch
73
89
  end
74
90
  end
75
91
 
76
92
  def to_s
77
- s = +"#{@type}/#{@subtype}"
78
-
79
- s << ';' << @parameters.map { |k, v| "#{k}=#{v}" }.join(';') if @parameters.count > 0
93
+ return "#{@type}/#{@subtype}" if @parameters.empty?
80
94
 
81
- s
95
+ "#{@type}/#{@subtype};#{@parameters.map { |k, v| "#{k}=#{v}" }.join(";")}"
82
96
  end
83
97
  end
84
98
  end
@@ -22,8 +22,14 @@ module Datadog
22
22
  Datadog::AppSec::Context.active
23
23
  end
24
24
 
25
+ # NOTE: This is a temporary workaround for type checking.
26
+ #
27
+ # We want to move from possible nil-component to the disabled-component
28
+ # on an initialization error. Technically, telemetry will be never
29
+ # used if AppSec was not able to initialize, so it's safe to assume
30
+ # that telemetry will never be used and will be nil at the same time.
25
31
  def telemetry
26
- components.appsec&.telemetry
32
+ components.appsec&.telemetry || components.telemetry
27
33
  end
28
34
 
29
35
  def security_engine
@@ -436,6 +436,23 @@ module Datadog
436
436
  o.default true
437
437
  end
438
438
 
439
+ # The profiler gathers data by sending `SIGPROF` unix signals to Ruby application threads.
440
+ #
441
+ # When using `Kernel#exec` on Linux, it can happen that a signal sent before calling `exec` arrives after
442
+ # the new process is running, causing it to fail with the `Profiling timer expired` error message.
443
+ # To avoid this, the profiler installs a monkey patch on `Kernel#exec` to stop profiling before actually
444
+ # calling `exec`.
445
+ # This monkey patch is available for Ruby 2.7+; let us know if you need it on earlier Rubies.
446
+ # For more details see https://github.com/DataDog/dd-trace-rb/issues/5101 .
447
+ #
448
+ # @default `DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED` environment variable as a boolean,
449
+ # otherwise `true`
450
+ option :shutdown_on_exec_enabled do |o|
451
+ o.env 'DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED'
452
+ o.type :bool
453
+ o.default true
454
+ end
455
+
439
456
  # Configures how much wall-time overhead the profiler targets. The profiler will dynamically adjust the
440
457
  # interval between samples it takes so as to try and maintain the property that it spends no longer than
441
458
  # this amount of wall-clock time profiling. For example, with the default value of 2%, the profiler will
@@ -48,6 +48,7 @@ module Datadog
48
48
  "DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE",
49
49
  "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS",
50
50
  "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES",
51
+ "DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS",
51
52
  "DD_ENV",
52
53
  "DD_ERROR_TRACKING_HANDLED_ERRORS",
53
54
  "DD_ERROR_TRACKING_HANDLED_ERRORS_INCLUDE",
@@ -81,6 +82,7 @@ module Datadog
81
82
  "DD_PROFILING_NO_SIGNALS_WORKAROUND_ENABLED",
82
83
  "DD_PROFILING_OVERHEAD_TARGET_PERCENTAGE",
83
84
  "DD_PROFILING_PREVIEW_OTEL_CONTEXT_ENABLED",
85
+ "DD_PROFILING_SHUTDOWN_ON_EXEC_ENABLED",
84
86
  "DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED",
85
87
  "DD_PROFILING_SKIP_MYSQL2_CHECK",
86
88
  "DD_PROFILING_TIMELINE_ENABLED",
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../tag_builder'
4
4
  require_relative '../utils'
5
+ require_relative '../environment/process'
5
6
 
6
7
  module Datadog
7
8
  module Core
@@ -13,6 +14,11 @@ module Datadog
13
14
  'is_crash' => 'true',
14
15
  )
15
16
 
17
+ if settings.experimental_propagate_process_tags_enabled
18
+ process_tags = Environment::Process.serialized
19
+ hash['process_tags'] = process_tags unless process_tags.empty?
20
+ end
21
+
16
22
  Utils.encode_tags(hash)
17
23
  end
18
24
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ # Deterministic sampler using Knuth multiplicative hash algorithm.
6
+ #
7
+ # This sampler provides consistent sampling decisions based on an input value,
8
+ # ensuring the same input always produces the same sampling decision for a given rate.
9
+ #
10
+ # The algorithm multiplies the input by a large prime (Knuth factor), takes modulo
11
+ # to constrain to a fixed range, and compares against a threshold derived from the sample rate.
12
+ #
13
+ # @api private
14
+ # @see https://en.wikipedia.org/wiki/Hash_function#Multiplicative_hashing
15
+ class KnuthSampler
16
+ # Maximum unsigned 64-bit integer for uniform distribution across 64-bit input space.
17
+ UINT64_MAX = (1 << 64) - 1
18
+ UINT64_MODULO = 1 << 64
19
+
20
+ # Golden ratio constant for optimal distribution.
21
+ # @see https://en.wikipedia.org/wiki/Hash_function#Fibonacci_hashing
22
+ DEFAULT_KNUTH_FACTOR = 11400714819323198485
23
+
24
+ attr_reader :rate
25
+
26
+ # @param rate [Float] Sampling rate between +0.0+ and +1.0+ (inclusive).
27
+ # +0.0+ means no samples are kept; +1.0+ means all samples are kept.
28
+ # Invalid values fall back to +1.0+ (sample everything).
29
+ # @param knuth_factor [Integer] Multiplicative constant for hashing.
30
+ # Different factors produce different sampling distributions.
31
+ def initialize(rate = 1.0, knuth_factor: DEFAULT_KNUTH_FACTOR)
32
+ @knuth_factor = knuth_factor
33
+
34
+ rate = rate.to_f
35
+ unless rate >= 0.0 && rate <= 1.0
36
+ Datadog.logger.warn("Sample rate #{rate} is not between 0.0 and 1.0, falling back to 1.0")
37
+ rate = 1.0
38
+ end
39
+
40
+ @rate = rate
41
+ @threshold = @rate * UINT64_MAX
42
+ end
43
+
44
+ # Determines if the given input should be sampled.
45
+ #
46
+ # This method is deterministic: the same input value always produces
47
+ # the same result for a given sample rate and configuration.
48
+ #
49
+ # @param input [Integer] Value to determine sampling decision.
50
+ # Typically a trace ID or incrementing counter.
51
+ # @return [Boolean] +true+ if input should be sampled, +false+ otherwise
52
+ def sample?(input)
53
+ ((input * @knuth_factor) % UINT64_MODULO) <= @threshold
54
+ end
55
+ end
56
+ end
57
+ end
@@ -14,10 +14,12 @@ module Datadog
14
14
  # read: lib/datadog/core/telemetry/logging.rb
15
15
  module Logger
16
16
  class << self
17
+ # (see Datadog::Core::Telemetry::Logging#report)
17
18
  def report(exception, level: :error, description: nil)
18
19
  instance&.report(exception, level: level, description: description)
19
20
  end
20
21
 
22
+ # (see Datadog::Core::Telemetry::Logging#error)
21
23
  def error(description)
22
24
  instance&.error(description)
23
25
  end
@@ -45,11 +45,20 @@ module Datadog
45
45
  end
46
46
  end
47
47
 
48
+ # @param exception [Exception] The exception to report
49
+ # @param level [Symbol] The log level (:error, :warn, :info, :debug)
50
+ # @param description [String, nil] A low cardinality description, without dynamic data
48
51
  def report(exception, level: :error, description: nil)
49
52
  # Anonymous exceptions to be logged as <Class:0x00007f8b1c0b3b40>
50
- message = +"#{exception.class.name || exception.class.inspect}" # standard:disable Style/RedundantInterpolation
53
+ message = +(exception.class.name || exception.class.inspect)
51
54
 
52
- message << ": #{description}" if description
55
+ telemetry_message = message_for_telemetry(exception)
56
+
57
+ if description || telemetry_message
58
+ message << ':'
59
+ message << " #{description}" if description
60
+ message << " (#{telemetry_message})" if telemetry_message
61
+ end
53
62
 
54
63
  event = Event::Log.new(
55
64
  message: message,
@@ -65,6 +74,15 @@ module Datadog
65
74
 
66
75
  log!(event)
67
76
  end
77
+
78
+ private
79
+
80
+ # A constant string representing the exception
81
+ def message_for_telemetry(exception)
82
+ return unless exception.instance_variable_defined?(:@telemetry_message)
83
+
84
+ exception.instance_variable_get(:@telemetry_message)
85
+ end
68
86
  end
69
87
  end
70
88
  end
@@ -45,6 +45,28 @@ module Datadog
45
45
  o.default []
46
46
  end
47
47
 
48
+ # An array of variable and key names to exclude from the
49
+ # built-in redaction list.
50
+ #
51
+ # This allows users to capture values of variables that would
52
+ # otherwise be redacted by the default identifier list.
53
+ # For example, if an application has a "session" variable
54
+ # that does not contain sensitive data, "session" can be added
55
+ # to this list to exclude it from redaction.
56
+ #
57
+ # The names will be normalized the same way as redacted_identifiers,
58
+ # by removing the following symbols: _, -, @, $, and then matched
59
+ # against the complete variable or key name while ignoring the case.
60
+ option :redaction_excluded_identifiers do |o|
61
+ o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS"
62
+ o.env_parser do |value|
63
+ value&.split(",")&.map(&:strip)
64
+ end
65
+
66
+ o.type :array
67
+ o.default []
68
+ end
69
+
48
70
  # An array of class names, values of which will be redacted from
49
71
  # dynamic instrumentation snapshots. Example: FooClass.
50
72
  # If a name is suffixed by '*', it becomes a wildcard and
@@ -13,6 +13,10 @@ module Datadog
13
13
  # redaction. Additional names can be provided by the user via the
14
14
  # settings.dynamic_instrumentation.redacted_identifiers setting or
15
15
  # the DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS environment
16
+ # variable. Users can also exclude specific identifiers from the default
17
+ # redaction list via the
18
+ # settings.dynamic_instrumentation.redaction_excluded_identifiers setting or
19
+ # the DD_DYNAMIC_INSTRUMENTATION_REDACTION_EXCLUDED_IDENTIFIERS environment
16
20
  # variable. Currently no class names are subject to redaction by default;
17
21
  # class names can be provided via the
18
22
  # settings.dynamic_instrumentation.redacted_type_names setting or
@@ -61,7 +65,10 @@ module Datadog
61
65
  names.map! do |name|
62
66
  normalize(name)
63
67
  end
64
- Set.new(names)
68
+ excluded = settings.dynamic_instrumentation.redaction_excluded_identifiers.map do |name|
69
+ normalize(name)
70
+ end
71
+ Set.new(names) - Set.new(excluded)
65
72
  end
66
73
  end
67
74
 
@@ -82,6 +82,10 @@ module Datadog
82
82
  Datadog::Profiling::Ext::DirMonkeyPatches.apply!
83
83
  end
84
84
 
85
+ if can_apply_exec_monkey_patch?(settings)
86
+ Datadog::Profiling::Ext::ExecMonkeyPatch.apply!
87
+ end
88
+
85
89
  [profiler, {profiling_enabled: true}]
86
90
  end
87
91
 
@@ -434,6 +438,15 @@ module Datadog
434
438
  settings.profiling.advanced.dir_interruption_workaround_enabled
435
439
  end
436
440
 
441
+ private_class_method def self.can_apply_exec_monkey_patch?(settings)
442
+ return false if RUBY_VERSION < "2.7"
443
+
444
+ # This file is 2.7+ only so we only require it here once we've checked the Ruby version
445
+ require "datadog/profiling/ext/exec_monkey_patch"
446
+
447
+ settings.profiling.advanced.shutdown_on_exec_enabled
448
+ end
449
+
437
450
  private_class_method def self.enable_gvl_profiling?(settings, logger)
438
451
  return false if RUBY_VERSION < "3.2"
439
452
 
@@ -70,6 +70,9 @@ module Datadog
70
70
 
71
71
  uncompressed_code_provenance = code_provenance_collector.refresh.generate_json if code_provenance_collector
72
72
 
73
+ process_tags = Datadog.configuration.experimental_propagate_process_tags_enabled ?
74
+ Core::Environment::Process.serialized : ''
75
+
73
76
  Flush.new(
74
77
  start: start,
75
78
  finish: finish,
@@ -80,6 +83,7 @@ module Datadog
80
83
  settings: Datadog.configuration,
81
84
  profile_seq: sequence_tracker.get_next,
82
85
  ).to_a,
86
+ process_tags: process_tags,
83
87
  internal_metadata: internal_metadata.merge(
84
88
  {
85
89
  worker_stats: worker_stats,
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Profiling
5
+ module Ext
6
+ # The profiler gathers data by sending `SIGPROF` unix signals to Ruby application threads.
7
+ #
8
+ # When using `Kernel#exec` on Linux, it can happen that a signal sent before calling `exec` arrives after
9
+ # the new process is running, causing it to fail with the `Profiling timer expired` error message.
10
+ # To avoid this, the profiler installs a monkey patch on `Kernel#exec` to stop profiling before actually
11
+ # calling `exec`.
12
+ # This monkey patch is available for Ruby 2.7+; let us know if you need it on earlier Rubies.
13
+ # For more details see https://github.com/DataDog/dd-trace-rb/issues/5101 .
14
+ module ExecMonkeyPatch
15
+ def self.apply!
16
+ ::Object.prepend(ObjectMonkeyPatch)
17
+
18
+ true
19
+ end
20
+
21
+ module ObjectMonkeyPatch
22
+ private
23
+
24
+ def exec(...)
25
+ Datadog.send(:components, allow_initialization: false)&.profiler&.shutdown!(report_last_profile: false)
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -13,6 +13,7 @@ module Datadog
13
13
  :code_provenance_file_name,
14
14
  :code_provenance_data,
15
15
  :tags_as_array,
16
+ :process_tags,
16
17
  :internal_metadata_json,
17
18
  :info_json
18
19
 
@@ -23,6 +24,7 @@ module Datadog
23
24
  code_provenance_file_name:,
24
25
  code_provenance_data:,
25
26
  tags_as_array:,
27
+ process_tags:,
26
28
  internal_metadata:,
27
29
  info_json:
28
30
  )
@@ -32,6 +34,7 @@ module Datadog
32
34
  @code_provenance_file_name = code_provenance_file_name
33
35
  @code_provenance_data = code_provenance_data
34
36
  @tags_as_array = tags_as_array
37
+ @process_tags = process_tags
35
38
  @internal_metadata_json = JSON.generate(internal_metadata)
36
39
  @info_json = info_json
37
40
  end
@@ -31,10 +31,11 @@ module Datadog
31
31
  scheduler.start(on_failure_proc: proc { component_failed(:scheduler) })
32
32
  end
33
33
 
34
- def shutdown!
34
+ def shutdown!(report_last_profile: true)
35
35
  Datadog.logger.debug("Shutting down profiler")
36
36
 
37
37
  stop_worker
38
+ scheduler.disable_reporting unless report_last_profile
38
39
  stop_scheduler
39
40
  end
40
41
 
@@ -57,11 +58,8 @@ module Datadog
57
58
  Datadog::Core::Telemetry::Logger
58
59
  .error("Detected issue with profiler (#{failed_component} component), stopping profiling")
59
60
 
60
- # We explicitly not stop the crash tracker in this situation, under the assumption that, if a component failed,
61
- # we're operating in a degraded state and crash tracking may still be helpful.
62
-
63
61
  if failed_component == :worker
64
- scheduler.mark_profiler_failed
62
+ scheduler.disable_reporting
65
63
  stop_scheduler
66
64
  elsif failed_component == :scheduler
67
65
  stop_worker
@@ -24,7 +24,7 @@ module Datadog
24
24
  attr_reader \
25
25
  :exporter,
26
26
  :transport,
27
- :profiler_failed
27
+ :reporting_disabled
28
28
 
29
29
  public
30
30
 
@@ -36,7 +36,7 @@ module Datadog
36
36
  )
37
37
  @exporter = exporter
38
38
  @transport = transport
39
- @profiler_failed = false
39
+ @reporting_disabled = false
40
40
  @stop_requested = false
41
41
 
42
42
  # Workers::Async::Thread settings
@@ -83,14 +83,15 @@ module Datadog
83
83
  true
84
84
  end
85
85
 
86
- # This is called by the Profiler class whenever an issue happened in the profiler. This makes sure that even
87
- # if there is data to be flushed, we don't try to flush it.
88
- def mark_profiler_failed
89
- @profiler_failed = true
86
+ # Called by the Profiler when it wants to prevent any further reporting,
87
+ # even if there is data to be flushed.
88
+ # Used when the profiler failed or we're otherwise in a hurry to shut down.
89
+ def disable_reporting
90
+ @reporting_disabled = true
90
91
  end
91
92
 
92
93
  def work_pending?
93
- !profiler_failed && exporter.can_flush? && (run_loop? || !stop_requested?)
94
+ !reporting_disabled && exporter.can_flush? && (run_loop? || !stop_requested?)
94
95
  end
95
96
 
96
97
  def reset_after_fork
@@ -50,6 +50,7 @@ module Datadog
50
50
  FORM_FIELD_TAG_PROFILER_VERSION => profiler_version,
51
51
  'profile_seq' => profile_seq.to_s,
52
52
  )
53
+
53
54
  user_tag_keys = settings.tags.keys
54
55
  hash.keep_if { |tag| user_tag_keys.include?(tag) || ALLOWED_TAGS.include?(tag) }
55
56
  Core::Utils.encode_tags(hash)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'sampler'
4
- require_relative '../utils'
4
+ require_relative '../../core/knuth_sampler'
5
5
 
6
6
  module Datadog
7
7
  module Tracing
@@ -9,46 +9,35 @@ module Datadog
9
9
  # {Datadog::Tracing::Sampling::RateSampler} is based on a sample rate.
10
10
  class RateSampler < Sampler
11
11
  KNUTH_FACTOR = 1111111111111111111
12
- UINT64_MODULO = (1 << 64)
13
12
 
14
13
  # Initialize a {Datadog::Tracing::Sampling::RateSampler}.
15
14
  # This sampler keeps a random subset of the traces. Its main purpose is to
16
15
  # reduce the instrumentation footprint.
17
16
  #
18
17
  # @param sample_rate [Numeric] the sample rate between 0.0 and 1.0, inclusive.
19
- # 0.0 means that no trace will be sampled; 1.0 means that all traces will be sampled.
18
+ # 0.0 means that no trace will be sampled; 1.0 means that all traces will be sampled.
20
19
  def initialize(sample_rate = 1.0, decision: nil)
21
20
  super()
22
-
23
- unless sample_rate >= 0.0 && sample_rate <= 1.0
24
- Datadog.logger.warn('sample rate is not between 0 and 1, falling back to 1')
25
- sample_rate = 1.0
26
- end
27
-
28
- self.sample_rate = sample_rate
29
-
21
+ @sampler = Core::KnuthSampler.new(sample_rate, knuth_factor: KNUTH_FACTOR)
30
22
  @decision = decision
31
23
  end
32
24
 
33
25
  def sample_rate(*_)
34
- @sample_rate
26
+ @sampler.rate
35
27
  end
36
28
 
37
29
  def sample_rate=(sample_rate)
38
- @sample_rate = sample_rate
39
- @sampling_id_threshold = sample_rate * Tracing::Utils::EXTERNAL_MAX_ID
30
+ @sampler = Core::KnuthSampler.new(sample_rate, knuth_factor: KNUTH_FACTOR)
40
31
  end
41
32
 
42
33
  def sample?(trace)
43
- ((trace.id * KNUTH_FACTOR) % UINT64_MODULO) <= @sampling_id_threshold
34
+ @sampler.sample?(trace.id)
44
35
  end
45
36
 
46
37
  def sample!(trace)
47
- sampled = sample?(trace)
48
-
49
- return false unless sampled
38
+ return false unless sample?(trace)
50
39
 
51
- trace.sample_rate = @sample_rate
40
+ trace.sample_rate = sample_rate
52
41
  trace.set_tag(Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, @decision) if @decision
53
42
 
54
43
  true
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 26
6
+ MINOR = 28
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
  BUILD = nil