datadog 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -2
  3. data/ext/datadog_profiling_loader/extconf.rb +15 -15
  4. data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
  5. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
  6. data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
  7. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +113 -43
  8. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
  9. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
  10. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
  11. data/ext/datadog_profiling_native_extension/collectors_stack.c +49 -37
  12. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  13. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
  14. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
  15. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
  17. data/ext/datadog_profiling_native_extension/extconf.rb +65 -60
  18. data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
  19. data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
  20. data/ext/datadog_profiling_native_extension/helpers.h +6 -17
  21. data/ext/datadog_profiling_native_extension/http_transport.c +3 -3
  22. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
  23. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
  24. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
  25. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +64 -138
  26. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
  27. data/ext/datadog_profiling_native_extension/profiling.c +0 -2
  28. data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
  29. data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
  30. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
  31. data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
  32. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  33. data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
  34. data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
  35. data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +19 -6
  36. data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
  37. data/ext/libdatadog_api/datadog_ruby_common.h +57 -0
  38. data/ext/libdatadog_api/extconf.rb +108 -0
  39. data/ext/libdatadog_api/macos_development.md +26 -0
  40. data/ext/libdatadog_extconf_helpers.rb +130 -0
  41. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +49 -0
  42. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
  43. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
  44. data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -0
  45. data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
  46. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
  47. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
  48. data/lib/datadog/appsec/processor/actions.rb +1 -1
  49. data/lib/datadog/appsec/response.rb +15 -1
  50. data/lib/datadog/appsec.rb +1 -0
  51. data/lib/datadog/core/configuration/components.rb +14 -12
  52. data/lib/datadog/core/configuration/settings.rb +54 -7
  53. data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
  54. data/lib/datadog/core/crashtracking/component.rb +111 -0
  55. data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
  56. data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
  57. data/lib/datadog/core/telemetry/component.rb +49 -2
  58. data/lib/datadog/core/telemetry/emitter.rb +9 -11
  59. data/lib/datadog/core/telemetry/event.rb +32 -1
  60. data/lib/datadog/core/telemetry/ext.rb +1 -0
  61. data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
  62. data/lib/datadog/core/telemetry/http/ext.rb +3 -0
  63. data/lib/datadog/core/telemetry/http/transport.rb +38 -9
  64. data/lib/datadog/core/telemetry/logging.rb +35 -0
  65. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
  66. data/lib/datadog/kit/appsec/events.rb +2 -4
  67. data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
  68. data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
  69. data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
  70. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +17 -17
  71. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
  72. data/lib/datadog/profiling/collectors/info.rb +3 -3
  73. data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
  74. data/lib/datadog/profiling/component.rb +69 -91
  75. data/lib/datadog/profiling/exporter.rb +3 -3
  76. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  77. data/lib/datadog/profiling/ext.rb +21 -21
  78. data/lib/datadog/profiling/flush.rb +1 -1
  79. data/lib/datadog/profiling/http_transport.rb +8 -6
  80. data/lib/datadog/profiling/load_native_extension.rb +5 -5
  81. data/lib/datadog/profiling/preload.rb +1 -1
  82. data/lib/datadog/profiling/profiler.rb +5 -8
  83. data/lib/datadog/profiling/scheduler.rb +31 -25
  84. data/lib/datadog/profiling/tag_builder.rb +2 -2
  85. data/lib/datadog/profiling/tasks/exec.rb +5 -5
  86. data/lib/datadog/profiling/tasks/setup.rb +16 -35
  87. data/lib/datadog/profiling.rb +4 -5
  88. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  89. data/lib/datadog/tracing/contrib/ext.rb +14 -0
  90. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +1 -1
  91. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +4 -1
  92. data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -0
  93. data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
  94. data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
  95. data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
  96. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
  97. data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
  98. data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
  99. data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
  100. data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
  101. data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
  102. data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
  103. data/lib/datadog/tracing/metadata/errors.rb +9 -1
  104. data/lib/datadog/tracing/metadata/ext.rb +4 -0
  105. data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
  106. data/lib/datadog/tracing/span.rb +9 -2
  107. data/lib/datadog/tracing/span_event.rb +41 -0
  108. data/lib/datadog/tracing/span_operation.rb +6 -2
  109. data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
  110. data/lib/datadog/version.rb +1 -1
  111. metadata +28 -10
  112. data/lib/datadog/profiling/crashtracker.rb +0 -91
  113. data/lib/datadog/profiling/ext/forking.rb +0 -98
@@ -1,123 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubygems'
4
- require 'pathname'
5
-
6
3
  module Datadog
7
4
  module Profiling
8
5
  # Helpers for extconf.rb
9
6
  module NativeExtensionHelpers
10
7
  # Can be set when customers want to skip compiling the native extension entirely
