datadog 2.41.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 +36 -2
- 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 +11 -3
- 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 +0 -24
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +509 -440
- 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_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +31 -0
- data/lib/datadog/di/instrumenter.rb +41 -1
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_notification_builder.rb +1 -0
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/event.rb +10 -3
- 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/thread_context.rb +0 -4
- data/lib/datadog/profiling/component.rb +8 -16
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/distributed/baggage.rb +0 -1
- 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/otel_thread_context.rb +30 -0
- data/lib/datadog/tracing/remote.rb +195 -27
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- 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/version.rb +1 -1
- metadata +14 -8
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
#include <stdbool.h>
|
|
4
4
|
|
|
5
|
-
// The private_vm_api_access.c includes
|
|
6
|
-
// so we use PRIVATE_VM_API_ACCESS_SKIP_RUBY_INCLUDES to be able to include
|
|
7
|
-
// without also dragging the incompatible includes
|
|
5
|
+
// The private_vm_api_access.c includes private VM headers (via datadog-ruby_core_source) which replace and conflict
|
|
6
|
+
// with any other Ruby headers; so we use PRIVATE_VM_API_ACCESS_SKIP_RUBY_INCLUDES to be able to include
|
|
7
|
+
// private_vm_api_access.h on that file without also dragging the incompatible includes
|
|
8
8
|
#ifndef PRIVATE_VM_API_ACCESS_SKIP_RUBY_INCLUDES
|
|
9
9
|
#include <ruby/thread_native.h>
|
|
10
10
|
#include <ruby/vm.h>
|
|
@@ -86,8 +86,20 @@ VALUE current_fiber_for(VALUE thread);
|
|
|
86
86
|
|
|
87
87
|
void self_test_current_fiber_for(void);
|
|
88
88
|
|
|
89
|
+
// Returns the module name without allocating, or nil if the module is anonymous (i.e., Module#name is nil).
|
|
90
|
+
// Never returns an empty String (returns nil instead if it the name would be empty).
|
|
91
|
+
// Also consider ddtrace_permanent_mod_name().
|
|
92
|
+
//
|
|
93
|
+
// Use this instead of rb_class2name() or rb_mod_name() which can trigger allocations
|
|
94
|
+
VALUE ddtrace_alloc_free_rb_mod_name(VALUE mod);
|
|
95
|
+
// Same as `ddtrace_alloc_free_rb_mod_name` but additionally returns nil (filters out)
|
|
96
|
+
// if the module does not have a permanent name (e.g. `#<Module:0x0123>::Foo`).
|
|
97
|
+
VALUE ddtrace_permanent_mod_name(VALUE mod);
|
|
98
|
+
|
|
89
99
|
ssize_t ddtrace_location_label(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, char *buf, size_t buf_size);
|
|
90
100
|
VALUE ddtrace_location_base_label(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq);
|
|
91
101
|
void* ddtrace_cme_cfunc_func(const rb_callable_method_entry_t *cme);
|
|
92
102
|
const char *ddtrace_cme_original_method_name(const rb_callable_method_entry_t *cme);
|
|
93
103
|
|
|
104
|
+
// Returns true for internal objects (like T_IMEMO/T_ICLASS/etc) and "hidden" objects (rb_class_of(obj) == 0)
|
|
105
|
+
bool ddtrace_is_internal_object_p(VALUE obj);
|
|
@@ -21,6 +21,7 @@ void collectors_dynamic_sampling_rate_init(VALUE profiling_module);
|
|
|
21
21
|
void collectors_idle_sampling_helper_init(VALUE profiling_module);
|
|
22
22
|
void collectors_stack_init(VALUE profiling_module);
|
|
23
23
|
void collectors_thread_context_init(VALUE profiling_module);
|
|
24
|
+
void collectors_heap_recorder_init(void);
|
|
24
25
|
void encoded_profile_init(VALUE profiling_module);
|
|
25
26
|
void http_transport_init(VALUE profiling_module);
|
|
26
27
|
void stack_recorder_init(VALUE profiling_module);
|
|
@@ -73,6 +74,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
73
74
|
collectors_idle_sampling_helper_init(profiling_module);
|
|
74
75
|
collectors_stack_init(profiling_module);
|
|
75
76
|
collectors_thread_context_init(profiling_module);
|
|
77
|
+
collectors_heap_recorder_init();
|
|
76
78
|
encoded_profile_init(profiling_module);
|
|
77
79
|
http_transport_init(profiling_module);
|
|
78
80
|
stack_recorder_init(profiling_module);
|
|
@@ -7,16 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
// The following global variables are initialized at startup to save expensive lookups later.
|
|
9
9
|
// They are not expected to be mutated outside of init.
|
|
10
|
-
static VALUE module_object_space = Qnil;
|
|
11
|
-
static ID _id2ref_id = Qnil;
|
|
12
10
|
static ID inspect_id = Qnil;
|
|
13
11
|
static ID to_s_id = Qnil;
|
|
14
12
|
|
|
15
13
|
void ruby_helpers_init(void) {
|
|
16
|
-
rb_global_variable(&module_object_space);
|
|
17
|
-
|
|
18
|
-
module_object_space = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
|
|
19
|
-
_id2ref_id = rb_intern("_id2ref");
|
|
20
14
|
inspect_id = rb_intern("inspect");
|
|
21
15
|
to_s_id = rb_intern("to_s");
|
|
22
16
|
}
|
|
@@ -113,40 +107,6 @@ void private_raise_enforce_syserr(
|
|
|
113
107
|
}
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
static VALUE _id2ref(VALUE obj_id) {
|
|
117
|
-
// Call ::ObjectSpace._id2ref natively. It will raise if the id is no longer valid
|
|
118
|
-
return rb_funcall(module_object_space, _id2ref_id, 1, obj_id);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
static VALUE _id2ref_failure(DDTRACE_UNUSED VALUE _unused1, DDTRACE_UNUSED VALUE _unused2) {
|
|
122
|
-
return Qfalse;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// See notes on header for important details
|
|
126
|
-
bool ruby_ref_from_id(VALUE obj_id, VALUE *value) {
|
|
127
|
-
// Call ::ObjectSpace._id2ref natively. It will raise if the id is no longer valid
|
|
128
|
-
// so we need to call it via rb_rescue2
|
|
129
|
-
// TODO: Benchmark rb_rescue2 vs rb_protect here
|
|
130
|
-
VALUE result = rb_rescue2(
|
|
131
|
-
_id2ref,
|
|
132
|
-
obj_id,
|
|
133
|
-
_id2ref_failure,
|
|
134
|
-
Qnil,
|
|
135
|
-
rb_eRangeError, // rb_eRangeError is the error used to flag invalid ids
|
|
136
|
-
0 // Required by API to be the last argument
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
if (result == Qfalse) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (value != NULL) {
|
|
144
|
-
(*value) = result;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
110
|
// Not part of public headers but is externed from Ruby
|
|
151
111
|
size_t rb_obj_memsize_of(VALUE obj);
|
|
152
112
|
|
|
@@ -162,16 +122,9 @@ size_t rb_obj_memsize_of(VALUE obj);
|
|
|
162
122
|
size_t ruby_obj_memsize_of(VALUE obj) {
|
|
163
123
|
switch (rb_type(obj)) {
|
|
164
124
|
case T_OBJECT:
|
|
165
|
-
// On Ruby 4.0, computing the size of a class/module/iclass seems to not be safe: `rb_obj_memsize_of` walks the
|
|
166
|
-
// per-namespace class extensions (`rb_class_classext_foreach` -> `classext_memsize` -> `rb_id_table_memsize`)
|
|
167
|
-
// and can crash the VM when called on objects tracked by heap profiling.
|
|
168
|
-
// See https://github.com/DataDog/dd-trace-rb/issues/5936. Class objects contribute negligibly to heap size,
|
|
169
|
-
// so we skip them (fall through to the `default` branch, returning 0) rather than risk a SIGSEGV.
|
|
170
|
-
#ifndef NO_SAFE_CLASS_MEMSIZE
|
|
171
125
|
case T_MODULE:
|
|
172
126
|
case T_CLASS:
|
|
173
127
|
case T_ICLASS:
|
|
174
|
-
#endif
|
|
175
128
|
case T_STRING:
|
|
176
129
|
case T_ARRAY:
|
|
177
130
|
case T_HASH:
|
|
@@ -198,36 +151,6 @@ size_t ruby_obj_memsize_of(VALUE obj) {
|
|
|
198
151
|
}
|
|
199
152
|
}
|
|
200
153
|
|
|
201
|
-
// Inspired by rb_class_of but without actually returning classes or potentially doing assertions
|
|
202
|
-
static bool ruby_is_obj_with_class(VALUE obj) {
|
|
203
|
-
if (!RB_SPECIAL_CONST_P(obj)) {
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
if (obj == RUBY_Qfalse) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
else if (obj == RUBY_Qnil) {
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
else if (obj == RUBY_Qtrue) {
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
else if (RB_FIXNUM_P(obj)) {
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
else if (RB_STATIC_SYM_P(obj)) {
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
else if (RB_FLONUM_P(obj)) {
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// This function is not present in the VM headers, but is a public symbol that can be invoked.
|
|
229
|
-
int rb_objspace_internal_object_p(VALUE obj);
|
|
230
|
-
|
|
231
154
|
#ifdef NO_RB_OBJ_INFO
|
|
232
155
|
const char* safe_object_info(DDTRACE_UNUSED VALUE obj) { return "(No rb_obj_info for current Ruby)"; }
|
|
233
156
|
#else
|
|
@@ -239,8 +162,7 @@ int rb_objspace_internal_object_p(VALUE obj);
|
|
|
239
162
|
#endif
|
|
240
163
|
|
|
241
164
|
VALUE ruby_safe_inspect(VALUE obj) {
|
|
242
|
-
if (
|
|
243
|
-
if (rb_objspace_internal_object_p(obj)) return rb_sprintf("(VM Internal, %s)", safe_object_info(obj));
|
|
165
|
+
if (ddtrace_is_internal_object_p(obj)) return rb_sprintf("(VM Internal, %s)", safe_object_info(obj));
|
|
244
166
|
// @ivoanjo: I saw crashes on Ruby 3.1.4 when trying to #inspect matchdata objects. I'm not entirely sure why this
|
|
245
167
|
// is needed, but since we only use this method for debug purposes I put in this alternative and decided not to
|
|
246
168
|
// dig deeper.
|
|
@@ -79,13 +79,6 @@ NORETURN(void private_raise_enforce_syserr(
|
|
|
79
79
|
const char *function_name
|
|
80
80
|
));
|
|
81
81
|
|
|
82
|
-
// Native wrapper to get an object ref from an id. Returns true on success and
|
|
83
|
-
// writes the ref to the value pointer parameter if !NULL. False if id doesn't
|
|
84
|
-
// reference a valid object (in which case value is not changed).
|
|
85
|
-
//
|
|
86
|
-
// Note: GVL can be released and other threads may get to run before this method returns
|
|
87
|
-
bool ruby_ref_from_id(VALUE obj_id, VALUE *value);
|
|
88
|
-
|
|
89
82
|
// Native wrapper to get the approximate/estimated current size of the passed
|
|
90
83
|
// object.
|
|
91
84
|
size_t ruby_obj_memsize_of(VALUE obj);
|
|
@@ -231,6 +231,18 @@ typedef struct {
|
|
|
231
231
|
bool serialize_ran;
|
|
232
232
|
} call_serialize_without_gvl_arguments;
|
|
233
233
|
|
|
234
|
+
typedef struct {
|
|
235
|
+
// Set by caller
|
|
236
|
+
stack_recorder_state *state;
|
|
237
|
+
locked_profile_slot active_slot;
|
|
238
|
+
ddog_prof_Slice_Location locations;
|
|
239
|
+
sample_values values;
|
|
240
|
+
sample_labels labels;
|
|
241
|
+
|
|
242
|
+
// Set by callee
|
|
243
|
+
ddog_prof_Profile_Result result;
|
|
244
|
+
} record_sample_arguments;
|
|
245
|
+
|
|
234
246
|
static VALUE _native_new(VALUE klass);
|
|
235
247
|
static void initialize_slot_concurrency_control(stack_recorder_state *state);
|
|
236
248
|
static void stack_recorder_typed_data_mark(void *data);
|
|
@@ -240,6 +252,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
|
|
|
240
252
|
static VALUE _native_serialize(VALUE self, VALUE recorder_instance);
|
|
241
253
|
static VALUE ruby_time_from(ddog_Timespec ddprof_time);
|
|
242
254
|
static void *call_serialize_without_gvl(void *call_args);
|
|
255
|
+
static VALUE record_sample_locked(VALUE record_sample_arguments_as_value);
|
|
256
|
+
static VALUE record_sample_unlock_ensure(VALUE record_sample_arguments_as_value);
|
|
243
257
|
static locked_profile_slot sampler_lock_active_profile(stack_recorder_state *state);
|
|
244
258
|
static void sampler_unlock_active_profile(locked_profile_slot active_slot);
|
|
245
259
|
static profile_slot* serializer_flip_active_and_inactive_slots(stack_recorder_state *state);
|
|
@@ -252,18 +266,19 @@ static VALUE _native_reset_after_fork(DDTRACE_UNUSED VALUE self, VALUE recorder_
|
|
|
252
266
|
static void serializer_set_start_timestamp_for_next_profile(stack_recorder_state *state, ddog_Timespec start_time);
|
|
253
267
|
static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE local_root_span_id, VALUE endpoint);
|
|
254
268
|
static void reset_profile_slot(profile_slot *slot, ddog_Timespec start_timestamp);
|
|
255
|
-
static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class);
|
|
256
269
|
static VALUE _native_start_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
257
270
|
static VALUE _native_end_fake_slow_heap_serialization(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
258
271
|
static VALUE _native_debug_heap_recorder(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
259
272
|
static VALUE _native_stats(DDTRACE_UNUSED VALUE self, VALUE instance);
|
|
260
273
|
static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns, long heap_iteration_prep_time_ns, long heap_profile_build_time_ns);
|
|
261
|
-
static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE
|
|
274
|
+
static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE record_id);
|
|
275
|
+
static VALUE _native_record_id_for(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE obj);
|
|
262
276
|
static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
263
|
-
static VALUE
|
|
277
|
+
static VALUE _native_heap_recorder_exhaust_record_ids(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
278
|
+
static VALUE _native_recorder_heap_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
264
279
|
static VALUE _native_benchmark_intern(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE string, VALUE times, VALUE use_all);
|
|
265
280
|
static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE_UNUSED VALUE _self);
|
|
266
|
-
static VALUE
|
|
281
|
+
static VALUE _native_commit_heap_recordings(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
267
282
|
|
|
268
283
|
void stack_recorder_init(VALUE profiling_module) {
|
|
269
284
|
VALUE stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject);
|
|
@@ -288,7 +303,6 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
|
288
303
|
rb_define_singleton_method(testing_module, "_native_slot_one_mutex_locked?", _native_is_slot_one_mutex_locked, 1);
|
|
289
304
|
rb_define_singleton_method(testing_module, "_native_slot_two_mutex_locked?", _native_is_slot_two_mutex_locked, 1);
|
|
290
305
|
rb_define_singleton_method(testing_module, "_native_record_endpoint", _native_record_endpoint, 3);
|
|
291
|
-
rb_define_singleton_method(testing_module, "_native_track_object", _native_track_object, 4);
|
|
292
306
|
rb_define_singleton_method(testing_module, "_native_start_fake_slow_heap_serialization",
|
|
293
307
|
_native_start_fake_slow_heap_serialization, 1);
|
|
294
308
|
rb_define_singleton_method(testing_module, "_native_end_fake_slow_heap_serialization",
|
|
@@ -296,11 +310,13 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
|
296
310
|
rb_define_singleton_method(testing_module, "_native_debug_heap_recorder",
|
|
297
311
|
_native_debug_heap_recorder, 1);
|
|
298
312
|
rb_define_singleton_method(testing_module, "_native_is_object_recorded?", _native_is_object_recorded, 2);
|
|
313
|
+
rb_define_singleton_method(testing_module, "_native_record_id_for", _native_record_id_for, 2);
|
|
299
314
|
rb_define_singleton_method(testing_module, "_native_heap_recorder_reset_last_update", _native_heap_recorder_reset_last_update, 1);
|
|
300
|
-
rb_define_singleton_method(testing_module, "
|
|
315
|
+
rb_define_singleton_method(testing_module, "_native_heap_recorder_exhaust_record_ids", _native_heap_recorder_exhaust_record_ids, 1);
|
|
316
|
+
rb_define_singleton_method(testing_module, "_native_recorder_heap_update", _native_recorder_heap_update, 1);
|
|
301
317
|
rb_define_singleton_method(testing_module, "_native_benchmark_intern", _native_benchmark_intern, 4);
|
|
302
318
|
rb_define_singleton_method(testing_module, "_native_test_managed_string_storage_produces_valid_profiles", _native_test_managed_string_storage_produces_valid_profiles, 0);
|
|
303
|
-
rb_define_singleton_method(testing_module, "
|
|
319
|
+
rb_define_singleton_method(testing_module, "_native_commit_heap_recordings", _native_commit_heap_recordings, 1);
|
|
304
320
|
|
|
305
321
|
ok_symbol = ID2SYM(rb_intern_const("ok"));
|
|
306
322
|
error_symbol = ID2SYM(rb_intern_const("error"));
|
|
@@ -398,7 +414,7 @@ static void stack_recorder_typed_data_mark(void *state_ptr) {
|
|
|
398
414
|
stack_recorder_state *state = (stack_recorder_state *) state_ptr;
|
|
399
415
|
|
|
400
416
|
rb_gc_mark(state->thread_context_collector_instance);
|
|
401
|
-
|
|
417
|
+
heap_recorder_mark(state->heap_recorder);
|
|
402
418
|
}
|
|
403
419
|
|
|
404
420
|
static void stack_recorder_typed_data_free(void *state_ptr) {
|
|
@@ -605,7 +621,28 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
|
605
621
|
stack_recorder_state *state;
|
|
606
622
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
607
623
|
|
|
608
|
-
|
|
624
|
+
record_sample_arguments args = {
|
|
625
|
+
.state = state,
|
|
626
|
+
.active_slot = sampler_lock_active_profile(state),
|
|
627
|
+
.locations = locations,
|
|
628
|
+
.values = values,
|
|
629
|
+
.labels = labels,
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// NOTE: The heap recorder is allowed to raise exceptions if something's wrong. We use `rb_ensure` to make sure we
|
|
633
|
+
// properly unlock the active slot mutex on our way out. Otherwise, this would later lead to deadlocks (since the
|
|
634
|
+
// active slot mutex is not expected to be locked forever).
|
|
635
|
+
rb_ensure(record_sample_locked, (VALUE) &args, record_sample_unlock_ensure, (VALUE) &args);
|
|
636
|
+
|
|
637
|
+
if (args.result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
|
638
|
+
raise_error(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&args.result.err));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
static VALUE record_sample_locked(VALUE record_sample_arguments_as_value) {
|
|
643
|
+
record_sample_arguments *args = (record_sample_arguments *) record_sample_arguments_as_value;
|
|
644
|
+
stack_recorder_state *state = args->state;
|
|
645
|
+
sample_values values = args->values;
|
|
609
646
|
|
|
610
647
|
// Note: We initialize this array to have ALL_VALUE_TYPES_COUNT but only tell libdatadog to use the first
|
|
611
648
|
// state->enabled_values_count values. This simplifies handling disabled value types -- we still put them on the
|
|
@@ -621,50 +658,39 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
|
621
658
|
metric_values[position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID]] = values.alloc_samples_unscaled;
|
|
622
659
|
metric_values[position_for[TIMELINE_VALUE_ID]] = values.timeline_wall_time_ns;
|
|
623
660
|
|
|
624
|
-
if (values.heap_sample) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
int exception_state = end_heap_allocation_recording_with_rb_protect(state->heap_recorder, locations);
|
|
634
|
-
if (exception_state) {
|
|
635
|
-
sampler_unlock_active_profile(active_slot);
|
|
636
|
-
rb_jump_tag(exception_state);
|
|
637
|
-
}
|
|
661
|
+
if (values.heap_sample != NULL) {
|
|
662
|
+
heap_recorder_record_allocation(
|
|
663
|
+
state->heap_recorder,
|
|
664
|
+
values.heap_sample->new_object,
|
|
665
|
+
values.alloc_samples,
|
|
666
|
+
values.heap_sample->alloc_class,
|
|
667
|
+
args->locations,
|
|
668
|
+
&values.heap_sample->out_needs_commit
|
|
669
|
+
);
|
|
638
670
|
}
|
|
639
671
|
|
|
640
|
-
|
|
641
|
-
&active_slot.data->profile,
|
|
672
|
+
args->result = ddog_prof_Profile_add(
|
|
673
|
+
&args->active_slot.data->profile,
|
|
642
674
|
(ddog_prof_Sample) {
|
|
643
|
-
.locations = locations,
|
|
675
|
+
.locations = args->locations,
|
|
644
676
|
.values = (ddog_Slice_I64) {.ptr = metric_values, .len = state->enabled_values_count},
|
|
645
|
-
.labels = labels.labels
|
|
677
|
+
.labels = args->labels.labels
|
|
646
678
|
},
|
|
647
679
|
/* To disable end_timestamp_ns to get aggregated profiles in pprof, replace the below with `0` */
|
|
648
|
-
labels.end_timestamp_ns
|
|
680
|
+
args->labels.end_timestamp_ns
|
|
649
681
|
);
|
|
650
682
|
|
|
651
|
-
active_slot.data->stats.recorded_samples++;
|
|
683
|
+
args->active_slot.data->stats.recorded_samples++;
|
|
652
684
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
|
656
|
-
raise_error(rb_eArgError, "Failed to record sample: %"PRIsVALUE, get_error_details_and_drop(&result.err));
|
|
657
|
-
}
|
|
685
|
+
return Qnil; // Unused
|
|
658
686
|
}
|
|
659
687
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
// be fixed with some refactoring but for now this leads to a less impactful change.
|
|
667
|
-
return start_heap_allocation_recording(state->heap_recorder, new_object, sample_weight, alloc_class);
|
|
688
|
+
static VALUE record_sample_unlock_ensure(VALUE record_sample_arguments_as_value) {
|
|
689
|
+
record_sample_arguments *args = (record_sample_arguments *) record_sample_arguments_as_value;
|
|
690
|
+
|
|
691
|
+
sampler_unlock_active_profile(args->active_slot);
|
|
692
|
+
|
|
693
|
+
return Qnil; // Unused
|
|
668
694
|
}
|
|
669
695
|
|
|
670
696
|
void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint) {
|
|
@@ -682,18 +708,18 @@ void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_
|
|
|
682
708
|
}
|
|
683
709
|
}
|
|
684
710
|
|
|
685
|
-
void
|
|
711
|
+
void recorder_heap_update_may_lose_gvl(VALUE recorder_instance) {
|
|
686
712
|
stack_recorder_state *state;
|
|
687
713
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
688
714
|
|
|
689
715
|
if (state->heap_clean_after_gc_enabled) heap_recorder_update_young_objects(state->heap_recorder);
|
|
690
716
|
}
|
|
691
717
|
|
|
692
|
-
void
|
|
718
|
+
void recorder_commit_heap_recordings_may_lose_gvl(VALUE recorder_instance) {
|
|
693
719
|
stack_recorder_state *state;
|
|
694
720
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
695
721
|
|
|
696
|
-
|
|
722
|
+
heap_recorder_commit_recordings_may_lose_gvl(state->heap_recorder);
|
|
697
723
|
}
|
|
698
724
|
|
|
699
725
|
#define MAX_LEN_HEAP_ITERATION_ERROR_MSG 256
|
|
@@ -938,15 +964,6 @@ static VALUE _native_record_endpoint(DDTRACE_UNUSED VALUE _self, VALUE recorder_
|
|
|
938
964
|
return Qtrue;
|
|
939
965
|
}
|
|
940
966
|
|
|
941
|
-
static VALUE _native_track_object(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE new_obj, VALUE weight, VALUE alloc_class) {
|
|
942
|
-
ENFORCE_TYPE(weight, T_FIXNUM);
|
|
943
|
-
bool needs_after_allocation = track_object(recorder_instance, new_obj, NUM2UINT(weight), char_slice_from_ruby_string(alloc_class));
|
|
944
|
-
|
|
945
|
-
// We could instead choose to automatically trigger the after allocation here; yet, it seems kinda nice to keep it manual for
|
|
946
|
-
// the tests so we can pull on each lever separately and observe "the sausage being made" in steps
|
|
947
|
-
return needs_after_allocation ? Qtrue : Qfalse;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
967
|
static void reset_profile_slot(profile_slot *slot, ddog_Timespec start_timestamp) {
|
|
951
968
|
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(&slot->profile);
|
|
952
969
|
if (reset_result.tag == DDOG_PROF_PROFILE_RESULT_ERR) {
|
|
@@ -1024,13 +1041,28 @@ static VALUE build_profile_stats(profile_slot *slot, long serialization_time_ns,
|
|
|
1024
1041
|
return stats_as_hash;
|
|
1025
1042
|
}
|
|
1026
1043
|
|
|
1027
|
-
static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE
|
|
1028
|
-
ENFORCE_TYPE(
|
|
1044
|
+
static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE record_id) {
|
|
1045
|
+
ENFORCE_TYPE(record_id, T_FIXNUM);
|
|
1046
|
+
|
|
1047
|
+
stack_recorder_state *state;
|
|
1048
|
+
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
1049
|
+
|
|
1050
|
+
return heap_recorder_testonly_is_object_recorded(state->heap_recorder, FIX2LONG(record_id));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
static VALUE _native_record_id_for(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE obj) {
|
|
1054
|
+
stack_recorder_state *state;
|
|
1055
|
+
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
1029
1056
|
|
|
1057
|
+
return heap_recorder_testonly_record_id_for(state->heap_recorder, obj);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
static VALUE _native_heap_recorder_exhaust_record_ids(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
|
1030
1061
|
stack_recorder_state *state;
|
|
1031
1062
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
1032
1063
|
|
|
1033
|
-
|
|
1064
|
+
heap_recorder_testonly_exhaust_record_ids(state->heap_recorder);
|
|
1065
|
+
return Qtrue;
|
|
1034
1066
|
}
|
|
1035
1067
|
|
|
1036
1068
|
static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
|
@@ -1042,8 +1074,8 @@ static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self,
|
|
|
1042
1074
|
return Qtrue;
|
|
1043
1075
|
}
|
|
1044
1076
|
|
|
1045
|
-
static VALUE
|
|
1046
|
-
|
|
1077
|
+
static VALUE _native_recorder_heap_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
|
1078
|
+
recorder_heap_update_may_lose_gvl(recorder_instance);
|
|
1047
1079
|
return Qtrue;
|
|
1048
1080
|
}
|
|
1049
1081
|
|
|
@@ -1153,11 +1185,11 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE
|
|
|
1153
1185
|
return rb_ary_new_from_args(2, encoded_pprof_1, encoded_pprof_2);
|
|
1154
1186
|
}
|
|
1155
1187
|
|
|
1156
|
-
static VALUE
|
|
1188
|
+
static VALUE _native_commit_heap_recordings(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
|
1157
1189
|
stack_recorder_state *state;
|
|
1158
1190
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
1159
1191
|
|
|
1160
|
-
|
|
1192
|
+
heap_recorder_commit_recordings_may_lose_gvl(state->heap_recorder);
|
|
1161
1193
|
|
|
1162
1194
|
return Qtrue;
|
|
1163
1195
|
}
|
|
@@ -3,14 +3,23 @@
|
|
|
3
3
|
#include <datadog/profiling.h>
|
|
4
4
|
#include <ruby.h>
|
|
5
5
|
|
|
6
|
+
typedef struct {
|
|
7
|
+
// In: The object being allocated, and its class. (Its weight is the `alloc_samples` in `sample_values` below.)
|
|
8
|
+
VALUE new_object;
|
|
9
|
+
ddog_CharSlice alloc_class;
|
|
10
|
+
// Out: Set by `record_sample` to whether a `recorder_commit_heap_recordings_may_lose_gvl` callback is needed
|
|
11
|
+
bool out_needs_commit;
|
|
12
|
+
} heap_sample_values;
|
|
13
|
+
|
|
6
14
|
typedef struct {
|
|
7
15
|
int64_t cpu_time_ns;
|
|
8
16
|
int64_t wall_time_ns;
|
|
9
17
|
uint32_t cpu_or_wall_samples;
|
|
10
18
|
uint32_t alloc_samples;
|
|
11
19
|
uint32_t alloc_samples_unscaled;
|
|
12
|
-
bool heap_sample;
|
|
13
20
|
int64_t timeline_wall_time_ns;
|
|
21
|
+
// When not NULL, this sample also gets recorded for heap profiling
|
|
22
|
+
heap_sample_values *heap_sample;
|
|
14
23
|
} sample_values;
|
|
15
24
|
|
|
16
25
|
typedef struct {
|
|
@@ -26,8 +35,7 @@ typedef struct {
|
|
|
26
35
|
|
|
27
36
|
void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations, sample_values values, sample_labels labels);
|
|
28
37
|
void record_endpoint(VALUE recorder_instance, uint64_t local_root_span_id, ddog_CharSlice endpoint);
|
|
29
|
-
|
|
30
|
-
void
|
|
31
|
-
void recorder_after_gc_step(VALUE recorder_instance);
|
|
38
|
+
void recorder_commit_heap_recordings_may_lose_gvl(VALUE recorder_instance);
|
|
39
|
+
void recorder_heap_update_may_lose_gvl(VALUE recorder_instance);
|
|
32
40
|
void recorder_install_on_serialize(VALUE recorder_instance, VALUE thread_context_collector_instance);
|
|
33
41
|
VALUE enforce_recorder_instance(VALUE object);
|
|
@@ -97,13 +97,3 @@ static inline VALUE get_error_details_and_drop(ddog_Error *error) {
|
|
|
97
97
|
// Returns the amount of characters written to string (which are necessarily
|
|
98
98
|
// bounded by capacity - 1 since the string will be null-terminated).
|
|
99
99
|
size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity);
|
|
100
|
-
|
|
101
|
-
#define IMEMO_MASK 0x0f
|
|
102
|
-
|
|
103
|
-
// Returns the imemo type of an imemo object.
|
|
104
|
-
// This mask is the same between Ruby 2.5 and 3.3-preview3. Furthermore, the intention of this method is to be used
|
|
105
|
-
// to call `rb_imemo_name` which correctly handles invalid numbers so even if the mask changes in the future, at most
|
|
106
|
-
// we'll get incorrect results (and never a VM crash)
|
|
107
|
-
static inline int ddtrace_imemo_type(VALUE imemo) {
|
|
108
|
-
return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
|
|
109
|
-
}
|
data/ext/libdatadog_api/di.c
CHANGED
|
@@ -14,6 +14,10 @@ void rb_objspace_each_objects(
|
|
|
14
14
|
int (*callback)(void *start, void *end, size_t stride, void *data),
|
|
15
15
|
void *data);
|
|
16
16
|
|
|
17
|
+
// The mask is unchanged between Ruby 2.5 and 4.1-dev, but the `enum imemo_type` values do get
|
|
18
|
+
// renumbered from time to time (e.g. Ruby 4.0 moved imemo_callinfo from 11 to 10), and 4.1-dev already uses all
|
|
19
|
+
// 16 values the mask allows. Thus IMEMO_TYPE_ISEQ must be re-checked when adding support for a new Ruby.
|
|
20
|
+
#define IMEMO_MASK 0x0f
|
|
17
21
|
#define IMEMO_TYPE_ISEQ 7
|
|
18
22
|
|
|
19
23
|
// The ID value of the string "mesg" which is used in Ruby source as
|
|
@@ -27,6 +31,12 @@ static ID id_mesg;
|
|
|
27
31
|
// method probes on Thread#[] / Thread#[]= cannot intercept guard reads/writes.
|
|
28
32
|
static ID id_datadog_di_in_probe;
|
|
29
33
|
|
|
34
|
+
// Returns the imemo type of an imemo object, that is `imemo_type()` from CRuby's internal/imemo.h.
|
|
35
|
+
// The caller must have already checked the object is a T_IMEMO, otherwise the result is meaningless.
|
|
36
|
+
static inline int ddtrace_imemo_type(VALUE imemo) {
|
|
37
|
+
return (RBASIC(imemo)->flags >> FL_USHIFT) & IMEMO_MASK;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
// Returns whether the argument is an IMEMO of type ISEQ.
|
|
31
41
|
static bool ddtrace_imemo_iseq_p(VALUE v) {
|
|
32
42
|
return rb_objspace_internal_object_p(v) && RB_TYPE_P(v, T_IMEMO) && ddtrace_imemo_type(v) == IMEMO_TYPE_ISEQ;
|
|
@@ -98,6 +98,9 @@ EXTENSION_NAME = "libdatadog_api.#{RUBY_VERSION[/\d+.\d+/]}_#{RUBY_PLATFORM}".fr
|
|
|
98
98
|
|
|
99
99
|
have_func("rb_iseq_type")
|
|
100
100
|
|
|
101
|
+
$defs << "-DHAVE_RUBY_THREAD_STORAGE_API" if RUBY_VERSION >= "3.3"
|
|
102
|
+
$defs << "-DHAVE_RUBY_THREAD_HAS_GVL_P" if have_func("ruby_thread_has_gvl_p")
|
|
103
|
+
|
|
101
104
|
create_makefile(EXTENSION_NAME)
|
|
102
105
|
|
|
103
106
|
# rubocop:enable Style/GlobalVars
|
data/ext/libdatadog_api/init.c
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include "crashtracker.h"
|
|
6
6
|
#include "feature_flags.h"
|
|
7
7
|
#include "library_config.h"
|
|
8
|
+
#include "otel_thread_context.h"
|
|
8
9
|
#include "process_discovery.h"
|
|
9
10
|
#include "trace_exporter.h"
|
|
10
11
|
|
|
@@ -28,4 +29,5 @@ void DDTRACE_EXPORT Init_libdatadog_api(void) {
|
|
|
28
29
|
|
|
29
30
|
VALUE tracing_module = rb_define_module_under(datadog_module, "Tracing");
|
|
30
31
|
trace_exporter_init(tracing_module);
|
|
32
|
+
otel_thread_context_init(tracing_module);
|
|
31
33
|
}
|