datadog 2.2.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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