datadog 2.0.0.beta1 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|