11
- ENV_NO_EXTENSION = 'DD_PROFILING_NO_EXTENSION'
8
+ ENV_NO_EXTENSION = "DD_PROFILING_NO_EXTENSION"
12
9
  # Can be set to force rubygems to fail gem installation when profiling extension could not be built
13
- ENV_FAIL_INSTALL_IF_MISSING_EXTENSION = 'DD_PROFILING_FAIL_INSTALL_IF_MISSING_EXTENSION'
10
+ ENV_FAIL_INSTALL_IF_MISSING_EXTENSION = "DD_PROFILING_FAIL_INSTALL_IF_MISSING_EXTENSION"
14
11
 
15
12
  # The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on debase-ruby_core_source
16
- CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?('2.6', '2.7', '3.0.', '3.1.', '3.2.')
17
-
18
- LIBDATADOG_VERSION = '~> 10.0.0.1.0'
13
+ CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?("2.6", "2.7", "3.0.", "3.1.", "3.2.")
19
14
 
20
15
  def self.fail_install_if_missing_extension?
21
- ENV[ENV_FAIL_INSTALL_IF_MISSING_EXTENSION].to_s.strip.downcase == 'true'
22
- end
23
-
24
- # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
25
- # libdatadog are moved after the extension gets compiled.
26
- #
27
- # Because the libddpprof native library is installed on a non-standard system path, in order for it to be
28
- # found by the system dynamic linker (e.g. what takes care of dlopen(), which is used to load the profiling
29
- # native extension), we need to add a "runpath" -- a list of folders to search for libdatadog.
30
- #
31
- # This runpath gets hardcoded at native library linking time. You can look at it using the `readelf` tool in
32
- # Linux: e.g. `readelf -d datadog_profiling_native_extension.2.7.3_x86_64-linux.so`.
33
- #
34
- # In older versions of the datadog gem, we only set as runpath an absolute path to libdatadog.
35
- # (This gets set automatically by the call
36
- # to `pkg_config('datadog_profiling_with_rpath')` in `extconf.rb`). This worked fine as long as libdatadog was **NOT**
37
- # moved from the folder it was present at datadog gem installation/linking time.
38
- #
39
- # Unfortunately, environments such as Heroku and AWS Elastic Beanstalk move gems around in the filesystem after
40
- # installation. Thus, the profiling native extension could not be loaded in these environments
41
- # (see https://github.com/DataDog/dd-trace-rb/issues/2067) because libdatadog could not be found.
42
- #
43
- # To workaround this issue, this method computes the **relative** path between the folder where the profiling
44
- # native extension is going to be installed and the folder where libdatadog is installed, and returns it
45
- # to be set as an additional runpath. (Yes, you can set multiple runpath folders to be searched).
46
- #
47
- # This way, if both gems are moved together (and it turns out that they are in these environments),
48
- # the relative path can still be traversed to find libdatadog.
49
- #
50
- # This is incredibly awful, and it's kinda bizarre how it's not possible to just find these paths at runtime
51
- # and set them correctly; rather than needing to set stuff at linking-time and then praying to $deity that
52
- # weird moves don't happen.
53
- #
54
- # As a curiosity, `LD_LIBRARY_PATH` can be used to influence the folders that get searched but **CANNOT BE
55
- # SET DYNAMICALLY**, e.g. it needs to be set at the start of the process (Ruby VM) and thus it's not something
56
- # we could setup when doing a `require`.
57
- #
58
- def self.libdatadog_folder_relative_to_native_lib_folder(
59
- current_folder: __dir__,
60
- libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
61
- )
62
- return unless libdatadog_pkgconfig_folder
63
-
64
- profiling_native_lib_folder = "#{current_folder}/../../lib/"
65
- libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
66
-
67
- Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(profiling_native_lib_folder)).to_s
68
- end
69
-
70
- # In https://github.com/DataDog/dd-trace-rb/pull/3582 we got a report of a customer for which the native extension
71
- # only got installed into the extensions folder.
72
- #
73
- # But then this fix was not enough to fully get them moving because then they started to see the issue from
74
- # https://github.com/DataDog/dd-trace-rb/issues/2067 / https://github.com/DataDog/dd-trace-rb/pull/2125 :
75
- #
76
- # > Profiling was requested but is not supported, profiling disabled: There was an error loading the profiling
77
- # > native extension due to 'RuntimeError Failure to load datadog_profiling_native_extension.3.2.2_x86_64-linux
78
- # > due to libdatadog_profiling.so: cannot open shared object file: No such file or directory
79
- #
80
- # The problem is that when loading the native extension from the extensions directory, the relative rpath we add
81
- # with the #libdatadog_folder_relative_to_native_lib_folder helper above is not correct, we need to add a relative
82
- # rpath to the extensions directory.
83
- #
84
- # So how do we find the full path where the native extension is placed?
85
- # * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/bundler/runtime.rb#L166
86
- # `extension_dirs = Dir["#{Gem.dir}/extensions/*/*/*"] + Dir["#{Gem.dir}/bundler/gems/extensions/*/*/*"]`
87
- # we get that's in one of two fixed subdirectories of `Gem.dir`
88
- # * From https://github.com/ruby/ruby/blob/83f02d42e0a3c39661dc99c049ab9a70ff227d5b/lib/rubygems/basic_specification.rb#L111-L115
89
- # we get the structure of the subdirectory (platform/extension_api_version/gem_and_version)
90
- #
91
- # Thus, `Gem.dir` of `/var/app/current/vendor/bundle/ruby/3.2.0` becomes (for instance)
92
- # `/var/app/current/vendor/bundle/ruby/3.2.0/extensions/x86_64-linux/3.2.0/datadog-2.0.0/` or
93
- # `/var/app/current/vendor/bundle/ruby/3.2.0/bundler/gems/extensions/x86_64-linux/3.2.0/datadog-2.0.0/`
94
- #
95
- # We then compute the relative path between these folders and the libdatadog folder, and use that as a relative path.
96
- def self.libdatadog_folder_relative_to_ruby_extensions_folders(
97
- gem_dir: Gem.dir,
98
- libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder
99
- )
100
- return unless libdatadog_pkgconfig_folder
101
-
102
- # For the purposes of calculating a folder relative to the other, we don't actually NEED to fill in the
103
- # platform, extension_api_version and gem version. We're basically just after how many folders it is deep from
104
- # the Gem.dir.
105
- expected_ruby_extensions_folders = [
106
- "#{gem_dir}/extensions/platform/extension_api_version/datadog_version/",
107
- "#{gem_dir}/bundler/gems/extensions/platform/extension_api_version/datadog_version/",
108
- ]
109
- libdatadog_lib_folder = "#{libdatadog_pkgconfig_folder}/../"
110
-
111
- expected_ruby_extensions_folders.map do |folder|
112
- Pathname.new(libdatadog_lib_folder).relative_path_from(Pathname.new(folder)).to_s
113
- end
16
+ ENV[ENV_FAIL_INSTALL_IF_MISSING_EXTENSION].to_s.strip.downcase == "true"
114
17
  end
