datadog 2.0.0.beta1 → 2.0.0.rc1
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 +181 -1
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +1 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +40 -32
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +23 -12
- data/ext/datadog_profiling_native_extension/crashtracker.c +108 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +9 -23
- data/ext/datadog_profiling_native_extension/heap_recorder.c +81 -4
- data/ext/datadog_profiling_native_extension/heap_recorder.h +12 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +1 -94
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +86 -0
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +4 -0
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +2 -12
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +25 -86
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +3 -5
- data/ext/datadog_profiling_native_extension/stack_recorder.c +161 -62
- data/lib/datadog/appsec/contrib/devise/tracking.rb +8 -0
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -13
- data/lib/datadog/appsec/event.rb +2 -2
- data/lib/datadog/core/configuration/components.rb +2 -1
- data/lib/datadog/core/configuration/option.rb +7 -5
- data/lib/datadog/core/configuration/settings.rb +34 -79
- data/lib/datadog/core/configuration.rb +20 -4
- data/lib/datadog/core/environment/platform.rb +7 -1
- data/lib/datadog/core/remote/client/capabilities.rb +2 -1
- data/lib/datadog/core/remote/client.rb +1 -5
- data/lib/datadog/core/remote/configuration/repository.rb +1 -1
- data/lib/datadog/core/remote/dispatcher.rb +3 -3
- data/lib/datadog/core/remote/transport/http/config.rb +5 -5
- data/lib/datadog/core/telemetry/client.rb +18 -10
- data/lib/datadog/core/telemetry/emitter.rb +9 -13
- data/lib/datadog/core/telemetry/event.rb +247 -57
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/heartbeat.rb +1 -3
- data/lib/datadog/core/telemetry/http/ext.rb +4 -1
- data/lib/datadog/core/telemetry/http/response.rb +4 -0
- data/lib/datadog/core/telemetry/http/transport.rb +9 -4
- data/lib/datadog/core/telemetry/request.rb +59 -0
- data/lib/datadog/core/utils/base64.rb +22 -0
- data/lib/datadog/opentelemetry/sdk/span_processor.rb +19 -2
- data/lib/datadog/opentelemetry/sdk/trace/span.rb +3 -17
- data/lib/datadog/profiling/collectors/code_provenance.rb +10 -4
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +25 -0
- data/lib/datadog/profiling/component.rb +49 -17
- data/lib/datadog/profiling/crashtracker.rb +91 -0
- data/lib/datadog/profiling/exporter.rb +6 -3
- data/lib/datadog/profiling/http_transport.rb +7 -11
- data/lib/datadog/profiling/load_native_extension.rb +14 -1
- data/lib/datadog/profiling/profiler.rb +9 -2
- data/lib/datadog/profiling/stack_recorder.rb +6 -2
- data/lib/datadog/profiling.rb +12 -0
- data/lib/datadog/tracing/component.rb +5 -1
- data/lib/datadog/tracing/configuration/dynamic.rb +39 -1
- data/lib/datadog/tracing/configuration/settings.rb +1 -0
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/action_view/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -0
- data/lib/datadog/tracing/contrib/active_record/integration.rb +11 -1
- data/lib/datadog/tracing/contrib/active_support/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +43 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +43 -5
- data/lib/datadog/tracing/contrib/trilogy/instrumentation.rb +1 -1
- data/lib/datadog/tracing/correlation.rb +3 -4
- data/lib/datadog/tracing/remote.rb +5 -1
- data/lib/datadog/tracing/sampling/ext.rb +5 -1
- data/lib/datadog/tracing/sampling/matcher.rb +75 -26
- data/lib/datadog/tracing/sampling/rule.rb +27 -4
- data/lib/datadog/tracing/sampling/rule_sampler.rb +19 -1
- data/lib/datadog/tracing/sampling/span/matcher.rb +13 -41
- data/lib/datadog/tracing/span.rb +7 -2
- data/lib/datadog/tracing/span_link.rb +92 -0
- data/lib/datadog/tracing/span_operation.rb +6 -4
- data/lib/datadog/tracing/trace_operation.rb +12 -0
- data/lib/datadog/tracing/tracer.rb +4 -3
- data/lib/datadog/tracing/transport/serializable_trace.rb +3 -1
- data/lib/datadog/tracing/utils.rb +16 -0
- data/lib/datadog/version.rb +1 -1
- metadata +10 -31
- data/lib/datadog/core/telemetry/collector.rb +0 -248
- data/lib/datadog/core/telemetry/v1/app_event.rb +0 -59
- data/lib/datadog/core/telemetry/v1/application.rb +0 -94
- data/lib/datadog/core/telemetry/v1/configuration.rb +0 -27
- data/lib/datadog/core/telemetry/v1/dependency.rb +0 -45
- data/lib/datadog/core/telemetry/v1/host.rb +0 -59
- data/lib/datadog/core/telemetry/v1/install_signature.rb +0 -38
- data/lib/datadog/core/telemetry/v1/integration.rb +0 -66
- data/lib/datadog/core/telemetry/v1/product.rb +0 -36
- data/lib/datadog/core/telemetry/v1/telemetry_request.rb +0 -108
- data/lib/datadog/core/telemetry/v2/app_client_configuration_change.rb +0 -41
- data/lib/datadog/core/telemetry/v2/request.rb +0 -29
@@ -0,0 +1,108 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <datadog/common.h>
|
3
|
+
#include <libdatadog_helpers.h>
|
4
|
+
|
5
|
+
static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
|
6
|
+
static VALUE _native_stop(DDTRACE_UNUSED VALUE _self);
|
7
|
+
|
8
|
+
// Used to report Ruby VM crashes.
|
9
|
+
// Once initialized, segfaults will be reported automatically using libdatadog.
|
10
|
+
|
11
|
+
void crashtracker_init(VALUE profiling_module) {
|
12
|
+
VALUE crashtracker_class = rb_define_class_under(profiling_module, "Crashtracker", rb_cObject);
|
13
|
+
|
14
|
+
rb_define_singleton_method(crashtracker_class, "_native_start_or_update_on_fork", _native_start_or_update_on_fork, -1);
|
15
|
+
rb_define_singleton_method(crashtracker_class, "_native_stop", _native_stop, 0);
|
16
|
+
}
|
17
|
+
|
18
|
+
static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) {
|
19
|
+
VALUE options;
|
20
|
+
rb_scan_args(argc, argv, "0:", &options);
|
21
|
+
|
22
|
+
VALUE exporter_configuration = rb_hash_fetch(options, ID2SYM(rb_intern("exporter_configuration")));
|
23
|
+
VALUE path_to_crashtracking_receiver_binary = rb_hash_fetch(options, ID2SYM(rb_intern("path_to_crashtracking_receiver_binary")));
|
24
|
+
VALUE ld_library_path = rb_hash_fetch(options, ID2SYM(rb_intern("ld_library_path")));
|
25
|
+
VALUE tags_as_array = rb_hash_fetch(options, ID2SYM(rb_intern("tags_as_array")));
|
26
|
+
VALUE action = rb_hash_fetch(options, ID2SYM(rb_intern("action")));
|
27
|
+
VALUE upload_timeout_seconds = rb_hash_fetch(options, ID2SYM(rb_intern("upload_timeout_seconds")));
|
28
|
+
|
29
|
+
VALUE start_action = ID2SYM(rb_intern("start"));
|
30
|
+
VALUE update_on_fork_action = ID2SYM(rb_intern("update_on_fork"));
|
31
|
+
|
32
|
+
ENFORCE_TYPE(exporter_configuration, T_ARRAY);
|
33
|
+
ENFORCE_TYPE(tags_as_array, T_ARRAY);
|
34
|
+
ENFORCE_TYPE(path_to_crashtracking_receiver_binary, T_STRING);
|
35
|
+
ENFORCE_TYPE(ld_library_path, T_STRING);
|
36
|
+
ENFORCE_TYPE(action, T_SYMBOL);
|
37
|
+
ENFORCE_TYPE(upload_timeout_seconds, T_FIXNUM);
|
38
|
+
|
39
|
+
if (action != start_action && action != update_on_fork_action) rb_raise(rb_eArgError, "Unexpected action: %+"PRIsVALUE, action);
|
40
|
+
|
41
|
+
VALUE version = ddtrace_version();
|
42
|
+
ddog_prof_Endpoint endpoint = endpoint_from(exporter_configuration);
|
43
|
+
|
44
|
+
// Tags are heap-allocated, so after here we can't raise exceptions otherwise we'll leak this memory
|
45
|
+
// Start of exception-free zone to prevent leaks {{
|
46
|
+
ddog_Vec_Tag tags = convert_tags(tags_as_array);
|
47
|
+
|
48
|
+
ddog_prof_CrashtrackerConfiguration config = {
|
49
|
+
.additional_files = {},
|
50
|
+
// The Ruby VM already uses an alt stack to detect stack overflows so the crash handler must not overwrite it.
|
51
|
+
//
|
52
|
+
// @ivoanjo: Specifically, with `create_alt_stack = true` I saw a segfault, such as Ruby 2.6's bug with
|
53
|
+
// "Process.detach(fork { exit! }).instance_variable_get(:@foo)" being turned into a
|
54
|
+
// "-e:1:in `instance_variable_get': stack level too deep (SystemStackError)" by Ruby.
|
55
|
+
//
|
56
|
+
// The Ruby crash handler also seems to get confused when this option is enabled and
|
57
|
+
// "Process.kill('SEGV', Process.pid)" gets run.
|
58
|
+
.create_alt_stack = false,
|
59
|
+
.endpoint = endpoint,
|
60
|
+
.resolve_frames = DDOG_PROF_STACKTRACE_COLLECTION_ENABLED,
|
61
|
+
.timeout_secs = FIX2INT(upload_timeout_seconds),
|
62
|
+
};
|
63
|
+
|
64
|
+
ddog_prof_CrashtrackerMetadata metadata = {
|
65
|
+
.profiling_library_name = DDOG_CHARSLICE_C("dd-trace-rb"),
|
66
|
+
.profiling_library_version = char_slice_from_ruby_string(version),
|
67
|
+
.family = DDOG_CHARSLICE_C("ruby"),
|
68
|
+
.tags = &tags,
|
69
|
+
};
|
70
|
+
|
71
|
+
ddog_prof_EnvVar ld_library_path_env = {
|
72
|
+
.key = DDOG_CHARSLICE_C("LD_LIBRARY_PATH"),
|
73
|
+
.val = char_slice_from_ruby_string(ld_library_path),
|
74
|
+
};
|
75
|
+
|
76
|
+
ddog_prof_CrashtrackerReceiverConfig receiver_config = {
|
77
|
+
.args = {},
|
78
|
+
.env = {.ptr = &ld_library_path_env, .len = 1},
|
79
|
+
.path_to_receiver_binary = char_slice_from_ruby_string(path_to_crashtracking_receiver_binary),
|
80
|
+
.optional_stderr_filename = {},
|
81
|
+
.optional_stdout_filename = {},
|
82
|
+
};
|
83
|
+
|
84
|
+
ddog_prof_CrashtrackerResult result =
|
85
|
+
action == start_action ?
|
86
|
+
ddog_prof_Crashtracker_init(config, receiver_config, metadata) :
|
87
|
+
ddog_prof_Crashtracker_update_on_fork(config, receiver_config, metadata);
|
88
|
+
|
89
|
+
// Clean up before potentially raising any exceptions
|
90
|
+
ddog_Vec_Tag_drop(tags);
|
91
|
+
// }} End of exception-free zone to prevent leaks
|
92
|
+
|
93
|
+
if (result.tag == DDOG_PROF_CRASHTRACKER_RESULT_ERR) {
|
94
|
+
rb_raise(rb_eRuntimeError, "Failed to start/update the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
|
95
|
+
}
|
96
|
+
|
97
|
+
return Qtrue;
|
98
|
+
}
|
99
|
+
|
100
|
+
static VALUE _native_stop(DDTRACE_UNUSED VALUE _self) {
|
101
|
+
ddog_prof_CrashtrackerResult result = ddog_prof_Crashtracker_shutdown();
|
102
|
+
|
103
|
+
if (result.tag == DDOG_PROF_CRASHTRACKER_RESULT_ERR) {
|
104
|
+
rb_raise(rb_eRuntimeError, "Failed to stop the crash tracker: %"PRIsVALUE, get_error_details_and_drop(&result.err));
|
105
|
+
}
|
106
|
+
|
107
|
+
return Qtrue;
|
108
|
+
}
|
@@ -126,7 +126,7 @@ if RUBY_PLATFORM.include?('linux')
|
|
126
126
|
# have_library 'pthread'
|
127
127
|
# have_func 'pthread_getcpuclockid'
|
128
128
|
# ```
|
129
|
-
# but
|
129
|
+
# but it's slower to build
|
130
130
|
# so instead we just assume that we have the function we need on Linux, and nowhere else
|
131
131
|
$defs << '-DHAVE_PTHREAD_GETCPUCLOCKID'
|
132
132
|
end
|
@@ -163,6 +163,11 @@ $defs << '-DNO_THREAD_TID' if RUBY_VERSION < '3.1'
|
|
163
163
|
# On older Rubies, there was no jit_return member on the rb_control_frame_t struct
|
164
164
|
$defs << '-DNO_JIT_RETURN' if RUBY_VERSION < '3.1'
|
165
165
|
|
166
|
+
# On older Rubies, rb_gc_force_recycle allowed to free objects in a way that
|
167
|
+
# would be invisible to free tracepoints, finalizers and without cleaning
|
168
|
+
# obj_to_id_tbl mappings.
|
169
|
+
$defs << '-DHAVE_WORKING_RB_GC_FORCE_RECYCLE' if RUBY_VERSION < '3.1'
|
170
|
+
|
166
171
|
# On older Rubies, we need to use a backported version of this function. See private_vm_api_access.h for details.
|
167
172
|
$defs << '-DUSE_BACKPORTED_RB_PROFILE_FRAME_METHOD_NAME' if RUBY_VERSION < '3'
|
168
173
|
|
@@ -175,34 +180,15 @@ $defs << '-DNO_IMEMO_NAME' if RUBY_VERSION < '3'
|
|
175
180
|
# On older Rubies, objects would not move
|
176
181
|
$defs << '-DNO_T_MOVED' if RUBY_VERSION < '2.7'
|
177
182
|
|
183
|
+
# On older Rubies, there was no RUBY_SEEN_OBJ_ID flag
|
184
|
+
$defs << '-DNO_SEEN_OBJ_ID_FLAG' if RUBY_VERSION < '2.7'
|
185
|
+
|
178
186
|
# On older Rubies, rb_global_vm_lock_struct did not include the owner field
|
179
187
|
$defs << '-DNO_GVL_OWNER' if RUBY_VERSION < '2.6'
|
180
188
|
|
181
189
|
# On older Rubies, there was no thread->invoke_arg
|
182
190
|
$defs << '-DNO_THREAD_INVOKE_ARG' if RUBY_VERSION < '2.6'
|
183
191
|
|
184
|
-
# On older Rubies, we need to use rb_thread_t instead of rb_execution_context_t
|
185
|
-
$defs << '-DUSE_THREAD_INSTEAD_OF_EXECUTION_CONTEXT' if RUBY_VERSION < '2.5'
|
186
|
-
|
187
|
-
# On older Rubies, extensions can't use GET_VM()
|
188
|
-
$defs << '-DNO_GET_VM' if RUBY_VERSION < '2.5'
|
189
|
-
|
190
|
-
# On older Rubies...
|
191
|
-
if RUBY_VERSION < '2.4'
|
192
|
-
# ...we need to use RUBY_VM_NORMAL_ISEQ_P instead of VM_FRAME_RUBYFRAME_P
|
193
|
-
$defs << '-DUSE_ISEQ_P_INSTEAD_OF_RUBYFRAME_P'
|
194
|
-
# ...we use a legacy copy of rb_vm_frame_method_entry
|
195
|
-
$defs << '-DUSE_LEGACY_RB_VM_FRAME_METHOD_ENTRY'
|
196
|
-
end
|
197
|
-
|
198
|
-
# On older Rubies, rb_gc_force_recycle allowed to free objects in a way that
|
199
|
-
# would be invisible to free tracepoints, finalizers and without cleaning
|
200
|
-
# obj_to_id_tbl mappings.
|
201
|
-
$defs << '-DHAVE_WORKING_RB_GC_FORCE_RECYCLE' if RUBY_VERSION < '3.1'
|
202
|
-
|
203
|
-
# On older Rubies, there was no RUBY_SEEN_OBJ_ID flag
|
204
|
-
$defs << '-DNO_SEEN_OBJ_ID_FLAG' if RUBY_VERSION < '2.7'
|
205
|
-
|
206
192
|
# If we got here, libdatadog is available and loaded
|
207
193
|
ENV['PKG_CONFIG_PATH'] = "#{ENV['PKG_CONFIG_PATH']}:#{Libdatadog.pkgconfig_folder}"
|
208
194
|
Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV['PKG_CONFIG_PATH'].inspect}\n")
|
@@ -10,6 +10,13 @@
|
|
10
10
|
#define CAN_APPLY_GC_FORCE_RECYCLE_BUG_WORKAROUND
|
11
11
|
#endif
|
12
12
|
|
13
|
+
// Minimum age (in GC generations) of heap objects we want to include in heap
|
14
|
+
// recorder iterations. Object with age 0 represent objects that have yet to undergo
|
15
|
+
// a GC and, thus, may just be noise/trash at instant of iteration and are usually not
|
16
|
+
// relevant for heap profiles as the great majority should be trivially reclaimed
|
17
|
+
// during the next GC.
|
18
|
+
#define ITERATION_MIN_AGE 1
|
19
|
+
|
13
20
|
// A compact representation of a stacktrace frame for a heap allocation.
|
14
21
|
typedef struct {
|
15
22
|
char *name;
|
@@ -137,6 +144,11 @@ struct heap_recorder {
|
|
137
144
|
// mutation of the data so iteration can occur without acquiring a lock.
|
138
145
|
// NOTE: Contrary to object_records, this table has no ownership of its data.
|
139
146
|
st_table *object_records_snapshot;
|
147
|
+
// The GC gen/epoch/count in which we prepared the current iteration.
|
148
|
+
//
|
149
|
+
// This enables us to calculate the age of iterated objects in the above snapshot by
|
150
|
+
// comparing it against an object's alloc_gen.
|
151
|
+
size_t iteration_gen;
|
140
152
|
|
141
153
|
// Data for a heap recording that was started but not yet ended
|
142
154
|
recording active_recording;
|
@@ -146,6 +158,13 @@ struct heap_recorder {
|
|
146
158
|
|
147
159
|
// Sampling state
|
148
160
|
uint num_recordings_skipped;
|
161
|
+
|
162
|
+
struct stats_last_update {
|
163
|
+
size_t objects_alive;
|
164
|
+
size_t objects_dead;
|
165
|
+
size_t objects_skipped;
|
166
|
+
size_t objects_frozen;
|
167
|
+
} stats_last_update;
|
149
168
|
};
|
150
169
|
static heap_record* get_or_create_heap_record(heap_recorder*, ddog_prof_Slice_Location);
|
151
170
|
static void cleanup_heap_record_if_unused(heap_recorder*, heap_record*);
|
@@ -353,11 +372,16 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
|
|
353
372
|
return;
|
354
373
|
}
|
355
374
|
|
375
|
+
heap_recorder->iteration_gen = rb_gc_count();
|
376
|
+
|
356
377
|
if (heap_recorder->object_records_snapshot != NULL) {
|
357
378
|
// we could trivially handle this but we raise to highlight and catch unexpected usages.
|
358
379
|
rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
|
359
380
|
}
|
360
381
|
|
382
|
+
// Reset last update stats, we'll be building them from scratch during the st_foreach call below
|
383
|
+
heap_recorder->stats_last_update = (struct stats_last_update) {};
|
384
|
+
|
361
385
|
st_foreach(heap_recorder->object_records, st_object_record_update, (st_data_t) heap_recorder);
|
362
386
|
|
363
387
|
heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
|
@@ -413,6 +437,22 @@ bool heap_recorder_for_each_live_object(
|
|
413
437
|
return true;
|
414
438
|
}
|
415
439
|
|
440
|
+
VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder) {
|
441
|
+
VALUE arguments[] = {
|
442
|
+
ID2SYM(rb_intern("num_object_records")), /* => */ LONG2NUM(heap_recorder->object_records->num_entries),
|
443
|
+
ID2SYM(rb_intern("num_heap_records")), /* => */ LONG2NUM(heap_recorder->heap_records->num_entries),
|
444
|
+
|
445
|
+
// Stats as of last update
|
446
|
+
ID2SYM(rb_intern("last_update_objects_alive")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_alive),
|
447
|
+
ID2SYM(rb_intern("last_update_objects_dead")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_dead),
|
448
|
+
ID2SYM(rb_intern("last_update_objects_skipped")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_skipped),
|
449
|
+
ID2SYM(rb_intern("last_update_objects_frozen")), /* => */ LONG2NUM(heap_recorder->stats_last_update.objects_frozen),
|
450
|
+
};
|
451
|
+
VALUE hash = rb_hash_new();
|
452
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(hash, arguments[i], arguments[i+1]);
|
453
|
+
return hash;
|
454
|
+
}
|
455
|
+
|
416
456
|
void heap_recorder_testonly_assert_hash_matches(ddog_prof_Slice_Location locations) {
|
417
457
|
heap_stack *stack = heap_stack_new(locations);
|
418
458
|
heap_record_key stack_based_key = (heap_record_key) {
|
@@ -459,6 +499,13 @@ static int st_object_record_entry_free(DDTRACE_UNUSED st_data_t key, st_data_t v
|
|
459
499
|
return ST_DELETE;
|
460
500
|
}
|
461
501
|
|
502
|
+
// Check to see if an object should not be included in a heap recorder iteration.
|
503
|
+
// This centralizes the checking logic to ensure it's equally applied between
|
504
|
+
// preparation and iteration codepaths.
|
505
|
+
static inline bool should_exclude_from_iteration(object_record *obj_record) {
|
506
|
+
return obj_record->object_data.gen_age < ITERATION_MIN_AGE;
|
507
|
+
}
|
508
|
+
|
462
509
|
static int st_object_record_update(st_data_t key, st_data_t value, st_data_t extra_arg) {
|
463
510
|
long obj_id = (long) key;
|
464
511
|
object_record *record = (object_record*) value;
|
@@ -466,9 +513,24 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
466
513
|
|
467
514
|
VALUE ref;
|
468
515
|
|
516
|
+
size_t iteration_gen = recorder->iteration_gen;
|
517
|
+
size_t alloc_gen = record->object_data.alloc_gen;
|
518
|
+
// Guard against potential overflows given unsigned types here.
|
519
|
+
record->object_data.gen_age = alloc_gen < iteration_gen ? iteration_gen - alloc_gen : 0;
|
520
|
+
|
521
|
+
if (should_exclude_from_iteration(record)) {
|
522
|
+
// If an object won't be included in the current iteration, there's
|
523
|
+
// no point checking for liveness or updating its size, so exit early.
|
524
|
+
// NOTE: This means that there should be an equivalent check during actual
|
525
|
+
// iteration otherwise we'd iterate/expose stale object data.
|
526
|
+
recorder->stats_last_update.objects_skipped++;
|
527
|
+
return ST_CONTINUE;
|
528
|
+
}
|
529
|
+
|
469
530
|
if (!ruby_ref_from_id(LONG2NUM(obj_id), &ref)) {
|
470
531
|
// Id no longer associated with a valid ref. Need to delete this object record!
|
471
532
|
on_committed_object_record_cleanup(recorder, record);
|
533
|
+
recorder->stats_last_update.objects_dead++;
|
472
534
|
return ST_DELETE;
|
473
535
|
}
|
474
536
|
|
@@ -503,6 +565,7 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
503
565
|
RB_FL_SET(ref, RUBY_FL_SEEN_OBJ_ID);
|
504
566
|
|
505
567
|
on_committed_object_record_cleanup(recorder, record);
|
568
|
+
recorder->stats_last_update.objects_dead++;
|
506
569
|
return ST_DELETE;
|
507
570
|
}
|
508
571
|
|
@@ -516,6 +579,11 @@ static int st_object_record_update(st_data_t key, st_data_t value, st_data_t ext
|
|
516
579
|
record->object_data.is_frozen = RB_OBJ_FROZEN(ref);
|
517
580
|
}
|
518
581
|
|
582
|
+
recorder->stats_last_update.objects_alive++;
|
583
|
+
if (record->object_data.is_frozen) {
|
584
|
+
recorder->stats_last_update.objects_frozen++;
|
585
|
+
}
|
586
|
+
|
519
587
|
return ST_CONTINUE;
|
520
588
|
}
|
521
589
|
|
@@ -525,8 +593,16 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val
|
|
525
593
|
const heap_stack *stack = record->heap_record->stack;
|
526
594
|
iteration_context *context = (iteration_context*) extra;
|
527
595
|
|
528
|
-
|
596
|
+
const heap_recorder *recorder = context->heap_recorder;
|
597
|
+
|
598
|
+
if (should_exclude_from_iteration(record)) {
|
599
|
+
// Skip objects that should not be included in iteration
|
600
|
+
// NOTE: This matches the short-circuiting condition in st_object_record_update
|
601
|
+
// and prevents iteration over stale objects.
|
602
|
+
return ST_CONTINUE;
|
603
|
+
}
|
529
604
|
|
605
|
+
ddog_prof_Location *locations = recorder->reusable_locations;
|
530
606
|
for (uint16_t i = 0; i < stack->frames_len; i++) {
|
531
607
|
const heap_frame *frame = &stack->frames[i];
|
532
608
|
ddog_prof_Location *location = &locations[i];
|
@@ -725,9 +801,10 @@ void object_record_free(object_record *record) {
|
|
725
801
|
|
726
802
|
VALUE object_record_inspect(object_record *record) {
|
727
803
|
heap_frame top_frame = record->heap_record->stack->frames[0];
|
728
|
-
|
729
|
-
|
730
|
-
|
804
|
+
live_object_data object_data = record->object_data;
|
805
|
+
VALUE inspect = rb_sprintf("obj_id=%ld weight=%d size=%zu location=%s:%d alloc_gen=%zu gen_age=%zu frozen=%d ",
|
806
|
+
record->obj_id, object_data.weight, object_data.size, top_frame.filename,
|
807
|
+
(int) top_frame.line, object_data.alloc_gen, object_data.gen_age, object_data.is_frozen);
|
731
808
|
|
732
809
|
const char *class = record->object_data.class;
|
733
810
|
if (class != NULL) {
|
@@ -27,7 +27,9 @@ typedef struct live_object_data {
|
|
27
27
|
// could be seen as being representative of 50 objects.
|
28
28
|
unsigned int weight;
|
29
29
|
|
30
|
-
// Size of this object
|
30
|
+
// Size of this object in memory.
|
31
|
+
// NOTE: This only gets updated during heap_recorder_prepare_iteration and only
|
32
|
+
// for those objects that meet the minimum iteration age requirements.
|
31
33
|
size_t size;
|
32
34
|
|
33
35
|
// The class of the object that we're tracking.
|
@@ -39,6 +41,10 @@ typedef struct live_object_data {
|
|
39
41
|
// This enables us to calculate the age of this object in terms of GC executions.
|
40
42
|
size_t alloc_gen;
|
41
43
|
|
44
|
+
// The age of this object in terms of GC generations.
|
45
|
+
// NOTE: This only gets updated during heap_recorder_prepare_iteration
|
46
|
+
size_t gen_age;
|
47
|
+
|
42
48
|
// Whether this object was previously seen as being frozen. If this is the case,
|
43
49
|
// we'll skip any further size updates since frozen objects are supposed to be
|
44
50
|
// immutable.
|
@@ -144,6 +150,11 @@ bool heap_recorder_for_each_live_object(
|
|
144
150
|
bool (*for_each_callback)(heap_recorder_iteration_data data, void* extra_arg),
|
145
151
|
void *for_each_callback_extra_arg);
|
146
152
|
|
153
|
+
// Return a Ruby hash containing a snapshot of this recorder's interesting state at calling time.
|
154
|
+
// WARN: This allocates in the Ruby VM and therefore should not be called without the
|
155
|
+
// VM lock or during GC.
|
156
|
+
VALUE heap_recorder_state_snapshot(heap_recorder *heap_recorder);
|
157
|
+
|
147
158
|
// v--- TEST-ONLY APIs ---v
|
148
159
|
|
149
160
|
// Assert internal hashing logic is valid for the provided locations and its
|
@@ -11,11 +11,6 @@
|
|
11
11
|
static VALUE ok_symbol = Qnil; // :ok in Ruby
|
12
12
|
static VALUE error_symbol = Qnil; // :error in Ruby
|
13
13
|
|
14
|
-
static ID agentless_id; // id of :agentless in Ruby
|
15
|
-
static ID agent_id; // id of :agent in Ruby
|
16
|
-
|
17
|
-
static ID log_failure_to_process_tag_id; // id of :log_failure_to_process_tag in Ruby
|
18
|
-
|
19
14
|
static VALUE library_version_string = Qnil;
|
20
15
|
|
21
16
|
struct call_exporter_without_gvl_arguments {
|
@@ -30,9 +25,6 @@ inline static ddog_ByteSlice byte_slice_from_ruby_string(VALUE string);
|
|
30
25
|
static VALUE _native_validate_exporter(VALUE self, VALUE exporter_configuration);
|
31
26
|
static ddog_prof_Exporter_NewResult create_exporter(VALUE exporter_configuration, VALUE tags_as_array);
|
32
27
|
static VALUE handle_exporter_failure(ddog_prof_Exporter_NewResult exporter_result);
|
33
|
-
static ddog_Endpoint endpoint_from(VALUE exporter_configuration);
|
34
|
-
static ddog_Vec_Tag convert_tags(VALUE tags_as_array);
|
35
|
-
static void safely_log_failure_to_process_tag(ddog_Vec_Tag tags, VALUE err_details);
|
36
28
|
static VALUE _native_do_export(
|
37
29
|
VALUE self,
|
38
30
|
VALUE exporter_configuration,
|
@@ -60,9 +52,6 @@ void http_transport_init(VALUE profiling_module) {
|
|
60
52
|
|
61
53
|
ok_symbol = ID2SYM(rb_intern_const("ok"));
|
62
54
|
error_symbol = ID2SYM(rb_intern_const("error"));
|
63
|
-
agentless_id = rb_intern_const("agentless");
|
64
|
-
agent_id = rb_intern_const("agent");
|
65
|
-
log_failure_to_process_tag_id = rb_intern_const("log_failure_to_process_tag");
|
66
55
|
|
67
56
|
library_version_string = ddtrace_version();
|
68
57
|
rb_global_variable(&library_version_string);
|
@@ -94,7 +83,7 @@ static ddog_prof_Exporter_NewResult create_exporter(VALUE exporter_configuration
|
|
94
83
|
|
95
84
|
// This needs to be called BEFORE convert_tags since it can raise an exception and thus cause the ddog_Vec_Tag
|
96
85
|
// to be leaked.
|
97
|
-
|
86
|
+
ddog_prof_Endpoint endpoint = endpoint_from(exporter_configuration);
|
98
87
|
|
99
88
|
ddog_Vec_Tag tags = convert_tags(tags_as_array);
|
100
89
|
|
@@ -116,88 +105,6 @@ static VALUE handle_exporter_failure(ddog_prof_Exporter_NewResult exporter_resul
|
|
116
105
|
rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&exporter_result.err));
|
117
106
|
}
|
118
107
|
|
119
|
-
static ddog_Endpoint endpoint_from(VALUE exporter_configuration) {
|
120
|
-
ENFORCE_TYPE(exporter_configuration, T_ARRAY);
|
121
|
-
|
122
|
-
ID working_mode = SYM2ID(rb_ary_entry(exporter_configuration, 0)); // SYM2ID verifies its input so we can do this safely
|
123
|
-
|
124
|
-
if (working_mode != agentless_id && working_mode != agent_id) {
|
125
|
-
rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
|
126
|
-
}
|
127
|
-
|
128
|
-
if (working_mode == agentless_id) {
|
129
|
-
VALUE site = rb_ary_entry(exporter_configuration, 1);
|
130
|
-
VALUE api_key = rb_ary_entry(exporter_configuration, 2);
|
131
|
-
ENFORCE_TYPE(site, T_STRING);
|
132
|
-
ENFORCE_TYPE(api_key, T_STRING);
|
133
|
-
|
134
|
-
return ddog_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
|
135
|
-
} else { // agent_id
|
136
|
-
VALUE base_url = rb_ary_entry(exporter_configuration, 1);
|
137
|
-
ENFORCE_TYPE(base_url, T_STRING);
|
138
|
-
|
139
|
-
return ddog_Endpoint_agent(char_slice_from_ruby_string(base_url));
|
140
|
-
}
|
141
|
-
}
|
142
|
-
|
143
|
-
__attribute__((warn_unused_result))
|
144
|
-
static ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
|
145
|
-
ENFORCE_TYPE(tags_as_array, T_ARRAY);
|
146
|
-
|
147
|
-
long tags_count = RARRAY_LEN(tags_as_array);
|
148
|
-
ddog_Vec_Tag tags = ddog_Vec_Tag_new();
|
149
|
-
|
150
|
-
for (long i = 0; i < tags_count; i++) {
|
151
|
-
VALUE name_value_pair = rb_ary_entry(tags_as_array, i);
|
152
|
-
|
153
|
-
if (!RB_TYPE_P(name_value_pair, T_ARRAY)) {
|
154
|
-
ddog_Vec_Tag_drop(tags);
|
155
|
-
ENFORCE_TYPE(name_value_pair, T_ARRAY);
|
156
|
-
}
|
157
|
-
|
158
|
-
// Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds
|
159
|
-
VALUE tag_name = rb_ary_entry(name_value_pair, 0);
|
160
|
-
VALUE tag_value = rb_ary_entry(name_value_pair, 1);
|
161
|
-
|
162
|
-
if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) {
|
163
|
-
ddog_Vec_Tag_drop(tags);
|
164
|
-
ENFORCE_TYPE(tag_name, T_STRING);
|
165
|
-
ENFORCE_TYPE(tag_value, T_STRING);
|
166
|
-
}
|
167
|
-
|
168
|
-
ddog_Vec_Tag_PushResult push_result =
|
169
|
-
ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value));
|
170
|
-
|
171
|
-
if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) {
|
172
|
-
// libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch.
|
173
|
-
// We warn users about such tags, and then just ignore them.
|
174
|
-
safely_log_failure_to_process_tag(tags, get_error_details_and_drop(&push_result.err));
|
175
|
-
}
|
176
|
-
}
|
177
|
-
|
178
|
-
return tags;
|
179
|
-
}
|
180
|
-
|
181
|
-
static VALUE log_failure_to_process_tag(VALUE err_details) {
|
182
|
-
VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
|
183
|
-
VALUE profiling_module = rb_const_get(datadog_module, rb_intern("Profiling"));
|
184
|
-
VALUE http_transport_class = rb_const_get(profiling_module, rb_intern("HttpTransport"));
|
185
|
-
|
186
|
-
return rb_funcall(http_transport_class, log_failure_to_process_tag_id, 1, err_details);
|
187
|
-
}
|
188
|
-
|
189
|
-
// Since we are calling into Ruby code, it may raise an exception. This method ensure that dynamically-allocated tags
|
190
|
-
// get cleaned before propagating the exception.
|
191
|
-
static void safely_log_failure_to_process_tag(ddog_Vec_Tag tags, VALUE err_details) {
|
192
|
-
int exception_state;
|
193
|
-
rb_protect(log_failure_to_process_tag, err_details, &exception_state);
|
194
|
-
|
195
|
-
if (exception_state) { // An exception was raised
|
196
|
-
ddog_Vec_Tag_drop(tags); // clean up
|
197
|
-
rb_jump_tag(exception_state); // "Re-raise" exception
|
198
|
-
}
|
199
|
-
}
|
200
|
-
|
201
108
|
// Note: This function handles a bunch of libdatadog dynamically-allocated objects, so it MUST not use any Ruby APIs
|
202
109
|
// which can raise exceptions, otherwise the objects will be leaked.
|
203
110
|
static VALUE perform_export(
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
#include <ruby.h>
|
4
4
|
|
5
|
+
static VALUE log_failure_to_process_tag(VALUE err_details);
|
6
|
+
|
5
7
|
const char *ruby_value_type_to_string(enum ruby_value_type type) {
|
6
8
|
return ruby_value_type_to_char_slice(type).ptr;
|
7
9
|
}
|
@@ -60,3 +62,87 @@ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capa
|
|
60
62
|
ddog_Error_drop(error);
|
61
63
|
return error_msg_size;
|
62
64
|
}
|
65
|
+
|
66
|
+
__attribute__((warn_unused_result))
|
67
|
+
ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
|
68
|
+
ENFORCE_TYPE(exporter_configuration, T_ARRAY);
|
69
|
+
|
70
|
+
VALUE exporter_working_mode = rb_ary_entry(exporter_configuration, 0);
|
71
|
+
ENFORCE_TYPE(exporter_working_mode, T_SYMBOL);
|
72
|
+
ID working_mode = SYM2ID(exporter_working_mode);
|
73
|
+
|
74
|
+
ID agentless_id = rb_intern("agentless");
|
75
|
+
ID agent_id = rb_intern("agent");
|
76
|
+
|
77
|
+
if (working_mode != agentless_id && working_mode != agent_id) {
|
78
|
+
rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
|
79
|
+
}
|
80
|
+
|
81
|
+
if (working_mode == agentless_id) {
|
82
|
+
VALUE site = rb_ary_entry(exporter_configuration, 1);
|
83
|
+
VALUE api_key = rb_ary_entry(exporter_configuration, 2);
|
84
|
+
ENFORCE_TYPE(site, T_STRING);
|
85
|
+
ENFORCE_TYPE(api_key, T_STRING);
|
86
|
+
|
87
|
+
return ddog_prof_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
|
88
|
+
} else { // agent_id
|
89
|
+
VALUE base_url = rb_ary_entry(exporter_configuration, 1);
|
90
|
+
ENFORCE_TYPE(base_url, T_STRING);
|
91
|
+
|
92
|
+
return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
__attribute__((warn_unused_result))
|
97
|
+
ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
|
98
|
+
ENFORCE_TYPE(tags_as_array, T_ARRAY);
|
99
|
+
|
100
|
+
long tags_count = RARRAY_LEN(tags_as_array);
|
101
|
+
ddog_Vec_Tag tags = ddog_Vec_Tag_new();
|
102
|
+
|
103
|
+
for (long i = 0; i < tags_count; i++) {
|
104
|
+
VALUE name_value_pair = rb_ary_entry(tags_as_array, i);
|
105
|
+
|
106
|
+
if (!RB_TYPE_P(name_value_pair, T_ARRAY)) {
|
107
|
+
ddog_Vec_Tag_drop(tags);
|
108
|
+
ENFORCE_TYPE(name_value_pair, T_ARRAY);
|
109
|
+
}
|
110
|
+
|
111
|
+
// Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds
|
112
|
+
VALUE tag_name = rb_ary_entry(name_value_pair, 0);
|
113
|
+
VALUE tag_value = rb_ary_entry(name_value_pair, 1);
|
114
|
+
|
115
|
+
if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) {
|
116
|
+
ddog_Vec_Tag_drop(tags);
|
117
|
+
ENFORCE_TYPE(tag_name, T_STRING);
|
118
|
+
ENFORCE_TYPE(tag_value, T_STRING);
|
119
|
+
}
|
120
|
+
|
121
|
+
ddog_Vec_Tag_PushResult push_result =
|
122
|
+
ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value));
|
123
|
+
|
124
|
+
if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) {
|
125
|
+
// libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch.
|
126
|
+
// We warn users about such tags, and then just ignore them.
|
127
|
+
|
128
|
+
int exception_state;
|
129
|
+
rb_protect(log_failure_to_process_tag, get_error_details_and_drop(&push_result.err), &exception_state);
|
130
|
+
|
131
|
+
// Since we are calling into Ruby code, it may raise an exception. Ensure that dynamically-allocated tags
|
132
|
+
// get cleaned before propagating the exception.
|
133
|
+
if (exception_state) {
|
134
|
+
ddog_Vec_Tag_drop(tags);
|
135
|
+
rb_jump_tag(exception_state); // "Re-raise" exception
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
return tags;
|
141
|
+
}
|
142
|
+
|
143
|
+
static VALUE log_failure_to_process_tag(VALUE err_details) {
|
144
|
+
VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
|
145
|
+
VALUE logger = rb_funcall(datadog_module, rb_intern("logger"), 0);
|
146
|
+
|
147
|
+
return rb_funcall(logger, rb_intern("warn"), 1, rb_sprintf("Failed to add tag to profiling request: %"PRIsVALUE, err_details));
|
148
|
+
}
|
@@ -40,3 +40,7 @@ ddog_CharSlice ruby_value_type_to_char_slice(enum ruby_value_type type);
|
|
40
40
|
inline static char* string_from_char_slice(ddog_CharSlice slice) {
|
41
41
|
return ruby_strndup(slice.ptr, slice.len);
|
42
42
|
}
|
43
|
+
|
44
|
+
ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration);
|
45
|
+
|
46
|
+
ddog_Vec_Tag convert_tags(VALUE tags_as_array);
|
@@ -15,7 +15,7 @@ module Datadog
|
|
15
15
|
# The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on debase-ruby_core_source
|
16
16
|
CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?('2.6', '2.7', '3.0.', '3.1.', '3.2.')
|
17
17
|
|
18
|
-
LIBDATADOG_VERSION = '~>
|
18
|
+
LIBDATADOG_VERSION = '~> 9.0.0.1.0'
|
19
19
|
|
20
20
|
def self.fail_install_if_missing_extension?
|
21
21
|
ENV[ENV_FAIL_INSTALL_IF_MISSING_EXTENSION].to_s.strip.downcase == 'true'
|
@@ -86,7 +86,6 @@ module Datadog
|
|
86
86
|
on_macos? ||
|
87
87
|
on_unknown_os? ||
|
88
88
|
on_unsupported_cpu_arch? ||
|
89
|
-
on_unsupported_ruby_version? ||
|
90
89
|
expected_to_use_mjit_but_mjit_is_disabled? ||
|
91
90
|
libdatadog_not_available? ||
|
92
91
|
libdatadog_not_usable?
|
@@ -166,7 +165,7 @@ module Datadog
|
|
166
165
|
|
167
166
|
# Validation for this check is done in extconf.rb because it relies on mkmf
|
168
167
|
PKG_CONFIG_IS_MISSING = explain_issue(
|
169
|
-
|
168
|
+
# ----------------------------------------------------------------------------+
|
170
169
|
'the `pkg-config` system tool is missing.',
|
171
170
|
'This issue can usually be fixed by installing one of the following:',
|
172
171
|
'the `pkg-config` package on Homebrew and Debian/Ubuntu-based Linux;',
|
@@ -260,15 +259,6 @@ module Datadog
|
|
260
259
|
architecture_not_supported unless RUBY_PLATFORM.start_with?('x86_64', 'aarch64', 'arm64')
|
261
260
|
end
|
262
261
|
|
263
|
-
private_class_method def self.on_unsupported_ruby_version?
|
264
|
-
ruby_version_not_supported = explain_issue(
|
265
|
-
'the profiler only supports Ruby 2.3 or newer.',
|
266
|
-
suggested: UPGRADE_RUBY,
|
267
|
-
)
|
268
|
-
|
269
|
-
ruby_version_not_supported if RUBY_VERSION.start_with?('2.1.', '2.2.')
|
270
|
-
end
|
271
|
-
|
272
262
|
# On some Rubies, we require the mjit header to be present. If Ruby was installed without MJIT support, we also skip
|
273
263
|
# building the extension.
|
274
264
|
private_class_method def self.expected_to_use_mjit_but_mjit_is_disabled?
|