datadog 2.2.0 → 2.4.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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -2
  3. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
  4. data/ext/datadog_profiling_loader/extconf.rb +14 -26
  5. data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
  6. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
  7. data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
  8. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +257 -69
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +53 -28
  10. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
  11. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
  12. data/ext/datadog_profiling_native_extension/collectors_stack.c +136 -81
  13. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +661 -48
  15. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -1
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +83 -0
  17. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +53 -0
  18. data/ext/datadog_profiling_native_extension/extconf.rb +91 -69
  19. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  20. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  21. data/ext/datadog_profiling_native_extension/heap_recorder.c +54 -12
  22. data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
  23. data/ext/datadog_profiling_native_extension/helpers.h +6 -17
  24. data/ext/datadog_profiling_native_extension/http_transport.c +41 -9
  25. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
  26. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
  27. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
  28. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +116 -139
  29. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +20 -11
  30. data/ext/datadog_profiling_native_extension/profiling.c +1 -3
  31. data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
  32. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
  33. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
  34. data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
  35. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  36. data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
  37. data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
  38. data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +37 -22
  39. data/ext/libdatadog_api/datadog_ruby_common.c +83 -0
  40. data/ext/libdatadog_api/datadog_ruby_common.h +53 -0
  41. data/ext/libdatadog_api/extconf.rb +108 -0
  42. data/ext/libdatadog_api/macos_development.md +26 -0
  43. data/ext/libdatadog_extconf_helpers.rb +130 -0
  44. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  45. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  46. data/lib/datadog/appsec/component.rb +29 -8
  47. data/lib/datadog/appsec/configuration/settings.rb +2 -2
  48. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  49. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  50. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  51. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +35 -0
  52. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +109 -0
  53. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +71 -0
  54. data/lib/datadog/appsec/contrib/graphql/integration.rb +54 -0
  55. data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
  56. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
  57. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +3 -6
  58. data/lib/datadog/appsec/event.rb +1 -1
  59. data/lib/datadog/appsec/processor/actions.rb +1 -1
  60. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  61. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  62. data/lib/datadog/appsec/processor.rb +36 -37
  63. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  64. data/lib/datadog/appsec/remote.rb +7 -3
  65. data/lib/datadog/appsec/response.rb +15 -1
  66. data/lib/datadog/appsec.rb +3 -2
  67. data/lib/datadog/core/configuration/components.rb +18 -15
  68. data/lib/datadog/core/configuration/settings.rb +135 -9
  69. data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
  70. data/lib/datadog/core/crashtracking/component.rb +111 -0
  71. data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
  72. data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
  73. data/lib/datadog/core/environment/execution.rb +5 -5
  74. data/lib/datadog/core/metrics/client.rb +7 -0
  75. data/lib/datadog/core/rate_limiter.rb +183 -0
  76. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  77. data/lib/datadog/core/remote/component.rb +4 -2
  78. data/lib/datadog/core/remote/negotiation.rb +4 -4
  79. data/lib/datadog/core/remote/tie.rb +2 -0
  80. data/lib/datadog/core/runtime/metrics.rb +1 -1
  81. data/lib/datadog/core/telemetry/component.rb +51 -2
  82. data/lib/datadog/core/telemetry/emitter.rb +9 -11
  83. data/lib/datadog/core/telemetry/event.rb +37 -1
  84. data/lib/datadog/core/telemetry/ext.rb +1 -0
  85. data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
  86. data/lib/datadog/core/telemetry/http/ext.rb +3 -0
  87. data/lib/datadog/core/telemetry/http/transport.rb +38 -9
  88. data/lib/datadog/core/telemetry/logger.rb +51 -0
  89. data/lib/datadog/core/telemetry/logging.rb +71 -0
  90. data/lib/datadog/core/telemetry/request.rb +13 -1
  91. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  92. data/lib/datadog/core/utils/time.rb +12 -0
  93. data/lib/datadog/di/code_tracker.rb +168 -0
  94. data/lib/datadog/di/configuration/settings.rb +163 -0
  95. data/lib/datadog/di/configuration.rb +11 -0
  96. data/lib/datadog/di/error.rb +31 -0
  97. data/lib/datadog/di/extensions.rb +16 -0
  98. data/lib/datadog/di/probe.rb +133 -0
  99. data/lib/datadog/di/probe_builder.rb +41 -0
  100. data/lib/datadog/di/redactor.rb +188 -0
  101. data/lib/datadog/di/serializer.rb +193 -0
  102. data/lib/datadog/di.rb +14 -0
  103. data/lib/datadog/kit/appsec/events.rb +2 -4
  104. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  105. data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
  106. data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
  107. data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +28 -26
  109. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
  110. data/lib/datadog/profiling/collectors/info.rb +15 -6
  111. data/lib/datadog/profiling/collectors/thread_context.rb +30 -2
  112. data/lib/datadog/profiling/component.rb +89 -95
  113. data/lib/datadog/profiling/exporter.rb +3 -3
  114. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  115. data/lib/datadog/profiling/ext.rb +21 -21
  116. data/lib/datadog/profiling/flush.rb +1 -1
  117. data/lib/datadog/profiling/http_transport.rb +14 -7
  118. data/lib/datadog/profiling/load_native_extension.rb +5 -5
  119. data/lib/datadog/profiling/preload.rb +1 -1
  120. data/lib/datadog/profiling/profiler.rb +5 -8
  121. data/lib/datadog/profiling/scheduler.rb +33 -25
  122. data/lib/datadog/profiling/stack_recorder.rb +3 -0
  123. data/lib/datadog/profiling/tag_builder.rb +2 -2
  124. data/lib/datadog/profiling/tasks/exec.rb +5 -5
  125. data/lib/datadog/profiling/tasks/setup.rb +16 -35
  126. data/lib/datadog/profiling.rb +4 -5
  127. data/lib/datadog/single_step_instrument.rb +12 -0
  128. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  129. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  130. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  131. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  132. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  133. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  134. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  135. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +4 -1
  136. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  137. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  138. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  139. data/lib/datadog/tracing/contrib/ext.rb +14 -0
  140. data/lib/datadog/tracing/contrib/faraday/middleware.rb +9 -0
  141. data/lib/datadog/tracing/contrib/grape/endpoint.rb +19 -0
  142. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  143. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  144. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  145. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +14 -10
  146. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +10 -4
  147. data/lib/datadog/tracing/contrib/http/instrumentation.rb +18 -15
  148. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -5
  149. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  150. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +5 -0
  151. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  152. data/lib/datadog/tracing/contrib/lograge/patcher.rb +15 -0
  153. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  154. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
  155. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
  156. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  157. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  158. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  159. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
  160. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  161. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
  162. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  163. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
  164. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  165. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  166. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  167. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  168. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
  169. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
  170. data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
  171. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  172. data/lib/datadog/tracing/metadata/errors.rb +9 -1
  173. data/lib/datadog/tracing/metadata/ext.rb +6 -0
  174. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  175. data/lib/datadog/tracing/remote.rb +5 -2
  176. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  177. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  178. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  179. data/lib/datadog/tracing/sampling/rule_sampler.rb +9 -5
  180. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  181. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  182. data/lib/datadog/tracing/span.rb +9 -2
  183. data/lib/datadog/tracing/span_event.rb +41 -0
  184. data/lib/datadog/tracing/span_operation.rb +6 -2
  185. data/lib/datadog/tracing/trace_operation.rb +26 -2
  186. data/lib/datadog/tracing/tracer.rb +14 -12
  187. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  188. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  189. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
  190. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  191. data/lib/datadog/tracing/workers.rb +1 -1
  192. data/lib/datadog/version.rb +1 -1
  193. metadata +46 -11
  194. data/lib/datadog/profiling/crashtracker.rb +0 -91
  195. data/lib/datadog/profiling/ext/forking.rb +0 -98
  196. data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -34,19 +34,17 @@ module Datadog