115
18
 
116
19
  # Used to check if profiler is supported, including user-visible clear messages explaining why their
117
20
  # system may not be supported.
118
21
  module Supported
119
22
  private_class_method def self.explain_issue(*reason, suggested:)
120
- { reason: reason, suggested: suggested }
23
+ {reason: reason, suggested: suggested}
121
24
  end
122
25
 
123
26
  def self.supported?
@@ -143,16 +46,16 @@ module Datadog
143
46
  outcome =
144
47
  if fail_install
145
48
  [
146
- 'Failing installation immediately because the ',
49
+ "Failing installation immediately because the ",
147
50
  "`#{ENV_FAIL_INSTALL_IF_MISSING_EXTENSION}` environment variable is set",
148
- 'to `true`.',
149
- 'When contacting support, please include the <mkmf.log> file that is shown ',
150
- 'below.',
51
+ "to `true`.",
52
+ "When contacting support, please include the <mkmf.log> file that is shown ",
53
+ "below.",
151
54
  ]
152
55
  else
153
56
  [
154
- 'The Datadog Continuous Profiler will not be available,',
155
- 'but all other datadog features will work fine!',
57
+ "The Datadog Continuous Profiler will not be available,",
58
+ "but all other datadog features will work fine!",
156
59
  ]
157
60
  end
158
61
 
@@ -170,22 +73,14 @@ module Datadog
170
73
 
171
74
  # This will be saved in a file to later be presented while operating the gem
172
75
  def self.render_skipped_reason_file(reason:, suggested:)
173
- [*reason, *suggested].join(' ')
174
- end
175
-
176
- # mkmf sets $PKGCONFIG after the `pkg_config` gets used in extconf.rb. When `pkg_config` is unsuccessful, we use
177
- # this helper to decide if we can show more specific error message vs a generic "something went wrong".
178
- def self.pkg_config_missing?(command: $PKGCONFIG) # rubocop:disable Style/GlobalVars
179
- pkg_config_available = command && xsystem("#{command} --version")
180
-
181
- pkg_config_available != true
76
+ [*reason, *suggested].join(" ")
182
77
  end
183
78
 
184
79
  CONTACT_SUPPORT = [
185
- 'For help solving this issue, please contact Datadog support at',
186
- '<https://docs.datadoghq.com/help/>.',
187
- 'You can also check out the Continuous Profiler troubleshooting page at',
188
- '<https://dtdg.co/ruby-profiler-troubleshooting>.'
80
+ "For help solving this issue, please contact Datadog support at",
81
+ "<https://docs.datadoghq.com/help/>.",
82
+ "You can also check out the Continuous Profiler troubleshooting page at",
83
+ "<https://dtdg.co/ruby-profiler-troubleshooting>."
189
84
  ].freeze
