datadog 2.2.0 → 2.3.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 (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