datadog 2.42.0 → 2.44.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 +67 -2
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +46 -5
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +80 -36
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
- data/ext/datadog_profiling_native_extension/heap_recorder.c +13 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.h +4 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +34 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +1 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +17 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +5 -4
- 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 +51 -15
- data/ext/libdatadog_api/trace_exporter.c +642 -22
- 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/open_feature/transport.rb +5 -0
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/profiling/collectors/info.rb +2 -6
- data/lib/datadog/symbol_database/extractor.rb +84 -102
- data/lib/datadog/tracing/component.rb +2 -0
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +6 -0
- 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/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/otel_thread_context.rb +78 -9
- data/lib/datadog/tracing/tracer.rb +15 -2
- data/lib/datadog/tracing/transport/native.rb +18 -26
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
- data/lib/datadog/tracing/transport/traces.rb +3 -23
- data/lib/datadog/version.rb +1 -1
- metadata +25 -7
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
|
@@ -207,6 +207,7 @@ typedef struct {
|
|
|
207
207
|
// however this only matters for a very short period when a thread starts.
|
|
208
208
|
struct per_thread_context {
|
|
209
209
|
sampling_buffer sampling_buffer;
|
|
210
|
+
bool prepare_sample_on_gc_finish;
|
|
210
211
|
char thread_id[THREAD_ID_LIMIT_CHARS];
|
|
211
212
|
ddog_CharSlice thread_id_char_slice;
|
|
212
213
|
char thread_invoke_location[THREAD_INVOKE_LOCATION_LIMIT_CHARS];
|
|
@@ -311,7 +312,6 @@ static void update_metrics_and_sample(
|
|
|
311
312
|
thread_context_collector_state *state,
|
|
312
313
|
VALUE thread_being_sampled,
|
|
313
314
|
per_thread_context *thread_context,
|
|
314
|
-
long current_cpu_time_ns,
|
|
315
315
|
long current_monotonic_wall_time_ns,
|
|
316
316
|
bool force_sample
|
|
317
317
|
);
|
|
@@ -385,6 +385,7 @@ static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
|
|
|
385
385
|
static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
|
|
386
386
|
static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
387
387
|
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self);
|
|
388
|
+
static VALUE _native_request_prepare_on_gc_finish(DDTRACE_UNUSED VALUE self);
|
|
388
389
|
static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
389
390
|
static VALUE _native_remove_per_thread_context_for(DDTRACE_UNUSED VALUE self, VALUE thread);
|
|
390
391
|
static VALUE _native_global_reset_per_thread_context(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
|
|
@@ -424,6 +425,7 @@ void collectors_thread_context_init(VALUE profiling_module) {
|
|
|
424
425
|
rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
|
|
425
426
|
rb_define_singleton_method(testing_module, "_native_system_epoch_time_now_ns", _native_system_epoch_time_now_ns, 1);
|
|
426
427
|
rb_define_singleton_method(testing_module, "_native_prepare_sample_inside_signal_handler", _native_prepare_sample_inside_signal_handler, 0);
|
|
428
|
+
rb_define_singleton_method(testing_module, "_native_request_prepare_on_gc_finish", _native_request_prepare_on_gc_finish, 0);
|
|
427
429
|
rb_define_singleton_method(testing_module, "_native_remove_per_thread_context_for", _native_remove_per_thread_context_for, 1);
|
|
428
430
|
rb_define_singleton_method(testing_module, "_native_global_reset_per_thread_context", _native_global_reset_per_thread_context, 1);
|
|
429
431
|
rb_define_singleton_method(testing_module, "_native_mark_thread_as_profiler_internal", _native_mark_thread_as_profiler_internal, 1);
|
|
@@ -679,7 +681,7 @@ static VALUE _native_on_gc_start(DDTRACE_UNUSED VALUE self, VALUE collector_inst
|
|
|
679
681
|
static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
|
|
680
682
|
debug_enter_unsafe_context();
|
|
681
683
|
|
|
682
|
-
(void) !thread_context_collector_on_gc_finish(collector_instance);
|
|
684
|
+
(void) !thread_context_collector_on_gc_finish(collector_instance, true);
|
|
683
685
|
|
|
684
686
|
debug_leave_unsafe_context();
|
|
685
687
|
|
|
@@ -741,6 +743,7 @@ static void record_sampling_overhead(thread_context_collector_state *state, per_
|
|
|
741
743
|
// Assumption 3: This function IS NOT called from a signal handler. This function is not async-signal-safe.
|
|
742
744
|
// Assumption 4: This function IS NOT called in a reentrant way.
|
|
743
745
|
// Assumption 5: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
746
|
+
// Assumption 6: When called while the profiler is active, `during_sample` MUST be set.
|
|
744
747
|
//
|
|
745
748
|
bool thread_context_collector_sample(VALUE self_instance, long current_monotonic_wall_time_ns) {
|
|
746
749
|
thread_context_collector_state *state;
|
|
@@ -748,25 +751,30 @@ bool thread_context_collector_sample(VALUE self_instance, long current_monotonic
|
|
|
748
751
|
|
|
749
752
|
VALUE current_thread = rb_thread_current();
|
|
750
753
|
per_thread_context *current_thread_context = get_or_create_context_for(current_thread);
|
|
751
|
-
|
|
754
|
+
|
|
755
|
+
// Sample the current thread (which will use the current CPU-time) first.
|
|
756
|
+
// This ensures any CPU-time the profiler spends sampling (e.g. the rest of the function) is not wrongly blamed on this thread,
|
|
757
|
+
// and instead is separately accounted by `record_sampling_overhead()` below.
|
|
758
|
+
update_metrics_and_sample(
|
|
759
|
+
state,
|
|
760
|
+
current_thread,
|
|
761
|
+
current_thread_context,
|
|
762
|
+
current_monotonic_wall_time_ns,
|
|
763
|
+
false);
|
|
752
764
|
|
|
753
765
|
VALUE threads = thread_list(state);
|
|
754
766
|
|
|
755
767
|
const long thread_count = RARRAY_LEN(threads);
|
|
756
768
|
for (long i = 0; i < thread_count; i++) {
|
|
757
|
-
VALUE thread =
|
|
758
|
-
|
|
769
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
770
|
+
ENFORCE_THREAD(thread);
|
|
759
771
|
|
|
760
|
-
|
|
761
|
-
// to avoid blaming the time the profiler took on whatever is currently running on the thread,
|
|
762
|
-
// and instead we report that time the profiler took as sampling overhead below.
|
|
763
|
-
long current_cpu_time_ns = (thread == current_thread) ? cpu_time_at_sample_start_for_current_thread : cpu_time_now_ns(thread_context);
|
|
772
|
+
if (thread == current_thread) continue; // Already sampled above
|
|
764
773
|
|
|
765
774
|
update_metrics_and_sample(
|
|
766
775
|
state,
|
|
767
776
|
thread,
|
|
768
|
-
|
|
769
|
-
current_cpu_time_ns,
|
|
777
|
+
get_or_create_context_for(thread),
|
|
770
778
|
current_monotonic_wall_time_ns,
|
|
771
779
|
false);
|
|
772
780
|
}
|
|
@@ -790,10 +798,10 @@ static void update_metrics_and_sample(
|
|
|
790
798
|
thread_context_collector_state *state,
|
|
791
799
|
VALUE thread_being_sampled,
|
|
792
800
|
per_thread_context *thread_context,
|
|
793
|
-
long current_cpu_time_ns,
|
|
794
801
|
long current_monotonic_wall_time_ns,
|
|
795
802
|
bool force_sample
|
|
796
803
|
) {
|
|
804
|
+
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
797
805
|
bool is_gvl_waiting_state =
|
|
798
806
|
handle_gvl_waiting(state, thread_being_sampled, thread_context, current_cpu_time_ns);
|
|
799
807
|
|
|
@@ -888,7 +896,12 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
888
896
|
}
|
|
889
897
|
|
|
890
898
|
// Here we record the wall-time first and in on_gc_finish we record it second to try to avoid having wall-time be slightly < cpu-time
|
|
891
|
-
|
|
899
|
+
long wall_time_at_start_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE);
|
|
900
|
+
|
|
901
|
+
// If our start timestamp is not OK, we skip tracking this GC as well
|
|
902
|
+
if (wall_time_at_start_ns == 0) return;
|
|
903
|
+
|
|
904
|
+
thread_context->gc_tracking.wall_time_at_start_ns = wall_time_at_start_ns;
|
|
892
905
|
thread_context->gc_tracking.cpu_time_at_start_ns = cpu_time_now_ns(thread_context);
|
|
893
906
|
}
|
|
894
907
|
|
|
@@ -902,8 +915,9 @@ void thread_context_collector_on_gc_start(VALUE self_instance) {
|
|
|
902
915
|
//
|
|
903
916
|
// Assumption 1: This function is called in a thread that is holding the Global VM Lock. Caller is responsible for enforcing this.
|
|
904
917
|
// Assumption 2: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
918
|
+
// Assumption 3: If allow_prepare_sample is true while profiling is active, during_sample MUST be set by the caller.
|
|
905
919
|
__attribute__((warn_unused_result))
|
|
906
|
-
bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
920
|
+
bool thread_context_collector_on_gc_finish(VALUE self_instance, bool allow_prepare_sample) {
|
|
907
921
|
thread_context_collector_state *state;
|
|
908
922
|
if (!rb_typeddata_is_kind_of(self_instance, &thread_context_collector_typed_data)) return false;
|
|
909
923
|
// This should never fail when the above check passes
|
|
@@ -914,10 +928,16 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
914
928
|
// Context is created eagerly, so this should not normally be NULL (see on_gc_start).
|
|
915
929
|
if (thread_context == NULL) return false;
|
|
916
930
|
|
|
931
|
+
if (thread_context->prepare_sample_on_gc_finish) {
|
|
932
|
+
thread_context->prepare_sample_on_gc_finish = false;
|
|
933
|
+
if (allow_prepare_sample) prepare_sample_thread(rb_thread_current(), &thread_context->sampling_buffer);
|
|
934
|
+
}
|
|
935
|
+
|
|
917
936
|
long cpu_time_at_start_ns = thread_context->gc_tracking.cpu_time_at_start_ns;
|
|
918
937
|
long wall_time_at_start_ns = thread_context->gc_tracking.wall_time_at_start_ns;
|
|
919
938
|
|
|
920
|
-
|
|
939
|
+
// Both fields are always set and cleared together, so checking one is enough
|
|
940
|
+
if (cpu_time_at_start_ns == INVALID_TIME) {
|
|
921
941
|
// If this happened, it means that on_gc_start was either never called for the thread OR it was called but no thread
|
|
922
942
|
// context existed at the time. The former can be the result of a bug, but since we can't distinguish them, we just
|
|
923
943
|
// do nothing.
|
|
@@ -980,6 +1000,7 @@ bool thread_context_collector_on_gc_finish(VALUE self_instance) {
|
|
|
980
1000
|
// Assumption 2: This function is allowed to raise exceptions. Caller is responsible for handling them, if needed.
|
|
981
1001
|
// Assumption 3: Unlike `on_gc_start` and `on_gc_finish`, this method is allowed to allocate memory as needed.
|
|
982
1002
|
// Assumption 4: This function is called from the main Ractor (if Ruby has support for Ractors).
|
|
1003
|
+
// Assumption 5: When called while the profiler is active, `during_sample` MUST be set.
|
|
983
1004
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance) {
|
|
984
1005
|
thread_context_collector_state *state;
|
|
985
1006
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -1319,6 +1340,8 @@ void thread_context_collector_reset_all_per_thread_contexts(VALUE self_instance)
|
|
|
1319
1340
|
const long thread_count = RARRAY_LEN(threads);
|
|
1320
1341
|
for (long i = 0; i < thread_count; i++) {
|
|
1321
1342
|
VALUE thread = rb_ary_entry(threads, i);
|
|
1343
|
+
ENFORCE_THREAD(thread);
|
|
1344
|
+
|
|
1322
1345
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1323
1346
|
if (thread_context != NULL) {
|
|
1324
1347
|
bool is_profiler_internal_thread = thread_context->is_profiler_internal_thread;
|
|
@@ -1387,6 +1410,7 @@ static VALUE per_thread_context_to_ruby_hash(per_thread_context *thread_context)
|
|
|
1387
1410
|
ID2SYM(rb_intern("gvl_state_change_count")), /* => */ ULL2NUM(thread_context->gvl_state_change_count),
|
|
1388
1411
|
ID2SYM(rb_intern("gvl_state_change_count_at_previous_sample")), /* => */ ULL2NUM(thread_context->gvl_state_change_count_at_previous_sample),
|
|
1389
1412
|
ID2SYM(rb_intern("was_skipped_at_last_sample")), /* => */ thread_context->was_skipped_at_last_sample ? Qtrue : Qfalse,
|
|
1413
|
+
ID2SYM(rb_intern("prepare_sample_on_gc_finish")), /* => */ thread_context->prepare_sample_on_gc_finish ? Qtrue : Qfalse,
|
|
1390
1414
|
ID2SYM(rb_intern("is_profiler_internal_thread")), /* => */ thread_context->is_profiler_internal_thread ? Qtrue : Qfalse,
|
|
1391
1415
|
};
|
|
1392
1416
|
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(context_as_hash, arguments[i], arguments[i+1]);
|
|
@@ -1433,7 +1457,9 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
|
|
|
1433
1457
|
VALUE threads = thread_list(state);
|
|
1434
1458
|
const long thread_count = RARRAY_LEN(threads);
|
|
1435
1459
|
for (long i = 0; i < thread_count; i++) {
|
|
1436
|
-
VALUE thread =
|
|
1460
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
1461
|
+
ENFORCE_THREAD(thread);
|
|
1462
|
+
|
|
1437
1463
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
1438
1464
|
if (thread_context != NULL) {
|
|
1439
1465
|
rb_hash_aset(result, thread, per_thread_context_to_ruby_hash(thread_context));
|
|
@@ -1443,9 +1469,6 @@ static VALUE _native_per_thread_context(DDTRACE_UNUSED VALUE _self, VALUE collec
|
|
|
1443
1469
|
}
|
|
1444
1470
|
|
|
1445
1471
|
static long update_time_since_previous_sample(long *time_at_previous_sample_ns, long current_time_ns, per_thread_context *thread_context) {
|
|
1446
|
-
// If we didn't have a time for the previous sample, we use the current one
|
|
1447
|
-
if (*time_at_previous_sample_ns == INVALID_TIME) *time_at_previous_sample_ns = current_time_ns;
|
|
1448
|
-
|
|
1449
1472
|
// We don't expect to be sampling a thread (and thus updating these counters) while Ruby is doing GC (between
|
|
1450
1473
|
// `thread_context_collector_on_gc_start` and `thread_context_collector_on_gc_finish`)
|
|
1451
1474
|
if (thread_context->gc_tracking.cpu_time_at_start_ns != INVALID_TIME) {
|
|
@@ -1467,6 +1490,13 @@ static long update_time_since_previous_sample(long *time_at_previous_sample_ns,
|
|
|
1467
1490
|
}
|
|
1468
1491
|
|
|
1469
1492
|
static long update_cpu_time_since_previous_sample(per_thread_context *thread_context, long current_cpu_time_ns) {
|
|
1493
|
+
// A previous `cpu_time_now_ns` may have failed to read the clock and thus invalidated our baseline; in that case we
|
|
1494
|
+
// use the current time, so this sample gets no cpu-time and the next one gets an accurate delta.
|
|
1495
|
+
// Note wall-time never needs this: it is always seeded with a real value in `initialize_context`.
|
|
1496
|
+
if (thread_context->cpu_time_at_previous_sample_ns == INVALID_TIME) {
|
|
1497
|
+
thread_context->cpu_time_at_previous_sample_ns = current_cpu_time_ns;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1470
1500
|
long elapsed_time_ns = update_time_since_previous_sample(
|
|
1471
1501
|
&thread_context->cpu_time_at_previous_sample_ns,
|
|
1472
1502
|
current_cpu_time_ns,
|
|
@@ -1669,7 +1699,7 @@ static VALUE thread_list(thread_context_collector_state *state) {
|
|
|
1669
1699
|
// the current thread.
|
|
1670
1700
|
//
|
|
1671
1701
|
// Assumptions for this function are same as for `thread_context_collector_sample` except that this function is
|
|
1672
|
-
// expected to be called from a signal handler and to be async-signal-safe.
|
|
1702
|
+
// expected to be called from a signal handler and to be async-signal-safe, and `during_sample` MUST be unset.
|
|
1673
1703
|
//
|
|
1674
1704
|
// Also, no allocation (Ruby or malloc) can happen.
|
|
1675
1705
|
bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
@@ -1680,13 +1710,20 @@ bool thread_context_collector_prepare_sample_inside_signal_handler(void) {
|
|
|
1680
1710
|
return prepare_sample_thread(current_thread, &thread_context->sampling_buffer);
|
|
1681
1711
|
}
|
|
1682
1712
|
|
|
1713
|
+
// Called from the sampling signal handler; must not allocate or inspect the Ruby stack during GC.
|
|
1714
|
+
void thread_context_collector_request_prepare_on_gc_finish(void) {
|
|
1715
|
+
per_thread_context *thread_context = get_per_thread_context(rb_thread_current());
|
|
1716
|
+
if (thread_context != NULL) thread_context->prepare_sample_on_gc_finish = true;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1683
1719
|
// This method gets called from inside the RUBY_INTERNAL_EVENT_NEWOBJ tracepoint so it should neither allocate in the
|
|
1684
1720
|
// Ruby heap nor release the GVL (https://github.com/DataDog/dd-trace-rb/pull/4240).
|
|
1685
1721
|
//
|
|
1686
1722
|
// Returns true if `thread_context_collector_commit_heap_recordings_may_lose_gvl` needs to be called (to do work
|
|
1687
1723
|
// that can't be done from inside the tracepoint, such as allocate new objects), and false if it doesn't
|
|
1688
1724
|
//
|
|
1689
|
-
// The callers must ensure thread_context is non-NULL.
|
|
1725
|
+
// Assumption 1: The callers must ensure thread_context is non-NULL.
|
|
1726
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
1690
1727
|
bool thread_context_collector_sample_allocation(VALUE self_instance, per_thread_context *thread_context, unsigned int sample_weight, VALUE new_object) {
|
|
1691
1728
|
thread_context_collector_state *state;
|
|
1692
1729
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -1932,6 +1969,7 @@ static void ddtrace_otel_trace_identifiers_for(
|
|
|
1932
1969
|
*numeric_span_id = resolved_numeric_span_id;
|
|
1933
1970
|
}
|
|
1934
1971
|
|
|
1972
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
1935
1973
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples) {
|
|
1936
1974
|
thread_context_collector_state *state;
|
|
1937
1975
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -2146,7 +2184,9 @@ static VALUE _native_mark_thread_as_profiler_internal(DDTRACE_UNUSED VALUE self,
|
|
|
2146
2184
|
// flush is not lost. on_serialize (below) also flushes profiler-internal threads during periodic
|
|
2147
2185
|
// serialization, but it can't help at shutdown: by the time the final serialize runs, these
|
|
2148
2186
|
// threads are already dead and absent from thread_list.
|
|
2149
|
-
|
|
2187
|
+
//
|
|
2188
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2189
|
+
VALUE thread_context_collector_profiler_internal_thread_done(VALUE self_instance) {
|
|
2150
2190
|
thread_context_collector_state *state;
|
|
2151
2191
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
2152
2192
|
|
|
@@ -2156,21 +2196,23 @@ void thread_context_collector_profiler_internal_thread_done(VALUE self_instance)
|
|
|
2156
2196
|
rb_raise(rb_eRuntimeError, "current thread %"PRIsVALUE" is not profiler-internal thread", current_thread);
|
|
2157
2197
|
}
|
|
2158
2198
|
|
|
2159
|
-
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2160
2199
|
long current_monotonic_wall_time_ns = monotonic_wall_time_now_ns(RAISE_ON_FAILURE);
|
|
2161
2200
|
|
|
2162
2201
|
update_metrics_and_sample(
|
|
2163
2202
|
state,
|
|
2164
2203
|
current_thread,
|
|
2165
2204
|
thread_context,
|
|
2166
|
-
current_cpu_time_ns,
|
|
2167
2205
|
current_monotonic_wall_time_ns,
|
|
2168
2206
|
true);
|
|
2207
|
+
|
|
2208
|
+
return Qnil;
|
|
2169
2209
|
}
|
|
2170
2210
|
|
|
2171
2211
|
// Flushes threads whose last per-tick sample was skipped (either by the SUSPENDED-skip
|
|
2172
2212
|
// optimization, or by is_profiler_internal_thread) so their accumulated time is recorded.
|
|
2173
2213
|
// Called by the stack recorder at the start of _native_serialize (regular periodic flush).
|
|
2214
|
+
//
|
|
2215
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2174
2216
|
void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
2175
2217
|
thread_context_collector_state *state;
|
|
2176
2218
|
TypedData_Get_Struct(self_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
|
|
@@ -2180,19 +2222,19 @@ void thread_context_collector_on_serialize(VALUE self_instance) {
|
|
|
2180
2222
|
const long thread_count = RARRAY_LEN(threads);
|
|
2181
2223
|
|
|
2182
2224
|
for (long i = 0; i < thread_count; i++) {
|
|
2183
|
-
VALUE thread =
|
|
2225
|
+
VALUE thread = rb_ary_entry(threads, i);
|
|
2226
|
+
ENFORCE_THREAD(thread);
|
|
2227
|
+
|
|
2184
2228
|
per_thread_context *thread_context = get_per_thread_context(thread);
|
|
2185
2229
|
|
|
2186
2230
|
if (thread_context != NULL && (thread_context->was_skipped_at_last_sample || thread_context->is_profiler_internal_thread)) {
|
|
2187
|
-
long current_cpu_time_ns = cpu_time_now_ns(thread_context);
|
|
2188
|
-
// We need to force_sample=true otherwise this sample would be skipped too
|
|
2189
2231
|
update_metrics_and_sample(
|
|
2190
2232
|
state,
|
|
2191
2233
|
thread,
|
|
2192
2234
|
thread_context,
|
|
2193
|
-
current_cpu_time_ns,
|
|
2194
2235
|
current_monotonic_wall_time_ns,
|
|
2195
|
-
true
|
|
2236
|
+
true // We need to force_sample=true otherwise this sample would be skipped too
|
|
2237
|
+
);
|
|
2196
2238
|
}
|
|
2197
2239
|
}
|
|
2198
2240
|
}
|
|
@@ -2236,6 +2278,8 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2236
2278
|
|
|
2237
2279
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
2238
2280
|
// We must only use async-signal-safe functions here, see notes in the caller
|
|
2281
|
+
//
|
|
2282
|
+
// Assumption 1: When called while the profiler is active, `during_sample` MUST be set.
|
|
2239
2283
|
__attribute__((warn_unused_result))
|
|
2240
2284
|
on_gvl_running_result thread_context_collector_on_gvl_running(VALUE thread, per_thread_context *thread_context, uint32_t waiting_for_gvl_threshold_ns) {
|
|
2241
2285
|
// Bump the event counter and clears the state bit to "running"
|
|
@@ -2312,7 +2356,8 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2312
2356
|
//
|
|
2313
2357
|
// ---
|
|
2314
2358
|
//
|
|
2315
|
-
// Always called with the GVL, either from a postponed_job or from tests.
|
|
2359
|
+
// Assumption 1: Always called with the GVL, either from a postponed_job or from tests.
|
|
2360
|
+
// Assumption 2: When called while the profiler is active, `during_sample` MUST be set.
|
|
2316
2361
|
//
|
|
2317
2362
|
// NOTE: In normal use, current_thread is expected to be == rb_thread_current(); the `current_thread` parameter only
|
|
2318
2363
|
// exists to enable testing.
|
|
@@ -2332,18 +2377,12 @@ static VALUE _native_on_gvl_released(DDTRACE_UNUSED VALUE self, VALUE thread) {
|
|
|
2332
2377
|
return Qfalse;
|
|
2333
2378
|
}
|
|
2334
2379
|
|
|
2335
|
-
// We don't actually account for cpu-time during Waiting for GVL. BUT, we may choose to push an
|
|
2336
|
-
// extra sample to represent the period prior to Waiting for GVL. To support that, we retrieve the current
|
|
2337
|
-
// cpu-time of the thread and let `update_metrics_and_sample` decide what to do with it.
|
|
2338
|
-
long cpu_time_for_thread = cpu_time_now_ns(thread_context);
|
|
2339
|
-
|
|
2340
2380
|
// TODO: Should we update the dynamic sampling rate overhead tracking with this sample as well?
|
|
2341
2381
|
|
|
2342
2382
|
update_metrics_and_sample(
|
|
2343
2383
|
state,
|
|
2344
2384
|
current_thread,
|
|
2345
2385
|
thread_context,
|
|
2346
|
-
cpu_time_for_thread,
|
|
2347
2386
|
current_monotonic_wall_time_ns,
|
|
2348
2387
|
false);
|
|
2349
2388
|
|
|
@@ -2554,3 +2593,8 @@ static VALUE _native_system_epoch_time_now_ns(DDTRACE_UNUSED VALUE self, VALUE c
|
|
|
2554
2593
|
static VALUE _native_prepare_sample_inside_signal_handler(DDTRACE_UNUSED VALUE self) {
|
|
2555
2594
|
return thread_context_collector_prepare_sample_inside_signal_handler() ? Qtrue : Qfalse;
|
|
2556
2595
|
}
|
|
2596
|
+
|
|
2597
|
+
static VALUE _native_request_prepare_on_gc_finish(DDTRACE_UNUSED VALUE self) {
|
|
2598
|
+
thread_context_collector_request_prepare_on_gc_finish();
|
|
2599
|
+
return Qnil;
|
|
2600
|
+
}
|
|
@@ -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_request_prepare_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);
|
|
@@ -20,14 +21,14 @@ VALUE thread_context_collector_sample_after_gc(VALUE self_instance);
|
|
|
20
21
|
VALUE thread_context_collector_heap_update_may_lose_gvl(VALUE self_instance);
|
|
21
22
|
void thread_context_collector_resolve_otel_span_key_may_lose_gvl(VALUE self_instance);
|
|
22
23
|
void thread_context_collector_on_gc_start(VALUE self_instance);
|
|
23
|
-
__attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(VALUE self_instance);
|
|
24
|
+
__attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(VALUE self_instance, bool allow_prepare_sample);
|
|
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
|
void thread_context_collector_on_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 {
|
|
@@ -260,7 +260,9 @@ dir_config("ruby") # allow user to pass in non-standard core include directory
|
|
|
260
260
|
#
|
|
261
261
|
# To workaround this issue, we override `with_cppflags` for datadog-ruby_core_source to still include `$CPPFLAGS`.
|
|
262
262
|
Datadog::RubyCoreSource.define_singleton_method(:with_cppflags) do |newflags, &block|
|
|
263
|
-
|
|
263
|
+
# Note: The `Shellwords.escape` is an additional workaround for ruby_core_source supplying
|
|
264
|
+
# one unescaped -I argument containing the complete header path, not the CPPFLAGS thing above.
|
|
265
|
+
super("#{Shellwords.escape(newflags)} #{$CPPFLAGS}", &block)
|
|
264
266
|
end
|
|
265
267
|
|
|
266
268
|
Datadog::RubyCoreSource
|
|
@@ -270,7 +272,7 @@ Datadog::RubyCoreSource
|
|
|
270
272
|
have_header("vm_core.h") &&
|
|
271
273
|
have_header("iseq.h", "vm_core.h") &&
|
|
272
274
|
# These are only used on Ruby 3+
|
|
273
|
-
(RUBY_VERSION < "3" || have_header("ractor_core.h") && have_header("internal/class.h"))
|
|
275
|
+
(RUBY_VERSION < "3" || have_header("ractor_core.h") && have_header("internal/class.h", "vm_core.h"))
|
|
274
276
|
|
|
275
277
|
if headers_available
|
|
276
278
|
# Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
|
|
@@ -1193,6 +1193,19 @@ VALUE heap_recorder_testonly_record_id_for(heap_recorder *heap_recorder, VALUE o
|
|
|
1193
1193
|
return context.result;
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
void heap_recorder_testonly_set_object_alloc_gen(heap_recorder *heap_recorder, long record_id, size_t alloc_gen) {
|
|
1197
|
+
if (heap_recorder == NULL) {
|
|
1198
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
object_record *record = NULL;
|
|
1202
|
+
if (!st_lookup(heap_recorder->object_records, (st_data_t) record_id, (st_data_t *) &record)) {
|
|
1203
|
+
raise_error(rb_eArgError, "record_id %ld is not being tracked", record_id);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
record->object_data.alloc_gen = alloc_gen;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1196
1209
|
void heap_recorder_testonly_exhaust_record_ids(heap_recorder *heap_recorder) {
|
|
1197
1210
|
if (heap_recorder == NULL) {
|
|
1198
1211
|
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
@@ -190,6 +190,10 @@ VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, lo
|
|
|
190
190
|
// NOTE: This is a linear scan over the tracked objects; fine for the small numbers tests track.
|
|
191
191
|
VALUE heap_recorder_testonly_record_id_for(heap_recorder *heap_recorder, VALUE obj);
|
|
192
192
|
|
|
193
|
+
// Overrides the GC gen a tracked object is considered to have been allocated in. This lets tests give an object an
|
|
194
|
+
// exact age, instead of relying on an exact number of GCs having run.
|
|
195
|
+
void heap_recorder_testonly_set_object_alloc_gen(heap_recorder *heap_recorder, long record_id, size_t alloc_gen);
|
|
196
|
+
|
|
193
197
|
// Used to ensure that a GC actually triggers an update of the objects
|
|
194
198
|
void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder);
|
|
195
199
|
|
|
@@ -278,6 +278,23 @@ VALUE thread_name_for(VALUE thread) {
|
|
|
278
278
|
return thread_struct_from_object(thread)->name;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
// This test doubles as a validation for the layout of the rb_thread_t structure. Because the `name` is almost at
|
|
282
|
+
// the end of the structure, if somehow we can't read the name, this probably means we're getting the structure wrong
|
|
283
|
+
// and we should stop immediately (this includes covering the use of `stat_insn_usage` on Ruby <= 3.2).
|
|
284
|
+
//
|
|
285
|
+
// One example where this happens is in patches such as
|
|
286
|
+
// https://github.com/gitlabhq/omnibus-gitlab/blob/master/config/patches/ruby/thread-memory-allocations-3.2.patch
|
|
287
|
+
// that add items to the structure and thus shift its position from where we expect.
|
|
288
|
+
void self_test_thread_name_for(void) {
|
|
289
|
+
VALUE thread = rb_eval_string("Thread.new { Thread.current.name = 'dd-prof-test' }.join");
|
|
290
|
+
VALUE expected_name = rb_funcall(thread, rb_intern("name"), 0);
|
|
291
|
+
if (thread_name_for(thread) != expected_name) {
|
|
292
|
+
rb_raise(rb_eRuntimeError,
|
|
293
|
+
"thread_name_for() self-test failed; this usually means the `rb_thread_t` on this Ruby doesn't match what the profiler expects"
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
281
298
|
// -----------------------------------------------------------------------------
|
|
282
299
|
// The sources below are modified versions of code extracted from the Ruby project.
|
|
283
300
|
// Each function is annotated with its origin, why we imported it, and the changes made.
|
|
@@ -539,6 +556,14 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
|
|
|
539
556
|
cfp = RUBY_VM_NEXT_CONTROL_FRAME(end_cfp);
|
|
540
557
|
|
|
541
558
|
for (i=0; i<limit && cfp != top_sentinel; cfp = RUBY_VM_NEXT_CONTROL_FRAME(cfp)) {
|
|
559
|
+
#ifndef NO_T_MOVED
|
|
560
|
+
if (cfp->iseq && RB_TYPE_P((VALUE) cfp->iseq, T_MOVED)) {
|
|
561
|
+
// The profiler is not supposed to sample during GC compaction, so T_MOVED is not expected here.
|
|
562
|
+
// Yet, crash tracking also uses this walker and may run at any time. For now, we choose to skip these frames.
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
#endif
|
|
566
|
+
|
|
542
567
|
if (cfp->iseq && !cfp->pc) {
|
|
543
568
|
// Fix: Do nothing -- this frame should not be used
|
|
544
569
|
//
|
|
@@ -982,8 +1007,15 @@ VALUE ddtrace_alloc_free_rb_mod_name(VALUE mod) {
|
|
|
982
1007
|
// Checking for '#' in the name is equivalent on older Rubies:
|
|
983
1008
|
// non-permanent names contain '#' from `#<Module:0x...>` or `#<Class:0x...>` prefixes.
|
|
984
1009
|
static bool has_permanent_classpath(DDTRACE_UNUSED VALUE mod, DDTRACE_UNUSED VALUE mod_name) {
|
|
985
|
-
#if defined(
|
|
986
|
-
|
|
1010
|
+
#if defined(RCLASS_EXT_PRIME) // 4.0+
|
|
1011
|
+
// We can't actually use RCLASS_PERMANENT_CLASSPATH_P(mod) because that uses
|
|
1012
|
+
// RCLASS_EXT_READABLE() which uses rb_current_box(), which is a private symbol:
|
|
1013
|
+
// https://github.com/DataDog/dd-trace-rb/issues/6338
|
|
1014
|
+
// Instead we use `RCLASS_EXT_PRIME(mod)->permanent_classpath`, which is equivalent for our usage:
|
|
1015
|
+
// the two can only differ for boxable (= core) modules with no permanent name, i.e. metaclasses like
|
|
1016
|
+
// String.singleton_class, and is_metaclass() already replaced those with the attached module, so we display
|
|
1017
|
+
// `String.foo` and not `#<Class:String>#foo`. Same for the name in ddtrace_alloc_free_rb_mod_name().
|
|
1018
|
+
return RCLASS_EXT_PRIME(mod)->permanent_classpath;
|
|
987
1019
|
#elif defined(HAVE_PERMANENT_CLASSPATH) // 3.3 - 3.4
|
|
988
1020
|
return RCLASS_EXT(mod)->permanent_classpath;
|
|
989
1021
|
#else // 3.2 and older
|
|
@@ -45,6 +45,7 @@ uint64_t native_thread_id_for(VALUE thread);
|
|
|
45
45
|
void ddtrace_thread_list(VALUE result_array);
|
|
46
46
|
bool is_thread_alive(VALUE thread);
|
|
47
47
|
VALUE thread_name_for(VALUE thread);
|
|
48
|
+
void self_test_thread_name_for(void);
|
|
48
49
|
|
|
49
50
|
int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
|
|
50
51
|
|
|
@@ -103,6 +103,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
|
|
106
|
+
self_test_thread_name_for();
|
|
106
107
|
self_test_clock_id();
|
|
107
108
|
self_test_current_fiber_for();
|
|
108
109
|
self_test_mn_enabled();
|
|
@@ -275,6 +275,7 @@ static VALUE _native_is_object_recorded(DDTRACE_UNUSED VALUE _self, VALUE record
|
|
|
275
275
|
static VALUE _native_record_id_for(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE obj);
|
|
276
276
|
static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
277
277
|
static VALUE _native_heap_recorder_exhaust_record_ids(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
278
|
+
static VALUE _native_heap_recorder_set_object_alloc_gen(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE record_id, VALUE alloc_gen);
|
|
278
279
|
static VALUE _native_recorder_heap_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance);
|
|
279
280
|
static VALUE _native_benchmark_intern(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE string, VALUE times, VALUE use_all);
|
|
280
281
|
static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE_UNUSED VALUE _self);
|
|
@@ -313,6 +314,7 @@ void stack_recorder_init(VALUE profiling_module) {
|
|
|
313
314
|
rb_define_singleton_method(testing_module, "_native_record_id_for", _native_record_id_for, 2);
|
|
314
315
|
rb_define_singleton_method(testing_module, "_native_heap_recorder_reset_last_update", _native_heap_recorder_reset_last_update, 1);
|
|
315
316
|
rb_define_singleton_method(testing_module, "_native_heap_recorder_exhaust_record_ids", _native_heap_recorder_exhaust_record_ids, 1);
|
|
317
|
+
rb_define_singleton_method(testing_module, "_native_heap_recorder_set_object_alloc_gen", _native_heap_recorder_set_object_alloc_gen, 3);
|
|
316
318
|
rb_define_singleton_method(testing_module, "_native_recorder_heap_update", _native_recorder_heap_update, 1);
|
|
317
319
|
rb_define_singleton_method(testing_module, "_native_benchmark_intern", _native_benchmark_intern, 4);
|
|
318
320
|
rb_define_singleton_method(testing_module, "_native_test_managed_string_storage_produces_valid_profiles", _native_test_managed_string_storage_produces_valid_profiles, 0);
|
|
@@ -537,10 +539,6 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
|
537
539
|
stack_recorder_state *state;
|
|
538
540
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
539
541
|
|
|
540
|
-
ddog_Timespec finish_timestamp = system_epoch_now_timespec();
|
|
541
|
-
// Need to do this while still holding the Global VM Lock; see comments on method for why
|
|
542
|
-
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
|
543
|
-
|
|
544
542
|
if (state->thread_context_collector_instance != Qnil) {
|
|
545
543
|
thread_context_collector_on_serialize(state->thread_context_collector_instance);
|
|
546
544
|
}
|
|
@@ -553,6 +551,10 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
|
553
551
|
heap_recorder_prepare_iteration(state->heap_recorder);
|
|
554
552
|
long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - heap_iteration_prep_start_time_ns;
|
|
555
553
|
|
|
554
|
+
ddog_Timespec finish_timestamp = system_epoch_now_timespec();
|
|
555
|
+
// Need to do this while still holding the Global VM Lock; see comments on method for why
|
|
556
|
+
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
|
557
|
+
|
|
556
558
|
// We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
|
|
557
559
|
// is pending
|
|
558
560
|
call_serialize_without_gvl_arguments args = {
|
|
@@ -1065,6 +1067,17 @@ static VALUE _native_heap_recorder_exhaust_record_ids(DDTRACE_UNUSED VALUE _self
|
|
|
1065
1067
|
return Qtrue;
|
|
1066
1068
|
}
|
|
1067
1069
|
|
|
1070
|
+
static VALUE _native_heap_recorder_set_object_alloc_gen(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE record_id, VALUE alloc_gen) {
|
|
1071
|
+
ENFORCE_TYPE(record_id, T_FIXNUM);
|
|
1072
|
+
ENFORCE_TYPE(alloc_gen, T_FIXNUM);
|
|
1073
|
+
|
|
1074
|
+
stack_recorder_state *state;
|
|
1075
|
+
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
1076
|
+
|
|
1077
|
+
heap_recorder_testonly_set_object_alloc_gen(state->heap_recorder, FIX2LONG(record_id), NUM2SIZET(alloc_gen));
|
|
1078
|
+
return Qtrue;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1068
1081
|
static VALUE _native_heap_recorder_reset_last_update(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance) {
|
|
1069
1082
|
stack_recorder_state *state;
|
|
1070
1083
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
@@ -7,20 +7,21 @@
|
|
|
7
7
|
#include "extconf.h"
|
|
8
8
|
#include "ruby_helpers.h"
|
|
9
9
|
|
|
10
|
-
#define SECONDS_AS_NS(value) (value *
|
|
11
|
-
#define MILLIS_AS_NS(value)
|
|
12
|
-
#define MICROS_AS_NS(value)
|
|
10
|
+
#define SECONDS_AS_NS(value) ((value) * 1000L * 1000L * 1000L)
|
|
11
|
+
#define MILLIS_AS_NS(value) ((value) * 1000L * 1000L)
|
|
12
|
+
#define MICROS_AS_NS(value) ((value) * 1000L)
|
|
13
13
|
|
|
14
14
|
typedef enum { RAISE_ON_FAILURE, DO_NOT_RAISE_ON_FAILURE } raise_on_failure_setting;
|
|
15
15
|
|
|
16
16
|
#define INVALID_TIME -1
|
|
17
17
|
|
|
18
18
|
typedef struct {
|
|
19
|
+
// INVALID_TIME until the first conversion, and the only field used to decide if the state needs to be (re)computed
|
|
19
20
|
long system_epoch_ns_reference;
|
|
20
21
|
long delta_to_epoch_ns;
|
|
21
22
|
} monotonic_to_system_epoch_state;
|
|
22
23
|
|
|
23
|
-
#define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME
|
|
24
|
+
#define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME}
|
|
24
25
|
|
|
25
26
|
static inline long retrieve_clock_as_ns(clockid_t clock_id, raise_on_failure_setting raise_on_failure) {
|
|
26
27
|
struct timespec clock_value;
|
data/ext/libdatadog_api/di.c
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
#include "datadog_ruby_common.h"
|
|
4
4
|
|
|
5
|
+
// rb_iseq is CRuby-specific
|
|
6
|
+
#ifndef TRUFFLERUBY
|
|
7
|
+
|
|
5
8
|
// Prototypes for Ruby functions declared in internal Ruby headers.
|
|
6
9
|
// rb_iseqw_new wraps an internal iseq pointer into a Ruby-visible
|
|
7
10
|
// RubyVM::InstructionSequence object.
|
|
@@ -207,7 +210,9 @@ void di_init(VALUE datadog_module) {
|
|
|
207
210
|
rb_define_singleton_method(di_module, "enter_probe", enter_probe, 0);
|
|
208
211
|
rb_define_singleton_method(di_module, "leave_probe", leave_probe, 0);
|
|
209
212
|
rb_define_singleton_method(di_module, "hash?", is_hash, 1);
|
|
210
|
-
#ifdef HAVE_RB_ISEQ_TYPE
|
|
211
|
-
|
|
212
|
-
#endif
|
|
213
|
+
#ifdef HAVE_RB_ISEQ_TYPE
|
|
214
|
+
rb_define_singleton_method(di_module, "iseq_type", iseq_type, 1);
|
|
215
|
+
#endif
|
|
213
216
|
}
|
|
217
|
+
|
|
218
|
+
#endif // TRUFFLERUBY
|
|
@@ -24,7 +24,7 @@ end
|
|
|
24
24
|
if ENV["DD_NO_EXTENSION"].to_s.strip.downcase == "true"
|
|
25
25
|
skip_building_extension!("the `DD_NO_EXTENSION` environment variable is/was set to `true` during installation")
|
|
26
26
|
end
|
|
27
|
-
skip_building_extension!("current Ruby VM is not supported")
|
|
27
|
+
skip_building_extension!("current Ruby VM is not supported") unless %w[ruby truffleruby].include?(RUBY_ENGINE)
|
|
28
28
|
skip_building_extension!("Microsoft Windows is not supported") if Gem.win_platform?
|
|
29
29
|
|
|
30
30
|
libdatadog_issue = Datadog::LibdatadogExtconfHelpers.load_libdatadog_or_get_issue
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// Forward declarations
|
|
14
14
|
static VALUE configuration_new(VALUE klass, VALUE json_str);
|
|
15
15
|
static void configuration_free(void *ptr);
|
|
16
|
+
static VALUE configuration_get_observe_full_evaluation_data(VALUE self);
|
|
16
17
|
static VALUE configuration_get_assignment(
|
|
17
18
|
VALUE self, VALUE flag_key, VALUE expected_type, VALUE context);
|
|
18
19
|
|
|
@@ -94,6 +95,7 @@ void feature_flags_init(VALUE core_module) {
|
|
|
94
95
|
rb_undef_alloc_func(configuration_class);
|
|
95
96
|
rb_define_singleton_method(configuration_class, "new", configuration_new, 1);
|
|
96
97
|
rb_define_method(configuration_class, "get_assignment", configuration_get_assignment, 3);
|
|
98
|
+
rb_define_method(configuration_class, "observe_full_evaluation_data", configuration_get_observe_full_evaluation_data, 0);
|
|
97
99
|
|
|
98
100
|
rb_gc_register_address(&resolution_details_class);
|
|
99
101
|
resolution_details_class = rb_define_class_under(feature_flags_module, "ResolutionDetails", rb_cObject);
|
|
@@ -137,6 +139,12 @@ static VALUE configuration_new(VALUE klass, VALUE json_str) {
|
|
|
137
139
|
return TypedData_Wrap_Struct(klass, &configuration_data_type, result.ok);
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
static VALUE configuration_get_observe_full_evaluation_data(VALUE self) {
|
|
143
|
+
ddog_ffe_Handle_Configuration configuration =
|
|
144
|
+
(ddog_ffe_Handle_Configuration)rb_check_typeddata(self, &configuration_data_type);
|
|
145
|
+
return ddog_ffe_configuration_get_observe_full_evaluation_data(configuration) ? Qtrue : Qfalse;
|
|
146
|
+
}
|
|
147
|
+
|
|
140
148
|
static void configuration_free(void *ptr) {
|
|
141
149
|
ddog_ffe_Handle_Configuration config = (ddog_ffe_Handle_Configuration)ptr;
|
|
142
150
|
ddog_ffe_configuration_drop(&config);
|