190
85
 
191
86
  GET_IN_TOUCH = [
@@ -193,83 +88,83 @@ module Datadog
193
88
  ].freeze
194
89
 
195
90
  UPGRADE_RUBY = [
196
- 'Upgrade to a modern Ruby to enable profiling for your app.'
91
+ "Upgrade to a modern Ruby to enable profiling for your app."
197
92
  ].freeze
198
93
 
199
94
  # Validation for this check is done in extconf.rb because it relies on mkmf
200
95
  FAILED_TO_CONFIGURE_LIBDATADOG = explain_issue(
201
- 'there was a problem in setting up the `libdatadog` dependency.',
96
+ "there was a problem in setting up the `libdatadog` dependency.",
202
97
  suggested: CONTACT_SUPPORT,
203
98
  )
204
99
 
205
100
  # Validation for this check is done in extconf.rb because it relies on mkmf
206
101
  COMPILATION_BROKEN = explain_issue(
207
- 'compilation of the Ruby VM just-in-time header failed.',
208
- 'Your C compiler or Ruby VM just-in-time compiler seem to be broken.',
102
+ "compilation of the Ruby VM just-in-time header failed.",
103
+ "Your C compiler or Ruby VM just-in-time compiler seem to be broken.",
209
104
  suggested: CONTACT_SUPPORT,
210
105
  )
211
106
 
212
107
  # Validation for this check is done in extconf.rb because it relies on mkmf
213
108
  PKG_CONFIG_IS_MISSING = explain_issue(
214
109
  # ----------------------------------------------------------------------------+
215
- 'the `pkg-config` system tool is missing.',
216
- 'This issue can usually be fixed by installing one of the following:',
217
- 'the `pkg-config` package on Homebrew and Debian/Ubuntu-based Linux;',
218
- 'the `pkgconf` package on Arch and Alpine-based Linux;',
219
- 'the `pkgconf-pkg-config` package on Fedora/Red Hat-based Linux.',
220
- '(Tip: When fixing this, ensure `pkg-config` is installed **before**',
221
- 'running `bundle install`, and remember to clear any installed gems cache).',
110
+ "the `pkg-config` system tool is missing.",
111
+ "This issue can usually be fixed by installing one of the following:",
112
+ "the `pkg-config` package on Homebrew and Debian/Ubuntu-based Linux;",
113
+ "the `pkgconf` package on Arch and Alpine-based Linux;",
114
+ "the `pkgconf-pkg-config` package on Fedora/Red Hat-based Linux.",
115
+ "(Tip: When fixing this, ensure `pkg-config` is installed **before**",
116
+ "running `bundle install`, and remember to clear any installed gems cache).",
222
117
  suggested: CONTACT_SUPPORT,
223
118
  )
224
119
 
225
120
  # Validation for this check is done in extconf.rb because it relies on mkmf
226
121
  COMPILER_ATOMIC_MISSING = explain_issue(
227
- 'your C compiler is missing support for the <stdatomic.h> header.',
228
- 'This issue can usually be fixed by upgrading to a later version of your',
229
- 'operating system image or compiler.',
122
+ "your C compiler is missing support for the <stdatomic.h> header.",
123
+ "This issue can usually be fixed by upgrading to a later version of your",
124
+ "operating system image or compiler.",
230
125
  suggested: CONTACT_SUPPORT,
231
126
  )
232
127
 
233
128
  private_class_method def self.disabled_via_env?
234
129
  report_disabled = [
235
- 'If you needed to use this, please tell us why on',
236
- '<https://github.com/DataDog/dd-trace-rb/issues/new> so we can fix it :)',
130
+ "If you needed to use this, please tell us why on",
131
+ "<https://github.com/DataDog/dd-trace-rb/issues/new> so we can fix it :)",
237
132
  ].freeze
238
133
 
239
134
  disabled_via_env = explain_issue(
240
- 'the `DD_PROFILING_NO_EXTENSION` environment variable is/was set to',
241
- '`true` during installation.',
135
+ "the `DD_PROFILING_NO_EXTENSION` environment variable is/was set to",
136
+ "`true` during installation.",
242
137
  suggested: report_disabled,
243
138
  )
244
139
 
245
- return unless ENV[ENV_NO_EXTENSION].to_s.strip.downcase == 'true'
140
+ return unless ENV[ENV_NO_EXTENSION].to_s.strip.downcase == "true"
246
141
 
247
142
  disabled_via_env
248
143
  end
249
144
 
250
145
  private_class_method def self.on_jruby?
