datadog 2.17.0 → 2.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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +63 -56
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +263 -76
  5. data/ext/datadog_profiling_native_extension/collectors_stack.h +20 -3
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +62 -12
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
  8. data/ext/datadog_profiling_native_extension/extconf.rb +7 -0
  9. data/ext/datadog_profiling_native_extension/heap_recorder.c +239 -363
  10. data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -6
  11. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +22 -0
  12. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +8 -5
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +38 -26
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -4
  15. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -13
  16. data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -11
  17. data/ext/datadog_profiling_native_extension/stack_recorder.c +154 -57
  18. data/ext/libdatadog_api/extconf.rb +2 -2
  19. data/ext/libdatadog_api/library_config.c +54 -12
  20. data/ext/libdatadog_api/library_config.h +6 -0
  21. data/ext/libdatadog_api/process_discovery.c +2 -7
  22. data/ext/libdatadog_extconf_helpers.rb +1 -1
  23. data/lib/datadog/appsec/api_security/lru_cache.rb +9 -2
  24. data/lib/datadog/appsec/api_security/route_extractor.rb +71 -0
  25. data/lib/datadog/appsec/api_security/sampler.rb +59 -0
  26. data/lib/datadog/appsec/api_security.rb +14 -0
  27. data/lib/datadog/appsec/assets/waf_rules/recommended.json +257 -85
  28. data/lib/datadog/appsec/assets/waf_rules/strict.json +10 -78
  29. data/lib/datadog/appsec/component.rb +30 -54
  30. data/lib/datadog/appsec/configuration/settings.rb +60 -2
  31. data/lib/datadog/appsec/context.rb +6 -6
  32. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +1 -1
  33. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +27 -16
  34. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +1 -1
  35. data/lib/datadog/appsec/processor/rule_loader.rb +5 -6
  36. data/lib/datadog/appsec/remote.rb +15 -55
  37. data/lib/datadog/appsec/security_engine/engine.rb +194 -0
  38. data/lib/datadog/appsec/security_engine/runner.rb +10 -11
  39. data/lib/datadog/appsec.rb +4 -7
  40. data/lib/datadog/core/configuration/agent_settings.rb +52 -0
  41. data/lib/datadog/core/configuration/agent_settings_resolver.rb +1 -43
  42. data/lib/datadog/core/configuration/components.rb +2 -4
  43. data/lib/datadog/core/configuration/option.rb +9 -9
  44. data/lib/datadog/core/configuration/settings.rb +42 -10
  45. data/lib/datadog/core/configuration/stable_config.rb +1 -2
  46. data/lib/datadog/core/crashtracking/tag_builder.rb +4 -22
  47. data/lib/datadog/core/process_discovery/tracer_memfd.rb +15 -0
  48. data/lib/datadog/core/process_discovery.rb +5 -1
  49. data/lib/datadog/core/remote/configuration/repository.rb +12 -0
  50. data/lib/datadog/core/tag_builder.rb +56 -0
  51. data/lib/datadog/core/telemetry/component.rb +8 -4
  52. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +1 -0
  53. data/lib/datadog/core/telemetry/event/app_started.rb +148 -40
  54. data/lib/datadog/core/telemetry/logger.rb +5 -4
  55. data/lib/datadog/core/telemetry/logging.rb +11 -5
  56. data/lib/datadog/core/transport/http/adapters/net.rb +17 -2
  57. data/lib/datadog/core/transport/http/builder.rb +2 -2
  58. data/lib/datadog/core/transport/http/env.rb +8 -0
  59. data/lib/datadog/core/utils.rb +7 -0
  60. data/lib/datadog/di/instrumenter.rb +48 -5
  61. data/lib/datadog/di/probe_notification_builder.rb +37 -42
  62. data/lib/datadog/di/probe_notifier_worker.rb +9 -1
  63. data/lib/datadog/di/serializer.rb +10 -2
  64. data/lib/datadog/di/transport/http/input.rb +10 -0
  65. data/lib/datadog/di/transport/input.rb +10 -2
  66. data/lib/datadog/di.rb +0 -6
  67. data/lib/datadog/kit/appsec/events/v2.rb +195 -0
  68. data/lib/datadog/profiling/collectors/code_provenance.rb +17 -8
  69. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +6 -0
  70. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -0
  71. data/lib/datadog/profiling/collectors/info.rb +41 -0
  72. data/lib/datadog/profiling/collectors/thread_context.rb +16 -1
  73. data/lib/datadog/profiling/component.rb +8 -9
  74. data/lib/datadog/profiling/exporter.rb +9 -3
  75. data/lib/datadog/profiling/ext.rb +0 -12
  76. data/lib/datadog/profiling/http_transport.rb +2 -2
  77. data/lib/datadog/profiling/profiler.rb +2 -0
  78. data/lib/datadog/profiling/scheduler.rb +2 -1
  79. data/lib/datadog/profiling/sequence_tracker.rb +44 -0
  80. data/lib/datadog/profiling/stack_recorder.rb +5 -5
  81. data/lib/datadog/profiling/tag_builder.rb +7 -37
  82. data/lib/datadog/profiling/tasks/setup.rb +2 -0
  83. data/lib/datadog/profiling.rb +1 -0
  84. data/lib/datadog/single_step_instrument.rb +9 -0
  85. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +15 -0
  86. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +19 -12
  87. data/lib/datadog/tracing/contrib/action_pack/ext.rb +2 -0
  88. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +7 -1
  89. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +13 -0
  90. data/lib/datadog/tracing/contrib/lograge/patcher.rb +4 -2
  91. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +16 -6
  92. data/lib/datadog/tracing/contrib/rails/patcher.rb +4 -1
  93. data/lib/datadog/tracing/contrib/rails/runner.rb +61 -40
  94. data/lib/datadog/tracing/contrib/sidekiq/ext.rb +1 -0
  95. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +5 -2
  96. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  97. data/lib/datadog/tracing/span_event.rb +1 -1
  98. data/lib/datadog/tracing/span_operation.rb +22 -0
  99. data/lib/datadog/tracing/sync_writer.rb +1 -1
  100. data/lib/datadog/tracing/trace_operation.rb +12 -4
  101. data/lib/datadog/tracing/tracer.rb +6 -2
  102. data/lib/datadog/version.rb +1 -1
  103. data/lib/datadog.rb +7 -0
  104. metadata +14 -10
  105. data/lib/datadog/appsec/assets/waf_rules/processors.json +0 -321
  106. data/lib/datadog/appsec/assets/waf_rules/scanners.json +0 -1023
  107. data/lib/datadog/appsec/processor/rule_merger.rb +0 -171
  108. data/lib/datadog/appsec/processor.rb +0 -107
