datadog 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/ext/datadog_profiling_loader/extconf.rb +15 -15
- data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +113 -43
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
- data/ext/datadog_profiling_native_extension/collectors_stack.c +49 -37
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +65 -60
- data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
- data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/helpers.h +6 -17
- data/ext/datadog_profiling_native_extension/http_transport.c +3 -3
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +64 -138
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
- data/ext/datadog_profiling_native_extension/profiling.c +0 -2
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
- data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
- data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +19 -6
- data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +57 -0
- data/ext/libdatadog_api/extconf.rb +108 -0
- data/ext/libdatadog_api/macos_development.md +26 -0
- data/ext/libdatadog_extconf_helpers.rb +130 -0
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +49 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
- data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
- data/lib/datadog/appsec/processor/actions.rb +1 -1
- data/lib/datadog/appsec/response.rb +15 -1
- data/lib/datadog/appsec.rb +1 -0
- data/lib/datadog/core/configuration/components.rb +14 -12
- data/lib/datadog/core/configuration/settings.rb +54 -7
- data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
- data/lib/datadog/core/crashtracking/component.rb +111 -0
- data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
- data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
- data/lib/datadog/core/telemetry/component.rb +49 -2
- data/lib/datadog/core/telemetry/emitter.rb +9 -11
- data/lib/datadog/core/telemetry/event.rb +32 -1
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
- data/lib/datadog/core/telemetry/http/ext.rb +3 -0
- data/lib/datadog/core/telemetry/http/transport.rb +38 -9
- data/lib/datadog/core/telemetry/logging.rb +35 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
- data/lib/datadog/kit/appsec/events.rb +2 -4
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +17 -17
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
- data/lib/datadog/profiling/collectors/info.rb +3 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
- data/lib/datadog/profiling/component.rb +69 -91
- data/lib/datadog/profiling/exporter.rb +3 -3
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/profiling/ext.rb +21 -21
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/http_transport.rb +8 -6
- data/lib/datadog/profiling/load_native_extension.rb +5 -5
- data/lib/datadog/profiling/preload.rb +1 -1
- data/lib/datadog/profiling/profiler.rb +5 -8
- data/lib/datadog/profiling/scheduler.rb +31 -25
- data/lib/datadog/profiling/tag_builder.rb +2 -2
- data/lib/datadog/profiling/tasks/exec.rb +5 -5
- data/lib/datadog/profiling/tasks/setup.rb +16 -35
- data/lib/datadog/profiling.rb +4 -5
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/ext.rb +14 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +1 -1
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +4 -1
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
- data/lib/datadog/tracing/metadata/errors.rb +9 -1
- data/lib/datadog/tracing/metadata/ext.rb +4 -0
- data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
- data/lib/datadog/tracing/span.rb +9 -2
- data/lib/datadog/tracing/span_event.rb +41 -0
- data/lib/datadog/tracing/span_operation.rb +6 -2
- data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
- data/lib/datadog/version.rb +1 -1
- metadata +28 -10
- data/lib/datadog/profiling/crashtracker.rb +0 -91
- 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 =
|
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 =
|
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?(
|
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 ==
|
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
|
-
{
|
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
|
-
|
49
|
+
"Failing installation immediately because the ",
|
147
50
|
"`#{ENV_FAIL_INSTALL_IF_MISSING_EXTENSION}` environment variable is set",
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
155
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
208
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
236
|
-
|
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
|
-
|
241
|
-
|
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 ==
|
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
|
-
|
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 ==
|
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
|
-
|
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 ==
|
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
|
-
|
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
|
-
|
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[
|
180
|
+
macos_testing_override = ENV["DD_PROFILING_MACOS_TESTING"] == "true"
|
286
181
|
|
287
|
-
macos_not_supported if RUBY_PLATFORM.include?(
|
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
|
-
|
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?(
|
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
|
-
|
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?(
|
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
|
-
|
313
|
-
|
314
|
-
|
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[
|
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
|
-
|
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
|
-
|
330
|
-
|
331
|
-
*
|
332
|
-
*Array(
|
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
|
-
|
343
|
-
|
231
|
+
"the `libdatadog` gem installed on your system is missing binaries for your",
|
232
|
+
"platform variant.",
|
344
233
|
"(Your platform: `#{Libdatadog.current_platform}`)",
|
345
|
-
|
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
|
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
|
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,
|
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
|
-
|
483
|
-
|
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 (
|
486
|
-
|
487
|
-
|
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
|
-
|
527
|
+
stack_buffer[i].as.ruby_frame.line = 0;
|
517
528
|
} else {
|
518
|
-
|
529
|
+
stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
|
519
530
|
}
|
520
531
|
#else // Ruby < 3.1
|
521
|
-
|
532
|
+
stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
|
522
533
|
#endif
|
523
534
|
|
524
|
-
|
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
|
-
|
531
|
-
|
532
|
-
|
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
|
|