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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +7 -6
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
  6. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -5
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
  9. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +2 -2
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
  12. data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
  13. data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
  14. data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
  15. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
  16. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
  17. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +9 -8
  18. data/ext/datadog_profiling_native_extension/profiling.c +20 -15
  19. data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
  20. data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
  21. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
  22. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
  23. data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
  24. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
  25. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
  26. data/ext/libdatadog_api/crashtracker.c +5 -8
  27. data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
  28. data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
  29. data/ext/libdatadog_api/ddsketch.c +4 -8
  30. data/ext/libdatadog_api/feature_flags.c +5 -5
  31. data/ext/libdatadog_api/helpers.h +27 -0
  32. data/ext/libdatadog_api/init.c +4 -0
  33. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
  34. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
  35. data/lib/datadog/appsec/component.rb +1 -1
  36. data/lib/datadog/appsec/context.rb +3 -3
  37. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  38. data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
  39. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
  40. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
  41. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  42. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
  43. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
  44. data/lib/datadog/appsec/ext.rb +2 -0
  45. data/lib/datadog/appsec/metrics/collector.rb +8 -3
  46. data/lib/datadog/appsec/metrics/exporter.rb +7 -0
  47. data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
  48. data/lib/datadog/appsec/metrics.rb +5 -5
  49. data/lib/datadog/appsec/remote.rb +4 -4
  50. data/lib/datadog/appsec.rb +7 -1
  51. data/lib/datadog/core/configuration/settings.rb +17 -0
  52. data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
  53. data/lib/datadog/core/telemetry/logger.rb +2 -0
  54. data/lib/datadog/core/telemetry/logging.rb +20 -2
  55. data/lib/datadog/profiling/component.rb +13 -0
  56. data/lib/datadog/profiling/exporter.rb +4 -0
  57. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  58. data/lib/datadog/profiling/flush.rb +3 -0
  59. data/lib/datadog/profiling/profiler.rb +3 -5
  60. data/lib/datadog/profiling/scheduler.rb +8 -7
  61. data/lib/datadog/profiling/tag_builder.rb +1 -0
  62. data/lib/datadog/version.rb +1 -1
  63. 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
- rb_raise(rb_eArgError, "Heap sample rate must be a positive integer value but was %d", sample_rate);
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
- rb_raise(rb_eRuntimeError, "Detected consecutive heap allocation recording starts without end.");
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
- rb_raise(rb_eRuntimeError, "Detected a bignum object id. These are not supported by heap profiling.");
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
- rb_raise(rb_eRuntimeError, "Ended a heap recording that was not started");
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
- rb_raise(rb_eRuntimeError, "New heap recorder iteration prepared without the previous one having been finished.");
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
- rb_raise(rb_eRuntimeError, "Failed to create heap snapshot.");
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
- rb_raise(rb_eRuntimeError, "Heap recorder iteration finished without having been prepared.");
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
- rb_raise(rb_eArgError, "heap_recorder is NULL");
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
- rb_raise(rb_eRuntimeError, "Reached maximum number of tracked objects for heap record");
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) rb_raise(rb_eRuntimeError, "Unexpected NULL when reading existing record");
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
- rb_raise(rb_eRuntimeError, "Object ids are supposed to be unique. We got 2 allocation recordings with "
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
- rb_raise(rb_eRuntimeError, "Attempted to cleanup an untracked heap_record");
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) rb_raise(rb_eRuntimeError, "heap_recorder was NULL in on_committed_object_record_cleanup");
795
- if (heap_recorder->heap_records == NULL) rb_raise(rb_eRuntimeError, "heap_recorder->heap_records was NULL in on_committed_object_record_cleanup");
796
- if (record == NULL) rb_raise(rb_eRuntimeError, "record was NULL in on_committed_object_record_cleanup");
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) rb_raise(rb_eRuntimeError, "heap_record was NULL in on_committed_object_record_cleanup");
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
- rb_raise(rb_eRuntimeError, "Found stack with more than %d frames (%d)", MAX_FRAMES_LIMIT, frames_len);
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
- rb_raise(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some));
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
- rb_raise(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
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
- rb_raise(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err));
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
- rb_raise(rb_eArgError, "heap_recorder is NULL");
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
- rb_raise(rb_eArgError, "heap_recorder is NULL");
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) rb_raise(rb_eArgError, "heap profiling must be enabled");
982
- if (times > REUSABLE_FRAME_DETAILS_SIZE) rb_raise(rb_eArgError, "times cannot be > than 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
- rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
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) rb_raise(rb_eRuntimeError, "Unexpected: Validation token was empty at %s:%d", file, line);
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: */ NULL,
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
- rb_raise(rb_eRuntimeError, "Failed to intern string: %"PRIsVALUE, get_error_details_and_drop(&intern_result.err));
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
- rb_raise(rb_eRuntimeError, "Failed to intern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some));
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 *cfp = ec->cfp, *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec);
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
- int test_message_arg;
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 ? NUM2INT(test_message_arg) : -1;
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
- grab_gvl_and_raise(args.exception_class, "%s", args.test_message);
122
+ private_grab_gvl_and_raise(args.exception_class, 0, args.test_message, args.test_message_arg);
118
123
  }
