datadog 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/ext/datadog_profiling_loader/extconf.rb +15 -15
- data/ext/datadog_profiling_native_extension/clock_id.h +1 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -2
- data/ext/datadog_profiling_native_extension/clock_id_noop.c +1 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +113 -43
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +49 -26
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +34 -4
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +4 -0
- data/ext/datadog_profiling_native_extension/collectors_stack.c +49 -37
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +81 -19
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +110 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +57 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +65 -60
- data/ext/datadog_profiling_native_extension/heap_recorder.c +34 -6
- data/ext/datadog_profiling_native_extension/heap_recorder.h +3 -1
- data/ext/datadog_profiling_native_extension/helpers.h +6 -17
- data/ext/datadog_profiling_native_extension/http_transport.c +3 -3
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +0 -86
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +2 -23
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +61 -172
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +64 -138
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +17 -11
- data/ext/datadog_profiling_native_extension/profiling.c +0 -2
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +0 -33
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +1 -26
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +14 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/datadog_profiling_native_extension/time_helpers.c +0 -15
- data/ext/datadog_profiling_native_extension/time_helpers.h +36 -6
- data/ext/{datadog_profiling_native_extension → libdatadog_api}/crashtracker.c +19 -6
- data/ext/libdatadog_api/datadog_ruby_common.c +110 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +57 -0
- data/ext/libdatadog_api/extconf.rb +108 -0
- data/ext/libdatadog_api/macos_development.md +26 -0
- data/ext/libdatadog_extconf_helpers.rb +130 -0
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +49 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +73 -0
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +68 -0
- data/lib/datadog/appsec/contrib/graphql/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/graphql/patcher.rb +37 -0
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +59 -0
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +1 -1
- data/lib/datadog/appsec/processor/actions.rb +1 -1
- data/lib/datadog/appsec/response.rb +15 -1
- data/lib/datadog/appsec.rb +1 -0
- data/lib/datadog/core/configuration/components.rb +14 -12
- data/lib/datadog/core/configuration/settings.rb +54 -7
- data/lib/datadog/core/crashtracking/agent_base_url.rb +21 -0
- data/lib/datadog/core/crashtracking/component.rb +111 -0
- data/lib/datadog/core/crashtracking/tag_builder.rb +39 -0
- data/lib/datadog/core/diagnostics/environment_logger.rb +8 -11
- data/lib/datadog/core/telemetry/component.rb +49 -2
- data/lib/datadog/core/telemetry/emitter.rb +9 -11
- data/lib/datadog/core/telemetry/event.rb +32 -1
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/http/adapters/net.rb +10 -12
- data/lib/datadog/core/telemetry/http/ext.rb +3 -0
- data/lib/datadog/core/telemetry/http/transport.rb +38 -9
- data/lib/datadog/core/telemetry/logging.rb +35 -0
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +102 -0
- data/lib/datadog/kit/appsec/events.rb +2 -4
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +10 -0
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +23 -0
- data/lib/datadog/profiling/collectors/code_provenance.rb +7 -7
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +17 -17
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +11 -13
- data/lib/datadog/profiling/collectors/info.rb +3 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +4 -2
- data/lib/datadog/profiling/component.rb +69 -91
- data/lib/datadog/profiling/exporter.rb +3 -3
- data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
- data/lib/datadog/profiling/ext.rb +21 -21
- data/lib/datadog/profiling/flush.rb +1 -1
- data/lib/datadog/profiling/http_transport.rb +8 -6
- data/lib/datadog/profiling/load_native_extension.rb +5 -5
- data/lib/datadog/profiling/preload.rb +1 -1
- data/lib/datadog/profiling/profiler.rb +5 -8
- data/lib/datadog/profiling/scheduler.rb +31 -25
- data/lib/datadog/profiling/tag_builder.rb +2 -2
- data/lib/datadog/profiling/tasks/exec.rb +5 -5
- data/lib/datadog/profiling/tasks/setup.rb +16 -35
- data/lib/datadog/profiling.rb +4 -5
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/ext.rb +14 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +1 -1
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +4 -1
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +16 -0
- data/lib/datadog/tracing/contrib/mysql2/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/mysql2/instrumentation.rb +17 -13
- data/lib/datadog/tracing/contrib/pg/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +4 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment/ext.rb +28 -0
- data/lib/datadog/tracing/contrib/propagation/sql_comment/mode.rb +5 -1
- data/lib/datadog/tracing/contrib/propagation/sql_comment.rb +22 -10
- data/lib/datadog/tracing/contrib/trilogy/configuration/settings.rb +5 -0
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +4 -1
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +14 -16
- data/lib/datadog/tracing/metadata/errors.rb +9 -1
- data/lib/datadog/tracing/metadata/ext.rb +4 -0
- data/lib/datadog/tracing/pipeline/span_filter.rb +2 -2
- data/lib/datadog/tracing/span.rb +9 -2
- data/lib/datadog/tracing/span_event.rb +41 -0
- data/lib/datadog/tracing/span_operation.rb +6 -2
- data/lib/datadog/tracing/transport/serializable_trace.rb +3 -0
- data/lib/datadog/version.rb +1 -1
- metadata +28 -10
- data/lib/datadog/profiling/crashtracker.rb +0 -91
- data/lib/datadog/profiling/ext/forking.rb +0 -98
@@ -18,6 +18,22 @@ typedef struct {
|
|
18
18
|
rb_nativethread_id_t owner;
|
19
19
|
} current_gvl_owner;
|
20
20
|
|
21
|
+
typedef struct frame_info {
|
22
|
+
union {
|
23
|
+
struct {
|
24
|
+
VALUE iseq;
|
25
|
+
void *caching_pc; // For caching only
|
26
|
+
int line;
|
27
|
+
} ruby_frame;
|
28
|
+
struct {
|
29
|
+
VALUE caching_cme; // For caching only
|
30
|
+
ID method_id;
|
31
|
+
} native_frame;
|
32
|
+
} as;
|
33
|
+
bool is_ruby_frame : 1;
|
34
|
+
bool same_frame : 1;
|
35
|
+
} frame_info;
|
36
|
+
|
21
37
|
rb_nativethread_id_t pthread_id_for(VALUE thread);
|
22
38
|
bool is_current_thread_holding_the_gvl(void);
|
23
39
|
current_gvl_owner gvl_owner(void);
|
@@ -27,20 +43,10 @@ void ddtrace_thread_list(VALUE result_array);
|
|
27
43
|
bool is_thread_alive(VALUE thread);
|
28
44
|
VALUE thread_name_for(VALUE thread);
|
29
45
|
|
30
|
-
int ddtrace_rb_profile_frames(VALUE thread, int start, int limit,
|
46
|
+
int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
|
31
47
|
// Returns true if the current thread belongs to the main Ractor or if Ruby has no Ractor support
|
32
48
|
bool ddtrace_rb_ractor_main_p(void);
|
33
49
|
|
34
|
-
// Ruby 3.0 finally added support for showing CFUNC frames (frames for methods written using native code)
|
35
|
-
// in stack traces gathered via `rb_profile_frames` (https://github.com/ruby/ruby/pull/3299).
|
36
|
-
// To access this information on older Rubies, beyond using our custom `ddtrace_rb_profile_frames` above, we also need
|
37
|
-
// to backport the Ruby 3.0+ version of `rb_profile_frame_method_name`.
|
38
|
-
#ifdef USE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME
|
39
|
-
VALUE ddtrace_rb_profile_frame_method_name(VALUE frame);
|
40
|
-
#else // Ruby > 3.0, just use the stock functionality
|
41
|
-
#define ddtrace_rb_profile_frame_method_name rb_profile_frame_method_name
|
42
|
-
#endif
|
43
|
-
|
44
50
|
// See comment on `record_placeholder_stack_in_native_code` for a full explanation of what this means (and why we don't just return 0)
|
45
51
|
#define PLACEHOLDER_STACK_IN_NATIVE_CODE -1
|
46
52
|
|
@@ -19,7 +19,6 @@ void collectors_dynamic_sampling_rate_init(VALUE profiling_module);
|
|
19
19
|
void collectors_idle_sampling_helper_init(VALUE profiling_module);
|
20
20
|
void collectors_stack_init(VALUE profiling_module);
|
21
21
|
void collectors_thread_context_init(VALUE profiling_module);
|
22
|
-
void crashtracker_init(VALUE profiling_module);
|
23
22
|
void http_transport_init(VALUE profiling_module);
|
24
23
|
void stack_recorder_init(VALUE profiling_module);
|
25
24
|
|
@@ -54,7 +53,6 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
54
53
|
collectors_idle_sampling_helper_init(profiling_module);
|
55
54
|
collectors_stack_init(profiling_module);
|
56
55
|
collectors_thread_context_init(profiling_module);
|
57
|
-
crashtracker_init(profiling_module);
|
58
56
|
http_transport_init(profiling_module);
|
59
57
|
stack_recorder_init(profiling_module);
|
60
58
|
|
@@ -20,29 +20,6 @@ void ruby_helpers_init(void) {
|
|
20
20
|
to_s_id = rb_intern("to_s");
|
21
21
|
}
|
22
22
|
|
23
|
-
void raise_unexpected_type(
|
24
|
-
VALUE value,
|
25
|
-
const char *value_name,
|
26
|
-
const char *type_name,
|
27
|
-
const char *file,
|
28
|
-
int line,
|
29
|
-
const char* function_name
|
30
|
-
) {
|
31
|
-
rb_exc_raise(
|
32
|
-
rb_exc_new_str(
|
33
|
-
rb_eTypeError,
|
34
|
-
rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
|
35
|
-
rb_inspect(value),
|
36
|
-
value_name,
|
37
|
-
type_name,
|
38
|
-
file,
|
39
|
-
line,
|
40
|
-
function_name
|
41
|
-
)
|
42
|
-
)
|
43
|
-
);
|
44
|
-
}
|
45
|
-
|
46
23
|
#define MAX_RAISE_MESSAGE_SIZE 256
|
47
24
|
|
48
25
|
struct raise_arguments {
|
@@ -255,13 +232,3 @@ VALUE ruby_safe_inspect(VALUE obj) {
|
|
255
232
|
return rb_str_new_cstr("(Not inspectable)");
|
256
233
|
}
|
257
234
|
}
|
258
|
-
|
259
|
-
VALUE ddtrace_version(void) {
|
260
|
-
VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
|
261
|
-
ENFORCE_TYPE(ddtrace_module, T_MODULE);
|
262
|
-
VALUE version_module = rb_const_get(ddtrace_module, rb_intern("VERSION"));
|
263
|
-
ENFORCE_TYPE(version_module, T_MODULE);
|
264
|
-
VALUE version_string = rb_const_get(version_module, rb_intern("STRING"));
|
265
|
-
ENFORCE_TYPE(version_string, T_STRING);
|
266
|
-
return version_string;
|
267
|
-
}
|
@@ -1,9 +1,7 @@
|
|
1
1
|
#pragma once
|
2
2
|
|
3
|
-
#include <ruby.h>
|
4
3
|
#include <stdbool.h>
|
5
|
-
|
6
|
-
#include "helpers.h"
|
4
|
+
#include "datadog_ruby_common.h"
|
7
5
|
|
8
6
|
// Initialize internal data needed by some ruby helpers. Should be called during start, before any actual
|
9
7
|
// usage of ruby helpers.
|
@@ -39,27 +37,6 @@ static inline int check_if_pending_exception(void) {
|
|
39
37
|
return pending_exception;
|
40
38
|
}
|
41
39
|
|
42
|
-
#define ADD_QUOTES_HELPER(x) #x
|
43
|
-
#define ADD_QUOTES(x) ADD_QUOTES_HELPER(x)
|
44
|
-
|
45
|
-
// Ruby has a Check_Type(value, type) that is roughly equivalent to this BUT Ruby's version is rather cryptic when it fails
|
46
|
-
// e.g. "wrong argument type nil (expected String)". This is a replacement that prints more information to help debugging.
|
47
|
-
#define ENFORCE_TYPE(value, type) \
|
48
|
-
{ if (RB_UNLIKELY(!RB_TYPE_P(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), ADD_QUOTES(type), __FILE__, __LINE__, __func__); }
|
49
|
-
|
50
|
-
#define ENFORCE_BOOLEAN(value) \
|
51
|
-
{ if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); }
|
52
|
-
|
53
|
-
// Called by ENFORCE_TYPE; should not be used directly
|
54
|
-
NORETURN(void raise_unexpected_type(
|
55
|
-
VALUE value,
|
56
|
-
const char *value_name,
|
57
|
-
const char *type_name,
|
58
|
-
const char *file,
|
59
|
-
int line,
|
60
|
-
const char *function_name
|
61
|
-
));
|
62
|
-
|
63
40
|
#define VALUE_COUNT(array) (sizeof(array) / sizeof(VALUE))
|
64
41
|
|
65
42
|
NORETURN(
|
@@ -113,5 +90,3 @@ size_t ruby_obj_memsize_of(VALUE obj);
|
|
113
90
|
// return a string with the result of that call. Elsif the object responds to
|
114
91
|
// 'to_s', return a string with the result of that call. Otherwise, return Qnil.
|
115
92
|
VALUE ruby_safe_inspect(VALUE obj);
|
116
|
-
|
117
|
-
VALUE ddtrace_version(void);
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#pragma once
|
2
2
|
|
3
3
|
#include <signal.h>
|
4
|
+
#include "datadog_ruby_common.h"
|
4
5
|
|
5
6
|
void empty_signal_handler(DDTRACE_UNUSED int _signal, DDTRACE_UNUSED siginfo_t *_info, DDTRACE_UNUSED void *_ucontext);
|
6
7
|
void install_sigprof_signal_handler(void (*signal_handler_function)(int, siginfo_t *, void *), const char *handler_pretty_name);
|
@@ -342,6 +342,10 @@ static VALUE _native_new(VALUE klass) {
|
|
342
342
|
// Note: At this point, slot_one_profile and slot_two_profile contain null pointers. Libdatadog validates pointers
|
343
343
|
// before using them so it's ok for us to go ahead and create the StackRecorder object.
|
344
344
|
|
345
|
+
// Note: As of this writing, no new Ruby objects get created and stored in the state. If that ever changes, remember
|
346
|
+
// to keep them on the stack and mark them with RB_GC_GUARD -- otherwise it's possible for a GC to run and
|
347
|
+
// since the instance representing the state does not yet exist, such objects will not get marked.
|
348
|
+
|
345
349
|
VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state);
|
346
350
|
|
347
351
|
// NOTE: We initialize this because we want a new recorder to be operational even without initialization and our
|
@@ -612,12 +616,20 @@ void record_sample(VALUE recorder_instance, ddog_prof_Slice_Location locations,
|
|
612
616
|
metric_values[position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID]] = values.alloc_samples_unscaled;
|
613
617
|
metric_values[position_for[TIMELINE_VALUE_ID]] = values.timeline_wall_time_ns;
|
614
618
|
|
615
|
-
if (values.
|
619
|
+
if (values.heap_sample) {
|
616
620
|
// If we got an allocation sample end the heap allocation recording to commit the heap sample.
|
617
621
|
// FIXME: Heap sampling currently has to be done in 2 parts because the construction of locations is happening
|
618
622
|
// very late in the allocation-sampling path (which is shared with the cpu sampling path). This can
|
619
623
|
// be fixed with some refactoring but for now this leads to a less impactful change.
|
620
|
-
|
624
|
+
//
|
625
|
+
// NOTE: The heap recorder is allowed to raise exceptions if something's wrong. But we also need to handle it
|
626
|
+
// on this side to make sure we properly unlock the active slot mutex on our way out. Otherwise, this would
|
627
|
+
// later lead to deadlocks (since the active slot mutex is not expected to be locked forever).
|
628
|
+
int exception_state = end_heap_allocation_recording_with_rb_protect(state->heap_recorder, locations);
|
629
|
+
if (exception_state) {
|
630
|
+
sampler_unlock_active_profile(active_slot);
|
631
|
+
rb_jump_tag(exception_state);
|
632
|
+
}
|
621
633
|
}
|
622
634
|
|
623
635
|
ddog_prof_Profile_Result result = ddog_prof_Profile_add(
|
@@ -4,21 +4,6 @@
|
|
4
4
|
#include "ruby_helpers.h"
|
5
5
|
#include "time_helpers.h"
|
6
6
|
|
7
|
-
// Safety: This function is assumed never to raise exceptions by callers when raise_on_failure == false
|
8
|
-
long retrieve_clock_as_ns(clockid_t clock_id, bool raise_on_failure) {
|
9
|
-
struct timespec clock_value;
|
10
|
-
|
11
|
-
if (clock_gettime(clock_id, &clock_value) != 0) {
|
12
|
-
if (raise_on_failure) ENFORCE_SUCCESS_GVL(errno);
|
13
|
-
return 0;
|
14
|
-
}
|
15
|
-
|
16
|
-
return clock_value.tv_nsec + SECONDS_AS_NS(clock_value.tv_sec);
|
17
|
-
}
|
18
|
-
|
19
|
-
long monotonic_wall_time_now_ns(bool raise_on_failure) { return retrieve_clock_as_ns(CLOCK_MONOTONIC, raise_on_failure); }
|
20
|
-
long system_epoch_time_now_ns(bool raise_on_failure) { return retrieve_clock_as_ns(CLOCK_REALTIME, raise_on_failure); }
|
21
|
-
|
22
7
|
// Design: The monotonic_to_system_epoch_state struct is kept somewhere by the caller, and MUST be initialized to
|
23
8
|
// MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER.
|
24
9
|
//
|
@@ -1,12 +1,16 @@
|
|
1
1
|
#pragma once
|
2
2
|
|
3
3
|
#include <stdbool.h>
|
4
|
+
#include <errno.h>
|
5
|
+
#include <time.h>
|
6
|
+
|
7
|
+
#include "extconf.h" // Needed for HAVE_CLOCK_MONOTONIC_COARSE
|
8
|
+
#include "ruby_helpers.h"
|
4
9
|
|
5
10
|
#define SECONDS_AS_NS(value) (value * 1000 * 1000 * 1000L)
|
6
11
|
#define MILLIS_AS_NS(value) (value * 1000 * 1000L)
|
7
12
|
|
8
|
-
|
9
|
-
#define DO_NOT_RAISE_ON_FAILURE false
|
13
|
+
typedef enum { RAISE_ON_FAILURE, DO_NOT_RAISE_ON_FAILURE } raise_on_failure_setting;
|
10
14
|
|
11
15
|
#define INVALID_TIME -1
|
12
16
|
|
@@ -17,10 +21,36 @@ typedef struct {
|
|
17
21
|
|
18
22
|
#define MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER {.system_epoch_ns_reference = INVALID_TIME, .delta_to_epoch_ns = INVALID_TIME}
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
static inline long retrieve_clock_as_ns(clockid_t clock_id, raise_on_failure_setting raise_on_failure) {
|
25
|
+
struct timespec clock_value;
|
26
|
+
|
27
|
+
if (clock_gettime(clock_id, &clock_value) != 0) {
|
28
|
+
if (raise_on_failure == RAISE_ON_FAILURE) ENFORCE_SUCCESS_GVL(errno);
|
29
|
+
return 0;
|
30
|
+
}
|
31
|
+
|
32
|
+
return clock_value.tv_nsec + SECONDS_AS_NS(clock_value.tv_sec);
|
33
|
+
}
|
34
|
+
|
35
|
+
static inline long monotonic_wall_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_MONOTONIC, raise_on_failure); }
|
36
|
+
static inline long system_epoch_time_now_ns(raise_on_failure_setting raise_on_failure) { return retrieve_clock_as_ns(CLOCK_REALTIME, raise_on_failure); }
|
37
|
+
|
38
|
+
// Coarse instants use CLOCK_MONOTONIC_COARSE on Linux which is expected to provide resolution in the millisecond range:
|
39
|
+
// https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/sect-posix_clocks#Using_clock_getres_to_compare_clock_resolution
|
40
|
+
// We introduce here a separate type for it, so as to make it harder to misuse/more explicit when these timestamps are used
|
41
|
+
|
42
|
+
typedef struct coarse_instant {
|
43
|
+
long timestamp_ns;
|
44
|
+
} coarse_instant;
|
45
|
+
|
46
|
+
static inline coarse_instant to_coarse_instant(long timestamp_ns) { return (coarse_instant) {.timestamp_ns = timestamp_ns}; }
|
22
47
|
|
23
|
-
|
24
|
-
|
48
|
+
static inline coarse_instant monotonic_coarse_wall_time_now_ns(void) {
|
49
|
+
#ifdef HAVE_CLOCK_MONOTONIC_COARSE // Linux
|
50
|
+
return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC_COARSE, DO_NOT_RAISE_ON_FAILURE));
|
51
|
+
#else // macOS
|
52
|
+
return to_coarse_instant(retrieve_clock_as_ns(CLOCK_MONOTONIC, DO_NOT_RAISE_ON_FAILURE));
|
53
|
+
#endif
|
54
|
+
}
|
25
55
|
|
26
56
|
long monotonic_to_system_epoch_ns(monotonic_to_system_epoch_state *state, long monotonic_wall_time_ns);
|
@@ -1,15 +1,25 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
-
#include <datadog/
|
3
|
-
|
2
|
+
#include <datadog/profiling.h>
|
3
|
+
|
4
|
+
#include "datadog_ruby_common.h"
|
4
5
|
|
5
6
|
static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
6
7
|
static VALUE _native_stop(DDTRACE_UNUSED VALUE _self);
|
8
|
+
static void crashtracker_init(VALUE crashtracking_module);
|
7
9
|
|
8
10
|
// Used to report Ruby VM crashes.
|
9
11
|
// Once initialized, segfaults will be reported automatically using libdatadog.
|
10
12
|
|
11
|
-
void
|
12
|
-
VALUE
|
13
|
+
void DDTRACE_EXPORT Init_libdatadog_api(void) {
|
14
|
+
VALUE datadog_module = rb_define_module("Datadog");
|
15
|
+
VALUE core_module = rb_define_module_under(datadog_module, "Core");
|
16
|
+
VALUE crashtracking_module = rb_define_module_under(core_module, "Crashtracking");
|
17
|
+
|
18
|
+
crashtracker_init(crashtracking_module);
|
19
|
+
}
|
20
|
+
|
21
|
+
void crashtracker_init(VALUE crashtracking_module) {
|
22
|
+
VALUE crashtracker_class = rb_define_class_under(crashtracking_module, "Component", rb_cObject);
|
13
23
|
|
14
24
|
rb_define_singleton_method(crashtracker_class, "_native_start_or_update_on_fork", _native_start_or_update_on_fork, -1);
|
15
25
|
rb_define_singleton_method(crashtracker_class, "_native_stop", _native_stop, 0);
|
@@ -38,7 +48,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
|
|
38
48
|
|
39
49
|
if (action != start_action && action != update_on_fork_action) rb_raise(rb_eArgError, "Unexpected action: %+"PRIsVALUE, action);
|
40
50
|
|
41
|
-
VALUE version =
|
51
|
+
VALUE version = datadog_gem_version();
|
42
52
|
ddog_prof_Endpoint endpoint = endpoint_from(exporter_configuration);
|
43
53
|
|
44
54
|
// Tags are heap-allocated, so after here we can't raise exceptions otherwise we'll leak this memory
|
@@ -59,6 +69,9 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
|
|
59
69
|
.endpoint = endpoint,
|
60
70
|
.resolve_frames = DDOG_PROF_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER,
|
61
71
|
.timeout_secs = FIX2INT(upload_timeout_seconds),
|
72
|
+
// Waits for crash tracker to finish reporting the issue before letting the Ruby process die; see
|
73
|
+
// https://github.com/DataDog/libdatadog/pull/477 for details
|
74
|
+
.wait_for_receiver = true,
|
62
75
|
};
|
63
76
|
|
64
77
|
ddog_prof_CrashtrackerMetadata metadata = {
|
@@ -83,7 +96,7 @@ static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUS
|
|
83
96
|
|
84
97
|
ddog_prof_CrashtrackerResult result =
|
85
98
|
action == start_action ?
|
86
|
-
|
99
|
+
ddog_prof_Crashtracker_init_with_receiver(config, receiver_config, metadata) :
|
87
100
|
ddog_prof_Crashtracker_update_on_fork(config, receiver_config, metadata);
|
88
101
|
|
89
102
|
// Clean up before potentially raising any exceptions
|
@@ -0,0 +1,110 @@
|
|
1
|
+
#include "datadog_ruby_common.h"
|
2
|
+
|
3
|
+
// IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change!
|
4
|
+
|
5
|
+
void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name) {
|
6
|
+
rb_exc_raise(
|
7
|
+
rb_exc_new_str(
|
8
|
+
rb_eTypeError,
|
9
|
+
rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
|
10
|
+
rb_inspect(value),
|
11
|
+
value_name,
|
12
|
+
type_name,
|
13
|
+
file,
|
14
|
+
line,
|
15
|
+
function_name
|
16
|
+
)
|
17
|
+
)
|
18
|
+
);
|
19
|
+
}
|
20
|
+
|
21
|
+
VALUE datadog_gem_version(void) {
|
22
|
+
VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
|
23
|
+
ENFORCE_TYPE(ddtrace_module, T_MODULE);
|
24
|
+
VALUE version_module = rb_const_get(ddtrace_module, rb_intern("VERSION"));
|
25
|
+
ENFORCE_TYPE(version_module, T_MODULE);
|
26
|
+
VALUE version_string = rb_const_get(version_module, rb_intern("STRING"));
|
27
|
+
ENFORCE_TYPE(version_string, T_STRING);
|
28
|
+
return version_string;
|
29
|
+
}
|
30
|
+
|
31
|
+
__attribute__((warn_unused_result))
|
32
|
+
ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
|
33
|
+
ENFORCE_TYPE(exporter_configuration, T_ARRAY);
|
34
|
+
|
35
|
+
VALUE exporter_working_mode = rb_ary_entry(exporter_configuration, 0);
|
36
|
+
ENFORCE_TYPE(exporter_working_mode, T_SYMBOL);
|
37
|
+
ID working_mode = SYM2ID(exporter_working_mode);
|
38
|
+
|
39
|
+
ID agentless_id = rb_intern("agentless");
|
40
|
+
ID agent_id = rb_intern("agent");
|
41
|
+
|
42
|
+
if (working_mode != agentless_id && working_mode != agent_id) {
|
43
|
+
rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
|
44
|
+
}
|
45
|
+
|
46
|
+
if (working_mode == agentless_id) {
|
47
|
+
VALUE site = rb_ary_entry(exporter_configuration, 1);
|
48
|
+
VALUE api_key = rb_ary_entry(exporter_configuration, 2);
|
49
|
+
|
50
|
+
return ddog_prof_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
|
51
|
+
} else { // agent_id
|
52
|
+
VALUE base_url = rb_ary_entry(exporter_configuration, 1);
|
53
|
+
|
54
|
+
return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
static VALUE log_failure_to_process_tag(VALUE err_details) {
|
59
|
+
VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
|
60
|
+
VALUE logger = rb_funcall(datadog_module, rb_intern("logger"), 0);
|
61
|
+
|
62
|
+
return rb_funcall(logger, rb_intern("warn"), 1, rb_sprintf("Failed to convert tag: %"PRIsVALUE, err_details));
|
63
|
+
}
|
64
|
+
|
65
|
+
__attribute__((warn_unused_result))
|
66
|
+
ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
|
67
|
+
ENFORCE_TYPE(tags_as_array, T_ARRAY);
|
68
|
+
|
69
|
+
long tags_count = RARRAY_LEN(tags_as_array);
|
70
|
+
ddog_Vec_Tag tags = ddog_Vec_Tag_new();
|
71
|
+
|
72
|
+
for (long i = 0; i < tags_count; i++) {
|
73
|
+
VALUE name_value_pair = rb_ary_entry(tags_as_array, i);
|
74
|
+
|
75
|
+
if (!RB_TYPE_P(name_value_pair, T_ARRAY)) {
|
76
|
+
ddog_Vec_Tag_drop(tags);
|
77
|
+
ENFORCE_TYPE(name_value_pair, T_ARRAY);
|
78
|
+
}
|
79
|
+
|
80
|
+
// Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds
|
81
|
+
VALUE tag_name = rb_ary_entry(name_value_pair, 0);
|
82
|
+
VALUE tag_value = rb_ary_entry(name_value_pair, 1);
|
83
|
+
|
84
|
+
if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) {
|
85
|
+
ddog_Vec_Tag_drop(tags);
|
86
|
+
ENFORCE_TYPE(tag_name, T_STRING);
|
87
|
+
ENFORCE_TYPE(tag_value, T_STRING);
|
88
|
+
}
|
89
|
+
|
90
|
+
ddog_Vec_Tag_PushResult push_result =
|
91
|
+
ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value));
|
92
|
+
|
93
|
+
if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) {
|
94
|
+
// libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch.
|
95
|
+
// We warn users about such tags, and then just ignore them.
|
96
|
+
|
97
|
+
int exception_state;
|
98
|
+
rb_protect(log_failure_to_process_tag, get_error_details_and_drop(&push_result.err), &exception_state);
|
99
|
+
|
100
|
+
// Since we are calling into Ruby code, it may raise an exception. Ensure that dynamically-allocated tags
|
101
|
+
// get cleaned before propagating the exception.
|
102
|
+
if (exception_state) {
|
103
|
+
ddog_Vec_Tag_drop(tags);
|
104
|
+
rb_jump_tag(exception_state); // "Re-raise" exception
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
return tags;
|
110
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
// IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change!
|
4
|
+
|
5
|
+
#include <ruby.h>
|
6
|
+
#include <datadog/profiling.h>
|
7
|
+
|
8
|
+
// Used to mark symbols to be exported to the outside of the extension.
|
9
|
+
// Consider very carefully before tagging a function with this.
|
10
|
+
#define DDTRACE_EXPORT __attribute__ ((visibility ("default")))
|
11
|
+
|
12
|
+
// Used to mark function arguments that are deliberately left unused
|
13
|
+
#ifdef __GNUC__
|
14
|
+
#define DDTRACE_UNUSED __attribute__((unused))
|
15
|
+
#else
|
16
|
+
#define DDTRACE_UNUSED
|
17
|
+
#endif
|
18
|
+
|
19
|
+
#define ADD_QUOTES_HELPER(x) #x
|
20
|
+
#define ADD_QUOTES(x) ADD_QUOTES_HELPER(x)
|
21
|
+
|
22
|
+
// Ruby has a Check_Type(value, type) that is roughly equivalent to this BUT Ruby's version is rather cryptic when it fails
|
23
|
+
// e.g. "wrong argument type nil (expected String)". This is a replacement that prints more information to help debugging.
|
24
|
+
#define ENFORCE_TYPE(value, type) \
|
25
|
+
{ if (RB_UNLIKELY(!RB_TYPE_P(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), ADD_QUOTES(type), __FILE__, __LINE__, __func__); }
|
26
|
+
|
27
|
+
#define ENFORCE_BOOLEAN(value) \
|
28
|
+
{ if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); }
|
29
|
+
|
30
|
+
// Called by ENFORCE_TYPE; should not be used directly
|
31
|
+
NORETURN(void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name));
|
32
|
+
|
33
|
+
// Helper to retrieve Datadog::VERSION::STRING
|
34
|
+
VALUE datadog_gem_version(void);
|
35
|
+
|
36
|
+
static inline ddog_CharSlice char_slice_from_ruby_string(VALUE string) {
|
37
|
+
ENFORCE_TYPE(string, T_STRING);
|
38
|
+
ddog_CharSlice char_slice = {.ptr = RSTRING_PTR(string), .len = RSTRING_LEN(string)};
|
39
|
+
return char_slice;
|
40
|
+
}
|
41
|
+
|
42
|
+
__attribute__((warn_unused_result))
|
43
|
+
ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration);
|
44
|
+
|
45
|
+
__attribute__((warn_unused_result))
|
46
|
+
ddog_Vec_Tag convert_tags(VALUE tags_as_array);
|
47
|
+
|
48
|
+
static inline VALUE ruby_string_from_error(const ddog_Error *error) {
|
49
|
+
ddog_CharSlice char_slice = ddog_Error_message(error);
|
50
|
+
return rb_str_new(char_slice.ptr, char_slice.len);
|
51
|
+
}
|
52
|
+
|
53
|
+
static inline VALUE get_error_details_and_drop(ddog_Error *error) {
|
54
|
+
VALUE result = ruby_string_from_error(error);
|
55
|
+
ddog_Error_drop(error);
|
56
|
+
return result;
|
57
|
+
}
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# rubocop:disable Style/StderrPuts
|
2
|
+
# rubocop:disable Style/GlobalVars
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require_relative '../libdatadog_extconf_helpers'
|
6
|
+
|
7
|
+
def skip_building_extension!(reason)
|
8
|
+
$stderr.puts(
|
9
|
+
"WARN: Skipping build of libdatadog_api (#{reason}). Some functionality will not be available."
|
10
|
+
)
|
11
|
+
|
12
|
+
fail_install_if_missing_extension = ENV['DD_FAIL_INSTALL_IF_MISSING_EXTENSION'].to_s.strip.downcase == 'true'
|
13
|
+
|
14
|
+
if fail_install_if_missing_extension
|
15
|
+
require 'mkmf'
|
16
|
+
Logging.message("[datadog] Failure cause: #{reason}")
|
17
|
+
else
|
18
|
+
File.write('Makefile', 'all install clean: # dummy makefile that does nothing')
|
19
|
+
end
|
20
|
+
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
if ENV['DD_NO_EXTENSION'].to_s.strip.downcase == 'true'
|
25
|
+
skip_building_extension!('the `DD_NO_EXTENSION` environment variable is/was set to `true` during installation')
|
26
|
+
end
|
27
|
+
skip_building_extension!('current Ruby VM is not supported') if RUBY_ENGINE != 'ruby'
|
28
|
+
skip_building_extension!('Microsoft Windows is not supported') if Gem.win_platform?
|
29
|
+
skip_building_extension!('issue setting up `libdatadog` gem') if Datadog::LibdatadogExtconfHelpers.libdatadog_issue?
|
30
|
+
|
31
|
+
require 'mkmf'
|
32
|
+
|
33
|
+
# Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go.
|
34
|
+
# But we can enable it in CI, so that we quickly spot any new warnings that just got introduced.
|
35
|
+
append_cflags '-Werror' if ENV['DATADOG_GEM_CI'] == 'true'
|
36
|
+
|
37
|
+
# Older gcc releases may not default to C99 and we need to ask for this. This is also used:
|
38
|
+
# * by upstream Ruby -- search for gnu99 in the codebase
|
39
|
+
# * by msgpack, another datadog gem dependency
|
40
|
+
# (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
|
41
|
+
append_cflags '-std=gnu99'
|
42
|
+
|
43
|
+
# Allow defining variables at any point in a function
|
44
|
+
append_cflags '-Wno-declaration-after-statement'
|
45
|
+
|
46
|
+
# If we forget to include a Ruby header, the function call may still appear to work, but then
|
47
|
+
# cause a segfault later. Let's ensure that never happens.
|
48
|
+
append_cflags '-Werror-implicit-function-declaration'
|
49
|
+
|
50
|
+
# Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
|
51
|
+
append_cflags '-Wunused-parameter'
|
52
|
+
|
53
|
+
# The native extension is not intended to expose any symbols/functions for other native libraries to use;
|
54
|
+
# the sole exception being `Init_libdatadog_api` which needs to be visible for Ruby to call it when
|
55
|
+
# it `dlopen`s the library.
|
56
|
+
#
|
57
|
+
# By setting this compiler flag, we tell it to assume that everything is private unless explicitly stated.
|
58
|
+
# For more details see https://gcc.gnu.org/wiki/Visibility
|
59
|
+
append_cflags '-fvisibility=hidden'
|
60
|
+
|
61
|
+
# Avoid legacy C definitions
|
62
|
+
append_cflags '-Wold-style-definition'
|
63
|
+
|
64
|
+
# Enable all other compiler warnings
|
65
|
+
append_cflags '-Wall'
|
66
|
+
append_cflags '-Wextra'
|
67
|
+
|
68
|
+
if ENV['DDTRACE_DEBUG'] == 'true'
|
69
|
+
$defs << '-DDD_DEBUG'
|
70
|
+
CONFIG['optflags'] = '-O0'
|
71
|
+
CONFIG['debugflags'] = '-ggdb3'
|
72
|
+
end
|
73
|
+
|
74
|
+
# If we got here, libdatadog is available and loaded
|
75
|
+
ENV['PKG_CONFIG_PATH'] = "#{ENV['PKG_CONFIG_PATH']}:#{Libdatadog.pkgconfig_folder}"
|
76
|
+
Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV['PKG_CONFIG_PATH'].inspect}\n")
|
77
|
+
$stderr.puts("Using libdatadog #{Libdatadog::VERSION} from #{Libdatadog.pkgconfig_folder}")
|
78
|
+
|
79
|
+
unless pkg_config('datadog_profiling_with_rpath')
|
80
|
+
Logging.message("[datadog] Ruby detected the pkg-config command is #{$PKGCONFIG.inspect}\n")
|
81
|
+
|
82
|
+
if Datadog::LibdatadogExtconfHelpers.pkg_config_missing?
|
83
|
+
skip_building_extension!('the `pkg-config` system tool is missing')
|
84
|
+
else
|
85
|
+
skip_building_extension!('there was a problem in setting up the `libdatadog` dependency')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# See comments on the helper methods being used for why we need to additionally set this.
|
90
|
+
# The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
|
91
|
+
# experimentation. We need to get these special characters across a lot of tools untouched...
|
92
|
+
extra_relative_rpaths = [
|
93
|
+
Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_native_lib_folder(current_folder: __dir__),
|
94
|
+
*Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
|
95
|
+
]
|
96
|
+
extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
|
97
|
+
Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
|
98
|
+
|
99
|
+
# Tag the native extension library with the Ruby version and Ruby platform.
|
100
|
+
# This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
|
101
|
+
# the wrong library is never loaded.
|
102
|
+
# When requiring, we need to use the exact same string, including the version and the platform.
|
103
|
+
EXTENSION_NAME = "libdatadog_api.#{RUBY_VERSION[/\d+.\d+/]}_#{RUBY_PLATFORM}".freeze
|
104
|
+
|
105
|
+
create_makefile(EXTENSION_NAME)
|
106
|
+
|
107
|
+
# rubocop:enable Style/GlobalVars
|
108
|
+
# rubocop:enable Style/StderrPuts
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Developing on macOS
|
2
|
+
|
3
|
+
As of this writing (August 2024), the libdatadog builds on rubygems.org only support Linux.
|
4
|
+
|
5
|
+
We don't officially support using libdatadog for Ruby on other platforms yet, but it is possible to use it for local development on macOS.
|
6
|
+
(**Note that you don't need these instructions if you develop inside docker.**)
|
7
|
+
|
8
|
+
Here's how you can do so:
|
9
|
+
|
10
|
+
1. [Install rust](https://www.rust-lang.org/tools/install)
|
11
|
+
2. Install `cbindgen`: `cargo install cbindgen`
|
12
|
+
3. Clone [libdatadog](https://github.com/datadog/libdatadog)
|
13
|
+
4. Create a folder for building into based on your ruby platform:
|
14
|
+
|
15
|
+
```
|
16
|
+
export DD_RUBY_PLATFORM=`ruby -e 'puts Gem::Platform.local.to_s'`
|
17
|
+
mkdir -p my-libdatadog-build/$DD_RUBY_PLATFORM
|
18
|
+
```
|
19
|
+
|
20
|
+
5. Build libdatadog into this folder: `./build-profiling-ffi.sh my-libdatadog-build/$DD_RUBY_PLATFORM`
|
21
|
+
6. Tell the Ruby where to find libdatadog: `export LIBDATADOG_VENDOR_OVERRIDE=/full/path/to/my-libdatadog-build/` (Notice no platform here)
|
22
|
+
7. Run `bundle exec rake clean compile`
|
23
|
+
|
24
|
+
If you additionally want to run the profiler test suite, also remember to `export DD_PROFILING_MACOS_TESTING=true` and re-run `rake clean compile`.
|
25
|
+
|
26
|
+
These instructions can quickly get outdated, so feel free to open an issue if they're not working (and/or ping @ivoanjo).
|