datadog 2.43.0 → 2.45.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 +69 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +136 -132
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +146 -109
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
- data/ext/datadog_profiling_native_extension/extconf.rb +6 -2
- data/ext/datadog_profiling_native_extension/heap_recorder.c +2 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +122 -53
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +6 -2
- data/ext/datadog_profiling_native_extension/profiling.c +22 -23
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +3 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.c +10 -28
- data/ext/datadog_profiling_native_extension/stack_recorder.h +0 -1
- data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +13 -14
- data/ext/libdatadog_api/di.c +8 -3
- data/ext/libdatadog_api/extconf.rb +1 -1
- data/ext/libdatadog_api/feature_flags.c +8 -0
- data/ext/libdatadog_api/init.c +3 -1
- data/ext/libdatadog_api/otel_thread_context.c +38 -17
- data/ext/libdatadog_api/trace_exporter.c +77 -11
- data/ext/libdatadog_extconf_helpers.rb +14 -9
- data/lib/datadog/ai_guard/component.rb +17 -8
- data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +13 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
- data/lib/datadog/ai_guard/evaluation.rb +25 -11
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +58 -57
- data/lib/datadog/core/configuration/components.rb +9 -1
- data/lib/datadog/core/configuration/settings.rb +5 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
- data/lib/datadog/core/remote/client/capabilities.rb +10 -4
- data/lib/datadog/core/remote/component.rb +9 -2
- data/lib/datadog/core/remote/worker.rb +8 -0
- data/lib/datadog/di/instrumenter.rb +1 -1
- data/lib/datadog/di/probe_manager.rb +10 -5
- data/lib/datadog/di/proc_responder.rb +1 -1
- data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +57 -1
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +118 -0
- data/lib/datadog/open_feature/native_evaluator.rb +1 -14
- data/lib/datadog/open_feature/remote.rb +5 -5
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +7 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/profiling/component.rb +2 -3
- data/lib/datadog/profiling/exporter.rb +7 -5
- data/lib/datadog/symbol_database/extractor.rb +84 -102
- data/lib/datadog/tracing/component.rb +3 -1
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +9 -2
- data/lib/datadog/tracing/context.rb +24 -2
- data/lib/datadog/tracing/context_provider.rb +9 -5
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +6 -3
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -0
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/otel_thread_context.rb +76 -11
- data/lib/datadog/tracing/tracer.rb +15 -2
- data/lib/datadog/tracing/transport/native.rb +23 -22
- data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
- data/lib/datadog/tracing/transport/traces.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +23 -7
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
|
@@ -13,6 +13,7 @@ __attribute__((warn_unused_result)) bool thread_context_collector_sample(
|
|
|
13
13
|
long current_monotonic_wall_time_ns
|
|
14
14
|
);
|
|
15
15
|
__attribute__((warn_unused_result)) bool thread_context_collector_prepare_sample_inside_signal_handler(void);
|
|
16
|
+
void thread_context_collector_prepare_sample_on_gc_finish(void);
|
|
16
17
|
__attribute__((warn_unused_result)) bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object);
|
|
17
18
|
void thread_context_collector_commit_heap_recordings_may_lose_gvl(VALUE self_instance);
|
|
18
19
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
|
|
@@ -24,10 +25,10 @@ __attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(V
|
|
|
24
25
|
VALUE enforce_thread_context_collector_instance(VALUE object);
|
|
25
26
|
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash);
|
|
26
27
|
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance);
|
|
27
|
-
|
|
28
|
+
VALUE thread_context_prepare_serialize(VALUE self_instance);
|
|
28
29
|
void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance);
|
|
29
30
|
void thread_context_collector_profiler_internal_thread_started(void);
|
|
30
|
-
|
|
31
|
+
VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance);
|
|
31
32
|
|
|
32
33
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
33
34
|
typedef enum {
|
|
@@ -139,6 +139,8 @@ end
|
|
|
139
139
|
|
|
140
140
|
have_func "malloc_stats"
|
|
141
141
|
|
|
142
|
+
$defs << "-DHAVE_ZJIT_FRAME" if RUBY_VERSION >= "4.1"
|
|
143
|
+
|
|
142
144
|
# On older Rubies, there was no primitive mutex and condition variable implemented in `thread_sync.rb` (internal)
|
|
143
145
|
$defs << "-DNO_PRIMITIVE_MUTEX_AND_CONDITION_VARIABLE" if RUBY_VERSION < "4"
|
|
144
146
|
|
|
@@ -260,7 +262,9 @@ dir_config("ruby") # allow user to pass in non-standard core include directory
|
|
|
260
262
|
#
|
|
261
263
|
# To workaround this issue, we override `with_cppflags` for datadog-ruby_core_source to still include `$CPPFLAGS`.
|
|
262
264
|
Datadog::RubyCoreSource.define_singleton_method(:with_cppflags) do |newflags, &block|
|
|
263
|
-
|
|
265
|
+
# Note: The `Shellwords.escape` is an additional workaround for ruby_core_source supplying
|
|
266
|
+
# one unescaped -I argument containing the complete header path, not the CPPFLAGS thing above.
|
|
267
|
+
super("#{Shellwords.escape(newflags)} #{$CPPFLAGS}", &block)
|
|
264
268
|
end
|
|
265
269
|
|
|
266
270
|
Datadog::RubyCoreSource
|
|
@@ -270,7 +274,7 @@ Datadog::RubyCoreSource
|
|
|
270
274
|
have_header("vm_core.h") &&
|
|
271
275
|
have_header("iseq.h", "vm_core.h") &&
|
|
272
276
|
# These are only used on Ruby 3+
|
|
273
|
-
(RUBY_VERSION < "3" || have_header("ractor_core.h") && have_header("internal/class.h"))
|
|
277
|
+
(RUBY_VERSION < "3" || have_header("ractor_core.h") && have_header("internal/class.h", "vm_core.h"))
|
|
274
278
|
|
|
275
279
|
if headers_available
|
|
276
280
|
# Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
|
|
@@ -627,7 +627,7 @@ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_
|
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
size_t current_gc_gen = rb_gc_count();
|
|
630
|
-
long now_ns = monotonic_wall_time_now_ns(
|
|
630
|
+
long now_ns = monotonic_wall_time_now_ns();
|
|
631
631
|
|
|
632
632
|
if (!full_update) {
|
|
633
633
|
if (current_gc_gen == heap_recorder->update_gen) {
|
|
@@ -641,7 +641,7 @@ static VALUE heap_recorder_update_locked(VALUE heap_recorder_update_locked_args_
|
|
|
641
641
|
return Qnil;
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
-
if (
|
|
644
|
+
if ((now_ns - heap_recorder->last_update_ns) < MIN_TIME_BETWEEN_HEAP_RECORDER_UPDATES_NS) {
|
|
645
645
|
// We did an update not too long ago. Let's skip this one to avoid over-taxing the system.
|
|
646
646
|
heap_recorder->stats_lifetime.updates_skipped_time++;
|
|
647
647
|
return Qnil;
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
#pragma GCC diagnostic push
|
|
26
26
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
27
27
|
#include <iseq.h>
|
|
28
|
+
#ifdef HAVE_ZJIT_FRAME
|
|
29
|
+
#include <zjit.h>
|
|
30
|
+
#endif
|
|
28
31
|
#pragma GCC diagnostic pop
|
|
29
32
|
|
|
30
33
|
#ifndef NO_INTERNAL_CLASS_HEADER_INCLUDE
|
|
@@ -53,23 +56,45 @@
|
|
|
53
56
|
#define PRIVATE_VM_API_ACCESS_SKIP_RUBY_INCLUDES
|
|
54
57
|
#include "private_vm_api_access.h"
|
|
55
58
|
|
|
59
|
+
#ifdef HAVE_RUBY_RACTOR_H
|
|
60
|
+
typedef rb_ractor_t gvl_owner_context_t;
|
|
61
|
+
#else
|
|
62
|
+
typedef rb_vm_t gvl_owner_context_t;
|
|
63
|
+
#endif
|
|
64
|
+
|
|
56
65
|
static inline const rb_callable_method_entry_t* get_cfunc_method_entry(const rb_control_frame_t *cfp);
|
|
57
66
|
static const rb_callable_method_entry_t* safe_vm_frame_method_entry(const rb_control_frame_t *cfp);
|
|
67
|
+
static gvl_owner_context_t *get_gvl_owner_context(void);
|
|
68
|
+
|
|
69
|
+
typedef struct {
|
|
70
|
+
gvl_owner_context_t *main_ractor_gvl_owner_context;
|
|
71
|
+
const rb_data_type_t *thread_data_type;
|
|
72
|
+
} private_vm_api_global_t;
|
|
73
|
+
|
|
74
|
+
// Initialized once on the main ractor with the GVL held, before sampling starts; never rewritten.
|
|
75
|
+
// The native context stays at the same address across GC compaction and forks from the main ractor.
|
|
76
|
+
static private_vm_api_global_t private_vm_api_global;
|
|
77
|
+
|
|
78
|
+
void private_vm_api_access_init(void) {
|
|
79
|
+
private_vm_api_global.thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
|
|
80
|
+
private_vm_api_global.main_ractor_gvl_owner_context = get_gvl_owner_context();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void private_vm_api_access_self_test(void) {
|
|
84
|
+
if (private_vm_api_global.main_ractor_gvl_owner_context != get_gvl_owner_context()) {
|
|
85
|
+
rb_raise(rb_eRuntimeError, "BUG: main_ractor_gvl_owner_context changed unexpectedly");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
58
88
|
|
|
59
89
|
// MRI has a similar rb_thread_ptr() function which we can't call it directly
|
|
60
90
|
// because Ruby does not expose the thread_data_type publicly.
|
|
61
|
-
// Instead, we have our own version of that function, and we
|
|
91
|
+
// Instead, we have our own version of that function, and we initialize the thread_data_type pointer
|
|
62
92
|
// from a known-correct object: the current thread.
|
|
63
93
|
//
|
|
64
94
|
// Note that beyond returning the rb_thread_struct*, rb_check_typeddata() raises an exception
|
|
65
95
|
// if the argument passed in is not actually a `Thread` instance.
|
|
66
96
|
static inline rb_thread_t *thread_struct_from_object(VALUE thread) {
|
|
67
|
-
|
|
68
|
-
if (UNLIKELY(thread_data_type == NULL)) {
|
|
69
|
-
thread_data_type = RTYPEDDATA_TYPE(rb_thread_current());
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return (rb_thread_t *) rb_check_typeddata(thread, thread_data_type);
|
|
97
|
+
return (rb_thread_t *) rb_check_typeddata(thread, private_vm_api_global.thread_data_type);
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
rb_nativethread_id_t pthread_id_for(VALUE thread) {
|
|
@@ -86,19 +111,16 @@ rb_nativethread_id_t pthread_id_for(VALUE thread) {
|
|
|
86
111
|
#endif
|
|
87
112
|
}
|
|
88
113
|
|
|
89
|
-
//
|
|
114
|
+
// Combines checking for main ractor and GVL in one check that can be done safely from a signal handler, even
|
|
115
|
+
// if the thread is not a Ruby thread or if we're in the middle of GC.
|
|
90
116
|
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
// will have the GVL which is probably true for the situations that API was designed to be called from BUT this assumption
|
|
95
|
-
// does not hold true when calling `ruby_thread_has_gvl_p` from a signal handler. (Because the thread may have lost the
|
|
96
|
-
// GVL due to a scheduler decision, not because it decided to block.)
|
|
97
|
-
// I have also submitted https://bugs.ruby-lang.org/issues/19172 to discuss this with upstream Ruby developers.
|
|
117
|
+
// Note that Ruby has `ruby_thread_has_gvl_p`, but unfortunately it can return true in a signal
|
|
118
|
+
// handler even when the thread doesn't hold the GVL. It assumes that a thread outside a "blocking region" holds
|
|
119
|
+
// the GVL, but the scheduler can also take it away (https://bugs.ruby-lang.org/issues/19172).
|
|
98
120
|
//
|
|
99
|
-
//
|
|
100
|
-
bool
|
|
101
|
-
current_gvl_owner owner =
|
|
121
|
+
// This must not touch Ruby objects, since GC compaction may have protected their heap pages.
|
|
122
|
+
bool is_current_thread_in_main_ractor_and_holding_the_gvl(void) {
|
|
123
|
+
current_gvl_owner owner = main_ractor_gvl_owner();
|
|
102
124
|
return owner.valid && pthread_equal(pthread_self(), owner.owner);
|
|
103
125
|
}
|
|
104
126
|
|
|
@@ -112,23 +134,37 @@ static inline rb_ractor_t *ddtrace_get_ractor(void) {
|
|
|
112
134
|
}
|
|
113
135
|
#endif
|
|
114
136
|
|
|
137
|
+
static gvl_owner_context_t *get_gvl_owner_context(void) {
|
|
138
|
+
if (!ddtrace_rb_ractor_main_p()) {
|
|
139
|
+
rb_raise(rb_eRuntimeError, "BUG: get_gvl_owner_context must be called from the main ractor");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#ifdef HAVE_RUBY_RACTOR_H
|
|
143
|
+
return ddtrace_get_ractor();
|
|
144
|
+
#else
|
|
145
|
+
return GET_VM();
|
|
146
|
+
#endif
|
|
147
|
+
}
|
|
148
|
+
|
|
115
149
|
#ifndef NO_GVL_OWNER // Ruby < 2.6 doesn't have the owner/running field
|
|
116
150
|
// NOTE: Reading the owner in this is a racy read, because we're not grabbing the lock that Ruby uses to protect it.
|
|
117
151
|
//
|
|
118
152
|
// 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 `
|
|
153
|
+
// * In the case where a thread owns the GVL and calls `main_ractor_gvl_owner`, it will always see the correct value. That's
|
|
120
154
|
// because every thread sets itself as the owner when it grabs the GVL and unsets itself at the end.
|
|
121
|
-
// That means that `
|
|
155
|
+
// That means that `is_current_thread_in_main_ractor_and_holding_the_gvl` is always accurate.
|
|
122
156
|
// * In a case where we observe a different thread, then this may change by the time we do something with this value
|
|
123
157
|
// anyway. So unless we want to prevent the Ruby scheduler from switching threads, we need to deal with races here.
|
|
124
|
-
current_gvl_owner
|
|
158
|
+
current_gvl_owner main_ractor_gvl_owner(void) {
|
|
159
|
+
if (private_vm_api_global.main_ractor_gvl_owner_context == NULL) return (current_gvl_owner) {.valid = false};
|
|
160
|
+
|
|
125
161
|
const rb_thread_t *current_owner =
|
|
126
162
|
#ifndef NO_RB_THREAD_SCHED // Introduced in Ruby 3.2 as a replacement for struct rb_global_vm_lock_struct
|
|
127
|
-
|
|
163
|
+
private_vm_api_global.main_ractor_gvl_owner_context->threads.sched.running;
|
|
128
164
|
#elif HAVE_RUBY_RACTOR_H
|
|
129
|
-
|
|
165
|
+
private_vm_api_global.main_ractor_gvl_owner_context->threads.gvl.owner;
|
|
130
166
|
#else
|
|
131
|
-
|
|
167
|
+
private_vm_api_global.main_ractor_gvl_owner_context->gvl.owner;
|
|
132
168
|
#endif
|
|
133
169
|
|
|
134
170
|
if (current_owner == NULL) {
|
|
@@ -149,8 +185,9 @@ current_gvl_owner gvl_owner(void) {
|
|
|
149
185
|
#endif
|
|
150
186
|
}
|
|
151
187
|
#else
|
|
152
|
-
current_gvl_owner
|
|
153
|
-
rb_vm_t *vm =
|
|
188
|
+
current_gvl_owner main_ractor_gvl_owner(void) {
|
|
189
|
+
rb_vm_t *vm = private_vm_api_global.main_ractor_gvl_owner_context;
|
|
190
|
+
if (vm == NULL) return (current_gvl_owner) {.valid = false};
|
|
154
191
|
|
|
155
192
|
// BIG Issue: Ruby < 2.6 did not have the owner field. The really nice thing about the owner field is that it's
|
|
156
193
|
// "atomic" -- when a thread sets it, it "declares" two things in a single step
|
|
@@ -169,15 +206,15 @@ current_gvl_owner gvl_owner(void) {
|
|
|
169
206
|
// * Thread A sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread A`)
|
|
170
207
|
// * Thread A releases the GVL (`gvl.acquired == 0` + `running_thread == Thread A`)
|
|
171
208
|
// * Thread B grabs the GVL (`gvl.acquired == 1` + `running_thread == Thread A`)
|
|
172
|
-
// * Thread A calls
|
|
209
|
+
// * Thread A calls main_ractor_gvl_owner. Due to the current state (`gvl.acquired == 1` + `running_thread == Thread A`), this
|
|
173
210
|
// function returns an incorrect result.
|
|
174
211
|
// * Thread B finally sets `running_thread` (`gvl.acquired == 1` + `running_thread == Thread B`)
|
|
175
212
|
//
|
|
176
|
-
// This is especially problematic because we use `
|
|
213
|
+
// This is especially problematic because we use `main_ractor_gvl_owner` to implement `is_current_thread_in_main_ractor_and_holding_the_gvl` which
|
|
177
214
|
// is called in a signal handler to decide "is it safe for me to call `rb_postponed_job_register_one` or not".
|
|
178
215
|
// (See constraints in `collectors_cpu_and_wall_time_worker.c` comments for why).
|
|
179
216
|
//
|
|
180
|
-
// Thus an incorrect `
|
|
217
|
+
// Thus an incorrect `is_current_thread_in_main_ractor_and_holding_the_gvl` result may lead to issues inside `rb_postponed_job_register_one`.
|
|
181
218
|
//
|
|
182
219
|
// For this reason we default to use the "no signals workaround" on Ruby 2.5 by default, and we print a
|
|
183
220
|
// warning when customers force-enable it.
|
|
@@ -278,6 +315,23 @@ VALUE thread_name_for(VALUE thread) {
|
|
|
278
315
|
return thread_struct_from_object(thread)->name;
|
|
279
316
|
}
|
|
280
317
|
|
|
318
|
+
// This test doubles as a validation for the layout of the rb_thread_t structure. Because the `name` is almost at
|
|
319
|
+
// the end of the structure, if somehow we can't read the name, this probably means we're getting the structure wrong
|
|
320
|
+
// and we should stop immediately (this includes covering the use of `stat_insn_usage` on Ruby <= 3.2).
|
|
321
|
+
//
|
|
322
|
+
// One example where this happens is in patches such as
|
|
323
|
+
// https://github.com/gitlabhq/omnibus-gitlab/blob/master/config/patches/ruby/thread-memory-allocations-3.2.patch
|
|
324
|
+
// that add items to the structure and thus shift its position from where we expect.
|
|
325
|
+
void self_test_thread_name_for(void) {
|
|
326
|
+
VALUE thread = rb_eval_string("Thread.new { Thread.current.name = 'dd-prof-test' }.join");
|
|
327
|
+
VALUE expected_name = rb_funcall(thread, rb_intern("name"), 0);
|
|
328
|
+
if (thread_name_for(thread) != expected_name) {
|
|
329
|
+
rb_raise(rb_eRuntimeError,
|
|
330
|
+
"thread_name_for() self-test failed; this usually means the `rb_thread_t` on this Ruby doesn't match what the profiler expects"
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
281
335
|
// -----------------------------------------------------------------------------
|
|
282
336
|
// The sources below are modified versions of code extracted from the Ruby project.
|
|
283
337
|
// Each function is annotated with its origin, why we imported it, and the changes made.
|
|
@@ -356,7 +410,6 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id) {
|
|
|
356
410
|
ptrdiff_t n = pc - ISEQ_BODY(iseq)->iseq_encoded;
|
|
357
411
|
VM_ASSERT(n <= ISEQ_BODY(iseq)->iseq_size);
|
|
358
412
|
VM_ASSERT(n >= 0);
|
|
359
|
-
ASSUME(n >= 0);
|
|
360
413
|
size_t pos = n; /* no overflow */
|
|
361
414
|
if (LIKELY(pos)) {
|
|
362
415
|
/* use pos-1 because PC points next instruction at the beginning of instruction */
|
|
@@ -539,7 +592,38 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
|
|
|
539
592
|
cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
|
|
540
593
|
|
|
541
594
|
for (i=0; i<limit && cfp != top_sentinel; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
|
|
542
|
-
|
|
595
|
+
const VALUE *pc = cfp->pc;
|
|
596
|
+
#ifdef HAVE_ZJIT_FRAME
|
|
597
|
+
const rb_iseq_t *iseq = cfp->_iseq;
|
|
598
|
+
#else
|
|
599
|
+
const rb_iseq_t *iseq = cfp->iseq;
|
|
600
|
+
#endif
|
|
601
|
+
|
|
602
|
+
#if defined(HAVE_ZJIT_FRAME) && USE_ZJIT
|
|
603
|
+
// Based on CFP_ZJIT_FRAME, which we currently can't use directly.
|
|
604
|
+
if (CFP_ZJIT_FRAME_P(cfp)) {
|
|
605
|
+
if ((VALUE)cfp->jit_return == ZJIT_JIT_RETURN_C_FRAME) {
|
|
606
|
+
// ZJIT uses this sentinel for C method frames, which have no Ruby ISEQ or bytecode PC.
|
|
607
|
+
// Set both to NULL explicitly because ZJIT leaves those fields untouched, so they may contain stale data.
|
|
608
|
+
iseq = NULL;
|
|
609
|
+
pc = NULL;
|
|
610
|
+
} else {
|
|
611
|
+
const zjit_jit_frame_t *jit_frame = (const zjit_jit_frame_t *)((VALUE *)cfp->jit_return)[-1];
|
|
612
|
+
iseq = jit_frame->iseq;
|
|
613
|
+
pc = jit_frame->pc;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
#endif
|
|
617
|
+
|
|
618
|
+
#ifndef NO_T_MOVED
|
|
619
|
+
if (iseq && RB_TYPE_P((VALUE) iseq, T_MOVED)) {
|
|
620
|
+
// The profiler is not supposed to sample during GC compaction, so T_MOVED is not expected here.
|
|
621
|
+
// Yet, crash tracking also uses this walker and may run at any time. For now, we choose to skip these frames.
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
#endif
|
|
625
|
+
|
|
626
|
+
if (iseq && !pc) {
|
|
543
627
|
// Fix: Do nothing -- this frame should not be used
|
|
544
628
|
//
|
|
545
629
|
// rb_profile_frames does not do this check, but `backtrace_each` (`vm_backtrace.c`) does. This frame is not
|
|
@@ -568,8 +652,8 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
|
|
|
568
652
|
|
|
569
653
|
stack_buffer[i].same_frame =
|
|
570
654
|
stack_buffer[i].is_ruby_frame &&
|
|
571
|
-
stack_buffer[i].as.ruby_frame.iseq ==
|
|
572
|
-
stack_buffer[i].as.ruby_frame.caching_pc ==
|
|
655
|
+
stack_buffer[i].as.ruby_frame.iseq == iseq &&
|
|
656
|
+
stack_buffer[i].as.ruby_frame.caching_pc == pc &&
|
|
573
657
|
stack_buffer[i].cme == cme;
|
|
574
658
|
|
|
575
659
|
if (stack_buffer[i].same_frame) { // Nothing to do, buffer already contains this frame
|
|
@@ -577,8 +661,8 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
|
|
|
577
661
|
continue;
|
|
578
662
|
}
|
|
579
663
|
|
|
580
|
-
stack_buffer[i].as.ruby_frame.iseq =
|
|
581
|
-
stack_buffer[i].as.ruby_frame.caching_pc = (void *)
|
|
664
|
+
stack_buffer[i].as.ruby_frame.iseq = iseq;
|
|
665
|
+
stack_buffer[i].as.ruby_frame.caching_pc = (void *) pc;
|
|
582
666
|
stack_buffer[i].cme = cme;
|
|
583
667
|
|
|
584
668
|
// The topmost frame may not have an updated PC because the JIT
|
|
@@ -589,10 +673,10 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
|
|
|
589
673
|
if (cfp == top && cfp->jit_return) {
|
|
590
674
|
stack_buffer[i].as.ruby_frame.line = 0;
|
|
591
675
|
} else {
|
|
592
|
-
stack_buffer[i].as.ruby_frame.line = calc_lineno(
|
|
676
|
+
stack_buffer[i].as.ruby_frame.line = calc_lineno(iseq, pc);
|
|
593
677
|
}
|
|
594
678
|
#else // Ruby < 3.1
|
|
595
|
-
stack_buffer[i].as.ruby_frame.line = calc_lineno(
|
|
679
|
+
stack_buffer[i].as.ruby_frame.line = calc_lineno(iseq, pc);
|
|
596
680
|
#endif
|
|
597
681
|
|
|
598
682
|
stack_buffer[i].is_ruby_frame = true;
|
|
@@ -797,26 +881,11 @@ void self_test_mn_enabled(void) {
|
|
|
797
881
|
#endif
|
|
798
882
|
}
|
|
799
883
|
|
|
800
|
-
// Taken from upstream imemo.h at commit 6ebcf25de2859b5b6402b7e8b181066c32d0e0bf (November 2023, master branch)
|
|
801
|
-
// (See the Ruby project copyright and license above)
|
|
802
|
-
// to enable calling rb_imemo_name
|
|
803
|
-
//
|
|
804
|
-
// Modifications:
|
|
805
|
-
// * Added IMEMO_MASK define
|
|
806
|
-
// * Changed return type to int to avoid having to define `enum imemo_type`
|
|
807
|
-
static inline int ddtrace_imemo_type(VALUE imemo) {
|
|
808
|
-
// This mask is the same between Ruby 2.5 and 3.3-preview3. Furthermore, the intention of this method is to be used
|
|
809
|
-
// to call `rb_imemo_name` which correctly handles invalid numbers so even if the mask changes in the future, at most
|
|
810
|
-
// we'll get incorrect results (and never a VM crash)
|
|
811
|
-
#define IMEMO_MASK 0x0f
|
|
812
|
-
return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
884
|
// Safety: This function assumes the object passed in is of the imemo type. But in the worst case, you'll just get
|
|
816
885
|
// a string that doesn't make any sense.
|
|
817
886
|
#ifndef NO_IMEMO_NAME
|
|
818
887
|
const char *imemo_kind(VALUE imemo) {
|
|
819
|
-
return rb_imemo_name(
|
|
888
|
+
return rb_imemo_name(imemo_type(imemo));
|
|
820
889
|
}
|
|
821
890
|
#else
|
|
822
891
|
const char *imemo_kind(__attribute__((unused)) VALUE imemo) {
|
|
@@ -39,12 +39,16 @@ typedef struct {
|
|
|
39
39
|
} frame_info;
|
|
40
40
|
|
|
41
41
|
rb_nativethread_id_t pthread_id_for(VALUE thread);
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Must be called from the main ractor while holding the GVL
|
|
43
|
+
void private_vm_api_access_init(void);
|
|
44
|
+
void private_vm_api_access_self_test(void);
|
|
45
|
+
current_gvl_owner main_ractor_gvl_owner(void);
|
|
46
|
+
bool is_current_thread_in_main_ractor_and_holding_the_gvl(void);
|
|
44
47
|
uint64_t native_thread_id_for(VALUE thread);
|
|
45
48
|
void ddtrace_thread_list(VALUE result_array);
|
|
46
49
|
bool is_thread_alive(VALUE thread);
|
|
47
50
|
VALUE thread_name_for(VALUE thread);
|
|
51
|
+
void self_test_thread_name_for(void);
|
|
48
52
|
|
|
49
53
|
int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
|
|
50
54
|
|
|
@@ -37,9 +37,9 @@ static void *trigger_grab_gvl_and_raise_value_arg(void *trigger_args);
|
|
|
37
37
|
static VALUE _native_grab_gvl_and_raise_syserr(DDTRACE_UNUSED VALUE _self, VALUE syserr_errno, VALUE test_message, VALUE test_message_arg, VALUE release_gvl);
|
|
38
38
|
static void *trigger_grab_gvl_and_raise_syserr(void *trigger_args);
|
|
39
39
|
static VALUE _native_ddtrace_rb_ractor_main_p(DDTRACE_UNUSED VALUE _self);
|
|
40
|
-
static VALUE
|
|
41
|
-
static VALUE
|
|
42
|
-
static void *
|
|
40
|
+
static VALUE _native_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self);
|
|
41
|
+
static VALUE _native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self);
|
|
42
|
+
static void *testing_is_current_thread_in_main_ractor_and_holding_the_gvl(void *unused);
|
|
43
43
|
static VALUE _native_install_holding_the_gvl_signal_handler(DDTRACE_UNUSED VALUE _self);
|
|
44
44
|
static void holding_the_gvl_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
|
|
45
45
|
static VALUE _native_trigger_holding_the_gvl_signal_handler_on(DDTRACE_UNUSED VALUE _self, VALUE background_thread);
|
|
@@ -66,6 +66,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
66
66
|
rb_define_singleton_method(native_extension_module, "native_working?", native_working_p, 0);
|
|
67
67
|
rb_funcall(native_extension_module, rb_intern("private_class_method"), 1, ID2SYM(rb_intern("native_working?")));
|
|
68
68
|
|
|
69
|
+
private_vm_api_access_init();
|
|
69
70
|
ruby_helpers_init();
|
|
70
71
|
setup_signal_handler_init(profiling_module);
|
|
71
72
|
collectors_cpu_and_wall_time_worker_init(profiling_module);
|
|
@@ -88,11 +89,11 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
88
89
|
rb_define_singleton_method(testing_module, "_native_grab_gvl_and_raise_value_arg", _native_grab_gvl_and_raise_value_arg, 3);
|
|
89
90
|
rb_define_singleton_method(testing_module, "_native_grab_gvl_and_raise_syserr", _native_grab_gvl_and_raise_syserr, 4);
|
|
90
91
|
rb_define_singleton_method(testing_module, "_native_ddtrace_rb_ractor_main_p", _native_ddtrace_rb_ractor_main_p, 0);
|
|
91
|
-
rb_define_singleton_method(testing_module, "
|
|
92
|
+
rb_define_singleton_method(testing_module, "_native_is_current_thread_in_main_ractor_and_holding_the_gvl", _native_is_current_thread_in_main_ractor_and_holding_the_gvl, 0);
|
|
92
93
|
rb_define_singleton_method(
|
|
93
94
|
testing_module,
|
|
94
|
-
"
|
|
95
|
-
|
|
95
|
+
"_native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl",
|
|
96
|
+
_native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl,
|
|
96
97
|
0
|
|
97
98
|
);
|
|
98
99
|
rb_define_singleton_method(testing_module, "_native_install_holding_the_gvl_signal_handler", _native_install_holding_the_gvl_signal_handler, 0);
|
|
@@ -103,6 +104,8 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
|
|
107
|
+
private_vm_api_access_self_test();
|
|
108
|
+
self_test_thread_name_for();
|
|
106
109
|
self_test_clock_id();
|
|
107
110
|
self_test_current_fiber_for();
|
|
108
111
|
self_test_mn_enabled();
|
|
@@ -218,41 +221,37 @@ static VALUE _native_ddtrace_rb_ractor_main_p(DDTRACE_UNUSED VALUE _self) {
|
|
|
218
221
|
return ddtrace_rb_ractor_main_p() ? Qtrue : Qfalse;
|
|
219
222
|
}
|
|
220
223
|
|
|
221
|
-
static VALUE
|
|
222
|
-
return (
|
|
224
|
+
static VALUE _native_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self) {
|
|
225
|
+
return is_current_thread_in_main_ractor_and_holding_the_gvl() ? Qtrue : Qfalse;
|
|
223
226
|
}
|
|
224
227
|
|
|
225
|
-
static VALUE
|
|
226
|
-
return ((bool) rb_thread_call_without_gvl(
|
|
228
|
+
static VALUE _native_release_gvl_and_call_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED VALUE _self) {
|
|
229
|
+
return ((bool) rb_thread_call_without_gvl(testing_is_current_thread_in_main_ractor_and_holding_the_gvl, NULL, NULL, NULL)) ? Qtrue : Qfalse;
|
|
227
230
|
}
|
|
228
231
|
|
|
229
|
-
static void *
|
|
230
|
-
return (void *)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
static VALUE _native_install_holding_the_gvl_signal_handler(DDTRACE_UNUSED VALUE _self) {
|
|
234
|
-
install_sigprof_signal_handler(holding_the_gvl_signal_handler, "holding_the_gvl_signal_handler");
|
|
235
|
-
return Qtrue;
|
|
232
|
+
static void *testing_is_current_thread_in_main_ractor_and_holding_the_gvl(DDTRACE_UNUSED void *unused) {
|
|
233
|
+
return (void *) is_current_thread_in_main_ractor_and_holding_the_gvl();
|
|
236
234
|
}
|
|
237
235
|
|
|
238
236
|
static pthread_mutex_t holding_the_gvl_signal_handler_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
239
237
|
static pthread_cond_t holding_the_gvl_signal_handler_executed = PTHREAD_COND_INITIALIZER;
|
|
240
238
|
static VALUE holding_the_gvl_signal_handler_result[3];
|
|
241
239
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
static VALUE _native_install_holding_the_gvl_signal_handler(DDTRACE_UNUSED VALUE _self) {
|
|
241
|
+
install_sigprof_signal_handler(holding_the_gvl_signal_handler, "holding_the_gvl_signal_handler");
|
|
242
|
+
return Qtrue;
|
|
243
|
+
}
|
|
245
244
|
|
|
246
245
|
static void holding_the_gvl_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext) {
|
|
247
246
|
pthread_mutex_lock(&holding_the_gvl_signal_handler_mutex);
|
|
248
247
|
|
|
249
248
|
VALUE test_executed = Qtrue;
|
|
250
249
|
VALUE ruby_thread_has_gvl_p_result = ruby_thread_has_gvl_p() ? Qtrue : Qfalse;
|
|
251
|
-
VALUE
|
|
250
|
+
VALUE is_current_thread_in_main_ractor_and_holding_the_gvl_result = is_current_thread_in_main_ractor_and_holding_the_gvl() ? Qtrue : Qfalse;
|
|
252
251
|
|
|
253
252
|
holding_the_gvl_signal_handler_result[0] = test_executed;
|
|
254
253
|
holding_the_gvl_signal_handler_result[1] = ruby_thread_has_gvl_p_result;
|
|
255
|
-
holding_the_gvl_signal_handler_result[2] =
|
|
254
|
+
holding_the_gvl_signal_handler_result[2] = is_current_thread_in_main_ractor_and_holding_the_gvl_result;
|
|
256
255
|
|
|
257
256
|
pthread_cond_broadcast(&holding_the_gvl_signal_handler_executed);
|
|
258
257
|
pthread_mutex_unlock(&holding_the_gvl_signal_handler_mutex);
|
|
@@ -298,7 +297,7 @@ static VALUE _native_trigger_holding_the_gvl_signal_handler_on(DDTRACE_UNUSED VA
|
|
|
298
297
|
|
|
299
298
|
VALUE result = rb_hash_new();
|
|
300
299
|
rb_hash_aset(result, ID2SYM(rb_intern("ruby_thread_has_gvl_p")), holding_the_gvl_signal_handler_result[1]);
|
|
301
|
-
rb_hash_aset(result, ID2SYM(rb_intern("
|
|
300
|
+
rb_hash_aset(result, ID2SYM(rb_intern("is_current_thread_in_main_ractor_and_holding_the_gvl")), holding_the_gvl_signal_handler_result[2]);
|
|
302
301
|
return result;
|
|
303
302
|
}
|
|
304
303
|
|
|
@@ -66,7 +66,9 @@ void private_grab_gvl_and_raise(VALUE exception_class, int syserr_errno, const c
|
|
|
66
66
|
args.format_string = format_string;
|
|
67
67
|
va_start(args.va_args, format_string);
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// Callers run with the GVL or inside an explicit no-GVL region, so Ruby's blocking-region check is sufficient.
|
|
70
|
+
// We don't run in signal handlers or scheduler hooks, so we don't need is_current_thread_in_main_ractor_and_holding_the_gvl().
|
|
71
|
+
if (ruby_thread_has_gvl_p()) {
|
|
70
72
|
VALUE detailed_message = rb_vsprintf(format_string, args.va_args);
|
|
71
73
|
va_end(args.va_args);
|
|
72
74
|
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
#include <stdarg.h>
|
|
5
5
|
#include "datadog_ruby_common.h"
|
|
6
6
|
|
|
7
|
+
// Public since Ruby 4.0 (https://bugs.ruby-lang.org/issues/20877); older Rubies export it without a header declaration.
|
|
8
|
+
// Be careful about using this in the profiler; see notes on is_current_thread_in_main_ractor_and_holding_the_gvl().
|
|
9
|
+
int ruby_thread_has_gvl_p(void);
|
|
10
|
+
|
|
7
11
|
// Initialize internal data needed by some ruby helpers. Should be called during start, before any actual
|
|
8
12
|
// usage of ruby helpers.
|
|
9
13
|
void ruby_helpers_init(void);
|
|
@@ -36,8 +36,6 @@ static void install_sigprof_signal_handler_internal(
|
|
|
36
36
|
) {
|
|
37
37
|
struct sigaction existing_signal_handler_config = {.sa_sigaction = NULL};
|
|
38
38
|
struct sigaction signal_handler_config = {
|
|
39
|
-
// NOTE: Deliberately not SA_ONSTACK. `handle_sampling_signal` uses "am I running on the alternate signal stack?"
|
|
40
|
-
// to detect that if it interrupted another signal handler from the Ruby VM, see `is_running_on_alternate_signal_stack` for details.
|
|
41
39
|
.sa_flags = SA_RESTART | SA_SIGINFO,
|
|
42
40
|
.sa_sigaction = signal_handler_function
|
|
43
41
|
};
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
#include "time_helpers.h"
|
|
10
10
|
#include "heap_recorder.h"
|
|
11
11
|
#include "encoded_profile.h"
|
|
12
|
-
#include "collectors_thread_context.h"
|
|
13
12
|
|
|
14
13
|
// Used to wrap a ddog_prof_Profile in a Ruby object and expose Ruby-level serialization APIs
|
|
15
14
|
// This file implements the native bits of the Datadog::Profiling::StackRecorder class
|
|
@@ -178,10 +177,6 @@ typedef struct {
|
|
|
178
177
|
heap_recorder *heap_recorder;
|
|
179
178
|
bool heap_clean_after_gc_enabled;
|
|
180
179
|
|
|
181
|
-
// When set, _native_serialize will call thread_context_collector_on_serialize on this instance
|
|
182
|
-
// before serializing, so that threads suspended across the whole profile period still get sampled.
|
|
183
|
-
VALUE thread_context_collector_instance;
|
|
184
|
-
|
|
185
180
|
pthread_mutex_t mutex_slot_one;
|
|
186
181
|
profile_slot profile_slot_one;
|
|
187
182
|
pthread_mutex_t mutex_slot_two;
|
|
@@ -343,7 +338,6 @@ static VALUE _native_new(VALUE klass) {
|
|
|
343
338
|
// being leaked.
|
|
344
339
|
|
|
345
340
|
state->heap_clean_after_gc_enabled = false;
|
|
346
|
-
state->thread_context_collector_instance = Qnil;
|
|
347
341
|
|
|
348
342
|
ddog_prof_Slice_SampleType sample_types = {.ptr = all_sample_types, .len = ALL_VALUE_TYPES_COUNT};
|
|
349
343
|
|
|
@@ -415,7 +409,6 @@ static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_Sam
|
|
|
415
409
|
static void stack_recorder_typed_data_mark(void *state_ptr) {
|
|
416
410
|
stack_recorder_state *state = (stack_recorder_state *) state_ptr;
|
|
417
411
|
|
|
418
|
-
rb_gc_mark(state->thread_context_collector_instance);
|
|
419
412
|
heap_recorder_mark(state->heap_recorder);
|
|
420
413
|
}
|
|
421
414
|
|
|
@@ -539,21 +532,17 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
|
539
532
|
stack_recorder_state *state;
|
|
540
533
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
541
534
|
|
|
542
|
-
|
|
543
|
-
// Need to do this while still holding the Global VM Lock; see comments on method for why
|
|
544
|
-
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
|
545
|
-
|
|
546
|
-
if (state->thread_context_collector_instance != Qnil) {
|
|
547
|
-
thread_context_collector_on_serialize(state->thread_context_collector_instance);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
long heap_iteration_prep_start_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
535
|
+
long heap_iteration_prep_start_time_ns = monotonic_wall_time_now_ns();
|
|
551
536
|
// Prepare the iteration on heap recorder we'll be doing outside the GVL. The preparation needs to
|
|
552
537
|
// happen while holding the GVL.
|
|
553
538
|
// NOTE: While rare, it's possible for the GVL to be released inside this function (see comments on `heap_recorder_update`)
|
|
554
539
|
// and thus don't assume this is an "atomic" step -- other threads may get some running time in the meanwhile.
|
|
555
540
|
heap_recorder_prepare_iteration(state->heap_recorder);
|
|
556
|
-
long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns(
|
|
541
|
+
long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns() - heap_iteration_prep_start_time_ns;
|
|
542
|
+
|
|
543
|
+
ddog_Timespec finish_timestamp = system_epoch_now_timespec();
|
|
544
|
+
// Need to do this while still holding the Global VM Lock; see comments on method for why
|
|
545
|
+
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
|
557
546
|
|
|
558
547
|
// We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
|
|
559
548
|
// is pending
|
|
@@ -811,7 +800,7 @@ static void build_heap_profile_without_gvl(stack_recorder_state *state, profile_
|
|
|
811
800
|
static void *call_serialize_without_gvl(void *call_args) {
|
|
812
801
|
call_serialize_without_gvl_arguments *args = (call_serialize_without_gvl_arguments *) call_args;
|
|
813
802
|
|
|
814
|
-
long serialize_no_gvl_start_time_ns = monotonic_wall_time_now_ns(
|
|
803
|
+
long serialize_no_gvl_start_time_ns = monotonic_wall_time_now_ns();
|
|
815
804
|
|
|
816
805
|
profile_slot *slot_now_inactive = serializer_flip_active_and_inactive_slots(args->state);
|
|
817
806
|
args->slot = slot_now_inactive;
|
|
@@ -819,13 +808,13 @@ static void *call_serialize_without_gvl(void *call_args) {
|
|
|
819
808
|
// Now that we have the inactive profile with all but heap samples, lets fill it with heap data
|
|
820
809
|
// without needing to race with the active sampler
|
|
821
810
|
build_heap_profile_without_gvl(args->state, args->slot);
|
|
822
|
-
args->heap_profile_build_time_ns = monotonic_wall_time_now_ns(
|
|
811
|
+
args->heap_profile_build_time_ns = monotonic_wall_time_now_ns() - serialize_no_gvl_start_time_ns;
|
|
823
812
|
|
|
824
813
|
// Note: The profile gets reset by the serialize call
|
|
825
814
|
args->result = ddog_prof_Profile_serialize(&args->slot->profile, &args->slot->start_timestamp, &args->finish_timestamp);
|
|
826
815
|
args->advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(args->state->string_storage);
|
|
827
816
|
args->serialize_ran = true;
|
|
828
|
-
args->serialize_no_gvl_time_ns = long_max_of(0, monotonic_wall_time_now_ns(
|
|
817
|
+
args->serialize_no_gvl_time_ns = long_max_of(0, monotonic_wall_time_now_ns() - serialize_no_gvl_start_time_ns);
|
|
829
818
|
|
|
830
819
|
return NULL; // Unused
|
|
831
820
|
}
|
|
@@ -925,7 +914,7 @@ static VALUE test_slot_mutex_state(VALUE recorder_instance, int slot) {
|
|
|
925
914
|
}
|
|
926
915
|
|
|
927
916
|
static ddog_Timespec system_epoch_now_timespec(void) {
|
|
928
|
-
long now_ns = system_epoch_time_now_ns(
|
|
917
|
+
long now_ns = system_epoch_time_now_ns();
|
|
929
918
|
return (ddog_Timespec) {.seconds = now_ns / SECONDS_AS_NS(1), .nanoseconds = now_ns % SECONDS_AS_NS(1)};
|
|
930
919
|
}
|
|
931
920
|
|
|
@@ -1206,10 +1195,3 @@ static VALUE _native_commit_heap_recordings(DDTRACE_UNUSED VALUE _self, VALUE re
|
|
|
1206
1195
|
|
|
1207
1196
|
return Qtrue;
|
|
1208
1197
|
}
|
|
1209
|
-
|
|
1210
|
-
void recorder_install_on_serialize(VALUE recorder_instance, VALUE thread_context_collector_instance) {
|
|
1211
|
-
stack_recorder_state *state;
|
|
1212
|
-
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
1213
|
-
|
|
1214
|
-
state->thread_context_collector_instance = enforce_thread_context_collector_instance(thread_context_collector_instance);
|
|
1215
|
-
}
|
|
@@ -37,5 +37,4 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
|
37
37
|
void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint);
|
|
38
38
|
void recorder_commit_heap_recordings_may_lose_gvl(VALUE recorder_instance);
|
|
39
39
|
void recorder_heap_update_may_lose_gvl(VALUE recorder_instance);
|
|
40
|
-
void recorder_install_on_serialize(VALUE recorder_instance, VALUE thread_context_collector_instance);
|
|
41
40
|
VALUE enforce_recorder_instance(VALUE object);
|