119
124
 
120
- rb_raise(rb_eRuntimeError, "Failed to raise exception in _native_grab_gvl_and_raise; this should never happen");
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 >= 0) {
127
- grab_gvl_and_raise(args->exception_class, "%s%d", args->test_message, args->test_message_arg);
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
- grab_gvl_and_raise(args->exception_class, "%s", args->test_message);
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
- int test_message_arg;
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 ? NUM2INT(test_message_arg) : -1;
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
- grab_gvl_and_raise_syserr(args.syserr_errno, "%s", args.test_message);
158
+ private_grab_gvl_and_raise(Qnil, args.syserr_errno, args.test_message, args.test_message_arg);
154
159
  }
155
160
 
156
- rb_raise(rb_eRuntimeError, "Failed to raise exception in _native_grab_gvl_and_raise_syserr; this should never happen");
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 >= 0) {
163
- grab_gvl_and_raise_syserr(args->syserr_errno, "%s%d", args->test_message, args->test_message_arg);
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
- grab_gvl_and_raise_syserr(args->syserr_errno, "%s", args->test_message);
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) rb_raise(rb_eRuntimeError, "Could not signal background_thread");
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
- #define MAX_RAISE_MESSAGE_SIZE 256
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.exception_class = exception_class;
40
-
41
- va_list format_string_arguments;
42
- va_start(format_string_arguments, format_string);
43
- vsnprintf(args.exception_message, MAX_RAISE_MESSAGE_SIZE, format_string, format_string_arguments);
44
-
45
- if (is_current_thread_holding_the_gvl()) {
46
- rb_raise(
47
- rb_eRuntimeError,
48
- "grab_gvl_and_raise called by thread holding the global VM lock. exception_message: '%s'",
49
- args.exception_message
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
- rb_thread_call_with_gvl(trigger_raise, &args);
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 grab_gvl_and_raise_syserr(int syserr_errno, const char *format_string, ...) {
69
- syserr_raise_args args;
63
+ void private_grab_gvl_and_raise(VALUE exception_class, int syserr_errno, const char *format_string, ...) {
64
+ raise_args args;
70
65
 
71
- args.syserr_errno = syserr_errno;
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
- va_list format_string_arguments;
74
- va_start(format_string_arguments, format_string);
75
- vsnprintf(args.exception_message, MAX_RAISE_MESSAGE_SIZE, format_string, format_string_arguments);
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
- rb_raise(
79
- rb_eRuntimeError,
80
- "grab_gvl_and_raise_syserr called by thread holding the global VM lock. syserr_errno: %d, exception_message: '%s'",
81
- syserr_errno,
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(trigger_syserr_raise, &args);
97
+ rb_thread_call_with_gvl(trigger_raise, &args);
87
98
 
88
- rb_bug("[ddtrace] Unexpected: Reached the end of grab_gvl_and_raise_syserr while raising '%s'\n", args.exception_message);
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 raise_syserr(
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
- grab_gvl_and_raise_syserr(syserr_errno, "Failure returned by '%s' at %s:%d:in `%s'", expression, file, line, function_name);
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 grab_gvl_and_raise(VALUE exception_class, const char *format_string, ...)
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 grab_gvl_and_raise_syserr(int syserr_errno, const char *format_string, ...)
48
- __attribute__ ((format (printf, 2, 3)));
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)) raise_syserr(result_syserr_errno, have_gvl, ADD_QUOTES(expression), __FILE__, __LINE__, __func__); }
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 raise_syserr(
73
+ NORETURN(void private_raise_enforce_syserr(
62
74
  int syserr_errno,
63
75
  bool have_gvl,
64
76
  const char *expression,