datadog 2.35.0 → 2.36.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 +40 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +68 -31
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +37 -18
- data/ext/datadog_profiling_native_extension/collectors_stack.h +8 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +434 -300
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +9 -7
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +7 -8
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -12
- data/ext/datadog_profiling_native_extension/extconf.rb +2 -2
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
- data/ext/datadog_profiling_native_extension/heap_recorder.c +44 -26
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +14 -35
- data/ext/datadog_profiling_native_extension/profiling.c +41 -4
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +33 -34
- data/ext/datadog_profiling_native_extension/stack_recorder.c +24 -3
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
- data/ext/libdatadog_api/datadog_ruby_common.c +7 -8
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -12
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/route_extractor.rb +6 -0
- data/lib/datadog/appsec/component.rb +1 -1
- data/lib/datadog/appsec/configuration.rb +7 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +37 -4
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +64 -19
- data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -0
- data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +41 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +20 -7
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +92 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +33 -0
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +17 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +20 -3
- data/lib/datadog/appsec/default_header_tags.rb +10 -6
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/configuration/settings.rb +1 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/remote/component.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_started.rb +0 -21
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/forking.rb +3 -1
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +3 -1
- data/lib/datadog/core.rb +3 -0
- data/lib/datadog/di/base.rb +4 -1
- data/lib/datadog/di/component.rb +1 -1
- data/lib/datadog/error_tracking/collector.rb +2 -1
- data/lib/datadog/error_tracking/component.rb +2 -2
- data/lib/datadog/kit/tracing/method_tracer.rb +4 -1
- data/lib/datadog/opentelemetry/sdk/propagator.rb +9 -3
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
- data/lib/datadog/profiling/collectors/thread_context.rb +1 -0
- data/lib/datadog/profiling/component.rb +13 -15
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/ruby_version.rb +25 -0
- data/lib/datadog/symbol_database/component.rb +306 -98
- data/lib/datadog/symbol_database/extractor.rb +223 -84
- data/lib/datadog/tracing/configuration/ext.rb +13 -0
- data/lib/datadog/tracing/configuration/settings.rb +17 -0
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -0
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/grpc.rb +1 -0
- data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/http.rb +1 -0
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/karafka.rb +1 -0
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -1
- data/lib/datadog/tracing/contrib/rack/route_inference.rb +3 -1
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +2 -0
- data/lib/datadog/tracing/contrib/waterdrop.rb +1 -0
- data/lib/datadog/tracing/distributed/propagation.rb +33 -1
- data/lib/datadog/tracing/distributed/trace_context.rb +11 -2
- data/lib/datadog/tracing/trace_digest.rb +7 -0
- data/lib/datadog/tracing/trace_operation.rb +4 -1
- data/lib/datadog/tracing/tracer.rb +1 -0
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +4 -1
- metadata +8 -5
|
@@ -8,18 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
void thread_context_collector_sample(
|
|
10
10
|
VALUE self_instance,
|
|
11
|
-
long current_monotonic_wall_time_ns
|
|
12
|
-
VALUE profiler_overhead_stack_thread
|
|
11
|
+
long current_monotonic_wall_time_ns
|
|
13
12
|
);
|
|
14
|
-
__attribute__((warn_unused_result)) bool thread_context_collector_prepare_sample_inside_signal_handler(
|
|
15
|
-
__attribute__((warn_unused_result)) bool thread_context_collector_sample_allocation(VALUE self_instance, unsigned int sample_weight, VALUE new_object);
|
|
13
|
+
__attribute__((warn_unused_result)) bool thread_context_collector_prepare_sample_inside_signal_handler(void);
|
|
14
|
+
__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);
|
|
16
15
|
void thread_context_collector_after_allocation(VALUE self_instance);
|
|
17
16
|
void thread_context_collector_sample_skipped_allocation_samples(VALUE self_instance, unsigned int skipped_samples);
|
|
18
17
|
VALUE thread_context_collector_sample_after_gc(VALUE self_instance);
|
|
19
18
|
void thread_context_collector_on_gc_start(VALUE self_instance);
|
|
20
19
|
__attribute__((warn_unused_result)) bool thread_context_collector_on_gc_finish(VALUE self_instance);
|
|
21
20
|
VALUE enforce_thread_context_collector_instance(VALUE object);
|
|
22
|
-
|
|
21
|
+
void thread_context_collector_stats(VALUE self_instance, VALUE stats_hash);
|
|
22
|
+
void thread_context_collector_stats_reset_not_thread_safe(VALUE self_instance);
|
|
23
|
+
void thread_context_collector_on_serialize(VALUE self_instance);
|
|
23
24
|
|
|
24
25
|
#ifndef NO_GVL_INSTRUMENTATION
|
|
25
26
|
typedef enum {
|
|
@@ -33,7 +34,8 @@ VALUE enforce_thread_context_collector_instance(VALUE object);
|
|
|
33
34
|
long waiting_for_gvl_duration_ns;
|
|
34
35
|
} on_gvl_running_result;
|
|
35
36
|
|
|
36
|
-
void thread_context_collector_on_gvl_waiting(
|
|
37
|
-
__attribute__((warn_unused_result)) on_gvl_running_result thread_context_collector_on_gvl_running(
|
|
37
|
+
void thread_context_collector_on_gvl_waiting(per_thread_context *thread_context);
|
|
38
|
+
__attribute__((warn_unused_result)) on_gvl_running_result thread_context_collector_on_gvl_running(VALUE self_instance, VALUE thread, per_thread_context *thread_context);
|
|
38
39
|
VALUE thread_context_collector_sample_after_gvl_running(VALUE self_instance, VALUE current_thread, long current_monotonic_wall_time_ns);
|
|
40
|
+
void thread_context_collector_on_gvl_released(per_thread_context *thread_context);
|
|
39
41
|
#endif
|
|
@@ -29,16 +29,15 @@ void private_raise_exception(VALUE exception, const char *static_message) {
|
|
|
29
29
|
rb_exc_raise(exception);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// Helper for raising pre-formatted exceptions
|
|
33
|
-
void private_raise_error_formatted(VALUE exception_class, const char *detailed_message, const char *static_message) {
|
|
34
|
-
VALUE exception = rb_exc_new_cstr(exception_class, detailed_message);
|
|
35
|
-
private_raise_exception(exception, static_message);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
32
|
// Use `raise_error` the macro instead, as it provides additional argument checks.
|
|
39
33
|
void private_raise_error(VALUE exception_class, const char *fmt, ...) {
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
va_list args;
|
|
35
|
+
va_start(args, fmt);
|
|
36
|
+
VALUE detailed_message = rb_vsprintf(fmt, args);
|
|
37
|
+
va_end(args);
|
|
38
|
+
|
|
39
|
+
VALUE exception = rb_exc_new_str(exception_class, detailed_message);
|
|
40
|
+
private_raise_exception(exception, fmt);
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
VALUE datadog_gem_version(void) {
|
|
@@ -47,11 +47,6 @@ NORETURN(
|
|
|
47
47
|
__attribute__ ((format (printf, 2, 3)));
|
|
48
48
|
);
|
|
49
49
|
|
|
50
|
-
// Internal helper for raising pre-formatted exceptions
|
|
51
|
-
NORETURN(
|
|
52
|
-
void private_raise_error_formatted(VALUE exception_class, const char *detailed_message, const char *static_message)
|
|
53
|
-
);
|
|
54
|
-
|
|
55
50
|
// Raises an exception with separate telemetry-safe and detailed messages.
|
|
56
51
|
// NOTE: Raising an exception always invokes Ruby code so it requires the GVL and is not compatible with "debug_enter_unsafe_context".
|
|
57
52
|
// @see debug_enter_unsafe_context
|
|
@@ -61,13 +56,6 @@ NORETURN(
|
|
|
61
56
|
|
|
62
57
|
#define MAX_RAISE_MESSAGE_SIZE 256
|
|
63
58
|
|
|
64
|
-
#define FORMAT_VA_ERROR_MESSAGE(buf, fmt) \
|
|
65
|
-
char buf[MAX_RAISE_MESSAGE_SIZE]; \
|
|
66
|
-
va_list buf##_args; \
|
|
67
|
-
va_start(buf##_args, fmt); \
|
|
68
|
-
vsnprintf(buf, MAX_RAISE_MESSAGE_SIZE, fmt, buf##_args); \
|
|
69
|
-
va_end(buf##_args);
|
|
70
|
-
|
|
71
59
|
// Helper to retrieve Datadog::VERSION::STRING
|
|
72
60
|
VALUE datadog_gem_version(void);
|
|
73
61
|
|
|
@@ -174,8 +174,8 @@ $defs << "-DUSE_RACTOR_INTERNAL_APIS_DIRECTLY" if RUBY_VERSION < "3.3"
|
|
|
174
174
|
# On older Rubies, there was no GVL instrumentation API and APIs created to support it
|
|
175
175
|
$defs << "-DNO_GVL_INSTRUMENTATION" if RUBY_VERSION < "3.2"
|
|
176
176
|
|
|
177
|
-
#
|
|
178
|
-
$defs << "-
|
|
177
|
+
# rb_internal_thread_specific_*()
|
|
178
|
+
$defs << "-DHAVE_RUBY_THREAD_STORAGE_API" if RUBY_VERSION >= "3.3"
|
|
179
179
|
|
|
180
180
|
# On older Rubies, there was no struct rb_native_thread. See private_vm_api_acccess.c for details.
|
|
181
181
|
$defs << "-DNO_RB_NATIVE_THREAD" if RUBY_VERSION < "3.2"
|
|
@@ -4,49 +4,10 @@
|
|
|
4
4
|
#include "datadog_ruby_common.h"
|
|
5
5
|
#include "gvl_profiling_helper.h"
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
rb_internal_thread_specific_key_t
|
|
7
|
+
#ifdef HAVE_RUBY_THREAD_STORAGE_API
|
|
8
|
+
rb_internal_thread_specific_key_t per_thread_context_key;
|
|
9
9
|
|
|
10
|
-
void
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
#endif
|
|
15
|
-
|
|
16
|
-
#ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS // Ruby 3.2
|
|
17
|
-
__thread gvl_profiling_thread gvl_waiting_tls;
|
|
18
|
-
static bool gvl_profiling_state_thread_tracking_workaround_installed = false;
|
|
19
|
-
|
|
20
|
-
static void on_thread_start(
|
|
21
|
-
DDTRACE_UNUSED rb_event_flag_t _unused1,
|
|
22
|
-
DDTRACE_UNUSED const rb_internal_thread_event_data_t *_unused2,
|
|
23
|
-
DDTRACE_UNUSED void *_unused3
|
|
24
|
-
) {
|
|
25
|
-
gvl_waiting_tls = (gvl_profiling_thread) {.thread = NULL};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Hack: We're using the gvl_waiting_tls native thread-local to store per-thread information. Unfortunately, Ruby puts a big hole
|
|
29
|
-
// in our plan because it reuses native threads -- specifically, in Ruby 3.2, native threads are still 1:1 to Ruby
|
|
30
|
-
// threads (M:N wasn't a thing yet) BUT once a Ruby thread dies, the VM will keep the native thread around for a
|
|
31
|
-
// bit, and if another Ruby thread starts right after, Ruby will reuse the native thread, rather than create a new one.
|
|
32
|
-
//
|
|
33
|
-
// This will mean that the new Ruby thread will still have the same native thread-local data that we set on the
|
|
34
|
-
// old thread. For the purposes of our tracking, where we're keeping a pointer to the current thread object in
|
|
35
|
-
// thread-local storage **this is disastrous** since it means we'll be pointing at the wrong thread (and its
|
|
36
|
-
// memory may have been freed or reused since!)
|
|
37
|
-
//
|
|
38
|
-
// To work around this issue, once GVL profiling is enabled, we install an event hook on thread start
|
|
39
|
-
// events that clears the thread-local data. This guarantees that there will be no stale data -- any existing
|
|
40
|
-
// data will be cleared at thread start.
|
|
41
|
-
//
|
|
42
|
-
// Note that once installed, this event hook becomes permanent -- stopping the profiler does not stop this event
|
|
43
|
-
// hook, unlike all others. This is because we can't afford to miss any thread start events while the
|
|
44
|
-
// profiler is stopped (e.g. during reconfiguration) as that would mean stale data once the profiler starts again.
|
|
45
|
-
void gvl_profiling_state_thread_tracking_workaround(void) {
|
|
46
|
-
if (gvl_profiling_state_thread_tracking_workaround_installed) return;
|
|
47
|
-
|
|
48
|
-
rb_internal_thread_add_event_hook(on_thread_start, RUBY_INTERNAL_THREAD_EVENT_STARTED, NULL);
|
|
49
|
-
|
|
50
|
-
gvl_profiling_state_thread_tracking_workaround_installed = true;
|
|
10
|
+
void per_thread_context_tls_init(void) {
|
|
11
|
+
per_thread_context_key = rb_internal_thread_specific_key_create();
|
|
51
12
|
}
|
|
52
13
|
#endif
|
|
@@ -6,62 +6,30 @@
|
|
|
6
6
|
|
|
7
7
|
#include "extconf.h"
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
#include <ruby.h>
|
|
11
|
-
#include <ruby/thread.h>
|
|
12
|
-
#include "datadog_ruby_common.h"
|
|
9
|
+
#include <ruby.h>
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
// Opaque to this header; the full definition lives in collectors_thread_context.c.
|
|
12
|
+
typedef struct per_thread_context per_thread_context;
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
#ifdef HAVE_RUBY_THREAD_STORAGE_API
|
|
15
|
+
#include <ruby/thread.h>
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
return (gvl_profiling_thread) {.thread = thread};
|
|
21
|
-
}
|
|
17
|
+
extern rb_internal_thread_specific_key_t per_thread_context_key;
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
return thread_from_thread_object(event_data->thread);
|
|
25
|
-
}
|
|
19
|
+
void per_thread_context_tls_init(void);
|
|
26
20
|
|
|
27
|
-
static inline
|
|
28
|
-
return
|
|
21
|
+
static inline per_thread_context* get_per_thread_context(VALUE thread) {
|
|
22
|
+
return rb_internal_thread_specific_get(thread, per_thread_context_key);
|
|
29
23
|
}
|
|
30
24
|
|
|
31
|
-
static inline void
|
|
32
|
-
rb_internal_thread_specific_set(thread
|
|
25
|
+
static inline void set_per_thread_context(VALUE thread, per_thread_context* value) {
|
|
26
|
+
rb_internal_thread_specific_set(thread, per_thread_context_key, value);
|
|
33
27
|
}
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
#ifdef USE_GVL_PROFILING_3_2_WORKAROUNDS // Ruby 3.2
|
|
37
|
-
typedef struct { void *thread; } gvl_profiling_thread;
|
|
38
|
-
extern __thread gvl_profiling_thread gvl_waiting_tls;
|
|
39
|
-
|
|
40
|
-
static inline void gvl_profiling_init(void) { }
|
|
41
|
-
|
|
42
|
-
// NOTE: This is a hack that relies on the knowledge that on Ruby 3.2 the
|
|
43
|
-
// RUBY_INTERNAL_THREAD_EVENT_READY and RUBY_INTERNAL_THREAD_EVENT_RESUMED events always get called on the thread they
|
|
44
|
-
// are about. Thus, we can use our thread local storage hack to get this data, even though the event doesn't include it.
|
|
45
|
-
static inline gvl_profiling_thread thread_from_event(DDTRACE_UNUSED const void *event_data) {
|
|
46
|
-
return gvl_waiting_tls;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
void gvl_profiling_state_thread_tracking_workaround(void);
|
|
50
|
-
gvl_profiling_thread gvl_profiling_state_maybe_initialize(void);
|
|
28
|
+
#else
|
|
29
|
+
static inline void per_thread_context_tls_init(void) { }
|
|
51
30
|
|
|
52
31
|
// Implementing these on Ruby 3.2 requires access to private VM things, so the following methods are
|
|
53
32
|
// implemented in `private_vm_api_access.c`
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
void gvl_profiling_state_set(gvl_profiling_thread thread, intptr_t value);
|
|
57
|
-
#endif
|
|
58
|
-
|
|
59
|
-
#ifndef NO_GVL_INSTRUMENTATION // For all Rubies supporting GVL profiling (3.2+)
|
|
60
|
-
static inline intptr_t gvl_profiling_state_thread_object_get(VALUE thread) {
|
|
61
|
-
return gvl_profiling_state_get(thread_from_thread_object(thread));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static inline void gvl_profiling_state_thread_object_set(VALUE thread, intptr_t value) {
|
|
65
|
-
gvl_profiling_state_set(thread_from_thread_object(thread), value);
|
|
66
|
-
}
|
|
33
|
+
per_thread_context* get_per_thread_context(VALUE thread);
|
|
34
|
+
void set_per_thread_context(VALUE thread, per_thread_context* value);
|
|
67
35
|
#endif
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
#include "libdatadog_helpers.h"
|
|
6
6
|
#include "time_helpers.h"
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// note on calloc vs ruby_xcalloc use:
|
|
9
9
|
// * Whenever we're allocating memory after being called by the Ruby VM in a "regular" situation (e.g. initializer)
|
|
10
10
|
// we should use `ruby_xcalloc` to give the VM visibility into what we're doing + give it a chance to manage GC
|
|
11
11
|
// * BUT, when we're being called during a sample, being in the middle of an object allocation is a very special
|
|
@@ -58,7 +58,7 @@ typedef struct {
|
|
|
58
58
|
heap_frame frames[];
|
|
59
59
|
} heap_record;
|
|
60
60
|
static heap_record* heap_record_new(heap_recorder*, ddog_prof_Slice_Location);
|
|
61
|
-
static void heap_record_free(heap_recorder*, heap_record
|
|
61
|
+
static void heap_record_free(heap_recorder*, heap_record*, bool should_unintern);
|
|
62
62
|
|
|
63
63
|
#if MAX_FRAMES_LIMIT > UINT16_MAX
|
|
64
64
|
#error Frames len type not compatible with MAX_FRAMES_LIMIT
|
|
@@ -75,7 +75,7 @@ typedef struct {
|
|
|
75
75
|
live_object_data object_data;
|
|
76
76
|
} object_record;
|
|
77
77
|
static object_record* object_record_new(long, heap_record*, live_object_data);
|
|
78
|
-
static void object_record_free(heap_recorder*, object_record
|
|
78
|
+
static void object_record_free(heap_recorder*, object_record*, bool should_unintern);
|
|
79
79
|
static VALUE object_record_inspect(heap_recorder*, object_record*);
|
|
80
80
|
static object_record SKIPPED_RECORD = {0};
|
|
81
81
|
|
|
@@ -196,8 +196,8 @@ typedef struct {
|
|
|
196
196
|
static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
|
|
197
197
|
static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
|
|
198
198
|
static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record);
|
|
199
|
-
static int
|
|
200
|
-
static int
|
|
199
|
+
static int st_heap_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
|
|
200
|
+
static int st_object_record_entry_free_no_unintern(st_data_t, st_data_t, st_data_t);
|
|
201
201
|
static int st_object_record_update(st_data_t, st_data_t, st_data_t);
|
|
202
202
|
static int st_object_records_iterate(st_data_t, st_data_t, st_data_t);
|
|
203
203
|
static int st_object_records_debug(st_data_t key, st_data_t value, st_data_t extra);
|
|
@@ -250,17 +250,24 @@ void heap_recorder_free(heap_recorder *heap_recorder) {
|
|
|
250
250
|
heap_recorder_finish_iteration(heap_recorder);
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
// NOTE: We don't unintern the strings referenced by the records we're about to free, thus we use
|
|
254
|
+
// `..._no_unintern` and `should_unintern: false`. This is intentional: `heap_recorder_free` is only ever called as part of
|
|
255
|
+
// tearing down the entire stack recorder, and the caller drops the whole managed string storage right after
|
|
256
|
+
// (see `stack_recorder_typed_data_free`), so there's no need to spend effort updating the managed string table.
|
|
257
|
+
// Crucially, this also keeps us from crashing: this code runs from the stack recorder's GC free callback, and
|
|
258
|
+
// because uninterning can fail, we can't raise exceptions in the middle of a dfree.
|
|
259
|
+
|
|
253
260
|
// Clean-up all object records
|
|
254
|
-
st_foreach(heap_recorder->object_records,
|
|
261
|
+
st_foreach(heap_recorder->object_records, st_object_record_entry_free_no_unintern, (st_data_t) heap_recorder);
|
|
255
262
|
st_free_table(heap_recorder->object_records);
|
|
256
263
|
|
|
257
264
|
// Clean-up all heap records (this includes those only referred to by queued_samples)
|
|
258
|
-
st_foreach(heap_recorder->heap_records,
|
|
265
|
+
st_foreach(heap_recorder->heap_records, st_heap_record_entry_free_no_unintern, (st_data_t) heap_recorder);
|
|
259
266
|
st_free_table(heap_recorder->heap_records);
|
|
260
267
|
|
|
261
268
|
if (heap_recorder->active_recording != NULL && heap_recorder->active_recording != &SKIPPED_RECORD) {
|
|
262
269
|
// If there's a partial object record, clean it up as well
|
|
263
|
-
object_record_free(heap_recorder, heap_recorder->active_recording);
|
|
270
|
+
object_record_free(heap_recorder, heap_recorder->active_recording, false);
|
|
264
271
|
}
|
|
265
272
|
|
|
266
273
|
ruby_xfree(heap_recorder->reusable_locations);
|
|
@@ -306,7 +313,7 @@ void heap_recorder_after_fork(heap_recorder *heap_recorder) {
|
|
|
306
313
|
// simply be noticed on next heap_recorder_prepare_iteration.
|
|
307
314
|
//
|
|
308
315
|
// There is one small caveat though: fork only preserves one thread and in a Ruby app, that
|
|
309
|
-
// will be the thread holding
|
|
316
|
+
// will be the thread holding the GVL. Since we support iteration on the heap recorder
|
|
310
317
|
// outside of the GVL, any state specific to that interaction may be inconsistent after fork
|
|
311
318
|
// (e.g. an acquired lock for thread safety). Iteration operates on object_records_snapshot
|
|
312
319
|
// though and that one will be updated on next heap_recorder_prepare_iteration so we really
|
|
@@ -714,15 +721,17 @@ VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
|
|
|
714
721
|
// ==========================
|
|
715
722
|
// Heap Recorder Internal API
|
|
716
723
|
// ==========================
|
|
717
|
-
|
|
724
|
+
// NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
|
|
725
|
+
static int st_heap_record_entry_free_no_unintern(st_data_t key, DDTRACE_UNUSED st_data_t value, st_data_t extra_arg) {
|
|
718
726
|
heap_recorder *recorder = (heap_recorder *) extra_arg;
|
|
719
|
-
heap_record_free(recorder, (heap_record *) key);
|
|
727
|
+
heap_record_free(recorder, (heap_record *) key, false);
|
|
720
728
|
return ST_DELETE;
|
|
721
729
|
}
|
|
722
730
|
|
|
723
|
-
|
|
731
|
+
// NOTE: Only expected to be used from heap_recorder_free, which will separately destroy the string table
|
|
732
|
+
static int st_object_record_entry_free_no_unintern(DDTRACE_UNUSED st_data_t key, st_data_t value, st_data_t extra_arg) {
|
|
724
733
|
heap_recorder *recorder = (heap_recorder *) extra_arg;
|
|
725
|
-
object_record_free(recorder, (object_record *) value);
|
|
734
|
+
object_record_free(recorder, (object_record *) value, false);
|
|
726
735
|
return ST_DELETE;
|
|
727
736
|
}
|
|
728
737
|
|
|
@@ -888,7 +897,7 @@ static heap_record* get_or_create_heap_record(heap_recorder *heap_recorder, ddog
|
|
|
888
897
|
heap_record *new_or_existing_record = NULL; // Will be set inside update_heap_record_entry_with_new_allocation
|
|
889
898
|
bool existing = st_update(heap_recorder->heap_records, (st_data_t) stack, update_heap_record_entry_with_new_allocation, (st_data_t) &new_or_existing_record);
|
|
890
899
|
if (existing) {
|
|
891
|
-
heap_record_free(heap_recorder, stack);
|
|
900
|
+
heap_record_free(heap_recorder, stack, true);
|
|
892
901
|
}
|
|
893
902
|
|
|
894
903
|
return new_or_existing_record;
|
|
@@ -903,7 +912,7 @@ static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_rec
|
|
|
903
912
|
if (!st_delete(heap_recorder->heap_records, (st_data_t*) &heap_record, NULL)) {
|
|
904
913
|
raise_error(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
|
|
905
914
|
};
|
|
906
|
-
heap_record_free(heap_recorder, heap_record);
|
|
915
|
+
heap_record_free(heap_recorder, heap_record, true);
|
|
907
916
|
}
|
|
908
917
|
|
|
909
918
|
static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, object_record *record) {
|
|
@@ -925,7 +934,7 @@ static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, obj
|
|
|
925
934
|
// One less object using this heap record, it may have become unused...
|
|
926
935
|
cleanup_heap_record_if_unused(heap_recorder, heap_record);
|
|
927
936
|
|
|
928
|
-
object_record_free(heap_recorder, record);
|
|
937
|
+
object_record_free(heap_recorder, record, true);
|
|
929
938
|
}
|
|
930
939
|
|
|
931
940
|
// =================
|
|
@@ -939,8 +948,12 @@ object_record* object_record_new(long obj_id, heap_record *heap_record, live_obj
|
|
|
939
948
|
return record;
|
|
940
949
|
}
|
|
941
950
|
|
|
942
|
-
void object_record_free(heap_recorder *recorder, object_record *record) {
|
|
943
|
-
|
|
951
|
+
void object_record_free(heap_recorder *recorder, object_record *record, bool should_unintern) {
|
|
952
|
+
// When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
|
|
953
|
+
// string table is going to be destroyed anyway) and if there's any failures we can't raise
|
|
954
|
+
// in the middle of a dfree callback.
|
|
955
|
+
if (should_unintern) unintern_or_raise(recorder, record->object_data.class);
|
|
956
|
+
|
|
944
957
|
free(record); // See "note on calloc vs ruby_xcalloc use" above
|
|
945
958
|
}
|
|
946
959
|
|
|
@@ -1012,15 +1025,20 @@ heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location l
|
|
|
1012
1025
|
return stack;
|
|
1013
1026
|
}
|
|
1014
1027
|
|
|
1015
|
-
void heap_record_free(heap_recorder *recorder, heap_record *stack) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
//
|
|
1019
|
-
|
|
1020
|
-
ids
|
|
1021
|
-
|
|
1028
|
+
void heap_record_free(heap_recorder *recorder, heap_record *stack, bool should_unintern) {
|
|
1029
|
+
// When tearing down the whole recorder state, we skip uninterning as it's not needed (the managed
|
|
1030
|
+
// string table is going to be destroyed anyway) and if there's any failures we can't raise
|
|
1031
|
+
// in the middle of a dfree callback.
|
|
1032
|
+
if (should_unintern) {
|
|
1033
|
+
ddog_prof_ManagedStringId *ids = recorder->reusable_ids;
|
|
1034
|
+
|
|
1035
|
+
// Put all the ids in the same array; doesn't really matter the order
|
|
1036
|
+
for (u_int16_t i = 0; i < stack->frames_len; i++) {
|
|
1037
|
+
ids[i] = stack->frames[i].filename;
|
|
1038
|
+
ids[i + stack->frames_len] = stack->frames[i].name;
|
|
1039
|
+
}
|
|
1040
|
+
unintern_all_or_raise(recorder, (ddog_prof_Slice_ManagedStringId) { .ptr = ids, .len = stack->frames_len * 2 });
|
|
1022
1041
|
}
|
|
1023
|
-
unintern_all_or_raise(recorder, (ddog_prof_Slice_ManagedStringId) { .ptr = ids, .len = stack->frames_len * 2 });
|
|
1024
1042
|
|
|
1025
1043
|
free(stack); // See "note on calloc vs ruby_xcalloc use" above
|
|
1026
1044
|
}
|
|
@@ -790,15 +790,11 @@ static inline int ddtrace_imemo_type(VALUE imemo) {
|
|
|
790
790
|
}
|
|
791
791
|
#endif
|
|
792
792
|
|
|
793
|
-
#
|
|
793
|
+
#ifndef HAVE_RUBY_THREAD_STORAGE_API
|
|
794
794
|
#include "gvl_profiling_helper.h"
|
|
795
795
|
|
|
796
|
-
gvl_profiling_thread thread_from_thread_object(VALUE thread) {
|
|
797
|
-
return (gvl_profiling_thread) {.thread = thread_struct_from_object(thread)};
|
|
798
|
-
}
|
|
799
|
-
|
|
800
796
|
// Hack: In Ruby 3.3+ we attach gvl profiling state to Ruby threads using the
|
|
801
|
-
// rb_internal_thread_specific_* APIs. These APIs did not exist on Ruby 3.2. On Ruby 3.2 we instead store the
|
|
797
|
+
// rb_internal_thread_specific_* APIs. These APIs did not exist on Ruby <= 3.2. On Ruby <= 3.2 we instead store the
|
|
802
798
|
// needed data inside the `rb_thread_t` structure, specifically in `stat_insn_usage` as a Ruby FIXNUM.
|
|
803
799
|
//
|
|
804
800
|
// Why `stat_insn_usage`? We needed some per-thread storage, and while looking at the Ruby VM sources I noticed
|
|
@@ -806,38 +802,21 @@ static inline int ddtrace_imemo_type(VALUE imemo) {
|
|
|
806
802
|
// code. There's a comment attached to it "/* statistics data for profiler */" but other than marking this
|
|
807
803
|
// field for GC, I could not find any place in the VM commit history or on GitHub where this has ever been used.
|
|
808
804
|
//
|
|
809
|
-
// Thus, since this hack is only for 3.2, which presumably will never see this field either removed or used
|
|
810
|
-
//
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
return
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
void gvl_profiling_state_set(gvl_profiling_thread thread, intptr_t value) {
|
|
820
|
-
if (thread.thread == NULL) return;
|
|
821
|
-
((rb_thread_t *)thread.thread)->stat_insn_usage = LONG2FIX(value);
|
|
805
|
+
// Thus, since this hack is only for Ruby <= 3.2, which presumably will never see this field either removed or used
|
|
806
|
+
// we... kinda take it for our own usage. It's ugly, I know...
|
|
807
|
+
//
|
|
808
|
+
// 64-bit pointers actually use 48-bit virtual addresses (https://muxup.com/2023q4/storing-data-in-pointers),
|
|
809
|
+
// so we are sure the addresses fit in Fixnums.
|
|
810
|
+
per_thread_context *get_per_thread_context(VALUE thread) {
|
|
811
|
+
VALUE current_value = thread_struct_from_object(thread)->stat_insn_usage;
|
|
812
|
+
return RB_FIXNUM_P(current_value) ? (per_thread_context *) FIX2LONG(current_value) : NULL;
|
|
822
813
|
}
|
|
823
814
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
// Specifically, this method was created to be called from a RUBY_INTERNAL_THREAD_EVENT_RESUMED callback --
|
|
828
|
-
// when it's triggered, we know the thread the code gets executed on is holding the GVL, so we use this
|
|
829
|
-
// opportunity to initialize our thread-local value.
|
|
830
|
-
gvl_profiling_thread gvl_profiling_state_maybe_initialize(void) {
|
|
831
|
-
gvl_profiling_thread current_thread = gvl_waiting_tls;
|
|
832
|
-
|
|
833
|
-
if (current_thread.thread == NULL) {
|
|
834
|
-
// threads.sched.running is the thread currently holding the GVL, which when this gets executed is the
|
|
835
|
-
// current thread!
|
|
836
|
-
current_thread = (gvl_profiling_thread) {.thread = (void *) rb_current_ractor()->threads.sched.running};
|
|
837
|
-
gvl_waiting_tls = current_thread;
|
|
815
|
+
void set_per_thread_context(VALUE thread, per_thread_context *value) {
|
|
816
|
+
if (!RB_FIXABLE((intptr_t) value)) {
|
|
817
|
+
rb_bug("per_thread_context pointer does not fit in a Fixnum: %p", value);
|
|
838
818
|
}
|
|
839
|
-
|
|
840
|
-
return current_thread;
|
|
819
|
+
thread_struct_from_object(thread)->stat_insn_usage = value ? LONG2FIX((intptr_t) value) : Qfalse;
|
|
841
820
|
}
|
|
842
821
|
#endif
|
|
843
822
|
|
|
@@ -28,8 +28,11 @@ void crashtracking_runtime_stacks_init(void);
|
|
|
28
28
|
void setup_signal_handler_init(VALUE profiling_module);
|
|
29
29
|
|
|
30
30
|
static VALUE native_working_p(VALUE self);
|
|
31
|
-
static VALUE
|
|
31
|
+
static VALUE _native_raise_error_value_arg(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message_value_arg);
|
|
32
|
+
static VALUE _native_grab_gvl_and_raise_cstr_arg(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message, VALUE test_message_arg, VALUE release_gvl);
|
|
32
33
|
static void *trigger_grab_gvl_and_raise(void *trigger_args);
|
|
34
|
+
static VALUE _native_grab_gvl_and_raise_value_arg(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message_value_arg, VALUE release_gvl);
|
|
35
|
+
static void *trigger_grab_gvl_and_raise_value_arg(void *trigger_args);
|
|
33
36
|
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);
|
|
34
37
|
static void *trigger_grab_gvl_and_raise_syserr(void *trigger_args);
|
|
35
38
|
static VALUE _native_ddtrace_rb_ractor_main_p(DDTRACE_UNUSED VALUE _self);
|
|
@@ -78,7 +81,9 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
78
81
|
|
|
79
82
|
// Hosts methods used for testing the native code using RSpec
|
|
80
83
|
VALUE testing_module = rb_define_module_under(native_extension_module, "Testing");
|
|
81
|
-
rb_define_singleton_method(testing_module, "
|
|
84
|
+
rb_define_singleton_method(testing_module, "_native_raise_error_value_arg", _native_raise_error_value_arg, 2);
|
|
85
|
+
rb_define_singleton_method(testing_module, "_native_grab_gvl_and_raise_cstr_arg", _native_grab_gvl_and_raise_cstr_arg, 4);
|
|
86
|
+
rb_define_singleton_method(testing_module, "_native_grab_gvl_and_raise_value_arg", _native_grab_gvl_and_raise_value_arg, 3);
|
|
82
87
|
rb_define_singleton_method(testing_module, "_native_grab_gvl_and_raise_syserr", _native_grab_gvl_and_raise_syserr, 4);
|
|
83
88
|
rb_define_singleton_method(testing_module, "_native_ddtrace_rb_ractor_main_p", _native_ddtrace_rb_ractor_main_p, 0);
|
|
84
89
|
rb_define_singleton_method(testing_module, "_native_is_current_thread_holding_the_gvl", _native_is_current_thread_holding_the_gvl, 0);
|
|
@@ -103,13 +108,17 @@ static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
|
|
|
103
108
|
return Qtrue;
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
static VALUE _native_raise_error_value_arg(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message_value_arg) {
|
|
112
|
+
raise_error(exception_class, ">%"PRIsVALUE"<", test_message_value_arg);
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
typedef struct {
|
|
107
116
|
VALUE exception_class;
|
|
108
117
|
char *test_message;
|
|
109
118
|
char *test_message_arg;
|
|
110
119
|
} trigger_grab_gvl_and_raise_arguments;
|
|
111
120
|
|
|
112
|
-
static VALUE
|
|
121
|
+
static VALUE _native_grab_gvl_and_raise_cstr_arg(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message, VALUE test_message_arg, VALUE release_gvl) {
|
|
113
122
|
ENFORCE_TYPE(test_message, T_STRING);
|
|
114
123
|
|
|
115
124
|
trigger_grab_gvl_and_raise_arguments args;
|
|
@@ -124,7 +133,7 @@ static VALUE _native_grab_gvl_and_raise(DDTRACE_UNUSED VALUE _self, VALUE except
|
|
|
124
133
|
private_grab_gvl_and_raise(args.exception_class, 0, args.test_message, args.test_message_arg);
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
raise_error(rb_eRuntimeError, "Failed to raise exception in
|
|
136
|
+
raise_error(rb_eRuntimeError, "Failed to raise exception in _native_grab_gvl_and_raise_cstr_arg; this should never happen");
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
static void *trigger_grab_gvl_and_raise(void *trigger_args) {
|
|
@@ -139,6 +148,34 @@ static void *trigger_grab_gvl_and_raise(void *trigger_args) {
|
|
|
139
148
|
return NULL;
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
typedef struct {
|
|
152
|
+
VALUE exception_class;
|
|
153
|
+
VALUE test_message_value_arg;
|
|
154
|
+
} trigger_grab_gvl_and_raise_value_arguments;
|
|
155
|
+
|
|
156
|
+
static VALUE _native_grab_gvl_and_raise_value_arg(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message_value_arg, VALUE release_gvl) {
|
|
157
|
+
trigger_grab_gvl_and_raise_value_arguments args;
|
|
158
|
+
|
|
159
|
+
args.exception_class = exception_class;
|
|
160
|
+
args.test_message_value_arg = test_message_value_arg;
|
|
161
|
+
|
|
162
|
+
if (RTEST(release_gvl)) {
|
|
163
|
+
rb_thread_call_without_gvl(trigger_grab_gvl_and_raise_value_arg, &args, NULL, NULL);
|
|
164
|
+
} else {
|
|
165
|
+
private_grab_gvl_and_raise(args.exception_class, 0, ">%"PRIsVALUE"<", args.test_message_value_arg);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
raise_error(rb_eRuntimeError, "Failed to raise exception in _native_grab_gvl_and_raise_value_arg; this should never happen");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static void *trigger_grab_gvl_and_raise_value_arg(void *trigger_args) {
|
|
172
|
+
trigger_grab_gvl_and_raise_value_arguments *args = (trigger_grab_gvl_and_raise_value_arguments *) trigger_args;
|
|
173
|
+
|
|
174
|
+
private_grab_gvl_and_raise(args->exception_class, 0, ">%"PRIsVALUE"<", args->test_message_value_arg);
|
|
175
|
+
|
|
176
|
+
return NULL;
|
|
177
|
+
}
|
|
178
|
+
|
|
142
179
|
typedef struct {
|
|
143
180
|
int syserr_errno;
|
|
144
181
|
char *test_message;
|