251
146
  jruby_not_supported = explain_issue(
252
- 'JRuby is not supported by the Datadog Continuous Profiler.',
147
+ "JRuby is not supported by the Datadog Continuous Profiler.",
253
148
  suggested: GET_IN_TOUCH,
254
149
  )
255
150
 
256
- jruby_not_supported if RUBY_ENGINE == 'jruby'
151
+ jruby_not_supported if RUBY_ENGINE == "jruby"
257
152
  end
258
153
 
259
154
  private_class_method def self.on_truffleruby?
260
155
  truffleruby_not_supported = explain_issue(
261
- 'TruffleRuby is not supported by the datadog gem.',
156
+ "TruffleRuby is not supported by the datadog gem.",
262
157
  suggested: GET_IN_TOUCH,
263
158
  )
264
159
 
265
- truffleruby_not_supported if RUBY_ENGINE == 'truffleruby'
160
+ truffleruby_not_supported if RUBY_ENGINE == "truffleruby"
266
161
  end
267
162
 
268
163
  # See https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#microsoft-windows-support for current
269
164
  # state of Windows support in the datadog gem.
270
165
  private_class_method def self.on_windows?
271
166
  windows_not_supported = explain_issue(
272
- 'Microsoft Windows is not supported by the Datadog Continuous Profiler.',
167
+ "Microsoft Windows is not supported by the Datadog Continuous Profiler.",
273
168
  suggested: GET_IN_TOUCH,
274
169
  )
275
170
 
@@ -278,72 +173,66 @@ module Datadog
278
173
 
279
174
  private_class_method def self.on_macos?
280
175
  macos_not_supported = explain_issue(
281
- 'macOS is currently not supported by the Datadog Continuous Profiler.',
176
+ "macOS is currently not supported by the Datadog Continuous Profiler.",
282
177
  suggested: GET_IN_TOUCH,
283
178
  )
284
179
  # For development only; not supported otherwise
285
- macos_testing_override = ENV['DD_PROFILING_MACOS_TESTING'] == 'true'
180
+ macos_testing_override = ENV["DD_PROFILING_MACOS_TESTING"] == "true"
286
181
 
287
- macos_not_supported if RUBY_PLATFORM.include?('darwin') && !macos_testing_override
182
+ macos_not_supported if RUBY_PLATFORM.include?("darwin") && !macos_testing_override
288
183
  end
289
184
 
290
185
  private_class_method def self.on_unknown_os?
291
186
  unknown_os_not_supported = explain_issue(
292
- 'your operating system is not supported by the Datadog Continuous Profiler.',
187
+ "your operating system is not supported by the Datadog Continuous Profiler.",
293
188
  suggested: GET_IN_TOUCH,
294
189
  )
295
190
 
296
- unknown_os_not_supported unless RUBY_PLATFORM.include?('darwin') || RUBY_PLATFORM.include?('linux')
191
+ unknown_os_not_supported unless RUBY_PLATFORM.include?("darwin") || RUBY_PLATFORM.include?("linux")
297
192
  end
298
193
 
299
194
  private_class_method def self.on_unsupported_cpu_arch?
300
195
  architecture_not_supported = explain_issue(
301
- 'your CPU architecture is not supported by the Datadog Continuous Profiler.',
196
+ "your CPU architecture is not supported by the Datadog Continuous Profiler.",
302
197
  suggested: GET_IN_TOUCH,
303
198
  )
304
199
 
305
- architecture_not_supported unless RUBY_PLATFORM.start_with?('x86_64', 'aarch64', 'arm64')
200
+ architecture_not_supported unless RUBY_PLATFORM.start_with?("x86_64", "aarch64", "arm64")
306
201
  end
307
202
 
308
203
  # On some Rubies, we require the mjit header to be present. If Ruby was installed without MJIT support, we also skip
309
204
  # building the extension.
310
205
  private_class_method def self.expected_to_use_mjit_but_mjit_is_disabled?
311
206
  ruby_without_mjit = explain_issue(
312
- 'your Ruby has been compiled without JIT support (--disable-jit-support).',
313
- 'The profiling native extension requires a Ruby compiled with JIT support,',
314
- 'even if the JIT is not in use by the application itself.',
207
+ "your Ruby has been compiled without JIT support (--disable-jit-support).",
208
+ "The profiling native extension requires a Ruby compiled with JIT support,",
209
+ "even if the JIT is not in use by the application itself.",
315
210
  suggested: CONTACT_SUPPORT,
316
211
  )
317
212
 
318
- ruby_without_mjit if CAN_USE_MJIT_HEADER && RbConfig::CONFIG['MJIT_SUPPORT'] != 'yes'
213
+ ruby_without_mjit if CAN_USE_MJIT_HEADER && RbConfig::CONFIG["MJIT_SUPPORT"] != "yes"
319
214
  end