34
34
  end
35
35
 
36
36
  def post(env)
37
- begin
38
- post = ::Net::HTTP::Post.new(env.path, env.headers)
39
- post.body = env.body
40
-
41
- http_response = open do |http|
42
- http.request(post)
43
- end
44
-
45
- Response.new(http_response)
46
- rescue StandardError => e
47
- Datadog.logger.debug("Unable to send telemetry event to agent: #{e}")
48
- Telemetry::Http::InternalErrorResponse.new(e)
37
+ post = ::Net::HTTP::Post.new(env.path, env.headers)
38
+ post.body = env.body
39
+
40
+ http_response = open do |http|
41
+ http.request(post)
49
42
  end
43
+
44
+ Response.new(http_response)
45
+ rescue StandardError => e
46
+ Datadog.logger.debug("Unable to send telemetry event to agent: #{e}")
47
+ Telemetry::Http::InternalErrorResponse.new(e)
50
48
  end
51
49
 
52
50
  # Data structure for an HTTP Response
@@ -17,7 +17,10 @@ module Datadog
17
17
  CONTENT_TYPE_APPLICATION_JSON = 'application/json'
18
18
  API_VERSION = 'v2'
19
19
 
20
+ AGENTLESS_HOST_PREFIX = 'instrumentation-telemetry-intake'
21
+
20
22
  AGENT_ENDPOINT = '/telemetry/proxy/api/v2/apmtelemetry'