@@ -39,60 +39,70 @@ module Datadog
39
39
  end
40
40
 
41
41
  # Duration is in seconds.
42
+ # path is the actual path of the instrumented file.
42
43
  def build_executed(probe,
43
- trace_point: nil, rv: nil, duration: nil, caller_locations: nil,
44
- args: nil, kwargs: nil, serialized_entry_args: nil)
45
- snapshot = if probe.line? && probe.capture_snapshot?
46
- if trace_point.nil?
47
- raise "Cannot create snapshot because there is no trace point"
48
- end
49
- get_local_variables(trace_point)
50
- end
51
- # TODO check how many stack frames we should be keeping/sending,
52
- # this should be all frames for enriched probes and no frames for
53
- # non-enriched probes?
54
- build_snapshot(probe, rv: rv, snapshot: snapshot,
44
+ path: nil, rv: nil, duration: nil, caller_locations: nil,
45
+ serialized_locals: nil, args: nil, kwargs: nil, target_self: nil,
46
+ serialized_entry_args: nil)
47
+ build_snapshot(probe, rv: rv, serialized_locals: serialized_locals,
55
48
  # Actual path of the instrumented file.
56
- path: trace_point&.path,
57
- duration: duration, caller_locations: caller_locations, args: args, kwargs: kwargs,
49
+ path: path,
50
+ duration: duration,
51
+ # TODO check how many stack frames we should be keeping/sending,
52
+ # this should be all frames for enriched probes and no frames for
53
+ # non-enriched probes?
54
+ caller_locations: caller_locations,
55
+ args: args, kwargs: kwargs,
56
+ target_self: target_self,
58
57
  serialized_entry_args: serialized_entry_args)
59
58
  end
60
59
 