320
215
 
321
216
  private_class_method def self.libdatadog_not_available?
322
- begin
323
- gem 'libdatadog', LIBDATADOG_VERSION
324
- require 'libdatadog'
325
- nil
326
- # rubocop:disable Lint/RescueException
327
- rescue Exception => e
217
+ Datadog::LibdatadogExtconfHelpers.try_loading_libdatadog do |exception|
328
218
  explain_issue(
329
- 'there was an exception during loading of the `libdatadog` gem:',
330
- e.class.name,
331
- *e.message.split("\n"),
332
- *Array(e.backtrace),
333
- '.',
219
+ "there was an exception during loading of the `libdatadog` gem:",
220
+ exception.class.name,
221
+ *exception.message.split("\n"),
222
+ *Array(exception.backtrace),
223
+ ".",
334
224
  suggested: CONTACT_SUPPORT,
335
225
  )
336
226
  end
337
- # rubocop:enable Lint/RescueException
338
227
  end
339
228
 
340
229
  private_class_method def self.libdatadog_not_usable?
341
230
  no_binaries_for_current_platform = explain_issue(
342
- 'the `libdatadog` gem installed on your system is missing binaries for your',
343
- 'platform variant.',
231
+ "the `libdatadog` gem installed on your system is missing binaries for your",
232
+ "platform variant.",
344
233
  "(Your platform: `#{Libdatadog.current_platform}`)",
345
- '(Available binaries:',
346
- "`#{Libdatadog.available_binaries.join('`, `')}`)",
234
+ "(Available binaries:",
235
+ "`#{Libdatadog.available_binaries.join("`, `")}`)",
347
236
  suggested: CONTACT_SUPPORT,
348
237
  )
349
238
 