23
+ AGENTLESS_ENDPOINT = '/api/v2/apmtelemetry'
21
24
  end
22
25
  end
23
26
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../configuration/settings'
4
+ require_relative '../../environment/ext'
4
5
  require_relative '../../transport/ext'
5
6
  require_relative 'env'
6
7
  require_relative 'ext'
@@ -13,18 +14,42 @@ module Datadog
13
14
  # Class to send telemetry data to Telemetry API
14
15
  # Currently only supports the HTTP protocol.
15
16
  class Transport
17
+ def self.build_agent_transport(agent_settings)
18
+ Transport.new(
19
+ host: agent_settings.hostname,
20
+ port: agent_settings.port,
21
+ path: Http::Ext::AGENT_ENDPOINT
22
+ )
23
+ end
24
+
25
+ def self.build_agentless_transport(api_key:, dd_site:, url_override: nil)
26
+ url = url_override || "https://#{Http::Ext::AGENTLESS_HOST_PREFIX}.#{dd_site}:443"
27
+
28
+ uri = URI.parse(url)
29
+ raise "Invalid agentless mode URL: #{url}" if uri.host.nil?
30
+
31
+ Transport.new(
32
+ host: uri.host,
33
+ port: uri.port || 80,
34
+ path: Http::Ext::AGENTLESS_ENDPOINT,
35
+ ssl: uri.scheme == 'https' || uri.port == 443,
36
+ api_key: api_key
37
+ )
38
+ end
39
+
16
40
  attr_reader \
17
41
  :host,
18
42
  :port,
19
43
  :ssl,
20
- :path
21
-
22
- def initialize
23
- agent_settings = Configuration::AgentSettingsResolver.call(Datadog.configuration)
24
- @host = agent_settings.hostname
25
- @port = agent_settings.port
26
- @ssl = false
27
- @path = Http::Ext::AGENT_ENDPOINT
44
+ :path,
45
+ :api_key
46
+
47
+ def initialize(host:, port:, path:, ssl: false, api_key: nil)
48
+ @host = host
49
+ @port = port
50
+ @ssl = ssl
51
+ @path = path
52
+ @api_key = api_key
28
53
  end
29
54
 
30
55
  def request(request_type:, payload:)
@@ -38,7 +63,7 @@ module Datadog
38
63
  private
39
64
 
40
65
  def headers(request_type:, api_version: Http::Ext::API_VERSION)