61
- def build_snapshot(probe, rv: nil, snapshot: nil, path: nil,
62
- duration: nil, caller_locations: nil, args: nil, kwargs: nil,
60
+ def build_snapshot(probe, rv: nil, serialized_locals: nil, path: nil,
61
+ # In Ruby everything is a method, therefore we should always have
62
+ # a target self. However, if we are not capturing a snapshot,
63
+ # there is no need to pass in the target self.
64
+ target_self: nil,
65
+ duration: nil, caller_locations: nil,
66
+ args: nil, kwargs: nil,
63
67
  serialized_entry_args: nil)
68
+ if probe.capture_snapshot? && !target_self
69
+ raise ArgumentError, "Asked to build snapshot with snapshot capture but target_self is nil"
70
+ end
71
+
64
72
  # TODO also verify that non-capturing probe does not pass
65
73
  # snapshot or vars/args into this method
66
74
  captures = if probe.capture_snapshot?
67
75
  if probe.method?
76
+ return_arguments = {
77
+ "@return": serializer.serialize_value(rv,
78
+ depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
79
+ attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
80
+ self: serializer.serialize_value(target_self),
81
+ }
68
82
  {
69
83
  entry: {
70
84
  # standard:disable all
71
85
  arguments: if serialized_entry_args
72
86
  serialized_entry_args
73
87
  else
74
- (args || kwargs) && serializer.serialize_args(args, kwargs,
88
+ (args || kwargs) && serializer.serialize_args(args, kwargs, target_self,
75
89
  depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
76
90
  attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
77
91
  end,
78
- throwable: nil,
79
92
  # standard:enable all
80
93
  },
81
94
  return: {
82
- arguments: {
83
- "@return": serializer.serialize_value(rv,
84
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
85
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
86
- },
95
+ arguments: return_arguments,
87
96
  throwable: nil,
88
97
  },
89
98
  }
90
99
  elsif probe.line?
91
100
  {
92
- lines: snapshot && {
93
- probe.line_no => {locals: serializer.serialize_vars(snapshot,
94
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
95
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)},
101
+ lines: serialized_locals && {
102
+ probe.line_no => {
103
+ locals: serialized_locals,
104
+ arguments: {self: serializer.serialize_value(target_self)},
105
+ },
96
106
  },
97
107
  }
98
108
  end
@@ -194,21 +204,6 @@ module Datadog
194
204
  (Core::Utils::Time.now.to_f * 1000).to_i
195
205
  end
196
206
 
197
- def get_local_variables(trace_point)
198
- # binding appears to be constructed on access, therefore
199
- # 1) we should attempt to cache it and
200
- # 2) we should not call +binding+ until we actually need variable values.
201
- binding = trace_point.binding
202
-
203
- # steep hack - should never happen
204
- return {} unless binding
205
-
206
- binding.local_variables.each_with_object({}) do |name, map|
207
- value = binding.local_variable_get(name)
208
- map[name] = value
209
- end
210
- end
211
-
212
207
  def active_trace
213
208
  if defined?(Datadog::Tracing)
214
209
  Datadog::Tracing.active_trace
@@ -183,7 +183,15 @@ module Datadog
183
183
  end
184
184
 
185
185
  def do_send_snapshot(batch)
186
- snapshot_transport.send_input(batch)
186
+ snapshot_transport.send_input(batch, tags)
187
+ end
188
+
189
+ def tags
190
+ # DEV: The tags could be cached but they need to be recreated
191
+ # when process forks (since the child receives new runtime IDs).
192
+ Core::TagBuilder.tags(settings).merge(
193
+ 'debugger_version' => Core::Environment::Identity.gem_datadog_version,
194
+ )
187
195
  end
188
196
 
189
197
  [
@@ -36,6 +36,10 @@ module Datadog
36
36
  # efficient but there would be additional overhead from passing this
37
37
  # parameter all the time and the API would get more complex.
38
38
  #
39
+ # Note: "self" cannot be used as a parameter name in Ruby, therefore
40
+ # there should never be a conflict between instance variable
41
+ # serialization and method parameters.
42
+ #
39
43
  # @api private
40
44
  class Serializer
41
45
  # Third-party library integration / custom serializers.
@@ -82,7 +86,11 @@ module Datadog
82
86
  # between positional and keyword arguments. We convert positional
83
87
  # arguments to keyword arguments ("arg1", "arg2", ...) and ensure
84
88
  # the positional arguments are listed first.
85
- def serialize_args(args, kwargs,
89
+ #
90
+ # Instance variables are technically a hash just like kwargs,
91
+ # we take them as a separate parameter to avoid a hash merge
92
+ # in upstream code.
93
+ def serialize_args(args, kwargs, target_self,
86
94
  depth: settings.dynamic_instrumentation.max_capture_depth,
87
95
  attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count)
88
96
  counter = 0
@@ -91,7 +99,7 @@ module Datadog
91
99
  # Conversion to symbol is needed here to put args ahead of
92
100
  # kwargs when they are merged below.
93
101
  c[:"arg#{counter}"] = value
94
- end.update(kwargs)
102
+ end.update(kwargs).update(self: target_self)
95
103
  serialize_vars(combined, depth: depth, attribute_count: attribute_count)
96
104
  end
97
105
 
@@ -53,6 +53,16 @@ module Datadog
53
53
  # Encode body & type
54
54
  env.headers[HEADER_CONTENT_TYPE] = encoder.content_type
55
55
  env.body = env.request.parcel.data
56
+ env.query = {
57
+ # DEV: In theory we could serialize the tags here
58
+ # rather than requiring them to be pre-serialized.
59
+ # In practice the tags should be relatively static
60
+ # (they would change when process forks, and hostname
61
+ # could change at any time but probably we should ignore
62
+ # those changes), therefore serializing the tags
63
+ # every time would be wasteful.
64
+ ddtags: env.request.serialized_tags,
65
+ }
56
66
 
57
67
  super
58
68
  end
@@ -12,6 +12,13 @@ module Datadog
12
12
  end
13
13
 
14
14
  class Request < Datadog::Core::Transport::Request
15
+ attr_reader :serialized_tags
16
+
17
+ def initialize(parcel, serialized_tags)
18
+ super(parcel)
19
+
20
+ @serialized_tags = serialized_tags
21
+ end
15
22
  end
16
23
 
17
24
  class Transport
@@ -28,10 +35,11 @@ module Datadog
28
35
  @apis[HTTP::API::INPUT]
29
36
  end
30
37
 
31
- def send_input(payload)
38
+ def send_input(payload, tags)
32
39
  json = JSON.dump(payload)
33
40
  parcel = EncodedParcel.new(json)
34
- request = Request.new(parcel)
41
+ serialized_tags = Core::TagBuilder.serialize_tags(tags)
42
+ request = Request.new(parcel, serialized_tags)
35
43
 
36
44
  response = @client.send_input_payload(request)
37
45
  unless response.ok?
data/lib/datadog/di.rb CHANGED
@@ -35,9 +35,3 @@ module Datadog
35
35
  end
36
36
  end
37
37
  end
38
-
39
- # Line probes will not work on Ruby < 2.6 because of lack of :script_compiled
40
- # trace point. Activate DI automatically on supported Ruby versions but
41
- # always load its settings so that, for example, turning DI off when
42
- # we are on Ruby 2.5 does not produce exceptions.
43
- require_relative 'di/boot' if RUBY_VERSION >= '2.6' && RUBY_ENGINE != 'jruby'
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../identity'
4
+
5
+ module Datadog
6
+ module Kit
7
+ module AppSec
8
+ module Events
9
+ # The second version of Business Logic Events SDK
10
+ module V2
11
+ LOGIN_SUCCESS_EVENT = 'users.login.success'
12
+ LOGIN_FAILURE_EVENT = 'users.login.failure'
13
+ TELEMETRY_METRICS_NAMESPACE = 'appsec'
14
+ TELEMETRY_METRICS_SDK_EVENT = 'sdk.event'
15
+ TELEMETRY_METRICS_SDK_VERSION = 'v2'
16
+ TELEMETRY_METRICS_EVENTS_INTO_TYPES = {
17
+ LOGIN_SUCCESS_EVENT => 'login_success',
18
+ LOGIN_FAILURE_EVENT => 'login_failure'
19
+ }.freeze
20
+
21
+ class << self
22
+ # Attach user login success information to the service entry span
23
+ # and trigger AppSec event processing.
24
+ #
25
+ # @param login [String] The user login (e.g., username or email).
26
+ # @param user_or_id [String, Hash<Symbol, String>] (optional) If a
27
+ # String, considered as a user ID, if a Hash, considered as a user
28
+ # attributes. The Hash must include `:id` as a key.
29
+ # @param metadata [Hash<Symbol, String>] Additional flat free-form
30
+ # metadata to attach to the event.
31
+ #
32
+ # @example Login only
33
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_success('alice@example.com')
34
+ #
35
+ # @example Login and user attributes
36
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_success(
37
+ # 'alice@example.com',
38
+ # { id: 'user-123', email: 'alice@example.com', name: 'Alice' },
39
+ # ip: '192.168.1.1', device: 'mobile', 'usr.country': 'US'
40
+ # )
41
+ #
42
+ # @return [void]
43
+ def track_user_login_success(login, user_or_id = nil, metadata = {})
44
+ trace = service_entry_trace
45
+ span = service_entry_span
46
+
47
+ if trace.nil? || span.nil?
48
+ return Datadog.logger.warn(
49
+ 'Kit::AppSec: Tracing is not enabled. Please enable tracing if you want to track events'
50
+ )
51
+ end
52
+
53
+ raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
54
+ raise TypeError, '`metadata` argument must be a Hash' unless metadata.is_a?(Hash)
55
+
56
+ user_attributes = build_user_attributes(user_or_id, login)
57
+
58
+ set_span_tags(span, metadata, namespace: LOGIN_SUCCESS_EVENT)
59
+ set_span_tags(span, user_attributes, namespace: "#{LOGIN_SUCCESS_EVENT}.usr")
60
+ span.set_tag('appsec.events.users.login.success.track', 'true')
61
+ span.set_tag('_dd.appsec.events.users.login.success.sdk', 'true')
62
+
63
+ trace.keep!
64
+
65
+ record_event_telemetry_metric(LOGIN_SUCCESS_EVENT)
66
+ ::Datadog::AppSec::Instrumentation.gateway.push('appsec.events.user_lifecycle', LOGIN_SUCCESS_EVENT)
67
+
68
+ # NOTE: Guard-clause will not work with Steep typechecking
69
+ return Kit::Identity.set_user(trace, span, **user_attributes) if user_attributes.key?(:id) # steep:ignore
70
+
71
+ # NOTE: This is a fallback for the case when we don't have an ID,
72
+ # but need to trigger WAF.
73
+ user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(nil, login)
74
+ ::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
75
+ end
76
+
77
+ # Attach user login failure information to the service entry span
78
+ # and trigger AppSec event processing.
79
+ #
80
+ # @param login [String] The user login (e.g., username or email).
81
+ # @param user_exists [Boolean] Whether the user exists in the system.
82
+ # @param metadata [Hash<Symbol, String>] Additional flat free-form
83
+ # metadata to attach to the event.
84
+ #
85
+ # @example Login only
86
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_failure('alice@example.com')
87
+ #
88
+ # @example With user existence and metadata
89
+ # Datadog::Kit::AppSec::Events::V2.track_user_login_failure(
90
+ # 'alice@example.com',
91
+ # true,
92
+ # ip: '192.168.1.1', device: 'mobile', 'usr.country': 'US'
93
+ # )
94
+ #
95
+ # @return [void]
96
+ def track_user_login_failure(login, user_exists = false, metadata = {})
97
+ trace = service_entry_trace
98
+ span = service_entry_span
99
+
100
+ if trace.nil? || span.nil?
101
+ return Datadog.logger.warn(
102
+ 'Kit::AppSec: Tracing is not enabled. Please enable tracing if you want to track events'
103
+ )
104
+ end
105
+
106
+ raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
107
+ raise TypeError, '`metadata` argument must be a Hash' unless metadata.is_a?(Hash)
108
+
109
+ unless user_exists.is_a?(TrueClass) || user_exists.is_a?(FalseClass)
110
+ raise TypeError, '`user_exists` argument must be a boolean'
111
+ end
112
+
113
+ set_span_tags(span, metadata, namespace: LOGIN_FAILURE_EVENT)
114
+ span.set_tag('appsec.events.users.login.failure.track', 'true')
115
+ span.set_tag('_dd.appsec.events.users.login.failure.sdk', 'true')
116
+ span.set_tag('appsec.events.users.login.failure.usr.login', login)
117
+ span.set_tag('appsec.events.users.login.failure.usr.exists', user_exists.to_s)
118
+
119
+ trace.keep!
120
+
121
+ record_event_telemetry_metric(LOGIN_FAILURE_EVENT)
122
+ ::Datadog::AppSec::Instrumentation.gateway.push('appsec.events.user_lifecycle', LOGIN_FAILURE_EVENT)
123
+
124
+ user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(nil, login)
125
+ ::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
126
+ end
127
+
128
+ private
129
+
130
+ # NOTE: Current tracer implementation does not provide a way to
131
+ # get the service entry span. This is a shortcut we take now.
132
+ def service_entry_trace
133
+ return Datadog::Tracing.active_trace unless Datadog::AppSec.active_context
134
+
135
+ Datadog::AppSec.active_context&.trace
136
+ end
137
+
138
+ # NOTE: Current tracer implementation does not provide a way to
139
+ # get the service entry span. This is a shortcut we take now.
140
+ def service_entry_span
141
+ return Datadog::Tracing.active_span unless Datadog::AppSec.active_context
142
+
143
+ Datadog::AppSec.active_context&.span
144
+ end
145
+
146
+ def build_user_attributes(user_or_id, login)
147
+ raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
148
+
149
+ case user_or_id
150
+ when nil
151
+ { login: login }
152
+ when String
153
+ { login: login, id: user_or_id }
154
+ when Hash
155
+ raise ArgumentError, 'missing required user key `:id`' unless user_or_id.key?(:id)
156
+ raise TypeError, 'user key `:id` must be a String' unless user_or_id[:id].is_a?(String)
157
+
158
+ user_or_id.merge(login: login)
159
+ else
160
+ raise TypeError, '`user_or_id` argument must be either String or Hash'
161
+ end
162
+ end
163
+
164
+ def set_span_tags(span, tags, namespace:)
165
+ tags.each do |name, value|
166
+ next if value.nil?
167
+
168
+ span.set_tag("appsec.events.#{namespace}.#{name}", value)
169
+ end
170
+ end
171
+
172
+ # TODO: In case if we need to introduce telemetry metrics to the SDK v1
173
+ # or highly increase the number of metrics, this method should be
174
+ # extracted into a proper module.
175
+ def record_event_telemetry_metric(event)
176
+ telemetry = ::Datadog.send(:components, allow_initialization: false)&.telemetry
177
+
178
+ if telemetry.nil?
179
+ return Datadog.logger.debug(
180
+ 'Kit::AppSec: Telemetry component is unavailable. Skip recording SDK metrics'
181
+ )
182
+ end
183
+
184
+ tags = {
185
+ event_type: TELEMETRY_METRICS_EVENTS_INTO_TYPES[event],
186
+ sdk_version: TELEMETRY_METRICS_SDK_VERSION
187
+ }
188
+ telemetry.inc(TELEMETRY_METRICS_NAMESPACE, TELEMETRY_METRICS_SDK_EVENT, 1, tags: tags)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -14,7 +14,10 @@ module Datadog
14
14
  #
15
15
  # This class acts both as a collector (collecting data) as well as a recorder (records/serializes it)
16
16
  class CodeProvenance
17
- def initialize(standard_library_path: RbConfig::CONFIG.fetch("rubylibdir"))
17
+ def initialize(
18
+ standard_library_path: RbConfig::CONFIG.fetch("rubylibdir"),
19
+ ruby_native_filename: Datadog::Profiling::Collectors::Stack._native_ruby_native_filename
20
+ )
18
21
  @libraries_by_name = {}
19
22
  @libraries_by_path = {}
20
23
  @seen_files = Set.new
@@ -26,6 +29,7 @@ module Datadog
26
29
  name: "stdlib",
27
30
  version: RUBY_VERSION,
28
31
  path: standard_library_path,
32
+ extra_path: ruby_native_filename,
29
33
  )
30
34
  )
31
35
  end
@@ -37,10 +41,6 @@ module Datadog
37
41
  self
38
42
  end
39
43
 
40
- def generate
41
- seen_libraries
42
- end
43
-
44
44
  def generate_json
45
45
  JSON.generate(v1: seen_libraries.to_a)
46
46
  end
@@ -79,7 +79,15 @@ module Datadog
79
79
  loaded_specs.each do |spec|
80
80
  next if libraries_by_name.key?(spec.name)
81
81
 
82
- record_library(Library.new(kind: "library", name: spec.name, version: spec.version, path: spec.gem_dir))
82
+ record_library(
83
+ Library.new(
84
+ kind: "library",
85
+ name: spec.name,
86
+ version: spec.version,
87
+ path: spec.gem_dir,
88
+ extra_path: (spec.extension_dir if spec.extensions.any?),
89
+ )
90
+ )
83
91
  recorded_library = true
84
92
  end
85
93
 
@@ -110,11 +118,12 @@ module Datadog
110
118
  class Library
111
119
  attr_reader :kind, :name, :version
112
120
 
113
- def initialize(kind:, name:, version:, path:)
121
+ def initialize(kind:, name:, version:, path:, extra_path: nil)
122
+ extra_path = nil if extra_path&.empty?
114
123
  @kind = kind.freeze
115
124
  @name = name.dup.freeze
116
125
  @version = version.to_s.dup.freeze
117
- @paths = [path.dup.freeze].freeze
126
+ @paths = [path.dup.freeze, extra_path.dup.freeze].compact.freeze
118
127
  freeze
119
128
  end
120
129
 
@@ -23,6 +23,7 @@ module Datadog
23
23
  allocation_profiling_enabled:,
24
24
  allocation_counting_enabled:,
25
25
  gvl_profiling_enabled:,
26
+ sighandler_sampling_enabled:,
26
27
  # **NOTE**: This should only be used for testing; disabling the dynamic sampling rate will increase the
27
28
  # profiler overhead!
28
29
  dynamic_sampling_rate_enabled: true,
@@ -33,6 +34,9 @@ module Datadog
33
34
  Datadog.logger.warn(
34
35
  "Profiling dynamic sampling rate disabled. This should only be used for testing, and will increase overhead!"
35
36
  )
37
+ Datadog::Core::Telemetry::Logger.error(
38
+ "Profiling dynamic sampling rate disabled. This should only be used for testing, and will increase overhead!"
39
+ )
36
40
  end
37
41
 
38
42
  self.class._native_initialize(
@@ -46,6 +50,7 @@ module Datadog
46
50
  allocation_profiling_enabled: allocation_profiling_enabled,
47
51
  allocation_counting_enabled: allocation_counting_enabled,
48
52
  gvl_profiling_enabled: gvl_profiling_enabled,
53
+ sighandler_sampling_enabled: sighandler_sampling_enabled,
49
54
  skip_idle_samples_for_testing: skip_idle_samples_for_testing,
50
55
  )
51
56
  @worker_thread = nil
@@ -77,6 +82,7 @@ module Datadog
77
82
  "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
78
83
  )
79
84
  on_failure_proc&.call
85
+ Datadog::Core::Telemetry::Logger.report(e, description: "CpuAndWallTimeWorker thread error", pii_safe: true)
80
86
  end
81
87
  @worker_thread.name = self.class.name # Repeated from above to make sure thread gets named asap
82
88
  @worker_thread.thread_variable_set(:fork_safe, true)
@@ -41,6 +41,7 @@ module Datadog
41
41
  "IdleSamplingHelper thread error. " \
42
42
  "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
43
43
  )
44
+ Datadog::Core::Telemetry::Logger.report(e, description: "IdleSamplingHelper thread error", pii_safe: true)
44
45
  end
45
46
  @worker_thread.name = self.class.name # Repeated from above to make sure thread gets named asap
46
47
  @worker_thread.thread_variable_set(:fork_safe, true)
@@ -28,6 +28,37 @@ module Datadog
28
28
 
29
29
  private
30
30
 
31
+ # Ruby GC tuning environment variables
32
+ RUBY_GC_TUNING_ENV_VARS = [
33
+ "RUBY_GC_HEAP_FREE_SLOTS",
34
+ "RUBY_GC_HEAP_GROWTH_FACTOR",
35
+ "RUBY_GC_HEAP_GROWTH_MAX_SLOTS",
36
+ "RUBY_GC_HEAP_FREE_SLOTS_MIN_RATIO",
37
+ "RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO",
38
+ "RUBY_GC_HEAP_FREE_SLOTS_GOAL_RATIO",
39
+ "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR",
40
+ "RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO",
41
+ "RUBY_GC_MALLOC_LIMIT",
42
+ "RUBY_GC_MALLOC_LIMIT_MAX",
43
+ "RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR",
44
+ "RUBY_GC_OLDMALLOC_LIMIT",
45
+ "RUBY_GC_OLDMALLOC_LIMIT_MAX",
46
+ "RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR",
47
+ # INIT_SLOTS changed for Ruby 3.3+:
48
+ # * https://bugs.ruby-lang.org/issues/19785
49
+ # * https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/#:~:text=Removed%20environment%20variables
50
+ "RUBY_GC_HEAP_0_INIT_SLOTS",
51
+ "RUBY_GC_HEAP_1_INIT_SLOTS",
52
+ "RUBY_GC_HEAP_2_INIT_SLOTS",
53
+ "RUBY_GC_HEAP_3_INIT_SLOTS",
54
+ "RUBY_GC_HEAP_4_INIT_SLOTS",
55
+ # There was only one setting for older Rubies:
56
+ "RUBY_GC_HEAP_INIT_SLOTS",
57
+ # Ruby 2.x only, alias for others:
58
+ "RUBY_FREE_MIN",
59
+ "RUBY_HEAP_MIN_SLOTS",
60
+ ].freeze
61
+
31
62
  # Instead of trying to figure out real process start time by checking
32
63
  # /proc or some other complex/non-portable way, approximate start time
33
64
  # by time of requirement of this file.
@@ -51,6 +82,7 @@ module Datadog
51
82
  engine: Datadog::Core::Environment::Identity.lang_engine,
52
83
  version: Datadog::Core::Environment::Identity.lang_version,
53
84
  platform: Datadog::Core::Environment::Identity.lang_platform,
85
+ gc_tuning: collect_gc_tuning_info,
54
86
  }.freeze
55
87
  end
56
88
 
@@ -109,6 +141,15 @@ module Datadog
109
141
  v.inspect
110
142
  end
111
143
  end
144
+
145
+ def collect_gc_tuning_info
146
+ return @gc_tuning_info if defined?(@gc_tuning_info)
147
+
148
+ @gc_tuning_info = RUBY_GC_TUNING_ENV_VARS.each_with_object({}) do |var, hash|
149
+ current_value = ENV[var]
150
+ hash[var.to_sym] = current_value if current_value
151
+ end.freeze
152
+ end
112
153
  end
113
154
  end
114
155
  end
@@ -21,7 +21,8 @@ module Datadog
21
21
  endpoint_collection_enabled:,
22
22
  timeline_enabled:,
23
23
  waiting_for_gvl_threshold_ns:,
24
- otel_context_enabled:
24
+ otel_context_enabled:,
25
+ native_filenames_enabled:
25
26
  )
26
27
  tracer_context_key = safely_extract_context_key_from(tracer)
27
28
  self.class._native_initialize(
@@ -33,6 +34,7 @@ module Datadog
33
34
  timeline_enabled: timeline_enabled,
34
35
  waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
35
36
  otel_context_enabled: otel_context_enabled,
37
+ native_filenames_enabled: validate_native_filenames(native_filenames_enabled),
36
38
  )
37
39
  end
38
40
 
@@ -44,6 +46,7 @@ module Datadog
44
46
  timeline_enabled: false,
45
47
  waiting_for_gvl_threshold_ns: 10_000_000,
46
48
  otel_context_enabled: false,
49
+ native_filenames_enabled: true,
47
50
  **options
48
51
  )
49
52
  new(
@@ -54,6 +57,7 @@ module Datadog
54
57
  timeline_enabled: timeline_enabled,
55
58
  waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
56
59
  otel_context_enabled: otel_context_enabled,
60
+ native_filenames_enabled: native_filenames_enabled,
57
61
  **options,
58
62
  )
59
63
  end
@@ -81,6 +85,17 @@ module Datadog
81
85
  context = provider.instance_variable_get(:@context)
82
86
  context&.instance_variable_get(:@key)
83
87
  end
88
+
89
+ def validate_native_filenames(native_filenames_enabled)
90
+ if native_filenames_enabled && !Datadog::Profiling::Collectors::Stack._native_filenames_available?
91
+ Datadog.logger.debug(
92
+ "Native filenames are enabled, but the required dladdr API was not available. Disabling native filenames."
93
+ )
94
+ false
95
+ else
96
+ native_filenames_enabled
97
+ end
98
+ end
84
99
  end
85
100
  end
86
101
  end