@@ -311,7 +311,7 @@ VALUE thread_name_for(VALUE thread) {
311
311
  // with diagnostic stuff. See https://nelkinda.com/blog/suppress-warnings-in-gcc-and-clang/#d11e364 for details.
312
312
  #pragma GCC diagnostic push
313
313
  #pragma GCC diagnostic ignored "-Wunused-parameter"
314
- inline static int
314
+ static inline int
315
315
  calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
316
316
  {
317
317
  VM_ASSERT(iseq);
@@ -364,7 +364,7 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
364
364
  // Copyright (C) 1993-2012 Yukihiro Matsumoto
365
365
  // to support our custom rb_profile_frames (see below)
366
366
  // Modifications: None
367
- inline static int
367
+ static inline int
368
368
  calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
369
369
  {
370
370
  int lineno;
@@ -376,8 +376,8 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
376
376
  // Copyright (C) 1993-2012 Yukihiro Matsumoto
377
377
  // Modifications:
378
378
  // * Renamed rb_profile_frames => ddtrace_rb_profile_frames
379
- // * Add thread argument
380
- // * Add is_ruby_frame argument
379
+ // * Add thread argument (this is now upstream, actually!)
380
+ // * Add frame_flags.is_ruby_frame argument
381
381
  // * Removed `if (lines)` tests -- require/assume that like `buff`, `lines` is always specified
382
382
  // * Skip dummy frame that shows up in main thread
383
383
  // * Add `end_cfp == NULL` and `end_cfp <= cfp` safety checks. These are used in a bunch of places in
@@ -392,6 +392,9 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
392
392
  // was called from.
393
393
  // * Imported fix from https://github.com/ruby/ruby/pull/7116 to avoid sampling threads that are still being created
394
394
  // * Imported fix from https://github.com/ruby/ruby/pull/8415 to avoid potential crash when using YJIT.
395
+ // * Add frame_flags.same_frame and logic to skip redoing work if the buffer already contains the same data we're collecting
396
+ // * Skipped use of rb_callable_method_entry_t (cme) for Ruby frames as it doesn't impact us.
397
+ // * Imported fix from https://github.com/ruby/ruby/pull/8280 to keep us closer to upstream
395
398
  //
396
399
  // What is rb_profile_frames?
397
400
  // `rb_profile_frames` is a Ruby VM debug API added for use by profilers for sampling the stack trace of a Ruby thread.
@@ -421,8 +424,7 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
421
424
  // and friends). We've found quite a few situations where the data from rb_profile_frames and the reference APIs
422
425
  // disagree, and quite a few of them seem oversights/bugs (speculation from my part) rather than deliberate
423
426
  // decisions.
424
- int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, int *lines, bool* is_ruby_frame)
425
- {
427
+ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer) {
426
428
  int i;
427
429
  // Modified from upstream: Instead of using `GET_EC` to collect info from the current thread,
428
430
  // support sampling any thread (including the current) passed as an argument
@@ -466,7 +468,7 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, i
466
468
  // See comment on `record_placeholder_stack_in_native_code` for a full explanation of what this means (and why we don't just return 0)
467
469
  if (end_cfp <= cfp) return PLACEHOLDER_STACK_IN_NATIVE_CODE;
468
470
 
469
- for (i=0; i<limit && cfp != end_cfp;) {
471
+ for (i=0; i<limit && cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
470
472
  if (cfp->iseq && !cfp->pc) {
471
473
  // Fix: Do nothing -- this frame should not be used
472
474
  //
@@ -479,164 +481,88 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, VALUE *buff, i
479
481
  continue;
480
482
  }
481
483
 
482
- /* record frame info */
483
- cme = rb_vm_frame_method_entry(cfp);
484
+ stack_buffer[i].same_frame =
485
+ stack_buffer[i].is_ruby_frame &&
486
+ stack_buffer[i].as.ruby_frame.iseq == (VALUE) cfp->iseq &&
487
+ stack_buffer[i].as.ruby_frame.caching_pc == cfp->pc;
484
488
 
485
- if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ &&
486
- // Fix: Do not use callable method entry when iseq is for an eval.
487
- // TL;DR: This fix is needed for us to match the Ruby reference API information in the
488
- // "when sampling an eval/instance eval inside an object" spec.
489
- //
490
- // Longer note:
491
- // When a frame is a ruby frame (VM_FRAME_RUBYFRAME_P above), we can get information about it
492
- // by introspecting both the callable method entry, as well as the iseq directly.
493
- // Often they match... but sometimes they provide different info (as in the "iseq for an eval" situation
494
- // here).
495
- // If my reading of vm_backtrace.c is correct, the actual Ruby stack trace API **never** uses the
496
- // callable method entry for Ruby frames, but only for VM_METHOD_TYPE_CFUNC (see `backtrace_each` method
497
- // on that file).
498
- // So... why does `rb_profile_frames` do something different? Is it a bug? Is it because it exposes
499
- // more information than the Ruby stack frame API?
500
- // As a final note, the `backtracie` gem (https://github.com/ivoanjo/backtracie) can be used to introspect
501
- // the full metadata provided by both the callable method entry as well as the iseq, and is really useful
502
- // to debug and learn more about these differences.
503
- cfp->iseq->body->type != ISEQ_TYPE_EVAL) {
504
- buff[i] = (VALUE)cme;
505
- }
506
- else {
507
- buff[i] = (VALUE)cfp->iseq;
489
+ if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
490
+ i++;
491
+ continue;
508
492
  }
509
493
 
494
+ // dd-trace-rb NOTE:
495
+ // Upstream Ruby has code here to retrieve the rb_callable_method_entry_t (cme) and in some cases to use it
496
+ // instead of the iseq.
497
+ // In practice, they are usually the same; the difference is that when you have e.g. block, one gets you a
498
+ // reference to the block, and the other to the method containing the block.
499
+ // This would be important if we used `rb_profile_frame_label` and wanted the "block in foo" label instead
500
+ // of just "foo". But we're currently using `rb_profile_frame_base_label` which I believe is always the same
501
+ // between the rb_callable_method_entry_t and the iseq. Thus, to simplify a bit our logic and reduce a bit
502
+ // the overhead, we always use the iseq here.
503
+ //
504
+ // @ivoanjo: I've left the upstream Ruby code commented out below for reference, so it's more obvious that
505
+ // we're diverging, and we can easily compare and experiment with the upstream version in the future.
506
+ //
507
+ // cme = rb_vm_frame_method_entry(cfp);
508
+
509
+ // if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ &&
510
+ // // Fix: Do not use callable method entry when iseq is for an eval.
511
+ // // TL;DR: This fix is needed for us to match the Ruby reference API information in the
512
+ // // "when sampling an eval/instance eval inside an object" spec.
513
+ // cfp->iseq->body->type != ISEQ_TYPE_EVAL) {
514
+ // buff[i] = (VALUE)cme;
515
+ // }
516
+ // else {
517
+ stack_buffer[i].as.ruby_frame.iseq = (VALUE)cfp->iseq;
518
+ stack_buffer[i].as.ruby_frame.caching_pc = (void *) cfp->pc;
519
+ // }
520
+
510
521
  // The topmost frame may not have an updated PC because the JIT
511
522
  // may not have set one. The JIT compiler will update the PC
512
523
  // before entering a new function (so that `caller` will work),
513
524
  // so only the topmost frame could possibly have an out of date PC
514
525
  #ifndef NO_JIT_RETURN
515
526
  if (cfp == top && cfp->jit_return) {
516
- lines[i] = 0;
527
+ stack_buffer[i].as.ruby_frame.line = 0;
517
528
  } else {
518
- lines[i] = calc_lineno(cfp->iseq, cfp->pc);
529
+ stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
519
530
  }
520
531
  #else // Ruby < 3.1
521
- lines[i] = calc_lineno(cfp->iseq, cfp->pc);
532
+ stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
522
533
  #endif
523
534
 
524
- is_ruby_frame[i] = true;
535
+ stack_buffer[i].is_ruby_frame = true;
525
536
  i++;
526
537
  }
527
538
  else {
528
539
  cme = rb_vm_frame_method_entry(cfp);
529
540
  if (cme && cme->def->type == VM_METHOD_TYPE_CFUNC) {
530
- buff[i] = (VALUE)cme;
531
- lines[i] = 0;
532
- is_ruby_frame[i] = false;
541
+ if (start > 0) {
542
+ start--;
543
+ continue;
544
+ }
545
+
546
+ stack_buffer[i].same_frame =
547
+ !stack_buffer[i].is_ruby_frame &&
548
+ stack_buffer[i].as.native_frame.caching_cme == (VALUE) cme;
549
+
550
+ if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
551
+ i++;
552
+ continue;
553
+ }
554
+
555
+ stack_buffer[i].as.native_frame.caching_cme = (VALUE)cme;
556
+ stack_buffer[i].as.native_frame.method_id = cme->def->original_id;
557
+ stack_buffer[i].is_ruby_frame = false;
533
558
  i++;
534
559
  }
535
560
  }
536
- cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
537
561
  }
538
562
 
539
563
  return i;
540
564
  }
