datadog 2.40.0 → 2.42.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +120 -59
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +110 -61
- data/ext/datadog_profiling_native_extension/collectors_stack.c +13 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +141 -103
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
- data/ext/datadog_profiling_native_extension/heap_recorder.c +338 -255
- data/ext/datadog_profiling_native_extension/heap_recorder.h +46 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +526 -443
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
- data/ext/datadog_profiling_native_extension/stack_recorder.c +93 -61
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
- data/ext/libdatadog_api/di.c +10 -0
- data/ext/libdatadog_api/extconf.rb +3 -0
- data/ext/libdatadog_api/init.c +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +232 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +409 -71
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +1 -1
- data/lib/datadog/ai_guard/evaluation/request.rb +8 -8
- data/lib/datadog/ai_guard/evaluation.rb +10 -3
- data/lib/datadog/ai_guard/ext.rb +1 -1
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/appsec/configuration.rb +2 -2
- data/lib/datadog/appsec/context.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
- data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +2 -2
- data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
- data/lib/datadog/appsec/response.rb +1 -1
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
- data/lib/datadog/appsec/security_engine/engine.rb +3 -3
- data/lib/datadog/core/configuration/components.rb +3 -2
- data/lib/datadog/core/configuration/option_definition.rb +1 -1
- data/lib/datadog/core/configuration/options.rb +1 -1
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
- data/lib/datadog/core/metrics/helpers.rb +1 -1
- data/lib/datadog/core/metrics/options.rb +2 -2
- data/lib/datadog/core/process_discovery.rb +1 -1
- data/lib/datadog/core/remote/client.rb +1 -1
- data/lib/datadog/core/remote/configuration/repository.rb +2 -2
- data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
- data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
- data/lib/datadog/core/telemetry/event/log.rb +2 -2
- data/lib/datadog/core/telemetry/metric.rb +2 -2
- data/lib/datadog/core/telemetry/request.rb +2 -2
- data/lib/datadog/core/transport/http.rb +1 -1
- data/lib/datadog/core/transport/transport.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +127 -43
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +45 -14
- data/lib/datadog/di/el/compiler.rb +23 -0
- data/lib/datadog/di/el/expression.rb +8 -5
- data/lib/datadog/di/instrumenter.rb +46 -5
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_builder.rb +5 -4
- data/lib/datadog/di/probe_notification_builder.rb +3 -2
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/di/serializer.rb +29 -4
- data/lib/datadog/error_tracking/component.rb +1 -1
- data/lib/datadog/kit/appsec/events/v2.rb +2 -2
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
- data/lib/datadog/open_feature/exposures/event.rb +15 -8
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +33 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +11 -2
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
- data/lib/datadog/profiling/collectors/info.rb +1 -1
- data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
- data/lib/datadog/profiling/component.rb +8 -16
- data/lib/datadog/profiling/tasks/exec.rb +1 -1
- data/lib/datadog/symbol_database/extractor.rb +6 -6
- data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
- data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
- data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
- data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
- data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/ext.rb +3 -3
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/patchable.rb +1 -1
- data/lib/datadog/tracing/contrib/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
- data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/correlation.rb +2 -2
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +2 -2
- data/lib/datadog/tracing/distributed/baggage.rb +1 -3
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/metadata/tagging.rb +1 -1
- data/lib/datadog/tracing/otel_thread_context.rb +30 -0
- data/lib/datadog/tracing/remote.rb +197 -29
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/span.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +1 -1
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +5 -5
- data/lib/datadog/tracing/transport/io/traces.rb +1 -1
- data/lib/datadog/tracing/transport/native.rb +78 -54
- data/lib/datadog/tracing/writer.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +15 -8
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
|
@@ -2,50 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
// This file exports functions used to access private Ruby VM APIs and internals.
|
|
4
4
|
// To do this, it imports a few VM internal (private) headers.
|
|
5
|
+
// We rely on the datadog-ruby_core_source gem to get access to private VM headers; see
|
|
6
|
+
// https://github.com/DataDog/datadog-ruby_core_source for details.
|
|
5
7
|
//
|
|
6
8
|
// **Important Note**: Our medium/long-term plan is to stop relying on all private Ruby headers, and instead request and
|
|
7
9
|
// contribute upstream changes so that they become official public VM APIs.
|
|
8
10
|
//
|
|
9
11
|
// In the meanwhile, be very careful when changing things here :)
|
|
10
12
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
#include <ruby/defines.h>
|
|
14
|
+
|
|
15
|
+
// We can't do anything about warnings in VM headers, so we just use this technique to suppress them.
|
|
16
|
+
// See https://nelkinda.com/blog/suppress-warnings-in-gcc-and-clang/#d11e364 for details.
|
|
17
|
+
#pragma GCC diagnostic push
|
|
18
|
+
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
19
|
+
#pragma GCC diagnostic ignored "-Wattributes"
|
|
20
|
+
#pragma GCC diagnostic ignored "-Wpragmas"
|
|
21
|
+
#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
|
|
22
|
+
#include <vm_core.h>
|
|
23
|
+
#pragma GCC diagnostic pop
|
|
24
|
+
|
|
25
|
+
#pragma GCC diagnostic push
|
|
26
|
+
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
27
|
+
#include <iseq.h>
|
|
28
|
+
#pragma GCC diagnostic pop
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
// See https://nelkinda.com/blog/suppress-warnings-in-gcc-and-clang/#d11e364 for details.
|
|
30
|
+
#ifndef NO_INTERNAL_CLASS_HEADER_INCLUDE
|
|
20
31
|
#pragma GCC diagnostic push
|
|
21
32
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
22
|
-
|
|
23
|
-
#pragma GCC diagnostic ignored "-Wpragmas"
|
|
24
|
-
#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
|
|
25
|
-
#include <vm_core.h>
|
|
33
|
+
#include <internal/class.h>
|
|
26
34
|
#pragma GCC diagnostic pop
|
|
35
|
+
#endif
|
|
36
|
+
|
|
37
|
+
#include <ruby.h>
|
|
27
38
|
|
|
39
|
+
#ifndef NO_RACTOR_HEADER_INCLUDE
|
|
28
40
|
#pragma GCC diagnostic push
|
|
29
41
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
30
|
-
#include <
|
|
42
|
+
#include <ractor_core.h>
|
|
31
43
|
#pragma GCC diagnostic pop
|
|
32
|
-
|
|
33
|
-
#ifndef NO_INTERNAL_CLASS_HEADER_INCLUDE
|
|
34
|
-
#pragma GCC diagnostic push
|
|
35
|
-
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
36
|
-
#include <internal/class.h>
|
|
37
|
-
#pragma GCC diagnostic pop
|
|
38
|
-
#endif
|
|
39
|
-
|
|
40
|
-
#include <ruby.h>
|
|
41
|
-
|
|
42
|
-
#ifndef NO_RACTOR_HEADER_INCLUDE
|
|
43
|
-
#pragma GCC diagnostic push
|
|
44
|
-
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
45
|
-
#include <ractor_core.h>
|
|
46
|
-
#pragma GCC diagnostic pop
|
|
47
|
-
#endif
|
|
48
|
-
|
|
49
44
|
#endif
|
|
50
45
|
|
|
51
46
|
// This file can't include datadog_ruby_common.h so we replicate this here
|
|
@@ -59,6 +54,7 @@
|
|
|
59
54
|
#include "private_vm_api_access.h"
|
|
60
55
|
|
|
61
56
|
static inline const rb_callable_method_entry_t* get_cfunc_method_entry(const rb_control_frame_t *cfp);
|
|
57
|
+
static const rb_callable_method_entry_t* safe_vm_frame_method_entry(const rb_control_frame_t *cfp);
|
|
62
58
|
|
|
63
59
|
// MRI has a similar rb_thread_ptr() function which we can't call it directly
|
|
64
60
|
// because Ruby does not expose the thread_data_type publicly.
|
|
@@ -69,7 +65,9 @@ static inline const rb_callable_method_entry_t* get_cfunc_method_entry(const rb_
|
|
|
69
65
|
// if the argument passed in is not actually a `Thread` instance.
|
|
70
66
|
static inline rb_thread_t *thread_struct_from_object(VALUE thread) {
|
|
71
67
|
static const rb_data_type_t *thread_data_type = NULL;
|
|
72
|
-
if (UNLIKELY(thread_data_type == NULL))
|
|
68
|
+
if (UNLIKELY(thread_data_type == NULL)) {
|
|
69
|
+
thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
|
|
70
|
+
}
|
|
73
71
|
|
|
74
72
|
return (rb_thread_t *) rb_check_typeddata(thread, thread_data_type);
|
|
75
73
|
}
|
|
@@ -79,7 +77,9 @@ rb_nativethread_id_t pthread_id_for(VALUE thread) {
|
|
|
79
77
|
#ifndef NO_RB_NATIVE_THREAD
|
|
80
78
|
struct rb_native_thread* native_thread = thread_struct_from_object(thread)->nt;
|
|
81
79
|
// This can be NULL on Ruby 3.3 with MN threads (RUBY_MN_THREADS=1)
|
|
82
|
-
if (native_thread == NULL)
|
|
80
|
+
if (native_thread == NULL) {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
83
|
return native_thread->thread_id;
|
|
84
84
|
#else
|
|
85
85
|
return thread_struct_from_object(thread)->thread_id;
|
|
@@ -103,87 +103,93 @@ bool is_current_thread_holding_the_gvl(void) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
#ifdef HAVE_RUBY_RACTOR_H
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
static inline rb_ractor_t *ddtrace_get_ractor(void) {
|
|
107
|
+
#ifndef USE_RACTOR_INTERNAL_APIS_DIRECTLY // Ruby >= 3.3
|
|
108
|
+
return thread_struct_from_object(rb_thread_current())->ractor;
|
|
109
|
+
#else
|
|
110
|
+
return GET_RACTOR();
|
|
111
|
+
#endif
|
|
112
|
+
}
|
|
113
113
|
#endif
|
|
114
114
|
|
|
115
115
|
#ifndef NO_GVL_OWNER // Ruby < 2.6 doesn't have the owner/running field
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
#else
|
|
131
|
-
GET_VM()->gvl.owner;
|
|
132
|
-
#endif
|
|
133
|
-
|
|
134
|
-
if (current_owner == NULL) return (current_gvl_owner) {.valid = false};
|
|
135
|
-
|
|
136
|
-
#ifndef NO_RB_NATIVE_THREAD
|
|
137
|
-
struct rb_native_thread* current_owner_native_thread = current_owner->nt;
|
|
138
|
-
|
|
139
|
-
// This can be NULL on Ruby 3.3 with MN threads (RUBY_MN_THREADS=1)
|
|
140
|
-
if (current_owner_native_thread == NULL) return (current_gvl_owner) {.valid = false};
|
|
141
|
-
|
|
142
|
-
return (current_gvl_owner) {.valid = true, .owner = current_owner_native_thread->thread_id};
|
|
116
|
+
// NOTE: Reading the owner in this is a racy read, because we're not grabbing the lock that Ruby uses to protect it.
|
|
117
|
+
//
|
|
118
|
+
// While we could potentially grab this lock, I (@ivoanjo) think we actually don't need it because:
|
|
119
|
+
// * In the case where a thread owns the GVL and calls `gvl_owner`, it will always see the correct value. That's
|
|
120
|
+
// because every thread sets itself as the owner when it grabs the GVL and unsets itself at the end.
|
|
121
|
+
// That means that `is_current_thread_holding_the_gvl` is always accurate.
|
|
122
|
+
// * In a case where we observe a different thread, then this may change by the time we do something with this value
|
|
123
|
+
// anyway. So unless we want to prevent the Ruby scheduler from switching threads, we need to deal with races here.
|
|
124
|
+
current_gvl_owner gvl_owner(void) {
|
|
125
|
+
const rb_thread_t *current_owner =
|
|
126
|
+
#ifndef NO_RB_THREAD_SCHED // Introduced in Ruby 3.2 as a replacement for struct rb_global_vm_lock_struct
|
|
127
|
+
ddtrace_get_ractor()->threads.sched.running;
|
|
128
|
+
#elif HAVE_RUBY_RACTOR_H
|
|
129
|
+
ddtrace_get_ractor()->threads.gvl.owner;
|
|
143
130
|
#else
|
|
144
|
-
|
|
131
|
+
GET_VM()->gvl.owner;
|
|
145
132
|
#endif
|
|
133
|
+
|
|
134
|
+
if (current_owner == NULL) {
|
|
135
|
+
return (current_gvl_owner) {.valid = false};
|
|
146
136
|
}
|
|
147
|
-
#else
|
|
148
|
-
current_gvl_owner gvl_owner(void) {
|
|
149
|
-
rb_vm_t *vm = GET_VM();
|
|
150
137
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// * Declaration 1: Someone has the GVL
|
|
154
|
-
// * Declaration 2: That someone is the specific thread
|
|
155
|
-
//
|
|
156
|
-
// Observation 1: On older versions of Ruby, this ownership concept is actually split. Specifically, `gvl.acquired`
|
|
157
|
-
// is a boolean that represents declaration 1 above, and `vm->running_thread` (or `ruby_current_thread`/
|
|
158
|
-
// `ruby_current_execution_context_ptr`) represents declaration 2.
|
|
159
|
-
//
|
|
160
|
-
// Observation 2: In addition, when a thread releases the GVL, it only sets `gvl.acquired` back to 0 **BUT CRUCIALLY
|
|
161
|
-
// DOES NOT CHANGE THE OTHER global variables**.
|
|
162
|
-
//
|
|
163
|
-
// Observation 1+2 above lead to the following possible race:
|
|
164
|
-
// * Thread A grabs the GVL (`gvl.acquired == 1`)
|
|
165
|
-
// * Thread A sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread A`)
|
|
166
|
-
// * Thread A releases the GVL (`gvl.acquired == 0` + `running_thread == Thread A`)
|
|
167
|
-
// * Thread B grabs the GVL (`gvl.acquired == 1` + `running_thread == Thread A`)
|
|
168
|
-
// * Thread A calls gvl_owner. Due to the current state (`gvl.acquired == 1` + `running_thread == Thread A`), this
|
|
169
|
-
// function returns an incorrect result.
|
|
170
|
-
// * Thread B finally sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread B`)
|
|
171
|
-
//
|
|
172
|
-
// This is especially problematic because we use `gvl_owner` to implement `is_current_thread_holding_the_gvl` which
|
|
173
|
-
// is called in a signal handler to decide "is it safe for me to call `rb_postponed_job_register_one` or not".
|
|
174
|
-
// (See constraints in `collectors_cpu_and_wall_time_worker.c` comments for why).
|
|
175
|
-
//
|
|
176
|
-
// Thus an incorrect `is_current_thread_holding_the_gvl` result may lead to issues inside `rb_postponed_job_register_one`.
|
|
177
|
-
//
|
|
178
|
-
// For this reason we default to use the "no signals workaround" on Ruby 2.5 by default, and we print a
|
|
179
|
-
// warning when customers force-enable it.
|
|
180
|
-
bool gvl_acquired = vm->gvl.acquired != 0;
|
|
181
|
-
rb_thread_t *current_owner = vm->running_thread;
|
|
138
|
+
#ifndef NO_RB_NATIVE_THREAD
|
|
139
|
+
struct rb_native_thread* current_owner_native_thread = current_owner->nt;
|
|
182
140
|
|
|
183
|
-
|
|
141
|
+
// This can be NULL on Ruby 3.3 with MN threads (RUBY_MN_THREADS=1)
|
|
142
|
+
if (current_owner_native_thread == NULL) {
|
|
143
|
+
return (current_gvl_owner) {.valid = false};
|
|
144
|
+
}
|
|
184
145
|
|
|
146
|
+
return (current_gvl_owner) {.valid = true, .owner = current_owner_native_thread->thread_id};
|
|
147
|
+
#else
|
|
185
148
|
return (current_gvl_owner) {.valid = true, .owner = current_owner->thread_id};
|
|
149
|
+
#endif
|
|
150
|
+
}
|
|
151
|
+
#else
|
|
152
|
+
current_gvl_owner gvl_owner(void) {
|
|
153
|
+
rb_vm_t *vm = GET_VM();
|
|
154
|
+
|
|
155
|
+
// BIG Issue: Ruby < 2.6 did not have the owner field. The really nice thing about the owner field is that it's
|
|
156
|
+
// "atomic" -- when a thread sets it, it "declares" two things in a single step
|
|
157
|
+
// * Declaration 1: Someone has the GVL
|
|
158
|
+
// * Declaration 2: That someone is the specific thread
|
|
159
|
+
//
|
|
160
|
+
// Observation 1: On older versions of Ruby, this ownership concept is actually split. Specifically, `gvl.acquired`
|
|
161
|
+
// is a boolean that represents declaration 1 above, and `vm->running_thread` (or `ruby_current_thread`/
|
|
162
|
+
// `ruby_current_execution_context_ptr`) represents declaration 2.
|
|
163
|
+
//
|
|
164
|
+
// Observation 2: In addition, when a thread releases the GVL, it only sets `gvl.acquired` back to 0 **BUT CRUCIALLY
|
|
165
|
+
// DOES NOT CHANGE THE OTHER global variables**.
|
|
166
|
+
//
|
|
167
|
+
// Observation 1+2 above lead to the following possible race:
|
|
168
|
+
// * Thread A grabs the GVL (`gvl.acquired == 1`)
|
|
169
|
+
// * Thread A sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread A`)
|
|
170
|
+
// * Thread A releases the GVL (`gvl.acquired == 0` + `running_thread == Thread A`)
|
|
171
|
+
// * Thread B grabs the GVL (`gvl.acquired == 1` + `running_thread == Thread A`)
|
|
172
|
+
// * Thread A calls gvl_owner. Due to the current state (`gvl.acquired == 1` + `running_thread == Thread A`), this
|
|
173
|
+
// function returns an incorrect result.
|
|
174
|
+
// * Thread B finally sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread B`)
|
|
175
|
+
//
|
|
176
|
+
// This is especially problematic because we use `gvl_owner` to implement `is_current_thread_holding_the_gvl` which
|
|
177
|
+
// is called in a signal handler to decide "is it safe for me to call `rb_postponed_job_register_one` or not".
|
|
178
|
+
// (See constraints in `collectors_cpu_and_wall_time_worker.c` comments for why).
|
|
179
|
+
//
|
|
180
|
+
// Thus an incorrect `is_current_thread_holding_the_gvl` result may lead to issues inside `rb_postponed_job_register_one`.
|
|
181
|
+
//
|
|
182
|
+
// For this reason we default to use the "no signals workaround" on Ruby 2.5 by default, and we print a
|
|
183
|
+
// warning when customers force-enable it.
|
|
184
|
+
bool gvl_acquired = vm->gvl.acquired != 0;
|
|
185
|
+
rb_thread_t *current_owner = vm->running_thread;
|
|
186
|
+
|
|
187
|
+
if (!gvl_acquired || current_owner == NULL) {
|
|
188
|
+
return (current_gvl_owner) {.valid = false};
|
|
186
189
|
}
|
|
190
|
+
|
|
191
|
+
return (current_gvl_owner) {.valid = true, .owner = current_owner->thread_id};
|
|
192
|
+
}
|
|
187
193
|
#endif // NO_GVL_OWNER
|
|
188
194
|
|
|
189
195
|
// Taken from upstream vm_core.h at commit d9cf0388599a3234b9f3c06ddd006cd59a58ab8b (November 2022, Ruby 3.2 trunk)
|
|
@@ -191,7 +197,7 @@ bool is_current_thread_holding_the_gvl(void) {
|
|
|
191
197
|
// to support tid_for (see below)
|
|
192
198
|
// Modifications: None
|
|
193
199
|
#if defined(__linux__) || defined(__FreeBSD__)
|
|
194
|
-
#
|
|
200
|
+
#define RB_THREAD_T_HAS_NATIVE_ID
|
|
195
201
|
#endif
|
|
196
202
|
|
|
197
203
|
uint64_t native_thread_id_for(VALUE thread) {
|
|
@@ -199,7 +205,9 @@ uint64_t native_thread_id_for(VALUE thread) {
|
|
|
199
205
|
#if !defined(NO_THREAD_TID) && defined(RB_THREAD_T_HAS_NATIVE_ID)
|
|
200
206
|
#ifndef NO_RB_NATIVE_THREAD
|
|
201
207
|
struct rb_native_thread* native_thread = thread_struct_from_object(thread)->nt;
|
|
202
|
-
if (native_thread == NULL)
|
|
208
|
+
if (native_thread == NULL) {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
203
211
|
return native_thread->tid;
|
|
204
212
|
#else
|
|
205
213
|
return thread_struct_from_object(thread)->tid;
|
|
@@ -211,7 +219,9 @@ uint64_t native_thread_id_for(VALUE thread) {
|
|
|
211
219
|
uint64_t result;
|
|
212
220
|
// On macOS, this gives us the same identifier that shows up in activity monitor
|
|
213
221
|
int error = pthread_threadid_np(pthread_id, &result);
|
|
214
|
-
if (error)
|
|
222
|
+
if (error) {
|
|
223
|
+
rb_syserr_fail(error, "Unexpected failure in pthread_threadid_np");
|
|
224
|
+
}
|
|
215
225
|
return result;
|
|
216
226
|
#else
|
|
217
227
|
// Fallback, when we have nothing better (e.g. on Ruby < 3.1 on Linux)
|
|
@@ -243,11 +253,12 @@ void ddtrace_thread_list(VALUE result_array) {
|
|
|
243
253
|
// called from a different Ractor, but I'm not sure...
|
|
244
254
|
#ifdef HAVE_RUBY_RACTOR_H
|
|
245
255
|
rb_ractor_t *current_ractor = ddtrace_get_ractor();
|
|
246
|
-
ccan_list_for_each(¤t_ractor->threads.set, thread, lt_node)
|
|
256
|
+
ccan_list_for_each(¤t_ractor->threads.set, thread, lt_node)
|
|
247
257
|
#else
|
|
248
258
|
rb_vm_t *vm = GET_VM();
|
|
249
|
-
list_for_each(&vm->living_threads, thread, vmlt_node)
|
|
259
|
+
list_for_each(&vm->living_threads, thread, vmlt_node)
|
|
250
260
|
#endif
|
|
261
|
+
{
|
|
251
262
|
switch (thread->status) {
|
|
252
263
|
case THREAD_RUNNABLE:
|
|
253
264
|
case THREAD_STOPPED:
|
|
@@ -315,63 +326,72 @@ VALUE thread_name_for(VALUE thread) {
|
|
|
315
326
|
#pragma GCC diagnostic push
|
|
316
327
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
317
328
|
static inline int
|
|
318
|
-
calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
329
|
+
calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) {
|
|
330
|
+
VM_ASSERT(iseq);
|
|
331
|
+
VM_ASSERT(ISEQ_BODY(iseq));
|
|
332
|
+
VM_ASSERT(ISEQ_BODY(iseq)->iseq_encoded);
|
|
333
|
+
VM_ASSERT(ISEQ_BODY(iseq)->iseq_size);
|
|
334
|
+
if (pc == NULL) {
|
|
335
|
+
if (ISEQ_BODY(iseq)->type == ISEQ_TYPE_TOP) {
|
|
336
|
+
VM_ASSERT(! ISEQ_BODY(iseq)->local_table);
|
|
337
|
+
VM_ASSERT(! ISEQ_BODY(iseq)->local_table_size);
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
#ifndef NO_INT_FIRST_LINENO // Ruby 3.2+
|
|
341
|
+
if (lineno) {
|
|
342
|
+
*lineno = ISEQ_BODY(iseq)->location.first_lineno;
|
|
343
|
+
}
|
|
344
|
+
#else
|
|
345
|
+
if (lineno) {
|
|
346
|
+
*lineno = FIX2INT(ISEQ_BODY(iseq)->location.first_lineno);
|
|
347
|
+
}
|
|
348
|
+
#endif
|
|
335
349
|
#ifdef USE_ISEQ_NODE_ID
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return 1;
|
|
350
|
+
if (node_id) {
|
|
351
|
+
*node_id = -1;
|
|
339
352
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
353
|
+
#endif
|
|
354
|
+
return 1;
|
|
355
|
+
} else {
|
|
356
|
+
ptrdiff_t n = pc - ISEQ_BODY(iseq)->iseq_encoded;
|
|
357
|
+
VM_ASSERT(n <= ISEQ_BODY(iseq)->iseq_size);
|
|
358
|
+
VM_ASSERT(n >= 0);
|
|
359
|
+
ASSUME(n >= 0);
|
|
360
|
+
size_t pos = n; /* no overflow */
|
|
361
|
+
if (LIKELY(pos)) {
|
|
362
|
+
/* use pos-1 because PC points next instruction at the beginning of instruction */
|
|
363
|
+
pos--;
|
|
364
|
+
} else {
|
|
350
365
|
#if VMDEBUG && defined(HAVE_BUILTIN___BUILTIN_TRAP)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
__builtin_trap();
|
|
355
|
-
}
|
|
366
|
+
/* SDR() is not possible; that causes infinite loop. */
|
|
367
|
+
rb_print_backtrace();
|
|
368
|
+
__builtin_trap();
|
|
356
369
|
#endif
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method.
|
|
373
|
+
// We were only able to reproduce this issue on Ruby 2.6 and 2.7, not 2.5 or the 3.x series (tried 3.0, 3.2 and 3.4).
|
|
374
|
+
// Note that going out of bounds doesn't crash every time, as usual with C we may just read garbage or get lucky.
|
|
375
|
+
//
|
|
376
|
+
// For those problematic Rubies, we observed that when we try to take a sample in the middle of processing the
|
|
377
|
+
// VM `LEAVE` instruction, the value of `n` can violate the documented assumptions above and be
|
|
378
|
+
// `n > ISEQ_BODY(iseq)->iseq_size)`.
|
|
379
|
+
//
|
|
380
|
+
// To work around this and any other potential issues, we validate here that the bytecode position is sane.
|
|
381
|
+
if (RB_UNLIKELY(n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) {
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
357
384
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
//
|
|
362
|
-
// For those problematic Rubies, we observed that when we try to take a sample in the middle of processing the
|
|
363
|
-
// VM `LEAVE` instruction, the value of `n` can violate the documented assumptions above and be
|
|
364
|
-
// `n > ISEQ_BODY(iseq)->iseq_size)`.
|
|
365
|
-
//
|
|
366
|
-
// To work around this and any other potential issues, we validate here that the bytecode position is sane.
|
|
367
|
-
if (RB_UNLIKELY(n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
|
|
368
|
-
|
|
369
|
-
if (lineno) *lineno = rb_iseq_line_no(iseq, pos);
|
|
385
|
+
if (lineno) {
|
|
386
|
+
*lineno = rb_iseq_line_no(iseq, pos);
|
|
387
|
+
}
|
|
370
388
|
#ifdef USE_ISEQ_NODE_ID
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return 1;
|
|
389
|
+
if (node_id) {
|
|
390
|
+
*node_id = rb_iseq_node_id(iseq, pos);
|
|
374
391
|
}
|
|
392
|
+
#endif
|
|
393
|
+
return 1;
|
|
394
|
+
}
|
|
375
395
|
}
|
|
376
396
|
#pragma GCC diagnostic pop
|
|
377
397
|
|
|
@@ -380,11 +400,12 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
|
|
|
380
400
|
// to support our custom rb_profile_frames (see below)
|
|
381
401
|
// Modifications: None
|
|
382
402
|
static inline int
|
|
383
|
-
calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
403
|
+
calc_lineno(const rb_iseq_t *iseq, const VALUE *pc) {
|
|
404
|
+
int lineno;
|
|
405
|
+
if (calc_pos(iseq, pc, &lineno, NULL)) {
|
|
406
|
+
return lineno;
|
|
407
|
+
}
|
|
408
|
+
return 0;
|
|
388
409
|
}
|
|
389
410
|
|
|
390
411
|
// Taken from upstream vm_backtrace.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
|
|
@@ -443,176 +464,180 @@ calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
|
|
|
443
464
|
// disagree, and quite a few of them seem oversights/bugs (speculation from my part) rather than deliberate
|
|
444
465
|
// decisions.
|
|
445
466
|
int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
467
|
+
int i;
|
|
468
|
+
// Modified from upstream: Instead of using `GET_EC` to collect info from the current thread,
|
|
469
|
+
// support sampling any thread (including the current) passed as an argument
|
|
470
|
+
rb_thread_t *th = thread_struct_from_object(thread);
|
|
471
|
+
const rb_execution_context_t *ec = th->ec;
|
|
472
|
+
|
|
473
|
+
// As of this writing, we don't support profiling with MN enabled, and this only happens in that mode, but as we
|
|
474
|
+
// probably want to experiment with it in the future, I've decided to import https://github.com/ruby/ruby/pull/9310
|
|
475
|
+
// here.
|
|
476
|
+
if (ec == NULL) {
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
451
479
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
480
|
+
// Avoid sampling dead threads
|
|
481
|
+
if (th->status == THREAD_KILLED) {
|
|
482
|
+
return 0;
|
|
483
|
+
}
|
|
456
484
|
|
|
457
|
-
|
|
458
|
-
if (th->status == THREAD_KILLED) return 0;
|
|
485
|
+
const rb_control_frame_t *cfp = ec->cfp;
|
|
459
486
|
|
|
460
|
-
|
|
487
|
+
// This happens on newly-created threads (we even had a flaky test because of it)
|
|
488
|
+
if (cfp == NULL) {
|
|
489
|
+
return PLACEHOLDER_STACK_IN_NATIVE_CODE;
|
|
490
|
+
}
|
|
461
491
|
|
|
462
|
-
|
|
463
|
-
|
|
492
|
+
// I suspect this won't happen for ddtrace, but just-in-case we've imported a potential fix for
|
|
493
|
+
// https://github.com/ruby/ruby/pull/13643 by assuming that these can be NULL/zero with the cfp being non-NULL yet.
|
|
494
|
+
if (ec->vm_stack == NULL || ec->vm_stack_size == 0) {
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
464
497
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
498
|
+
const rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
|
|
499
|
+
#ifndef NO_JIT_RETURN
|
|
500
|
+
const rb_control_frame_t *top = cfp;
|
|
501
|
+
#endif
|
|
502
|
+
const rb_callable_method_entry_t *cme;
|
|
468
503
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
504
|
+
// `vm_backtrace.c` includes this check in several methods. This happens on newly-created threads, and may
|
|
505
|
+
// also (not entirely sure) happen on dead threads
|
|
506
|
+
if (end_cfp == NULL) {
|
|
507
|
+
return PLACEHOLDER_STACK_IN_NATIVE_CODE;
|
|
508
|
+
}
|
|
474
509
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
510
|
+
// Fix: Skip dummy frame that shows up in main thread.
|
|
511
|
+
//
|
|
512
|
+
// According to a comment in `backtrace_each` (`vm_backtrace.c`), there's two dummy frames that we should ignore
|
|
513
|
+
// at the base of every thread's stack.
|
|
514
|
+
// (see https://github.com/ruby/ruby/blob/4bd38e8120f2fdfdd47a34211720e048502377f1/vm_backtrace.c#L890-L914 )
|
|
515
|
+
//
|
|
516
|
+
// One is being pointed to by `RUBY_VM_END_CONTROL_FRAME(ec)`, and so we need to advance to the next one, and
|
|
517
|
+
// reaching it will be used as a condition to break out of the loop below.
|
|
518
|
+
//
|
|
519
|
+
// Note that in `backtrace_each` there's two calls to `RUBY_VM_NEXT_CONTROL_FRAME`, but the loop bounds there
|
|
520
|
+
// are computed in a different way, so the two calls really are equivalent to one here.
|
|
521
|
+
end_cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
|
|
478
522
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
523
|
+
// 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)
|
|
524
|
+
if (end_cfp <= cfp) {
|
|
525
|
+
return PLACEHOLDER_STACK_IN_NATIVE_CODE;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// This is the position just after the top of the stack -- e.g. where a new frame pushed on the stack would end up.
|
|
529
|
+
const rb_control_frame_t *top_sentinel = RUBY_VM_NEXT_CONTROL_FRAME(cfp);
|
|
530
|
+
|
|
531
|
+
// We iterate the stack from bottom (beginning of thread) to the top (currently-active frame). This is different
|
|
532
|
+
// from upstream rb_profile_frames, but actually matches what `backtrace_each` does (yes, different Ruby VM APIs
|
|
533
|
+
// iterate in different directions).
|
|
534
|
+
// We do this to better take advantage of the `same_frame` caching mechanism: By starting from the bottom of the
|
|
535
|
+
// stack towards the top, we can usually keep most of the stack intact when the code is only going up and down
|
|
536
|
+
// a few methods at the top. Before this change, the cache was really only useful if between samples the app had
|
|
537
|
+
// not moved from the current stack, as adding or removing one frame would invalidate the existing cache (because
|
|
538
|
+
// every position would shift).
|
|
539
|
+
cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
|
|
540
|
+
|
|
541
|
+
for (i=0; i<limit && cfp != top_sentinel; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
|
|
542
|
+
if (cfp->iseq && !cfp->pc) {
|
|
543
|
+
// Fix: Do nothing -- this frame should not be used
|
|
544
|
+
//
|
|
545
|
+
// rb_profile_frames does not do this check, but `backtrace_each` (`vm_backtrace.c`) does. This frame is not
|
|
546
|
+
// exposed by the Ruby backtrace APIs and for now we want to match its behavior 1:1
|
|
547
|
+
} else if (cfp->ep == NULL) {
|
|
548
|
+
// Do nothing -- this frame should not be used
|
|
549
|
+
//
|
|
550
|
+
// We're not sure this can ever happen, but we've seen a crash inside `VM_FRAME_RUBYFRAME_P` below (which
|
|
551
|
+
// dereferences `cfp->ep`), so "just in case" we're adding this extra sanity check to avoid crashing on a
|
|
552
|
+
// NULL `ep`.
|
|
553
|
+
} else if (VM_FRAME_RUBYFRAME_P(cfp)) {
|
|
554
|
+
if (start > 0) {
|
|
555
|
+
start--;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
cme = safe_vm_frame_method_entry(cfp);
|
|
560
|
+
|
|
561
|
+
// Upstream (Ruby 4.0) does:
|
|
562
|
+
// if (cme && cme->def->type == VM_METHOD_TYPE_ISEQ) {
|
|
563
|
+
// buff[i] = (VALUE)cme;
|
|
564
|
+
// } else {
|
|
565
|
+
// buff[i] = (VALUE)cfp->iseq;
|
|
566
|
+
// }
|
|
567
|
+
// We get both the iseq and CME because we need both to format like Ruby backtraces
|
|
568
|
+
|
|
569
|
+
stack_buffer[i].same_frame =
|
|
570
|
+
stack_buffer[i].is_ruby_frame &&
|
|
571
|
+
stack_buffer[i].as.ruby_frame.iseq == cfp->iseq &&
|
|
572
|
+
stack_buffer[i].as.ruby_frame.caching_pc == cfp->pc &&
|
|
573
|
+
stack_buffer[i].cme == cme;
|
|
574
|
+
|
|
575
|
+
if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
|
|
576
|
+
i++;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
stack_buffer[i].as.ruby_frame.iseq = cfp->iseq;
|
|
581
|
+
stack_buffer[i].as.ruby_frame.caching_pc = (void *) cfp->pc;
|
|
582
|
+
stack_buffer[i].cme = cme;
|
|
583
|
+
|
|
584
|
+
// The topmost frame may not have an updated PC because the JIT
|
|
585
|
+
// may not have set one. The JIT compiler will update the PC
|
|
586
|
+
// before entering a new function (so that `caller` will work),
|
|
587
|
+
// so only the topmost frame could possibly have an out of date PC
|
|
588
|
+
#ifndef NO_JIT_RETURN
|
|
589
|
+
if (cfp == top && cfp->jit_return) {
|
|
590
|
+
stack_buffer[i].as.ruby_frame.line = 0;
|
|
591
|
+
} else {
|
|
592
|
+
stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
|
|
521
593
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
// buff[i] = (VALUE)cfp->iseq;
|
|
535
|
-
// }
|
|
536
|
-
// We get both the iseq and CME because we need both to format like Ruby backtraces
|
|
537
|
-
|
|
538
|
-
stack_buffer[i].same_frame =
|
|
539
|
-
stack_buffer[i].is_ruby_frame &&
|
|
540
|
-
stack_buffer[i].as.ruby_frame.iseq == cfp->iseq &&
|
|
541
|
-
stack_buffer[i].as.ruby_frame.caching_pc == cfp->pc &&
|
|
542
|
-
stack_buffer[i].cme == cme;
|
|
543
|
-
|
|
544
|
-
if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
|
|
545
|
-
i++;
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
stack_buffer[i].as.ruby_frame.iseq = cfp->iseq;
|
|
550
|
-
stack_buffer[i].as.ruby_frame.caching_pc = (void *) cfp->pc;
|
|
551
|
-
stack_buffer[i].cme = cme;
|
|
552
|
-
|
|
553
|
-
// The topmost frame may not have an updated PC because the JIT
|
|
554
|
-
// may not have set one. The JIT compiler will update the PC
|
|
555
|
-
// before entering a new function (so that `caller` will work),
|
|
556
|
-
// so only the topmost frame could possibly have an out of date PC
|
|
557
|
-
#ifndef NO_JIT_RETURN
|
|
558
|
-
if (cfp == top && cfp->jit_return) {
|
|
559
|
-
stack_buffer[i].as.ruby_frame.line = 0;
|
|
560
|
-
} else {
|
|
561
|
-
stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
|
|
562
|
-
}
|
|
563
|
-
#else // Ruby < 3.1
|
|
564
|
-
stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
|
|
565
|
-
#endif
|
|
566
|
-
|
|
567
|
-
stack_buffer[i].is_ruby_frame = true;
|
|
568
|
-
i++;
|
|
594
|
+
#else // Ruby < 3.1
|
|
595
|
+
stack_buffer[i].as.ruby_frame.line = calc_lineno(cfp->iseq, cfp->pc);
|
|
596
|
+
#endif
|
|
597
|
+
|
|
598
|
+
stack_buffer[i].is_ruby_frame = true;
|
|
599
|
+
i++;
|
|
600
|
+
} else {
|
|
601
|
+
cme = get_cfunc_method_entry(cfp);
|
|
602
|
+
if (cme && cme->def->type == VM_METHOD_TYPE_CFUNC) {
|
|
603
|
+
if (start > 0) {
|
|
604
|
+
start--;
|
|
605
|
+
continue;
|
|
569
606
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
stack_buffer[i].same_frame =
|
|
579
|
-
!stack_buffer[i].is_ruby_frame &&
|
|
580
|
-
stack_buffer[i].cme == cme;
|
|
581
|
-
|
|
582
|
-
if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
|
|
583
|
-
i++;
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
stack_buffer[i].cme = cme;
|
|
588
|
-
stack_buffer[i].is_ruby_frame = false;
|
|
589
|
-
i++;
|
|
590
|
-
}
|
|
607
|
+
|
|
608
|
+
stack_buffer[i].same_frame =
|
|
609
|
+
!stack_buffer[i].is_ruby_frame &&
|
|
610
|
+
stack_buffer[i].cme == cme;
|
|
611
|
+
|
|
612
|
+
if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
|
|
613
|
+
i++;
|
|
614
|
+
continue;
|
|
591
615
|
}
|
|
616
|
+
|
|
617
|
+
stack_buffer[i].cme = cme;
|
|
618
|
+
stack_buffer[i].is_ruby_frame = false;
|
|
619
|
+
i++;
|
|
620
|
+
}
|
|
592
621
|
}
|
|
622
|
+
}
|
|
593
623
|
|
|
594
|
-
|
|
624
|
+
return i;
|
|
595
625
|
}
|
|
596
626
|
|
|
597
|
-
// Support code for older Rubies that cannot use the MJIT header
|
|
598
|
-
#ifndef RUBY_MJIT_HEADER
|
|
599
|
-
|
|
600
|
-
#define MJIT_STATIC // No-op on older Rubies
|
|
601
|
-
|
|
602
627
|
// Taken from upstream include/ruby/backward/2/bool.h at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
|
|
603
628
|
// Copyright (C) Ruby developers <ruby-core@ruby-lang.org>
|
|
604
629
|
// to support our custom rb_profile_frames (see above)
|
|
605
630
|
// Modifications: None
|
|
606
631
|
#ifndef FALSE
|
|
607
|
-
#
|
|
632
|
+
#define FALSE false
|
|
608
633
|
#elif FALSE
|
|
609
|
-
#
|
|
634
|
+
#error FALSE must be false
|
|
610
635
|
#endif
|
|
611
636
|
|
|
612
637
|
#ifndef TRUE
|
|
613
|
-
#
|
|
638
|
+
#define TRUE true
|
|
614
639
|
#elif ! TRUE
|
|
615
|
-
#
|
|
640
|
+
#error TRUE must be true
|
|
616
641
|
#endif
|
|
617
642
|
|
|
618
643
|
// Taken from upstream vm_insnhelper.c at commit 5f10bd634fb6ae8f74a4ea730176233b0ca96954 (March 2022, Ruby 3.2 trunk)
|
|
@@ -640,30 +665,37 @@ check_method_entry(VALUE obj, int can_be_svar) {
|
|
|
640
665
|
return NULL;
|
|
641
666
|
}
|
|
642
667
|
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
//
|
|
668
|
+
// Identical to upstream rb_vm_frame_method_entry (vm_insnhelper.c) with two additions:
|
|
669
|
+
// 1. FIXNUM_P check on ep[FLAGS] before each iteration to detect torn EPs
|
|
670
|
+
// 2. NULL check on ep after VM_ENV_PREV_EP
|
|
646
671
|
//
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
ep = VM_ENV_PREV_EP(ep);
|
|
672
|
+
// When the profiler's signal handler interrupts vm_make_env_each (vm.c) mid-escape,
|
|
673
|
+
// a child frame's SPECVAL can still point to the parent's old stack EP whose flags
|
|
674
|
+
// slot has been overwritten with (VALUE)env for GC marking.
|
|
675
|
+
static const rb_callable_method_entry_t *
|
|
676
|
+
safe_vm_frame_method_entry(const rb_control_frame_t *cfp) {
|
|
677
|
+
const VALUE *ep = cfp->ep;
|
|
678
|
+
rb_callable_method_entry_t *me;
|
|
679
|
+
|
|
680
|
+
// Torn-EP check before VM_ENV_LOCAL_P, check_method_entry, and VM_ENV_PREV_EP
|
|
681
|
+
// dereference ep
|
|
682
|
+
while (FIXNUM_P(ep[VM_ENV_DATA_INDEX_FLAGS]) && !VM_ENV_LOCAL_P(ep)) {
|
|
683
|
+
if ((me = check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], FALSE)) != NULL) {
|
|
684
|
+
return me;
|
|
662
685
|
}
|
|
686
|
+
ep = VM_ENV_PREV_EP(ep);
|
|
687
|
+
if (ep == NULL) {
|
|
688
|
+
return NULL;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// If we exited because of a torn EP (failed FIXNUM_P), bail out
|
|
693
|
+
if (!FIXNUM_P(ep[VM_ENV_DATA_INDEX_FLAGS])) {
|
|
694
|
+
return NULL;
|
|
695
|
+
}
|
|
663
696
|
|
|
664
|
-
|
|
697
|
+
return check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
|
|
665
698
|
}
|
|
666
|
-
#endif // RUBY_MJIT_HEADER
|
|
667
699
|
|
|
668
700
|
// Optimized version of rb_vm_frame_method_entry() for cfunc frames.
|
|
669
701
|
// Cfunc frames always have VM_ENV_FLAG_LOCAL set, so ep[-2] is the cme directly
|
|
@@ -676,43 +708,41 @@ get_cfunc_method_entry(const rb_control_frame_t *cfp) {
|
|
|
676
708
|
}
|
|
677
709
|
|
|
678
710
|
#ifndef NO_RACTORS
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
711
|
+
// This API and definition are exported as a public symbol by the VM BUT the function header is not defined in any public header, so we
|
|
712
|
+
// repeat it here to be able to use in our code.
|
|
713
|
+
#ifndef USE_RACTOR_INTERNAL_APIS_DIRECTLY
|
|
714
|
+
// Disable fast path for detecting multiple Ractors. Unfortunately this symbol is no longer visible on modern Ruby
|
|
715
|
+
// versions, so we need to do a bit more work.
|
|
716
|
+
struct rb_ractor_struct *ruby_single_main_ractor = NULL;
|
|
717
|
+
|
|
718
|
+
// Alternative implementation of rb_ractor_main_p_ that avoids relying on non-public symbols
|
|
719
|
+
bool rb_ractor_main_p_(void) {
|
|
720
|
+
// We need to get the main ractor in a bit of a roundabout way, since Ruby >= 3.3 hid `GET_VM()`
|
|
721
|
+
return ddtrace_get_ractor() == thread_struct_from_object(rb_thread_current())->vm->ractor.main_ractor;
|
|
722
|
+
}
|
|
723
|
+
#else
|
|
724
|
+
// Directly access Ruby internal fast path for detecting multiple Ractors.
|
|
725
|
+
extern struct rb_ractor_struct *ruby_single_main_ractor;
|
|
694
726
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
727
|
+
// Ruby 3.0 to 3.2 directly expose this symbol, we just need to tell the compiler it exists.
|
|
728
|
+
bool rb_ractor_main_p_(void);
|
|
729
|
+
#endif
|
|
698
730
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
{
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
else {
|
|
709
|
-
return rb_ractor_main_p_();
|
|
710
|
-
}
|
|
731
|
+
// Taken from upstream ractor_core.h at commit d9cf0388599a3234b9f3c06ddd006cd59a58ab8b (November 2022, Ruby 3.2 trunk)
|
|
732
|
+
// to allow us to ensure that we're always operating on the main ractor (if Ruby has ractors)
|
|
733
|
+
// Modifications:
|
|
734
|
+
// * None
|
|
735
|
+
bool ddtrace_rb_ractor_main_p(void) {
|
|
736
|
+
if (ruby_single_main_ractor) {
|
|
737
|
+
return true;
|
|
738
|
+
} else {
|
|
739
|
+
return rb_ractor_main_p_();
|
|
711
740
|
}
|
|
741
|
+
}
|
|
712
742
|
#else
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
743
|
+
// Simplify callers on older Rubies, instead of having them probe if the VM supports Ractors we just tell them that yes
|
|
744
|
+
// they're always on the main Ractor
|
|
745
|
+
bool ddtrace_rb_ractor_main_p(void) { return true; }
|
|
716
746
|
#endif // NO_RACTORS
|
|
717
747
|
|
|
718
748
|
// This is a tweaked and inlined version of
|
|
@@ -724,17 +754,23 @@ static const rb_iseq_t *maybe_thread_invoke_proc_iseq(VALUE thread_value) {
|
|
|
724
754
|
rb_thread_t *thread = thread_struct_from_object(thread_value);
|
|
725
755
|
|
|
726
756
|
#ifndef NO_THREAD_INVOKE_ARG // Ruby 2.6+
|
|
727
|
-
if (thread->invoke_type != thread_invoke_type_proc)
|
|
757
|
+
if (thread->invoke_type != thread_invoke_type_proc) {
|
|
758
|
+
return NULL;
|
|
759
|
+
}
|
|
728
760
|
|
|
729
761
|
VALUE proc = thread->invoke_arg.proc.proc;
|
|
730
762
|
#else
|
|
731
|
-
if (thread->first_func || !thread->first_proc)
|
|
763
|
+
if (thread->first_func || !thread->first_proc) {
|
|
764
|
+
return NULL;
|
|
765
|
+
}
|
|
732
766
|
|
|
733
767
|
VALUE proc = thread->first_proc;
|
|
734
768
|
#endif
|
|
735
769
|
|
|
736
770
|
const rb_iseq_t *iseq = rb_proc_get_iseq(proc, 0);
|
|
737
|
-
if (iseq == NULL)
|
|
771
|
+
if (iseq == NULL) {
|
|
772
|
+
return NULL;
|
|
773
|
+
}
|
|
738
774
|
|
|
739
775
|
rb_iseq_check(iseq);
|
|
740
776
|
return iseq;
|
|
@@ -743,7 +779,9 @@ static const rb_iseq_t *maybe_thread_invoke_proc_iseq(VALUE thread_value) {
|
|
|
743
779
|
VALUE invoke_location_for(VALUE thread, int *line_location) {
|
|
744
780
|
const rb_iseq_t *iseq = maybe_thread_invoke_proc_iseq(thread);
|
|
745
781
|
|
|
746
|
-
if (iseq == NULL)
|
|
782
|
+
if (iseq == NULL) {
|
|
783
|
+
return Qnil;
|
|
784
|
+
}
|
|
747
785
|
|
|
748
786
|
*line_location = NUM2INT(rb_iseq_first_lineno(iseq));
|
|
749
787
|
return ddtrace_iseq_path(iseq);
|
|
@@ -777,102 +815,104 @@ static inline int ddtrace_imemo_type(VALUE imemo) {
|
|
|
777
815
|
// Safety: This function assumes the object passed in is of the imemo type. But in the worst case, you'll just get
|
|
778
816
|
// a string that doesn't make any sense.
|
|
779
817
|
#ifndef NO_IMEMO_NAME
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
818
|
+
const char *imemo_kind(VALUE imemo) {
|
|
819
|
+
return rb_imemo_name(ddtrace_imemo_type(imemo));
|
|
820
|
+
}
|
|
783
821
|
#else
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
822
|
+
const char *imemo_kind(__attribute__((unused)) VALUE imemo) {
|
|
823
|
+
return NULL;
|
|
824
|
+
}
|
|
787
825
|
#endif
|
|
788
826
|
|
|
789
827
|
// This is used to workaround a VM bug. See "handle_sampling_signal" in "collectors_cpu_and_wall_time_worker" for details.
|
|
790
828
|
#ifdef NO_POSTPONED_TRIGGER
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
829
|
+
void *objspace_ptr_for_gc_finalize_deferred_workaround(void) {
|
|
830
|
+
return GET_VM()->objspace;
|
|
831
|
+
}
|
|
794
832
|
#endif
|
|
795
833
|
|
|
796
834
|
#ifndef HAVE_RUBY_THREAD_STORAGE_API
|
|
797
|
-
|
|
835
|
+
#include "gvl_profiling_helper.h"
|
|
798
836
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
837
|
+
// Hack: In Ruby 3.3+ we attach gvl profiling state to Ruby threads using the
|
|
838
|
+
// rb_internal_thread_specific_* APIs. These APIs did not exist on Ruby <= 3.2. On Ruby <= 3.2 we instead store the
|
|
839
|
+
// needed data inside the `rb_thread_t` structure, specifically in `stat_insn_usage` as a Ruby FIXNUM.
|
|
840
|
+
//
|
|
841
|
+
// Why `stat_insn_usage`? We needed some per-thread storage, and while looking at the Ruby VM sources I noticed
|
|
842
|
+
// that `stat_insn_usage` has been in `rb_thread_t` for a long time, but is not used anywhere in the VM
|
|
843
|
+
// code. There's a comment attached to it "/* statistics data for profiler */" but other than marking this
|
|
844
|
+
// field for GC, I could not find any place in the VM commit history or on GitHub where this has ever been used.
|
|
845
|
+
//
|
|
846
|
+
// Thus, since this hack is only for Ruby <= 3.2, which presumably will never see this field either removed or used
|
|
847
|
+
// we... kinda take it for our own usage. It's ugly, I know...
|
|
848
|
+
//
|
|
849
|
+
// 64-bit pointers actually use 48-bit virtual addresses (https://muxup.com/2023q4/storing-data-in-pointers),
|
|
850
|
+
// so we are sure the addresses fit in Fixnums.
|
|
851
|
+
per_thread_context *get_per_thread_context(VALUE thread) {
|
|
852
|
+
VALUE current_value = thread_struct_from_object(thread)->stat_insn_usage;
|
|
853
|
+
return RB_FIXNUM_P(current_value) ? (per_thread_context *) FIX2LONG(current_value) : NULL;
|
|
854
|
+
}
|
|
817
855
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
thread_struct_from_object(thread)->stat_insn_usage = value ? LONG2FIX((intptr_t) value) : Qfalse;
|
|
856
|
+
void set_per_thread_context(VALUE thread, per_thread_context *value) {
|
|
857
|
+
if (!RB_FIXABLE((intptr_t) value)) {
|
|
858
|
+
rb_bug("per_thread_context pointer does not fit in a Fixnum: %p", value);
|
|
823
859
|
}
|
|
860
|
+
thread_struct_from_object(thread)->stat_insn_usage = value ? LONG2FIX((intptr_t) value) : Qfalse;
|
|
861
|
+
}
|
|
824
862
|
#endif
|
|
825
863
|
|
|
826
864
|
// Is the VM smack in the middle of raising an exception?
|
|
827
865
|
bool is_raised_flag_set(VALUE thread) { return thread_struct_from_object(thread)->ec->raised_flag > 0; }
|
|
828
866
|
|
|
829
867
|
#ifndef NO_CURRENT_FIBER_FOR
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
868
|
+
// The following three declarations are all
|
|
869
|
+
// taken from upstream cont.c at commit d97884a58be32e829fd03a80cd521f4733d65c79 (February 2025, master branch)
|
|
870
|
+
// (See the Ruby project copyright and license above)
|
|
871
|
+
// to enable building `current_fiber_for`.
|
|
872
|
+
//
|
|
873
|
+
// We needed to copy them because they aren't otherwise exposed in any VM APIs or headers.
|
|
874
|
+
// @ivoanjo: I manually checked the Ruby 3.1, 3.2, 3.3 and 3.4 branches + master, and the parts we care about in these
|
|
875
|
+
// structures have not changed in many years (in fact, last change I spotted was for 2.7).
|
|
876
|
+
enum context_type {
|
|
877
|
+
CONTINUATION_CONTEXT = 0,
|
|
878
|
+
FIBER_CONTEXT = 1
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
typedef struct rb_context_struct { // This declaration is incomplete -- only contains up to `self` which is the part we care about
|
|
882
|
+
enum context_type type;
|
|
883
|
+
int argc;
|
|
884
|
+
int kw_splat;
|
|
885
|
+
VALUE self;
|
|
886
|
+
} rb_context_t;
|
|
887
|
+
|
|
888
|
+
struct rb_fiber_struct { // This declaration is incomplete -- only contains the first entry which is the part we care about
|
|
889
|
+
rb_context_t cont;
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
VALUE current_fiber_for(VALUE thread) {
|
|
893
|
+
VALUE self = thread_struct_from_object(thread)->ec->fiber_ptr->cont.self;
|
|
894
|
+
return self == 0 ? Qnil : self;
|
|
895
|
+
}
|
|
858
896
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
897
|
+
void self_test_current_fiber_for(void) {
|
|
898
|
+
VALUE expected_current_fiber = current_fiber_for(rb_thread_current());
|
|
899
|
+
VALUE actual_current_fiber = rb_fiber_current();
|
|
862
900
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
901
|
+
if (expected_current_fiber == Qnil) {
|
|
902
|
+
// On purpose above we tried reading before calling `rb_fiber_current()` so the fiber may have not existed yet.
|
|
903
|
+
// But now it should be there.
|
|
904
|
+
expected_current_fiber = current_fiber_for(rb_thread_current());
|
|
905
|
+
}
|
|
868
906
|
|
|
869
|
-
|
|
907
|
+
if (expected_current_fiber != actual_current_fiber) {
|
|
908
|
+
rb_raise(rb_eRuntimeError, "current_fiber_for() self-test failed");
|
|
870
909
|
}
|
|
910
|
+
}
|
|
871
911
|
#else
|
|
872
|
-
|
|
912
|
+
NORETURN(VALUE current_fiber_for(DDTRACE_UNUSED VALUE thread));
|
|
873
913
|
|
|
874
|
-
|
|
875
|
-
|
|
914
|
+
VALUE current_fiber_for(DDTRACE_UNUSED VALUE thread) { rb_raise(rb_eRuntimeError, "Not implemented for Ruby < 3.1"); }
|
|
915
|
+
void self_test_current_fiber_for(void) { } // Nothing to do
|
|
876
916
|
#endif
|
|
877
917
|
|
|
878
918
|
// Variant of functions related to Thread::Backtrace::Location#label in Ruby 4.0
|
|
@@ -883,7 +923,9 @@ bool is_raised_flag_set(VALUE thread) { return thread_struct_from_object(thread)
|
|
|
883
923
|
|
|
884
924
|
// Return true if a given location is a C method or supposed to behave like one.
|
|
885
925
|
static bool location_cfunc_p(const rb_callable_method_entry_t *cme) {
|
|
886
|
-
if (!cme)
|
|
926
|
+
if (!cme) {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
887
929
|
|
|
888
930
|
switch (cme->def->type) {
|
|
889
931
|
case VM_METHOD_TYPE_CFUNC:
|
|
@@ -920,12 +962,20 @@ static bool is_metaclass(VALUE mod, VALUE* attached) {
|
|
|
920
962
|
return false;
|
|
921
963
|
}
|
|
922
964
|
|
|
923
|
-
|
|
965
|
+
VALUE ddtrace_alloc_free_rb_mod_name(VALUE mod) {
|
|
924
966
|
#ifdef NO_ALLOC_FREE_MOD_NAME
|
|
925
|
-
|
|
967
|
+
VALUE name = rb_attr_get(mod, rb_intern("__classpath__"));
|
|
926
968
|
#else
|
|
927
|
-
|
|
969
|
+
VALUE name = rb_mod_name(mod);
|
|
928
970
|
#endif
|
|
971
|
+
// While Module#const_set rejects empty strings,
|
|
972
|
+
// an empty String is possible if `rb_const_set(mod, "", val)` was used
|
|
973
|
+
// but that's not understandable so consider those anonymous too.
|
|
974
|
+
if (name == Qnil || RSTRING_LEN(name) == 0) {
|
|
975
|
+
return Qnil;
|
|
976
|
+
} else {
|
|
977
|
+
return name;
|
|
978
|
+
}
|
|
929
979
|
}
|
|
930
980
|
|
|
931
981
|
// Ruby 3.3+ has a `permanent_classpath` flag on rb_classext_struct.
|
|
@@ -941,8 +991,19 @@ static bool has_permanent_classpath(DDTRACE_UNUSED VALUE mod, DDTRACE_UNUSED VAL
|
|
|
941
991
|
#endif
|
|
942
992
|
}
|
|
943
993
|
|
|
994
|
+
VALUE ddtrace_permanent_mod_name(VALUE mod) {
|
|
995
|
+
VALUE name = ddtrace_alloc_free_rb_mod_name(mod);
|
|
996
|
+
|
|
997
|
+
if (NIL_P(name) || !has_permanent_classpath(mod, name)) {
|
|
998
|
+
return Qnil;
|
|
999
|
+
} else {
|
|
1000
|
+
return name;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
944
1004
|
#define ONLY_METHOD_NAME ((ssize_t) -1)
|
|
945
1005
|
#define BUFFER_OUT_OF_SPACE ((ssize_t) -2)
|
|
1006
|
+
#define NO_METHOD_NAME ((ssize_t) -3)
|
|
946
1007
|
|
|
947
1008
|
static ssize_t rb_gen_method_name(VALUE owner, VALUE method_name, char *buf, size_t buf_size) {
|
|
948
1009
|
if (!(RB_TYPE_P(owner, T_CLASS) || RB_TYPE_P(owner, T_MODULE))) {
|
|
@@ -954,11 +1015,12 @@ static ssize_t rb_gen_method_name(VALUE owner, VALUE method_name, char *buf, siz
|
|
|
954
1015
|
if (is_metaclass(owner, &mod)) {
|
|
955
1016
|
separator = '.';
|
|
956
1017
|
}
|
|
957
|
-
|
|
1018
|
+
|
|
1019
|
+
VALUE mod_name = ddtrace_permanent_mod_name(mod);
|
|
958
1020
|
|
|
959
1021
|
// Exclude non-permanent names (e.g. `#<Module:0x0123>::Foo`) which break flamegraph aggregation
|
|
960
1022
|
// since they contain addresses that differ across processes/runs.
|
|
961
|
-
if (NIL_P(mod_name)
|
|
1023
|
+
if (NIL_P(mod_name)) {
|
|
962
1024
|
return ONLY_METHOD_NAME;
|
|
963
1025
|
}
|
|
964
1026
|
|
|
@@ -1032,6 +1094,9 @@ static ssize_t calculate_iseq_label(VALUE owner, const rb_iseq_t *iseq, char *bu
|
|
|
1032
1094
|
ssize_t ddtrace_location_label(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, char *buf, size_t buf_size) {
|
|
1033
1095
|
if (location_cfunc_p(cme)) {
|
|
1034
1096
|
VALUE method_name = rb_id2str(cme->def->original_id);
|
|
1097
|
+
if (method_name == Qfalse) {
|
|
1098
|
+
return NO_METHOD_NAME;
|
|
1099
|
+
}
|
|
1035
1100
|
return rb_gen_method_name(cme->owner, method_name, buf, buf_size);
|
|
1036
1101
|
} else {
|
|
1037
1102
|
VALUE owner = cme ? cme->owner : Qnil;
|
|
@@ -1039,6 +1104,7 @@ ssize_t ddtrace_location_label(const rb_callable_method_entry_t *cme, const rb_i
|
|
|
1039
1104
|
}
|
|
1040
1105
|
}
|
|
1041
1106
|
|
|
1107
|
+
// Returns a String or Qfalse (like rb_id2str())
|
|
1042
1108
|
VALUE ddtrace_location_base_label(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq) {
|
|
1043
1109
|
if (location_cfunc_p(cme)) {
|
|
1044
1110
|
return rb_id2str(cme->def->original_id);
|
|
@@ -1047,10 +1113,13 @@ VALUE ddtrace_location_base_label(const rb_callable_method_entry_t *cme, const r
|
|
|
1047
1113
|
}
|
|
1048
1114
|
}
|
|
1049
1115
|
|
|
1116
|
+
// Always returns a String
|
|
1050
1117
|
VALUE ddtrace_iseq_base_label(const rb_iseq_t *iseq) {
|
|
1051
1118
|
return rb_iseq_base_label(iseq);
|
|
1052
1119
|
}
|
|
1053
1120
|
|
|
1121
|
+
// Always returns a String
|
|
1122
|
+
// See https://github.com/ruby/ruby/blob/75aeb225b8558ff908ea78bf608dfbba09bdc2f9/iseq.c#L564-L565
|
|
1054
1123
|
VALUE ddtrace_iseq_path(const rb_iseq_t *iseq) {
|
|
1055
1124
|
return rb_iseq_path(iseq);
|
|
1056
1125
|
}
|
|
@@ -1070,3 +1139,17 @@ void* ddtrace_cme_cfunc_func(const rb_callable_method_entry_t *cme) {
|
|
|
1070
1139
|
const char *ddtrace_cme_original_method_name(const rb_callable_method_entry_t *cme) {
|
|
1071
1140
|
return rb_id2name(cme->def->original_id);
|
|
1072
1141
|
}
|
|
1142
|
+
|
|
1143
|
+
// This function is not present in the VM headers, but is a public symbol that can be invoked.
|
|
1144
|
+
int rb_objspace_internal_object_p(VALUE obj);
|
|
1145
|
+
|
|
1146
|
+
bool ddtrace_is_internal_object_p(VALUE obj) {
|
|
1147
|
+
if (RB_SPECIAL_CONST_P(obj)) {
|
|
1148
|
+
// Ruby special constants are not internal, except Qundef.
|
|
1149
|
+
// See enum ruby_special_consts in CRuby.
|
|
1150
|
+
return obj == Qundef;
|
|
1151
|
+
} else {
|
|
1152
|
+
// rb_objspace_internal_object_p() assumes non-immediate, so check that first above
|
|
1153
|
+
return rb_objspace_internal_object_p(obj);
|
|
1154
|
+
}
|
|
1155
|
+
}
|