datadog 2.26.0 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +7 -6
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -5
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +2 -2
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
- data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
- data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
- data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
- data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +9 -8
- data/ext/datadog_profiling_native_extension/profiling.c +20 -15
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
- data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
- data/ext/libdatadog_api/crashtracker.c +5 -8
- data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
- data/ext/libdatadog_api/ddsketch.c +4 -8
- data/ext/libdatadog_api/feature_flags.c +5 -5
- data/ext/libdatadog_api/helpers.h +27 -0
- data/ext/libdatadog_api/init.c +4 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
- data/lib/datadog/appsec/component.rb +1 -1
- data/lib/datadog/appsec/context.rb +3 -3
- data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
- data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
- data/lib/datadog/appsec/ext.rb +2 -0
- data/lib/datadog/appsec/metrics/collector.rb +8 -3
- data/lib/datadog/appsec/metrics/exporter.rb +7 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
- data/lib/datadog/appsec/metrics.rb +5 -5
- data/lib/datadog/appsec/remote.rb +4 -4
- data/lib/datadog/appsec.rb +7 -1
- data/lib/datadog/core/configuration/settings.rb +17 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
- data/lib/datadog/core/telemetry/logger.rb +2 -0
- data/lib/datadog/core/telemetry/logging.rb +20 -2
- data/lib/datadog/profiling/component.rb +13 -0
- data/lib/datadog/profiling/exporter.rb +4 -0
- data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
- data/lib/datadog/profiling/flush.rb +3 -0
- data/lib/datadog/profiling/profiler.rb +3 -5
- data/lib/datadog/profiling/scheduler.rb +8 -7
- data/lib/datadog/profiling/tag_builder.rb +1 -0
- data/lib/datadog/version.rb +1 -1
- metadata +6 -4
|
@@ -259,7 +259,7 @@ void heap_recorder_set_sample_rate(heap_recorder *heap_recorder, int sample_rate
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
if (sample_rate <= 0) {
|
|
262
|
-
|
|
262
|
+
raise_error(rb_eArgError, "Heap sample rate must be a positive integer value but was %d", sample_rate);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
heap_recorder->sample_rate = sample_rate;
|
|
@@ -300,7 +300,7 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
|
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
if (heap_recorder->active_recording != NULL) {
|
|
303
|
-
|
|
303
|
+
raise_error(rb_eRuntimeError, "Detected consecutive heap allocation recording starts without end.");
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
if (++heap_recorder->num_recordings_skipped < heap_recorder->sample_rate ||
|
|
@@ -323,7 +323,7 @@ void start_heap_allocation_recording(heap_recorder *heap_recorder, VALUE new_obj
|
|
|
323
323
|
|
|
324
324
|
VALUE ruby_obj_id = rb_obj_id(new_obj);
|
|
325
325
|
if (!FIXNUM_P(ruby_obj_id)) {
|
|
326
|
-
|
|
326
|
+
raise_error(rb_eRuntimeError, "Detected a bignum object id. These are not supported by heap profiling.");
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
heap_recorder->active_recording = object_record_new(
|
|
@@ -371,7 +371,7 @@ static VALUE end_heap_allocation_recording(VALUE protect_args) {
|
|
|
371
371
|
|
|
372
372
|
if (active_recording == NULL) {
|
|
373
373
|
// Recording ended without having been started?
|
|
374
|
-
|
|
374
|
+
raise_error(rb_eRuntimeError, "Ended a heap recording that was not started");
|
|
375
375
|
}
|
|
376
376
|
// From now on, mark the global active recording as invalid so we can short-circuit at any point
|
|
377
377
|
// and not end up with a still active recording. the local active_recording still holds the
|
|
@@ -487,14 +487,14 @@ void heap_recorder_prepare_iteration(heap_recorder *heap_recorder) {
|
|
|
487
487
|
|
|
488
488
|
if (heap_recorder->object_records_snapshot != NULL) {
|
|
489
489
|
// we could trivially handle this but we raise to highlight and catch unexpected usages.
|
|
490
|
-
|
|
490
|
+
raise_error(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
heap_recorder_update(heap_recorder, /* full_update: */ true);
|
|
494
494
|
|
|
495
495
|
heap_recorder->object_records_snapshot = st_copy(heap_recorder->object_records);
|
|
496
496
|
if (heap_recorder->object_records_snapshot == NULL) {
|
|
497
|
-
|
|
497
|
+
raise_error(rb_eRuntimeError, "Failed to create heap snapshot.");
|
|
498
498
|
}
|
|
499
499
|
}
|
|
500
500
|
|
|
@@ -505,7 +505,7 @@ void heap_recorder_finish_iteration(heap_recorder *heap_recorder) {
|
|
|
505
505
|
|
|
506
506
|
if (heap_recorder->object_records_snapshot == NULL) {
|
|
507
507
|
// we could trivially handle this but we raise to highlight and catch unexpected usages.
|
|
508
|
-
|
|
508
|
+
raise_error(rb_eRuntimeError, "Heap recorder iteration finished without having been prepared.");
|
|
509
509
|
}
|
|
510
510
|
|
|
511
511
|
st_free_table(heap_recorder->object_records_snapshot);
|
|
@@ -581,7 +581,7 @@ typedef struct {
|
|
|
581
581
|
|
|
582
582
|
VALUE heap_recorder_testonly_debug(heap_recorder *heap_recorder) {
|
|
583
583
|
if (heap_recorder == NULL) {
|
|
584
|
-
|
|
584
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
585
585
|
}
|
|
586
586
|
|
|
587
587
|
VALUE debug_str = rb_str_new2("object records:\n");
|
|
@@ -733,7 +733,7 @@ static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_rec
|
|
|
733
733
|
// needed to fully build the object_record.
|
|
734
734
|
active_recording->heap_record = heap_record;
|
|
735
735
|
if (heap_record->num_tracked_objects == UINT32_MAX) {
|
|
736
|
-
|
|
736
|
+
raise_error(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
|
|
737
737
|
}
|
|
738
738
|
heap_record->num_tracked_objects++;
|
|
739
739
|
|
|
@@ -741,11 +741,11 @@ static void commit_recording(heap_recorder *heap_recorder, heap_record *heap_rec
|
|
|
741
741
|
if (existing_error) {
|
|
742
742
|
object_record *existing_record = NULL;
|
|
743
743
|
st_lookup(heap_recorder->object_records, active_recording->obj_id, (st_data_t *) &existing_record);
|
|
744
|
-
if (existing_record == NULL)
|
|
744
|
+
if (existing_record == NULL) raise_error(rb_eRuntimeError, "Unexpected NULL when reading existing record");
|
|
745
745
|
|
|
746
746
|
VALUE existing_inspect = object_record_inspect(heap_recorder, existing_record);
|
|
747
747
|
VALUE new_inspect = object_record_inspect(heap_recorder, active_recording);
|
|
748
|
-
|
|
748
|
+
raise_error(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
|
|
749
749
|
"the same id. previous={%"PRIsVALUE"} new={%"PRIsVALUE"}", existing_inspect, new_inspect);
|
|
750
750
|
}
|
|
751
751
|
}
|
|
@@ -781,7 +781,7 @@ static void cleanup_heap_record_if_unused(heap_recorder *heap_recorder, heap_rec
|
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
if (!st_delete(heap_recorder->heap_records, (st_data_t*) &heap_record, NULL)) {
|
|
784
|
-
|
|
784
|
+
raise_error(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
|
|
785
785
|
};
|
|
786
786
|
heap_record_free(heap_recorder, heap_record);
|
|
787
787
|
}
|
|
@@ -791,14 +791,14 @@ static void on_committed_object_record_cleanup(heap_recorder *heap_recorder, obj
|
|
|
791
791
|
// (See PROF-10656 Datadog-internal for details). Just in case, I've sprinkled a bunch of NULL tests in this function for now.
|
|
792
792
|
// Once we figure out the issue we can get rid of them again.
|
|
793
793
|
|
|
794
|
-
if (heap_recorder == NULL)
|
|
795
|
-
if (heap_recorder->heap_records == NULL)
|
|
796
|
-
if (record == NULL)
|
|
794
|
+
if (heap_recorder == NULL) raise_error(rb_eRuntimeError, "heap_recorder was NULL in on_committed_object_record_cleanup");
|
|
795
|
+
if (heap_recorder->heap_records == NULL) raise_error(rb_eRuntimeError, "heap_recorder->heap_records was NULL in on_committed_object_record_cleanup");
|
|
796
|
+
if (record == NULL) raise_error(rb_eRuntimeError, "record was NULL in on_committed_object_record_cleanup");
|
|
797
797
|
|
|
798
798
|
// Starting with the associated heap record. There will now be one less tracked object pointing to it
|
|
799
799
|
heap_record *heap_record = record->heap_record;
|
|
800
800
|
|
|
801
|
-
if (heap_record == NULL)
|
|
801
|
+
if (heap_record == NULL) raise_error(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
|
|
802
802
|
|
|
803
803
|
heap_record->num_tracked_objects--;
|
|
804
804
|
|
|
@@ -862,7 +862,7 @@ heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location l
|
|
|
862
862
|
uint16_t frames_len = locations.len;
|
|
863
863
|
if (frames_len > MAX_FRAMES_LIMIT) {
|
|
864
864
|
// This is not expected as MAX_FRAMES_LIMIT is shared with the stacktrace construction mechanism
|
|
865
|
-
|
|
865
|
+
raise_error(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
|
|
866
866
|
}
|
|
867
867
|
heap_record *stack = calloc(1, sizeof(heap_record) + frames_len * sizeof(heap_frame)); // See "note on calloc vs ruby_xcalloc use" above
|
|
868
868
|
stack->num_tracked_objects = 0;
|
|
@@ -933,21 +933,21 @@ static void unintern_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId
|
|
|
933
933
|
|
|
934
934
|
ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern(recorder->string_storage, id);
|
|
935
935
|
if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
|
|
936
|
-
|
|
936
|
+
raise_error(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some));
|
|
937
937
|
}
|
|
938
938
|
}
|
|
939
939
|
|
|
940
940
|
static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids) {
|
|
941
941
|
ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern_all(recorder->string_storage, ids);
|
|
942
942
|
if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
|
|
943
|
-
|
|
943
|
+
raise_error(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
|
|
944
944
|
}
|
|
945
945
|
}
|
|
946
946
|
|
|
947
947
|
static VALUE get_ruby_string_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) {
|
|
948
948
|
ddog_StringWrapperResult get_string_result = ddog_prof_ManagedStringStorage_get_string(recorder->string_storage, id);
|
|
949
949
|
if (get_string_result.tag == DDOG_STRING_WRAPPER_RESULT_ERR) {
|
|
950
|
-
|
|
950
|
+
raise_error(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err));
|
|
951
951
|
}
|
|
952
952
|
VALUE ruby_string = ruby_string_from_vec_u8(get_string_result.ok.message);
|
|
953
953
|
ddog_StringWrapper_drop((ddog_StringWrapper *) &get_string_result.ok);
|
|
@@ -962,7 +962,7 @@ static inline double ewma_stat(double previous, double current) {
|
|
|
962
962
|
|
|
963
963
|
VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, VALUE obj_id) {
|
|
964
964
|
if (heap_recorder == NULL) {
|
|
965
|
-
|
|
965
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
966
966
|
}
|
|
967
967
|
|
|
968
968
|
// Check if object records contains an object with this object_id
|
|
@@ -971,15 +971,15 @@ VALUE heap_recorder_testonly_is_object_recorded(heap_recorder *heap_recorder, VA
|
|
|
971
971
|
|
|
972
972
|
void heap_recorder_testonly_reset_last_update(heap_recorder *heap_recorder) {
|
|
973
973
|
if (heap_recorder == NULL) {
|
|
974
|
-
|
|
974
|
+
raise_error(rb_eArgError, "heap_recorder is NULL");
|
|
975
975
|
}
|
|
976
976
|
|
|
977
977
|
heap_recorder->last_update_ns = 0;
|
|
978
978
|
}
|
|
979
979
|
|
|
980
980
|
void heap_recorder_testonly_benchmark_intern(heap_recorder *heap_recorder, ddog_CharSlice string, int times, bool use_all) {
|
|
981
|
-
if (heap_recorder == NULL)
|
|
982
|
-
if (times > REUSABLE_FRAME_DETAILS_SIZE)
|
|
981
|
+
if (heap_recorder == NULL) raise_error(rb_eArgError, "heap profiling must be enabled");
|
|
982
|
+
if (times > REUSABLE_FRAME_DETAILS_SIZE) raise_error(rb_eArgError, "times cannot be > than REUSABLE_FRAME_DETAILS_SIZE");
|
|
983
983
|
|
|
984
984
|
if (use_all) {
|
|
985
985
|
ddog_CharSlice *strings = heap_recorder->reusable_char_slices;
|
|
@@ -85,7 +85,7 @@ static ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
|
|
|
85
85
|
|
|
86
86
|
return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
|
|
87
87
|
} else {
|
|
88
|
-
|
|
88
|
+
raise_error(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -112,7 +112,7 @@ static ddog_prof_ProfileExporter_Result create_exporter(VALUE exporter_configura
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
static void validate_token(ddog_CancellationToken token, const char *file, int line) {
|
|
115
|
-
if (token.inner == NULL)
|
|
115
|
+
if (token.inner == NULL) raise_error(rb_eRuntimeError, "Unexpected: Validation token was empty at %s:%d", file, line);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
static VALUE handle_exporter_failure(ddog_prof_ProfileExporter_Result exporter_result) {
|
|
@@ -128,7 +128,8 @@ static VALUE perform_export(
|
|
|
128
128
|
ddog_prof_EncodedProfile *profile,
|
|
129
129
|
ddog_prof_Exporter_Slice_File files_to_compress_and_export,
|
|
130
130
|
ddog_CharSlice internal_metadata,
|
|
131
|
-
ddog_CharSlice info
|
|
131
|
+
ddog_CharSlice info,
|
|
132
|
+
ddog_CharSlice *process_tags
|
|
132
133
|
) {
|
|
133
134
|
ddog_prof_Request_Result build_result = ddog_prof_Exporter_Request_build(
|
|
134
135
|
exporter,
|
|
@@ -136,7 +137,7 @@ static VALUE perform_export(
|
|
|
136
137
|
files_to_compress_and_export,
|
|
137
138
|
/* files_to_export_unmodified: */ ddog_prof_Exporter_Slice_File_empty(),
|
|
138
139
|
/* optional_additional_tags: */ NULL,
|
|
139
|
-
/* optional_process_tags: */
|
|
140
|
+
/* optional_process_tags: */ process_tags,
|
|
140
141
|
&internal_metadata,
|
|
141
142
|
&info
|
|
142
143
|
);
|
|
@@ -212,6 +213,7 @@ static VALUE _native_do_export(
|
|
|
212
213
|
VALUE tags_as_array = rb_funcall(flush, rb_intern("tags_as_array"), 0);
|
|
213
214
|
VALUE internal_metadata_json = rb_funcall(flush, rb_intern("internal_metadata_json"), 0);
|
|
214
215
|
VALUE info_json = rb_funcall(flush, rb_intern("info_json"), 0);
|
|
216
|
+
VALUE process_tags = rb_funcall(flush, rb_intern("process_tags"), 0);
|
|
215
217
|
|
|
216
218
|
ENFORCE_TYPE(upload_timeout_milliseconds, T_FIXNUM);
|
|
217
219
|
enforce_encoded_profile_instance(encoded_profile);
|
|
@@ -219,6 +221,7 @@ static VALUE _native_do_export(
|
|
|
219
221
|
ENFORCE_TYPE(tags_as_array, T_ARRAY);
|
|
220
222
|
ENFORCE_TYPE(internal_metadata_json, T_STRING);
|
|
221
223
|
ENFORCE_TYPE(info_json, T_STRING);
|
|
224
|
+
ENFORCE_TYPE(process_tags, T_STRING);
|
|
222
225
|
|
|
223
226
|
// Code provenance can be disabled and in that case will be set to nil
|
|
224
227
|
bool have_code_provenance = !NIL_P(code_provenance_data);
|
|
@@ -239,6 +242,7 @@ static VALUE _native_do_export(
|
|
|
239
242
|
|
|
240
243
|
ddog_CharSlice internal_metadata = char_slice_from_ruby_string(internal_metadata_json);
|
|
241
244
|
ddog_CharSlice info = char_slice_from_ruby_string(info_json);
|
|
245
|
+
ddog_CharSlice process_tags_slice = char_slice_from_ruby_string(process_tags);
|
|
242
246
|
|
|
243
247
|
ddog_prof_ProfileExporter_Result exporter_result = create_exporter(exporter_configuration, tags_as_array);
|
|
244
248
|
// Note: Do not add anything that can raise exceptions after this line, as otherwise the exporter memory will leak
|
|
@@ -260,7 +264,8 @@ static VALUE _native_do_export(
|
|
|
260
264
|
to_ddog_prof_EncodedProfile(encoded_profile),
|
|
261
265
|
files_to_compress_and_export,
|
|
262
266
|
internal_metadata,
|
|
263
|
-
info
|
|
267
|
+
info,
|
|
268
|
+
&process_tags_slice
|
|
264
269
|
);
|
|
265
270
|
}
|
|
266
271
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "libdatadog_helpers.h"
|
|
2
2
|
|
|
3
3
|
#include <ruby.h>
|
|
4
|
+
#include "ruby_helpers.h"
|
|
4
5
|
|
|
5
6
|
const char *ruby_value_type_to_string(enum ruby_value_type type) {
|
|
6
7
|
return ruby_value_type_to_char_slice(type).ptr;
|
|
@@ -41,32 +42,12 @@ ddog_CharSlice ruby_value_type_to_char_slice(enum ruby_value_type type) {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity) {
|
|
45
|
-
if (capacity == 0 || string == NULL) {
|
|
46
|
-
// short-circuit, we can't write anything
|
|
47
|
-
ddog_Error_drop(error);
|
|
48
|
-
return 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
ddog_CharSlice error_msg_slice = ddog_Error_message(error);
|
|
52
|
-
size_t error_msg_size = error_msg_slice.len;
|
|
53
|
-
// Account for extra null char for proper cstring
|
|
54
|
-
if (error_msg_size >= capacity) {
|
|
55
|
-
// Error message too big, lets truncate it to capacity - 1 to allow for extra null at end
|
|
56
|
-
error_msg_size = capacity - 1;
|
|
57
|
-
}
|
|
58
|
-
strncpy(string, error_msg_slice.ptr, error_msg_size);
|
|
59
|
-
string[error_msg_size] = '\0';
|
|
60
|
-
ddog_Error_drop(error);
|
|
61
|
-
return error_msg_size;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
45
|
ddog_prof_ManagedStringId intern_or_raise(ddog_prof_ManagedStringStorage string_storage, ddog_CharSlice string) {
|
|
65
46
|
if (string.len == 0) return (ddog_prof_ManagedStringId) { 0 }; // Id 0 is always an empty string, no need to ask
|
|
66
47
|
|
|
67
48
|
ddog_prof_ManagedStringStorageInternResult intern_result = ddog_prof_ManagedStringStorage_intern(string_storage, string);
|
|
68
49
|
if (intern_result.tag == DDOG_PROF_MANAGED_STRING_STORAGE_INTERN_RESULT_ERR) {
|
|
69
|
-
|
|
50
|
+
raise_error(rb_eRuntimeError, "Failed to intern string: %"PRIsVALUE, get_error_details_and_drop(&intern_result.err));
|
|
70
51
|
}
|
|
71
52
|
return intern_result.ok;
|
|
72
53
|
}
|
|
@@ -79,6 +60,6 @@ void intern_all_or_raise(
|
|
|
79
60
|
) {
|
|
80
61
|
ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_intern_all(string_storage, strings, output_ids, output_ids_size);
|
|
81
62
|
if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) {
|
|
82
|
-
|
|
63
|
+
raise_error(rb_eRuntimeError, "Failed to intern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
|
|
83
64
|
}
|
|
84
65
|
}
|
|
@@ -7,11 +7,6 @@ static inline VALUE ruby_string_from_vec_u8(ddog_Vec_U8 string) {
|
|
|
7
7
|
return rb_str_new((char *) string.ptr, string.len);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
// Utility function to be able to extract an error cstring from a ddog_Error.
|
|
11
|
-
// Returns the amount of characters written to string (which are necessarily
|
|
12
|
-
// bounded by capacity - 1 since the string will be null-terminated).
|
|
13
|
-
size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capacity);
|
|
14
|
-
|
|
15
10
|
// Used for pretty printing this Ruby enum. Returns "T_UNKNOWN_OR_MISSING_RUBY_VALUE_TYPE_ENTRY" for unknown elements.
|
|
16
11
|
// In practice, there's a few types that the profiler will probably never encounter, but I've added all entries of
|
|
17
12
|
// ruby_value_type that Ruby uses so that we can also use this for debugging.
|
|
@@ -456,27 +456,28 @@ int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *st
|
|
|
456
456
|
// here.
|
|
457
457
|
if (ec == NULL) return 0;
|
|
458
458
|
|
|
459
|
+
// Avoid sampling dead threads
|
|
460
|
+
if (th->status == THREAD_KILLED) return 0;
|
|
461
|
+
|
|
462
|
+
const rb_control_frame_t *cfp = ec->cfp;
|
|
463
|
+
|
|
464
|
+
// This happens on newly-created threads (we even had a flaky test because of it)
|
|
465
|
+
if (cfp == NULL) return PLACEHOLDER_STACK_IN_NATIVE_CODE;
|
|
466
|
+
|
|
459
467
|
// I suspect this won't happen for ddtrace, but just-in-case we've imported a potential fix for
|
|
460
468
|
// https://github.com/ruby/ruby/pull/13643 by assuming that these can be NULL/zero with the cfp being non-NULL yet.
|
|
461
469
|
if (ec->vm_stack == NULL || ec->vm_stack_size == 0) return 0;
|
|
462
470
|
|
|
463
|
-
const rb_control_frame_t *
|
|
471
|
+
const rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
|
|
464
472
|
#ifndef NO_JIT_RETURN
|
|
465
473
|
const rb_control_frame_t *top = cfp;
|
|
466
474
|
#endif
|
|
467
475
|
const rb_callable_method_entry_t *cme;
|
|
468
476
|
|
|
469
|
-
// Avoid sampling dead threads
|
|
470
|
-
if (th->status == THREAD_KILLED) return 0;
|
|
471
|
-
|
|
472
477
|
// `vm_backtrace.c` includes this check in several methods. This happens on newly-created threads, and may
|
|
473
478
|
// also (not entirely sure) happen on dead threads
|
|
474
479
|
if (end_cfp == NULL) return PLACEHOLDER_STACK_IN_NATIVE_CODE;
|
|
475
480
|
|
|
476
|
-
// This should not happen for ddtrace (it can only happen when a thread is still being created), but I've imported
|
|
477
|
-
// it from https://github.com/ruby/ruby/pull/7116 in a "just in case" kind of mindset.
|
|
478
|
-
if (cfp == NULL) return 0;
|
|
479
|
-
|
|
480
481
|
// Fix: Skip dummy frame that shows up in main thread.
|
|
481
482
|
//
|
|
482
483
|
// According to a comment in `backtrace_each` (`vm_backtrace.c`), there's two dummy frames that we should ignore
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#include "clock_id.h"
|
|
9
9
|
#include "helpers.h"
|
|
10
10
|
#include "private_vm_api_access.h"
|
|
11
|
+
#include "datadog_ruby_common.h"
|
|
11
12
|
#include "ruby_helpers.h"
|
|
12
13
|
#include "setup_signal_handler.h"
|
|
13
14
|
#include "time_helpers.h"
|
|
@@ -50,6 +51,10 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
50
51
|
#endif
|
|
51
52
|
|
|
52
53
|
VALUE datadog_module = rb_define_module("Datadog");
|
|
54
|
+
|
|
55
|
+
// MUST be called before all other initialization
|
|
56
|
+
datadog_ruby_common_init();
|
|
57
|
+
|
|
53
58
|
VALUE profiling_module = rb_define_module_under(datadog_module, "Profiling");
|
|
54
59
|
VALUE native_extension_module = rb_define_module_under(profiling_module, "NativeExtension");
|
|
55
60
|
|
|
@@ -99,7 +104,7 @@ static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
|
|
|
99
104
|
typedef struct {
|
|
100
105
|
VALUE exception_class;
|
|
101
106
|
char *test_message;
|
|
102
|
-
|
|
107
|
+
char *test_message_arg;
|
|
103
108
|
} trigger_grab_gvl_and_raise_arguments;
|
|
104
109
|
|
|
105
110
|
static VALUE _native_grab_gvl_and_raise(DDTRACE_UNUSED VALUE _self, VALUE exception_class, VALUE test_message, VALUE test_message_arg, VALUE release_gvl) {
|
|
@@ -109,24 +114,24 @@ static VALUE _native_grab_gvl_and_raise(DDTRACE_UNUSED VALUE _self, VALUE except
|
|
|
109
114
|
|
|
110
115
|
args.exception_class = exception_class;
|
|
111
116
|
args.test_message = StringValueCStr(test_message);
|
|
112
|
-
args.test_message_arg = test_message_arg != Qnil ?
|
|
117
|
+
args.test_message_arg = test_message_arg != Qnil ? StringValueCStr(test_message_arg) : NULL;
|
|
113
118
|
|
|
114
119
|
if (RTEST(release_gvl)) {
|
|
115
120
|
rb_thread_call_without_gvl(trigger_grab_gvl_and_raise, &args, NULL, NULL);
|
|
116
121
|
} else {
|
|
117
|
-
|
|
122
|
+
private_grab_gvl_and_raise(args.exception_class, 0, args.test_message, args.test_message_arg);
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
raise_error(rb_eRuntimeError, "Failed to raise exception in _native_grab_gvl_and_raise; this should never happen");
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
static void *trigger_grab_gvl_and_raise(void *trigger_args) {
|
|
124
129
|
trigger_grab_gvl_and_raise_arguments *args = (trigger_grab_gvl_and_raise_arguments *) trigger_args;
|
|
125
130
|
|
|
126
|
-
if (args->test_message_arg
|
|
127
|
-
|
|
131
|
+
if (args->test_message_arg != NULL) {
|
|
132
|
+
private_grab_gvl_and_raise(args->exception_class, 0, args->test_message, args->test_message_arg);
|
|
128
133
|
} else {
|
|
129
|
-
|
|
134
|
+
private_grab_gvl_and_raise(args->exception_class, 0, args->test_message, NULL);
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
return NULL;
|
|
@@ -135,7 +140,7 @@ static void *trigger_grab_gvl_and_raise(void *trigger_args) {
|
|
|
135
140
|
typedef struct {
|
|
136
141
|
int syserr_errno;
|
|
137
142
|
char *test_message;
|
|
138
|
-
|
|
143
|
+
char *test_message_arg;
|
|
139
144
|
} trigger_grab_gvl_and_raise_syserr_arguments;
|
|
140
145
|
|
|
141
146
|
static VALUE _native_grab_gvl_and_raise_syserr(DDTRACE_UNUSED VALUE _self, VALUE syserr_errno, VALUE test_message, VALUE test_message_arg, VALUE release_gvl) {
|
|
@@ -145,24 +150,24 @@ static VALUE _native_grab_gvl_and_raise_syserr(DDTRACE_UNUSED VALUE _self, VALUE
|
|
|
145
150
|
|
|
146
151
|
args.syserr_errno = NUM2INT(syserr_errno);
|
|
147
152
|
args.test_message = StringValueCStr(test_message);
|
|
148
|
-
args.test_message_arg = test_message_arg != Qnil ?
|
|
153
|
+
args.test_message_arg = test_message_arg != Qnil ? StringValueCStr(test_message_arg) : NULL;
|
|
149
154
|
|
|
150
155
|
if (RTEST(release_gvl)) {
|
|
151
156
|
rb_thread_call_without_gvl(trigger_grab_gvl_and_raise_syserr, &args, NULL, NULL);
|
|
152
157
|
} else {
|
|
153
|
-
|
|
158
|
+
private_grab_gvl_and_raise(Qnil, args.syserr_errno, args.test_message, args.test_message_arg);
|
|
154
159
|
}
|
|
155
160
|
|
|
156
|
-
|
|
161
|
+
raise_error(rb_eRuntimeError, "Failed to raise exception in _native_grab_gvl_and_raise_syserr; this should never happen");
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
static void *trigger_grab_gvl_and_raise_syserr(void *trigger_args) {
|
|
160
165
|
trigger_grab_gvl_and_raise_syserr_arguments *args = (trigger_grab_gvl_and_raise_syserr_arguments *) trigger_args;
|
|
161
166
|
|
|
162
|
-
if (args->test_message_arg
|
|
163
|
-
|
|
167
|
+
if (args->test_message_arg != NULL) {
|
|
168
|
+
private_grab_gvl_and_raise(Qnil, args->syserr_errno, args->test_message, args->test_message_arg);
|
|
164
169
|
} else {
|
|
165
|
-
|
|
170
|
+
private_grab_gvl_and_raise(Qnil, args->syserr_errno, args->test_message, NULL);
|
|
166
171
|
}
|
|
167
172
|
|
|
168
173
|
return NULL;
|
|
@@ -248,7 +253,7 @@ static VALUE _native_trigger_holding_the_gvl_signal_handler_on(DDTRACE_UNUSED VA
|
|
|
248
253
|
|
|
249
254
|
replace_sigprof_signal_handler_with_empty_handler(holding_the_gvl_signal_handler);
|
|
250
255
|
|
|
251
|
-
if (holding_the_gvl_signal_handler_result[0] == Qfalse)
|
|
256
|
+
if (holding_the_gvl_signal_handler_result[0] == Qfalse) raise_error(rb_eRuntimeError, "Could not signal background_thread");
|
|
252
257
|
|
|
253
258
|
VALUE result = rb_hash_new();
|
|
254
259
|
rb_hash_aset(result, ID2SYM(rb_intern("ruby_thread_has_gvl_p")), holding_the_gvl_signal_handler_result[1]);
|
|
@@ -21,74 +21,85 @@ void ruby_helpers_init(void) {
|
|
|
21
21
|
to_s_id = rb_intern("to_s");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// Internal helper for raising pre-formatted syserr exceptions
|
|
25
|
+
static NORETURN(void private_raise_syserr_formatted(int syserr_errno, const char *detailed_message, const char *static_message)) {
|
|
26
|
+
VALUE exception = rb_syserr_new(syserr_errno, detailed_message);
|
|
27
|
+
private_raise_exception(exception, static_message);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Use `raise_syserr` the macro instead, as it provides additional argument checks.
|
|
31
|
+
void private_raise_syserr(int syserr_errno, const char *fmt, ...) {
|
|
32
|
+
FORMAT_VA_ERROR_MESSAGE(detailed_message, fmt);
|
|
33
|
+
private_raise_syserr_formatted(syserr_errno, detailed_message, fmt);
|
|
34
|
+
}
|
|
25
35
|
|
|
26
36
|
typedef struct {
|
|
27
37
|
VALUE exception_class;
|
|
38
|
+
int syserr_errno;
|
|
28
39
|
char exception_message[MAX_RAISE_MESSAGE_SIZE];
|
|
40
|
+
char telemetry_message[MAX_RAISE_MESSAGE_SIZE];
|
|
29
41
|
} raise_args;
|
|
30
42
|
|
|
31
43
|
static void *trigger_raise(void *raise_arguments) {
|
|
32
44
|
raise_args *args = (raise_args *) raise_arguments;
|
|
33
|
-
rb_raise(args->exception_class, "%s", args->exception_message);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
void grab_gvl_and_raise(VALUE exception_class, const char *format_string, ...) {
|
|
37
|
-
raise_args args;
|
|
38
45
|
|
|
39
|
-
args
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
args
|
|
46
|
+
if (args->syserr_errno) {
|
|
47
|
+
private_raise_syserr_formatted(
|
|
48
|
+
args->syserr_errno,
|
|
49
|
+
args->exception_message,
|
|
50
|
+
args->telemetry_message
|
|
51
|
+
);
|
|
52
|
+
} else {
|
|
53
|
+
private_raise_error_formatted(
|
|
54
|
+
args->exception_class,
|
|
55
|
+
args->exception_message,
|
|
56
|
+
args->telemetry_message
|
|
50
57
|
);
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
rb_bug("[ddtrace] Unexpected: Reached the end of grab_gvl_and_raise while raising '%s'\n", args.exception_message);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
typedef struct {
|
|
59
|
-
int syserr_errno;
|
|
60
|
-
char exception_message[MAX_RAISE_MESSAGE_SIZE];
|
|
61
|
-
} syserr_raise_args;
|
|
62
|
-
|
|
63
|
-
static void *trigger_syserr_raise(void *syserr_raise_arguments) {
|
|
64
|
-
syserr_raise_args *args = (syserr_raise_args *) syserr_raise_arguments;
|
|
65
|
-
rb_syserr_fail(args->syserr_errno, args->exception_message);
|
|
60
|
+
return NULL;
|
|
66
61
|
}
|
|
67
62
|
|
|
68
|
-
void
|
|
69
|
-
|
|
63
|
+
void private_grab_gvl_and_raise(VALUE exception_class, int syserr_errno, const char *format_string, ...) {
|
|
64
|
+
raise_args args;
|
|
70
65
|
|
|
71
|
-
|
|
66
|
+
if (syserr_errno != 0) {
|
|
67
|
+
args.exception_class = Qnil;
|
|
68
|
+
args.syserr_errno = syserr_errno;
|
|
69
|
+
} else {
|
|
70
|
+
args.exception_class = exception_class;
|
|
71
|
+
args.syserr_errno = 0;
|
|
72
|
+
}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
FORMAT_VA_ERROR_MESSAGE(formatted_exception_message, format_string);
|
|
75
|
+
snprintf(args.exception_message, MAX_RAISE_MESSAGE_SIZE, "%s", formatted_exception_message);
|
|
76
|
+
snprintf(args.telemetry_message, MAX_RAISE_MESSAGE_SIZE, "%s", format_string);
|
|
76
77
|
|
|
77
78
|
if (is_current_thread_holding_the_gvl()) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
char telemetry_message[MAX_RAISE_MESSAGE_SIZE];
|
|
80
|
+
snprintf(
|
|
81
|
+
telemetry_message,
|
|
82
|
+
MAX_RAISE_MESSAGE_SIZE,
|
|
83
|
+
"grab_gvl_and_raise called by thread holding the global VM lock: %s",
|
|
84
|
+
format_string
|
|
85
|
+
);
|
|
86
|
+
char exception_message[MAX_RAISE_MESSAGE_SIZE];
|
|
87
|
+
snprintf(
|
|
88
|
+
exception_message,
|
|
89
|
+
MAX_RAISE_MESSAGE_SIZE,
|
|
90
|
+
"grab_gvl_and_raise called by thread holding the global VM lock: %s",
|
|
82
91
|
args.exception_message
|
|
83
92
|
);
|
|
93
|
+
VALUE exception = rb_exc_new_cstr(rb_eRuntimeError, exception_message);
|
|
94
|
+
private_raise_exception(exception, telemetry_message);
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
rb_thread_call_with_gvl(
|
|
97
|
+
rb_thread_call_with_gvl(trigger_raise, &args);
|
|
87
98
|
|
|
88
|
-
rb_bug("[ddtrace] Unexpected: Reached the end of
|
|
99
|
+
rb_bug("[ddtrace] Unexpected: Reached the end of grab_gvl_and_raise while raising '%s'\n", args.exception_message);
|
|
89
100
|
}
|
|
90
101
|
|
|
91
|
-
void
|
|
102
|
+
void private_raise_enforce_syserr(
|
|
92
103
|
int syserr_errno,
|
|
93
104
|
bool have_gvl,
|
|
94
105
|
const char *expression,
|
|
@@ -99,7 +110,7 @@ void raise_syserr(
|
|
|
99
110
|
if (have_gvl) {
|
|
100
111
|
rb_exc_raise(rb_syserr_new_str(syserr_errno, rb_sprintf("Failure returned by '%s' at %s:%d:in `%s'", expression, file, line, function_name)));
|
|
101
112
|
} else {
|
|
102
|
-
|
|
113
|
+
private_grab_gvl_and_raise(Qnil, syserr_errno, "Failure returned by '%s' at %s:%d:in `%s'", expression, file, line, function_name);
|
|
103
114
|
}
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <stdbool.h>
|
|
4
|
+
#include <stdarg.h>
|
|
4
5
|
#include "datadog_ruby_common.h"
|
|
5
6
|
|
|
6
7
|
// Initialize internal data needed by some ruby helpers. Should be called during start, before any actual
|
|
@@ -39,26 +40,37 @@ static inline int check_if_pending_exception(void) {
|
|
|
39
40
|
|
|
40
41
|
#define VALUE_COUNT(array) (sizeof(array) / sizeof(VALUE))
|
|
41
42
|
|
|
43
|
+
// Raises a SysErr exception with the formatted string as its message.
|
|
44
|
+
// See `raise_error` for details about telemetry messages.
|
|
45
|
+
#define raise_syserr(syserr_errno, fmt, ...) \
|
|
46
|
+
private_raise_syserr(syserr_errno, "" fmt, ##__VA_ARGS__)
|
|
47
|
+
|
|
48
|
+
#define grab_gvl_and_raise(exception_class, fmt, ...) \
|
|
49
|
+
private_grab_gvl_and_raise(exception_class, 0, "" fmt, ##__VA_ARGS__)
|
|
50
|
+
|
|
42
51
|
NORETURN(
|
|
43
|
-
void
|
|
52
|
+
void private_raise_syserr(int syserr_errno, const char *fmt, ...)
|
|
44
53
|
__attribute__ ((format (printf, 2, 3)));
|
|
45
54
|
);
|
|
55
|
+
|
|
46
56
|
NORETURN(
|
|
47
|
-
void
|
|
48
|
-
__attribute__ ((format (printf,
|
|
57
|
+
void private_grab_gvl_and_raise(VALUE exception_class, int syserr_errno, const char *format_string, ...)
|
|
58
|
+
__attribute__ ((format (printf, 3, 4)));
|
|
49
59
|
);
|
|
50
60
|
|
|
61
|
+
|
|
62
|
+
|
|
51
63
|
#define ENFORCE_SUCCESS_GVL(expression) ENFORCE_SUCCESS_HELPER(expression, true)
|
|
52
64
|
#define ENFORCE_SUCCESS_NO_GVL(expression) ENFORCE_SUCCESS_HELPER(expression, false)
|
|
53
65
|
|
|
54
66
|
#define ENFORCE_SUCCESS_HELPER(expression, have_gvl) \
|
|
55
|
-
{ int result_syserr_errno = expression; if (RB_UNLIKELY(result_syserr_errno))
|
|
67
|
+
{ int result_syserr_errno = expression; if (RB_UNLIKELY(result_syserr_errno)) private_raise_enforce_syserr(result_syserr_errno, have_gvl, ADD_QUOTES(expression), __FILE__, __LINE__, __func__); }
|
|
56
68
|
|
|
57
69
|
#define RUBY_NUM_OR_NIL(val, condition, conv) ((val condition) ? conv(val) : Qnil)
|
|
58
70
|
#define RUBY_AVG_OR_NIL(total, count) ((count == 0) ? Qnil : DBL2NUM(((double) total) / count))
|
|
59
71
|
|
|
60
72
|
// Called by ENFORCE_SUCCESS_HELPER; should not be used directly
|
|
61
|
-
NORETURN(void
|
|
73
|
+
NORETURN(void private_raise_enforce_syserr(
|
|
62
74
|
int syserr_errno,
|
|
63
75
|
bool have_gvl,
|
|
64
76
|
const char *expression,
|