541
565
 
542
- #ifdef USE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME
543
-
544
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
545
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
546
- // to support our custom rb_profile_frame_method_name (see below)
547
- // Modifications: None
548
- static VALUE
549
- id2str(ID id)
550
- {
551
- VALUE str = rb_id2str(id);
552
- if (!str) return Qnil;
553
- return str;
554
- }
555
- #define rb_id2str(id) id2str(id)
556
-
557
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
558
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
559
- // to support our custom rb_profile_frame_method_name (see below)
560
- // Modifications: None
561
- static const rb_iseq_t *
562
- frame2iseq(VALUE frame)
563
- {
564
- if (NIL_P(frame)) return NULL;
565
-
566
- if (RB_TYPE_P(frame, T_IMEMO)) {
567
- switch (imemo_type(frame)) {
568
- case imemo_iseq:
569
- return (const rb_iseq_t *)frame;
570
- case imemo_ment:
571
- {
572
- const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
573
- switch (cme->def->type) {
574
- case VM_METHOD_TYPE_ISEQ:
575
- return cme->def->body.iseq.iseqptr;
576
- default:
577
- return NULL;
578
- }
579
- }
580
- default:
581
- break;
582
- }
583
- }
584
- rb_bug("frame2iseq: unreachable");
585
- }
586
-
587
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
588
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
589
- // to support our custom rb_profile_frame_method_name (see below)
590
- // Modifications: None
591
- static const rb_callable_method_entry_t *
592
- cframe(VALUE frame)
593
- {
594
- if (NIL_P(frame)) return NULL;
595
-
596
- if (RB_TYPE_P(frame, T_IMEMO)) {
597
- switch (imemo_type(frame)) {
598
- case imemo_ment:
599
- {
600
- const rb_callable_method_entry_t *cme = (rb_callable_method_entry_t *)frame;
601
- switch (cme->def->type) {
602
- case VM_METHOD_TYPE_CFUNC:
603
- return cme;
604
- default:
605
- return NULL;
606
- }
607
- }
608
- default:
609
- return NULL;
610
- }
611
- }
612
-
613
- return NULL;
614
- }
615
-
616
- // Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
617
- // Copyright (C) 1993-2012 Yukihiro Matsumoto
618
- //
619
- // Ruby 3.0 finally added support for showing CFUNC frames (frames for methods written using native code)
620
- // in stack traces gathered via `rb_profile_frames` (https://github.com/ruby/ruby/pull/3299).
621
- // To access this information on older Rubies, beyond using our custom `ddtrace_rb_profile_frames` above, we also need
622
- // to backport the Ruby 3.0+ version of `rb_profile_frame_method_name`.
623
- //
624
- // Modifications:
625
- // * Renamed rb_profile_frame_method_name => ddtrace_rb_profile_frame_method_name
626
- VALUE
627
- ddtrace_rb_profile_frame_method_name(VALUE frame)
628
- {
629
- const rb_callable_method_entry_t *cme = cframe(frame);
630
- if (cme) {
631
- ID mid = cme->def->original_id;
632
- return id2str(mid);
633
- }
634
- const rb_iseq_t *iseq = frame2iseq(frame);
635
- return iseq ? rb_iseq_method_name(iseq) : Qnil;
636
- }
637
-
638
- #endif // USE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME
639
-
640
566
  // Support code for older Rubies that cannot use the MJIT header
641
567
  #ifndef RUBY_MJIT_HEADER
642
568