41
- {
66
+ result = {
42
67
  Core::Transport::Ext::HTTP::HEADER_DD_INTERNAL_UNTRACED_REQUEST => '1',
43
68
  Ext::HEADER_CONTENT_TYPE => Http::Ext::CONTENT_TYPE_APPLICATION_JSON,
44
69
  Ext::HEADER_DD_TELEMETRY_API_VERSION => api_version,
@@ -49,6 +74,10 @@ module Datadog
49
74
  # Enable debug mode for telemetry
50
75
  # HEADER_TELEMETRY_DEBUG_ENABLED => 'true',
51
76
  }
77
+
78
+ result[Ext::HEADER_DD_API_KEY] = api_key unless api_key.nil?
79
+
80
+ result
52
81
  end
53
82
 
54
83
  def adapter
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Telemetry
6
+ # === INTERNAL USAGE ONLY ===
7
+ #
8
+ # Report telemetry logs via delegating to the telemetry component instance via mutex.
9
+ #
10
+ # IMPORTANT: Invoking this method during the lifecycle of component initialization will
11
+ # be no-op instead.
12
+ #
13
+ # For developer using this module:
14
+ # read: lib/datadog/core/telemetry/logging.rb
15
+ module Logger
16
+ class << self
17
+ def report(exception, level: :error, description: nil)
18
+ instance&.report(exception, level: level, description: description)
19
+ end
20
+
21
+ def error(description)
22
+ instance&.error(description)
23
+ end
24
+
25
+ private
26
+
27
+ def instance
28
+ # Component initialization uses a mutex to avoid having concurrent initialization.
29
+ # Trying to access the telemetry component during initialization (specifically:
30
+ # from the thread that's actually doing the initialization) would cause a deadlock,
31
+ # since accessing the components would try to recursively lock the mutex.
32
+ #
33
+ # To work around this, we use allow_initialization: false to avoid triggering this issue.
34
+ #
35
+ # The downside is: this leaves us unable to report telemetry during component initialization.
36
+ components = Datadog.send(:components, allow_initialization: false)
37
+
38
+ if components && components.telemetry
39
+ components.telemetry
40
+ else
41
+ Datadog.logger.warn(
42
+ 'Failed to send telemetry before components initialization or within components lifecycle'
43
+ )
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'event'
4
+
5
+ require 'pathname'
6
+
7
+ module Datadog
8
+ module Core
9
+ module Telemetry
10
+ # === INTERNAL USAGE ONLY ===
11
+ #
12
+ # Logging interface for sending telemetry logs... so we can fix them.
13
+ #
14
+ # For developer using this module:
15
+ # - MUST NOT provide any sensitive information (PII)
16
+ # - SHOULD reduce the data cardinality for batching/aggregation
17
+ #
18
+ # Before using it, ask yourself:
19
+ # - Do we need to know about this (ie. internal error or client error)?
20
+ # - How severe/critical is this error? (ie. error, warning, fatal)
21
+ # - What information needed to make it actionable?
22
+ #
23
+ module Logging
24
+ # Extract datadog stack trace from the exception
25
+ module DatadogStackTrace
26
+ GEM_ROOT = Pathname.new("#{__dir__}/../../../..").cleanpath.to_s
27
+
28
+ def self.from(exception)
29
+ backtrace = exception.backtrace
30
+
31
+ return unless backtrace
32
+ return if backtrace.empty?
33
+
34
+ stack_trace = +''
35
+ backtrace.each do |line|
36
+ stack_trace << if line.start_with?(GEM_ROOT)
37
+ line[GEM_ROOT.length..-1] || ''
38
+ else
39
+ 'REDACTED'
40
+ end
41
+ stack_trace << ','
42
+ end
43
+
44
+ stack_trace.chomp(',')
45
+ end
46
+ end
47
+
48
+ def report(exception, level: :error, description: nil)
49
+ # Annoymous exceptions to be logged as <Class:0x00007f8b1c0b3b40>
50
+ message = +''
51
+ message << (exception.class.name || exception.class.inspect)
52
+ message << ':' << description if description
53
+
54
+ event = Event::Log.new(
55
+ message: message,
56
+ level: level,
57
+ stack_trace: DatadogStackTrace.from(exception)
58
+ )
59
+
60
+ log!(event)
61
+ end
62
+
63
+ def error(description)
64
+ event = Event::Log.new(message: description, level: :error)
65
+
66
+ log!(event)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -31,6 +31,18 @@ module Datadog
31
31
 
32
32
  def application
33
33
  config = Datadog.configuration
34
+
35
+ tracer_version = Core::Environment::Identity.gem_datadog_version_semver2
36
+
37
+ # We need some to distinguish datadog-ci gem versions
38
+ # when examining telemetry metrics emitted from the datadog-ci gem.
39
+ #
40
+ # This code checks that Datadog::CI is loaded and ci mode is enabled and adds
41
+ # "-ci-X.Y.Z" suffix to the tracer version.
42
+ if defined?(::Datadog::CI::VERSION) && config.respond_to?(:ci) && config.ci.enabled
43
+ tracer_version = "#{tracer_version}-ci-#{::Datadog::CI::VERSION::STRING}"
44
+ end
45
+
34
46
  {
35
47
  env: config.env,
36
48
  language_name: Core::Environment::Ext::LANG,
@@ -39,7 +51,7 @@ module Datadog
39
51
  runtime_version: Core::Environment::Ext::ENGINE_VERSION,
40
52
  service_name: config.service,
41
53
  service_version: config.version,
42
- tracer_version: Core::Environment::Identity.gem_datadog_version_semver2
54
+ tracer_version: tracer_version
43
55
  }
44
56
  end
45
57
 
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Utils
6
+ # Monkey patches `Kernel#fork` and similar functions, adding an `at_fork` callback mechanism which
7
+ # is used to restart observability after the VM forks (e.g. in multiprocess Ruby apps).
8
+ module AtForkMonkeyPatch
9
+ AT_FORK_CHILD_BLOCKS = [] # rubocop:disable Style/MutableConstant Used to store blocks to run, mutable by design.
10
+ private_constant :AT_FORK_CHILD_BLOCKS
11
+
12
+ def self.supported?
13
+ Process.respond_to?(:fork)
14
+ end
15
+
16
+ def self.apply!
17
+ return false unless supported?
18
+
19
+ if RUBY_VERSION < '3.1'
20
+ [
21
+ ::Process.singleton_class, # Process.fork
22
+ ::Kernel.singleton_class, # Kernel.fork
23
+ ::Object, # fork without explicit receiver (it's defined as a method in ::Kernel)
24
+ # Note: Modifying Object as we do here is irreversible. During tests, this
25
+ # change will stick around even if we otherwise stub `Process` and `Kernel`
26
+ ].each { |target| target.prepend(KernelMonkeyPatch) }
27
+ end
28
+
29
+ ::Process.singleton_class.prepend(ProcessMonkeyPatch)
30
+
31
+ true
32
+ end
33
+
34
+ def self.run_at_fork_blocks(stage)
35
+ raise(ArgumentError, "Unsupported stage #{stage}") unless stage == :child
36
+
37
+ AT_FORK_CHILD_BLOCKS.each(&:call)
38
+ end
39
+
40
+ def self.at_fork(stage, &block)
41
+ raise(ArgumentError, "Unsupported stage #{stage}") unless stage == :child
42
+ raise(ArgumentError, 'Missing block argument') unless block
43
+
44
+ AT_FORK_CHILD_BLOCKS << block
45
+
46
+ true
47
+ end
48
+
49
+ # Adds `at_fork` behavior; see parent module for details.
50
+ module KernelMonkeyPatch
51
+ def fork
52
+ # If a block is provided, it must be wrapped to trigger callbacks.
53
+ child_block = if block_given?
54
+ proc do
55
+ AtForkMonkeyPatch.run_at_fork_blocks(:child)
56
+
57
+ # Invoke original block
58
+ yield
59
+ end
60
+ end
61
+
62
+ # Start fork
63
+ # If a block is provided, use the wrapped version.
64
+ result = child_block.nil? ? super : super(&child_block)
65
+
66
+ # When fork gets called without a block, it returns twice:
67
+ # If we're in the fork, result = nil: trigger child callbacks.
68
+ # If we're in the parent, result = pid: we do nothing.
69
+ # (If it gets called with a block, it only returns on the parent)
70
+ AtForkMonkeyPatch.run_at_fork_blocks(:child) if result.nil?
71
+
72
+ result
73
+ end
74
+ end
75
+
76
+ # Adds `at_fork` behavior; see parent module for details.
77
+ module ProcessMonkeyPatch
78
+ # Hook provided by Ruby 3.1+ for observability libraries that want to know about fork, see
79
+ # https://github.com/ruby/ruby/pull/5017 and https://bugs.ruby-lang.org/issues/17795
80
+ def _fork
81
+ pid = super
82
+
83
+ AtForkMonkeyPatch.run_at_fork_blocks(:child) if pid == 0
84
+
85
+ pid
86
+ end
87
+
88
+ # A call to Process.daemon ( https://rubyapi.org/3.1/o/process#method-c-daemon ) forks the current process and
89
+ # keeps executing code in the child process, killing off the parent, thus effectively replacing it.
90
+ # This is not covered by `_fork` and thus we have some extra code for it.
91
+ def daemon(*args)
92
+ result = super
93
+
94
+ AtForkMonkeyPatch.run_at_fork_blocks(:child)
95
+
96
+ result
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -34,6 +34,18 @@ module Datadog
34
34
  define_singleton_method(:now, &block)
35
35
  end
36
36
 
37
+ # Overrides the implementation of `#get_time
38
+ # with the provided callable.
39
+ #
40
+ # Overriding the method `#get_time` instead of
41
+ # indirectly calling `block` removes
42
+ # one level of method call overhead.
43
+ #
44
+ # @param block [Proc] block that accepts unit and returns timestamp in the requested unit
45
+ def get_time_provider=(block)
46
+ define_singleton_method(:get_time, &block)
47
+ end
48
+
37
49
  def measure(unit = :float_second)
38
50
  before = get_time(unit)
39
51
  yield
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module DI
5
+ # Tracks loaded Ruby code by source file and maintains a map from
6
+ # source file to the loaded code (instruction sequences).
7
+ # Also arranges for code in the loaded files to be instrumented by
8
+ # line probes that have already been received by the library.
9
+ #
10
+ # The loaded code is used to target line trace points when installing
11
+ # line probes which dramatically improves efficiency of line trace points.
12
+ #
13
+ # Note that, since most files will only be loaded one time (via the
14
+ # "require" mechanism), the code tracker needs to be global and not be
15
+ # recreated when the DI component is created.
16
+ #
17
+ # @api private
18
+ class CodeTracker
19
+ def initialize
20
+ @registry = {}
21
+ @trace_point_lock = Mutex.new
22
+ @registry_lock = Mutex.new
23
+ @compiled_trace_point = nil
24
+ end
25
+
26
+ # Starts tracking loaded code.
27
+ #
28
+ # This method should generally be called early in application boot
29
+ # process, because any code loaded before code tracking is enabled
30
+ # will not be instrumentable via line probes.
31
+ #
32
+ # Normally tracking should remain active for the lifetime of the
33
+ # process and would not be ever stopped.
34
+ def start
35
+ trace_point_lock.synchronize do
36
+ # If this code tracker is already running, we can do nothing or
37
+ # restart it (by disabling the trace point and recreating it).
38
+ # It is likely that some applications will attempt to activate
39
+ # DI more than once where the intention is to just activate DI;
40
+ # do not break such applications by clearing out the registry.
41
+ # For now, until there is a use case for recreating the trace point,
42
+ # do nothing if the code tracker has already started.
43
+ return if @compiled_trace_point
44
+
45
+ # Note: .trace enables the trace point.
46
+ @compiled_trace_point = TracePoint.trace(:script_compiled) do |tp|
47
+ # Useful attributes of the trace point object here:
48
+ # .instruction_sequence
49
+ # .instruction_sequence.path (either absolute file path for
50
+ # loaded or required code, or for eval'd code, if filename
51
+ # is specified as argument to eval, then this is the provided
52
+ # filename, otherwise this is a synthesized
53
+ # "(eval at <definition-file>:<line>)" string)
54
+ # .instruction_sequence.absolute_path (absolute file path when
55
+ # load or require are used to load code, nil for eval'd code
56
+ # regardless of whether filename was specified as an argument
57
+ # to eval on ruby 3.1+, same as path for eval'd code on ruby 3.0
58
+ # and lower)
59
+ # .method_id
60
+ # .path (refers to the code location that called the require/eval/etc.,
61
+ # not where the loaded code is; use .path on the instruction sequence
62
+ # to obtain the location of the compiled code)
63
+ # .eval_script
64
+ #
65
+ # For now just map the path to the instruction sequence.
66
+ path = tp.instruction_sequence.absolute_path
67
+ # Do not store mapping for eval'd code, since there is no way
68
+ # to target such code from dynamic instrumentation UI.
69
+ # eval'd code always sets tp.eval_script.
70
+ # When tp.eval_script is nil, code is either 'load'ed or 'require'd.
71
+ # steep, of course, complains about indexing with +path+
72
+ # without checking that it is not nil, so here, maybe there is
73
+ # some situation where path would in fact be nil and
74
+ # steep would end up saving the day.
75
+ if path && !tp.eval_script
76
+ registry_lock.synchronize do
77
+ registry[path] = tp.instruction_sequence
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # Returns whether this code tracker has been activated and is
85
+ # tracking.
86
+ def active?
87
+ trace_point_lock.synchronize do
88
+ !!@compiled_trace_point
89
+ end
90
+ end
91
+
92
+ # Returns an array of RubVM::InstructionSequence (i.e. the compiled code)
93
+ # for the provided path.
94
+ #
95
+ # The argument can be a full path to a Ruby source code file or a
96
+ # suffix (basename + one or more directories preceding the basename).
97
+ # The idea with suffix matches is that file paths are likely to
98
+ # be different between development and production environments and
99
+ # the source control system uses relative paths and doesn't have
100
+ # absolute paths at all.
101
+ #
102
+ # Suffix matches are not guaranteed to be correct, meaning there may
103
+ # be multiple files with the same basename and they may all match a
104
+ # given suffix. In such cases, this method will return all matching
105
+ # paths (and all of these paths will be attempted to be instrumented
106
+ # by upstream code).
107
+ #
108
+ # If the suffix matches one of the paths completely (which requires it
109
+ # to be an absolute path), only the exactly matching path is returned.
110
+ # Otherwise all known paths that end in the suffix are returned.
111
+ # If no paths match, an empty array is returned.
112
+ def iseqs_for_path(suffix)
113
+ registry_lock.synchronize do
114
+ exact = registry[suffix]
115
+ return [exact] if exact
116
+
117
+ inexact = []
118
+ registry.each do |path, iseq|
119
+ # Exact match is not possible here, meaning any matching path
120
+ # has to be longer than the suffix. Require full component matches,
121
+ # meaning either the first character of the suffix is a slash
122
+ # or the previous character in the path is a slash.
123
+ # For now only check for forward slashes for Unix-like OSes;
124
+ # backslash is a legitimate character of a file name in Unix
125
+ # therefore simply permitting forward or back slash is not
126
+ # sufficient, we need to perform an OS check to know which
127
+ # path separator to use.
128
+ if path.length > suffix.length && path.end_with?(suffix)
129
+ previous_char = path[path.length - suffix.length - 1]
130
+ inexact << iseq if previous_char == "/" || suffix[0] == "/"
131
+ end
132
+ end
133
+ inexact
134
+ end
135
+ end
136
+
137
+ # Stops tracking code that is being loaded.
138
+ #
139
+ # This method should ordinarily never be called - if a file is loaded
140
+ # when code tracking is not active, this file will not be instrumentable
141
+ # by line probes.
142
+ #
143
+ # This method is intended for test suite use only, where multiple
144
+ # code tracker instances are created, to fully clean up the old instances.
145
+ def stop
146
+ # Permit multiple stop calls.
147
+ trace_point_lock.synchronize do
148
+ @compiled_trace_point&.disable
149
+ # Clear the instance variable so that the trace point may be
150
+ # reinstated in the future.
151
+ @compiled_trace_point = nil
152
+ end
153
+ registry_lock.synchronize do
154
+ registry.clear
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ # Mapping from paths of loaded files to RubyVM::InstructionSequence
161
+ # objects representing compiled code of those files.
162
+ attr_reader :registry
163
+
164
+ attr_reader :trace_point_lock
165
+ attr_reader :registry_lock
166
+ end
167
+ end
168
+ end