From bb6216c3cd31e32dedad190ede62c94e70b881fa Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 14:17:57 +0100 Subject: [PATCH 1/9] Add temporary definitions for old libdatadog types so we can migrate incrementally --- .../collectors_gc_profiling_helper.h | 2 + .../collectors_stack.c | 14 +- .../collectors_stack.h | 4 +- .../collectors_thread_context.c | 4 +- .../heap_recorder.c | 12 +- .../heap_recorder.h | 2 + .../http_transport.c | 4 +- .../libdatadog_helpers.h | 183 ++++++++++++++++++ .../stack_recorder.c | 4 +- .../stack_recorder.h | 2 + 10 files changed, 210 insertions(+), 21 deletions(-) diff --git a/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.h b/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.h index 7a5096624c6..e693ef3bb14 100644 --- a/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.h +++ b/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.h @@ -1,5 +1,7 @@ #pragma once +#include "libdatadog_helpers.h" + void gc_profiling_init(void); bool gc_profiling_has_major_gc_finished(void); uint8_t gc_profiling_set_metadata(ddog_prof_Label *labels, int labels_length); diff --git a/ext/datadog_profiling_native_extension/collectors_stack.c b/ext/datadog_profiling_native_extension/collectors_stack.c index bf60dbc78b8..b7ea40ae249 100644 --- a/ext/datadog_profiling_native_extension/collectors_stack.c +++ b/ext/datadog_profiling_native_extension/collectors_stack.c @@ -97,7 +97,7 @@ typedef struct { sample_values values; sample_labels labels; VALUE thread; - ddog_prof_Location *locations; + fixme_ddog_prof_Location *locations; sampling_buffer *buffer; bool native_filenames_enabled; st_table *native_filenames_cache; @@ -172,7 +172,7 @@ static VALUE _native_sample(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self) { int max_frames_requested = sampling_buffer_check_max_frames(NUM2INT(max_frames)); - ddog_prof_Location *locations = ruby_xcalloc(max_frames_requested, sizeof(ddog_prof_Location)); + fixme_ddog_prof_Location *locations = ruby_xcalloc(max_frames_requested, sizeof(fixme_ddog_prof_Location)); sampling_buffer buffer; sampling_buffer_initialize(&buffer, max_frames_requested, locations); @@ -372,9 +372,9 @@ void sample_thread( int libdatadog_stores_stacks_flipped_from_rb_profile_frames_index = top_of_stack_position - i; - buffer->locations[libdatadog_stores_stacks_flipped_from_rb_profile_frames_index] = (ddog_prof_Location) { + buffer->locations[libdatadog_stores_stacks_flipped_from_rb_profile_frames_index] = (fixme_ddog_prof_Location) { .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}}, - .function = (ddog_prof_Function) {.name = name_slice, .filename = filename_slice}, + .function = (fixme_ddog_prof_Function) {.name = name_slice, .filename = filename_slice}, .line = line, }; } @@ -524,7 +524,7 @@ static void maybe_trim_template_random_ids(ddog_CharSlice *name_slice, ddog_Char static void add_truncated_frames_placeholder(sampling_buffer* buffer) { // Important note: The strings below are static so we don't need to worry about their lifetime. If we ever want to change // this to non-static strings, don't forget to check that lifetimes are properly respected. - buffer->locations[0] = (ddog_prof_Location) { + buffer->locations[0] = (fixme_ddog_prof_Location) { .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}}, .function = {.name = DDOG_CHARSLICE_C("Truncated Frames"), .filename = DDOG_CHARSLICE_C(""), .filename_id = {}}, .line = 0, @@ -571,7 +571,7 @@ void record_placeholder_stack( sample_labels labels, ddog_CharSlice placeholder_stack ) { - ddog_prof_Location placeholder_location = { + fixme_ddog_prof_Location placeholder_location = { .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}}, .function = {.name = DDOG_CHARSLICE_C(""), .filename = placeholder_stack}, .line = 0, @@ -601,7 +601,7 @@ uint16_t sampling_buffer_check_max_frames(int max_frames) { return max_frames; } -void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames, ddog_prof_Location *locations) { +void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames, fixme_ddog_prof_Location *locations) { sampling_buffer_check_max_frames(max_frames); buffer->max_frames = max_frames; diff --git a/ext/datadog_profiling_native_extension/collectors_stack.h b/ext/datadog_profiling_native_extension/collectors_stack.h index df8df5d9129..4a6c9339a24 100644 --- a/ext/datadog_profiling_native_extension/collectors_stack.h +++ b/ext/datadog_profiling_native_extension/collectors_stack.h @@ -11,7 +11,7 @@ // Used as scratch space during sampling typedef struct { uint16_t max_frames; - ddog_prof_Location *locations; + fixme_ddog_prof_Location *locations; frame_info *stack_buffer; bool pending_sample; bool is_marking; // Used to avoid recording a sample when marking @@ -36,7 +36,7 @@ void record_placeholder_stack( bool prepare_sample_thread(VALUE thread, sampling_buffer *buffer); uint16_t sampling_buffer_check_max_frames(int max_frames); -void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames, ddog_prof_Location *locations); +void sampling_buffer_initialize(sampling_buffer *buffer, uint16_t max_frames, fixme_ddog_prof_Location *locations); void sampling_buffer_free(sampling_buffer *buffer); void sampling_buffer_mark(sampling_buffer *buffer); static inline bool sampling_buffer_needs_marking(sampling_buffer *buffer) { diff --git a/ext/datadog_profiling_native_extension/collectors_thread_context.c b/ext/datadog_profiling_native_extension/collectors_thread_context.c index f2cc76c6021..a40839bffa4 100644 --- a/ext/datadog_profiling_native_extension/collectors_thread_context.c +++ b/ext/datadog_profiling_native_extension/collectors_thread_context.c @@ -122,7 +122,7 @@ typedef struct { // "Update this when modifying state struct" // Required by Datadog::Profiling::Collectors::Stack as a scratch buffer during sampling - ddog_prof_Location *locations; + fixme_ddog_prof_Location *locations; uint16_t max_frames; // Hashmap // Note: Be very careful when mutating this map, as it gets read e.g. in the middle of GC and signal handlers. @@ -505,7 +505,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel // Update this when modifying state struct state->max_frames = sampling_buffer_check_max_frames(NUM2INT(max_frames)); - state->locations = ruby_xcalloc(state->max_frames, sizeof(ddog_prof_Location)); + state->locations = ruby_xcalloc(state->max_frames, sizeof(fixme_ddog_prof_Location)); // hash_map_per_thread_context is already initialized, nothing to do here state->recorder_instance = enforce_recorder_instance(recorder_instance); state->endpoint_collection_enabled = (endpoint_collection_enabled == Qtrue); diff --git a/ext/datadog_profiling_native_extension/heap_recorder.c b/ext/datadog_profiling_native_extension/heap_recorder.c index f869d24c6d3..d814901a75c 100644 --- a/ext/datadog_profiling_native_extension/heap_recorder.c +++ b/ext/datadog_profiling_native_extension/heap_recorder.c @@ -132,7 +132,7 @@ struct heap_recorder { // Reusable arrays, implementing a flyweight pattern for things like iteration #define REUSABLE_LOCATIONS_SIZE MAX_FRAMES_LIMIT - ddog_prof_Location *reusable_locations; + fixme_ddog_prof_Location *reusable_locations; #define REUSABLE_FRAME_DETAILS_SIZE (2 * MAX_FRAMES_LIMIT) // because it'll be used for both function names AND file names) ddog_prof_ManagedStringId *reusable_ids; @@ -203,7 +203,7 @@ heap_recorder* heap_recorder_new(ddog_prof_ManagedStringStorage string_storage) recorder->heap_records = st_init_table(&st_hash_type_heap_record); recorder->object_records = st_init_numtable(); recorder->object_records_snapshot = NULL; - recorder->reusable_locations = ruby_xcalloc(REUSABLE_LOCATIONS_SIZE, sizeof(ddog_prof_Location)); + recorder->reusable_locations = ruby_xcalloc(REUSABLE_LOCATIONS_SIZE, sizeof(fixme_ddog_prof_Location)); recorder->reusable_ids = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_prof_ManagedStringId)); recorder->reusable_char_slices = ruby_xcalloc(REUSABLE_FRAME_DETAILS_SIZE, sizeof(ddog_CharSlice)); recorder->active_recording = NULL; @@ -681,10 +681,10 @@ static int st_object_records_iterate(DDTRACE_UNUSED st_data_t key, st_data_t val return ST_CONTINUE; } - ddog_prof_Location *locations = recorder->reusable_locations; + fixme_ddog_prof_Location *locations = recorder->reusable_locations; for (uint16_t i = 0; i < stack->frames_len; i++) { const heap_frame *frame = &stack->frames[i]; - locations[i] = (ddog_prof_Location) { + locations[i] = (fixme_ddog_prof_Location) { .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}}, .function = { .name = DDOG_CHARSLICE_C(""), @@ -872,7 +872,7 @@ heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location l ddog_CharSlice *strings = recorder->reusable_char_slices; // Put all the char slices in the same array; we'll pull them out in the same order from the ids array for (uint16_t i = 0; i < stack->frames_len; i++) { - const ddog_prof_Location *location = &locations.ptr[i]; + const fixme_ddog_prof_Location *location = &locations.ptr[i]; strings[i] = location->function.filename; strings[i + stack->frames_len] = location->function.name; } @@ -883,7 +883,7 @@ heap_record* heap_record_new(heap_recorder *recorder, ddog_prof_Slice_Location l stack->frames[i] = (heap_frame) { .filename = recorder->reusable_ids[i], .name = recorder->reusable_ids[i + stack->frames_len], - // ddog_prof_Location is a int64_t. We don't expect to have to profile files with more than + // fixme_ddog_prof_Location is a int64_t. We don't expect to have to profile files with more than // 2M lines so this cast should be fairly safe? .line = (int32_t) locations.ptr[i].line, }; diff --git a/ext/datadog_profiling_native_extension/heap_recorder.h b/ext/datadog_profiling_native_extension/heap_recorder.h index 7ff792b746d..cdef4309a32 100644 --- a/ext/datadog_profiling_native_extension/heap_recorder.h +++ b/ext/datadog_profiling_native_extension/heap_recorder.h @@ -3,6 +3,8 @@ #include #include +#include "libdatadog_helpers.h" + // A heap recorder keeps track of a collection of live heap objects. // // All allocations observed by this recorder for which a corresponding free was diff --git a/ext/datadog_profiling_native_extension/http_transport.c b/ext/datadog_profiling_native_extension/http_transport.c index 52e4db67c5d..15242520c9c 100644 --- a/ext/datadog_profiling_native_extension/http_transport.c +++ b/ext/datadog_profiling_native_extension/http_transport.c @@ -126,7 +126,7 @@ static VALUE handle_exporter_failure(ddog_prof_ProfileExporter_Result exporter_r static VALUE perform_export( ddog_prof_ProfileExporter *exporter, ddog_prof_EncodedProfile *profile, - ddog_prof_Exporter_Slice_File files_to_compress_and_export, + ddog_prof_Slice_Exporter_File files_to_compress_and_export, ddog_CharSlice internal_metadata, ddog_CharSlice info ) { @@ -227,7 +227,7 @@ static VALUE _native_do_export( int to_compress_length = have_code_provenance ? 1 : 0; ddog_prof_Exporter_File to_compress[to_compress_length]; - ddog_prof_Exporter_Slice_File files_to_compress_and_export = {.ptr = to_compress, .len = to_compress_length}; + ddog_prof_Slice_Exporter_File files_to_compress_and_export = {.ptr = to_compress, .len = to_compress_length}; if (have_code_provenance) { to_compress[0] = (ddog_prof_Exporter_File) { diff --git a/ext/datadog_profiling_native_extension/libdatadog_helpers.h b/ext/datadog_profiling_native_extension/libdatadog_helpers.h index 81732d9497d..8d8aa380339 100644 --- a/ext/datadog_profiling_native_extension/libdatadog_helpers.h +++ b/ext/datadog_profiling_native_extension/libdatadog_helpers.h @@ -3,6 +3,189 @@ #include #include "ruby_helpers.h" +typedef struct ddog_prof_ManagedStringId { + uint32_t value; +} ddog_prof_ManagedStringId; + +typedef struct ddog_prof_ManagedStringStorage { + const void *inner; +} ddog_prof_ManagedStringStorage; + +typedef struct ddog_prof_Label { + ddog_CharSlice key; + struct ddog_prof_ManagedStringId key_id; + /** + * At most one of the following must be present + */ + ddog_CharSlice str; + struct ddog_prof_ManagedStringId str_id; + int64_t num; + /** + * Should only be present when num is present. + * Specifies the units of num. + * Use arbitrary string (for example, "requests") as a custom count unit. + * If no unit is specified, consumer may apply heuristic to deduce the unit. + * Consumers may also interpret units like "bytes" and "kilobytes" as memory + * units and units like "seconds" and "nanoseconds" as time units, + * and apply appropriate unit conversions to these. + */ + ddog_CharSlice num_unit; + struct ddog_prof_ManagedStringId num_unit_id; +} ddog_prof_Label; + +typedef struct ddog_prof_Slice_Label { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const struct ddog_prof_Label *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_prof_Slice_Label; + +typedef struct ddog_prof_Slice_CharSlice { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const ddog_CharSlice *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_prof_Slice_CharSlice; + +typedef struct fixme_ddog_prof_Mapping { + /** + * Address at which the binary (or DLL) is loaded into memory. + */ + uint64_t memory_start; + /** + * The limit of the address range occupied by this mapping. + */ + uint64_t memory_limit; + /** + * Offset in the binary that corresponds to the first mapped address. + */ + uint64_t file_offset; + /** + * The object this entry is loaded from. This can be a filename on + * disk for the main binary and shared libraries, or virtual + * abstractions like "[vdso]". + */ + ddog_CharSlice filename; + struct ddog_prof_ManagedStringId filename_id; + /** + * A string that uniquely identifies a particular program version + * with high probability. E.g., for binaries generated by GNU tools, + * it could be the contents of the .note.gnu.build-id field. + */ + ddog_CharSlice build_id; + struct ddog_prof_ManagedStringId build_id_id; +} fixme_ddog_prof_Mapping; + +typedef struct fixme_ddog_prof_Function { + /** + * Name of the function, in human-readable form if available. + */ + ddog_CharSlice name; + struct ddog_prof_ManagedStringId name_id; + /** + * Name of the function, as identified by the system. + * For instance, it can be a C++ mangled name. + */ + ddog_CharSlice system_name; + struct ddog_prof_ManagedStringId system_name_id; + /** + * Source file containing the function. + */ + ddog_CharSlice filename; + struct ddog_prof_ManagedStringId filename_id; +} fixme_ddog_prof_Function; +typedef struct fixme_ddog_prof_Location { + /** + * todo: how to handle unknown mapping? + */ + struct fixme_ddog_prof_Mapping mapping; + struct fixme_ddog_prof_Function function; + /** + * The instruction address for this location, if available. It + * should be within [Mapping.memory_start...Mapping.memory_limit] + * for the corresponding mapping. A non-leaf address may be in the + * middle of a call instruction. It is up to display tools to find + * the beginning of the instruction if necessary. + */ + uint64_t address; + int64_t line; +} fixme_ddog_prof_Location; + +typedef struct ddog_prof_Slice_Location { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const struct fixme_ddog_prof_Location *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_prof_Slice_Location; + +typedef struct ddog_prof_Slice_ManagedStringId { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const struct ddog_prof_ManagedStringId *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_prof_Slice_ManagedStringId; + +typedef enum ddog_prof_Option_Error_Tag { + DDOG_PROF_OPTION_ERROR_SOME_ERROR, + DDOG_PROF_OPTION_ERROR_NONE_ERROR, +} ddog_prof_Option_Error_Tag; + +typedef struct ddog_prof_Option_Error { + ddog_prof_Option_Error_Tag tag; + union { + struct { + struct ddog_Error some; + }; + }; +} ddog_prof_Option_Error; + +typedef struct ddog_prof_Option_Error ddog_prof_MaybeError; + +typedef enum ddog_prof_ManagedStringStorageInternResult_Tag { + DDOG_PROF_MANAGED_STRING_STORAGE_INTERN_RESULT_OK, + DDOG_PROF_MANAGED_STRING_STORAGE_INTERN_RESULT_ERR, +} ddog_prof_ManagedStringStorageInternResult_Tag; + +typedef struct ddog_prof_ManagedStringStorageInternResult { + ddog_prof_ManagedStringStorageInternResult_Tag tag; + union { + struct { + struct ddog_prof_ManagedStringId ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_prof_ManagedStringStorageInternResult; + static inline VALUE ruby_string_from_vec_u8(ddog_Vec_U8 string) { return rb_str_new((char *) string.ptr, string.len); } diff --git a/ext/datadog_profiling_native_extension/stack_recorder.c b/ext/datadog_profiling_native_extension/stack_recorder.c index bc3b3439500..62e82790544 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.c +++ b/ext/datadog_profiling_native_extension/stack_recorder.c @@ -1073,8 +1073,8 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE int64_t metric_values[] = {1, 2, 3, 4, 5, 6, 7, 8}; ddog_prof_Label labels[] = {{.key_id = key, .str_id = key}}; - ddog_prof_Location locations[] = { - (ddog_prof_Location) { + fixme_ddog_prof_Location locations[] = { + (fixme_ddog_prof_Location) { .mapping = {.filename = DDOG_CHARSLICE_C(""), .build_id = DDOG_CHARSLICE_C(""), .build_id_id = {}}, .function = { .name = DDOG_CHARSLICE_C(""), diff --git a/ext/datadog_profiling_native_extension/stack_recorder.h b/ext/datadog_profiling_native_extension/stack_recorder.h index 9ad3d740640..d428d48a037 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.h +++ b/ext/datadog_profiling_native_extension/stack_recorder.h @@ -3,6 +3,8 @@ #include #include +#include "libdatadog_helpers.h" + typedef struct { int64_t cpu_time_ns; int64_t wall_time_ns; From fe4f89e4168d051da68c86f8ecd2f8adccbb2fa1 Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 14:21:30 +0100 Subject: [PATCH 2/9] Stub out managed string storage --- .../heap_recorder.c | 41 +++++++++++-------- .../libdatadog_helpers.c | 25 +++++++---- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/ext/datadog_profiling_native_extension/heap_recorder.c b/ext/datadog_profiling_native_extension/heap_recorder.c index d814901a75c..e5f6305fd49 100644 --- a/ext/datadog_profiling_native_extension/heap_recorder.c +++ b/ext/datadog_profiling_native_extension/heap_recorder.c @@ -931,28 +931,37 @@ st_index_t heap_record_hash_st(st_data_t key) { static void unintern_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) { if (id.value == 0) return; // Empty string, nothing to do - ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern(recorder->string_storage, id); - if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { - rb_raise(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some)); - } + // ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern(recorder->string_storage, id); + // if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { + // rb_raise(rb_eRuntimeError, "Failed to unintern id: %"PRIsVALUE, get_error_details_and_drop(&result.some)); + // } + (void)recorder; + (void)id; + rb_raise(rb_eRuntimeError, "Failed to unintern id: (FIXME stubbed out)"); } static void unintern_all_or_raise(heap_recorder *recorder, ddog_prof_Slice_ManagedStringId ids) { - ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern_all(recorder->string_storage, ids); - if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { - rb_raise(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some)); - } + // ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_unintern_all(recorder->string_storage, ids); + // if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { + // rb_raise(rb_eRuntimeError, "Failed to unintern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some)); + // } + (void)recorder; + (void)ids; + rb_raise(rb_eRuntimeError, "Failed to unintern_all: (FIXME stubbed out)"); } static VALUE get_ruby_string_or_raise(heap_recorder *recorder, ddog_prof_ManagedStringId id) { - ddog_StringWrapperResult get_string_result = ddog_prof_ManagedStringStorage_get_string(recorder->string_storage, id); - if (get_string_result.tag == DDOG_STRING_WRAPPER_RESULT_ERR) { - rb_raise(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err)); - } - VALUE ruby_string = ruby_string_from_vec_u8(get_string_result.ok.message); - ddog_StringWrapper_drop((ddog_StringWrapper *) &get_string_result.ok); - - return ruby_string; + // ddog_StringWrapperResult get_string_result = ddog_prof_ManagedStringStorage_get_string(recorder->string_storage, id); + // if (get_string_result.tag == DDOG_STRING_WRAPPER_RESULT_ERR) { + // rb_raise(rb_eRuntimeError, "Failed to get string: %"PRIsVALUE, get_error_details_and_drop(&get_string_result.err)); + // } + // VALUE ruby_string = ruby_string_from_vec_u8(get_string_result.ok.message); + // ddog_StringWrapper_drop((ddog_StringWrapper *) &get_string_result.ok); + + // return ruby_string; + (void)recorder; + (void)id; + rb_raise(rb_eRuntimeError, "Failed to get string: (FIXME stubbed out)"); } static inline double ewma_stat(double previous, double current) { diff --git a/ext/datadog_profiling_native_extension/libdatadog_helpers.c b/ext/datadog_profiling_native_extension/libdatadog_helpers.c index 16185b8080b..421ba587eae 100644 --- a/ext/datadog_profiling_native_extension/libdatadog_helpers.c +++ b/ext/datadog_profiling_native_extension/libdatadog_helpers.c @@ -64,11 +64,13 @@ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capa ddog_prof_ManagedStringId intern_or_raise(ddog_prof_ManagedStringStorage string_storage, ddog_CharSlice string) { if (string.len == 0) return (ddog_prof_ManagedStringId) { 0 }; // Id 0 is always an empty string, no need to ask - ddog_prof_ManagedStringStorageInternResult intern_result = ddog_prof_ManagedStringStorage_intern(string_storage, string); - if (intern_result.tag == DDOG_PROF_MANAGED_STRING_STORAGE_INTERN_RESULT_ERR) { - rb_raise(rb_eRuntimeError, "Failed to intern string: %"PRIsVALUE, get_error_details_and_drop(&intern_result.err)); - } - return intern_result.ok; + // ddog_prof_ManagedStringStorageInternResult intern_result = ddog_prof_ManagedStringStorage_intern(string_storage, string); + // if (intern_result.tag == DDOG_PROF_MANAGED_STRING_STORAGE_INTERN_RESULT_ERR) { + (void)string_storage; + (void)string; + rb_raise(rb_eRuntimeError, "Failed to intern string: (FIXME stubbed out)"); + // } + // return intern_result.ok; } void intern_all_or_raise( @@ -77,8 +79,13 @@ void intern_all_or_raise( ddog_prof_ManagedStringId *output_ids, uintptr_t output_ids_size ) { - ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_intern_all(string_storage, strings, output_ids, output_ids_size); - if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { - rb_raise(rb_eRuntimeError, "Failed to intern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some)); - } + // ddog_prof_MaybeError result = ddog_prof_ManagedStringStorage_intern_all(string_storage, strings, output_ids, output_ids_size); + // if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { + // rb_raise(rb_eRuntimeError, "Failed to intern_all: %"PRIsVALUE, get_error_details_and_drop(&result.some)); + // } + (void)string_storage; + (void)strings; + (void)output_ids; + (void)output_ids_size; + rb_raise(rb_eRuntimeError, "Failed to intern_all: (FIXME stubbed out)"); } From 125ce0d78b7272d18a8eb2c769caefa50cb13cc2 Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 14:44:53 +0100 Subject: [PATCH 3/9] Add more bridge types and stubs --- .../libdatadog_helpers.h | 145 +++++++++++++++++- .../stack_recorder.c | 44 +++--- 2 files changed, 165 insertions(+), 24 deletions(-) diff --git a/ext/datadog_profiling_native_extension/libdatadog_helpers.h b/ext/datadog_profiling_native_extension/libdatadog_helpers.h index 8d8aa380339..6ee4755763e 100644 --- a/ext/datadog_profiling_native_extension/libdatadog_helpers.h +++ b/ext/datadog_profiling_native_extension/libdatadog_helpers.h @@ -3,6 +3,9 @@ #include #include "ruby_helpers.h" +typedef struct ddog_prof_Profile { + struct ddog_prof_Profile *inner; +} ddog_prof_Profile; typedef struct ddog_prof_ManagedStringId { uint32_t value; } ddog_prof_ManagedStringId; @@ -186,6 +189,144 @@ typedef struct ddog_prof_ManagedStringStorageInternResult { }; } ddog_prof_ManagedStringStorageInternResult; +typedef struct fixme_ddog_prof_ValueType { + ddog_CharSlice type_; + ddog_CharSlice unit; +} fixme_ddog_prof_ValueType; + +typedef enum ddog_prof_ManagedStringStorageNewResult_Tag { + DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_OK, + DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR, +} ddog_prof_ManagedStringStorageNewResult_Tag; + +typedef struct ddog_prof_ManagedStringStorageNewResult { + ddog_prof_ManagedStringStorageNewResult_Tag tag; + union { + struct { + struct ddog_prof_ManagedStringStorage ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_prof_ManagedStringStorageNewResult; + +typedef enum ddog_prof_Profile_SerializeResult_Tag { + DDOG_PROF_PROFILE_SERIALIZE_RESULT_OK, + DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR, +} ddog_prof_Profile_SerializeResult_Tag; + +typedef struct ddog_prof_Profile_SerializeResult { + ddog_prof_Profile_SerializeResult_Tag tag; + union { + struct { + struct ddog_prof_EncodedProfile ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_prof_Profile_SerializeResult; + +typedef struct fixme_ddog_prof_Slice_ValueType { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const struct fixme_ddog_prof_ValueType *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} fixme_ddog_prof_Slice_ValueType; + +typedef enum ddog_prof_Profile_NewResult_Tag { + DDOG_PROF_PROFILE_NEW_RESULT_OK, + DDOG_PROF_PROFILE_NEW_RESULT_ERR, +} ddog_prof_Profile_NewResult_Tag; + +typedef struct ddog_prof_Profile_NewResult { + ddog_prof_Profile_NewResult_Tag tag; + union { + struct { + struct ddog_prof_Profile ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_prof_Profile_NewResult; + +typedef enum ddog_prof_Profile_Result_Tag { + DDOG_PROF_PROFILE_RESULT_OK, + DDOG_PROF_PROFILE_RESULT_ERR, +} ddog_prof_Profile_Result_Tag; + +typedef struct ddog_prof_Profile_Result { + ddog_prof_Profile_Result_Tag tag; + union { + struct { + /** + * Do not use the value of Ok. This value only exists to overcome + * Rust -> C code generation. + */ + bool ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_prof_Profile_Result; + +void fixme_ddog_prof_Profile_drop(struct ddog_prof_Profile *profile); + +DDOG_CHECK_RETURN +struct ddog_prof_ManagedStringStorageNewResult ddog_prof_ManagedStringStorage_new(void); + +/** + * TODO: @ivoanjo Should this take a `*mut ManagedStringStorage` like Profile APIs do? + */ + void ddog_prof_ManagedStringStorage_drop(struct ddog_prof_ManagedStringStorage storage); + + typedef struct ddog_prof_Period { + struct fixme_ddog_prof_ValueType type_; + int64_t value; +} ddog_prof_Period; + + /** + * Same as `ddog_profile_new` but also configures a `string_storage` for the profile. + * TODO: @ivoanjo Should this take a `*mut ManagedStringStorage` like Profile APIs do? + */ +DDOG_CHECK_RETURN +struct ddog_prof_Profile_NewResult ddog_prof_Profile_with_string_storage( + struct fixme_ddog_prof_Slice_ValueType sample_types, + const struct ddog_prof_Period *period, + struct ddog_prof_ManagedStringStorage string_storage +); + +typedef struct ddog_prof_Sample { + /** + * The leaf is at locations[0]. + */ + struct ddog_prof_Slice_Location locations; + /** + * The type and unit of each value is defined by the corresponding + * entry in Profile.sample_type. All samples must have the same + * number of values, the same as the length of Profile.sample_type. + * When aggregating multiple samples into a single sample, the + * result has a list of values that is the element-wise sum of the + * lists of the originals. + */ + struct ddog_Slice_I64 values; + /** + * label includes additional context for this sample. It can include + * things like a thread id, allocation size, etc + */ + struct ddog_prof_Slice_Label labels; +} ddog_prof_Sample; + static inline VALUE ruby_string_from_vec_u8(ddog_Vec_U8 string) { return rb_str_new((char *) string.ptr, string.len); } @@ -203,9 +344,9 @@ ddog_CharSlice ruby_value_type_to_char_slice(enum ruby_value_type type); ddog_prof_ManagedStringId intern_or_raise(ddog_prof_ManagedStringStorage string_storage, ddog_CharSlice string); -void intern_all_or_raise( +NORETURN(void intern_all_or_raise( ddog_prof_ManagedStringStorage string_storage, ddog_prof_Slice_CharSlice strings, ddog_prof_ManagedStringId *output_ids, uintptr_t output_ids_size -); +)); diff --git a/ext/datadog_profiling_native_extension/stack_recorder.c b/ext/datadog_profiling_native_extension/stack_recorder.c index 62e82790544..66284814bb9 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.c +++ b/ext/datadog_profiling_native_extension/stack_recorder.c @@ -139,7 +139,7 @@ static VALUE error_symbol = Qnil; // :error in Ruby // ``` // compiling ../../../../ext/ddtrace_profiling_native_extension/stack_recorder.c // ../../../../ext/ddtrace_profiling_native_extension/stack_recorder.c:23:1: error: initializer element is not constant -// static const ddog_prof_ValueType enabled_value_types[] = {CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE}; +// static const fixme_ddog_prof_ValueType enabled_value_types[] = {CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE}; // ^ // ``` #define VALUE_STRING(string) {.ptr = "" string, .len = sizeof(string) - 1} @@ -161,7 +161,7 @@ static VALUE error_symbol = Qnil; // :error in Ruby #define TIMELINE_VALUE {.type_ = VALUE_STRING("timeline"), .unit = VALUE_STRING("nanoseconds")} #define TIMELINE_VALUE_ID 7 -static const ddog_prof_ValueType all_value_types[] = +static const fixme_ddog_prof_ValueType all_value_types[] = {CPU_TIME_VALUE, CPU_SAMPLES_VALUE, WALL_TIME_VALUE, ALLOC_SAMPLES_VALUE, ALLOC_SAMPLES_UNSCALED_VALUE, HEAP_SAMPLES_VALUE, HEAP_SIZE_VALUE, TIMELINE_VALUE}; // This array MUST be kept in sync with all_value_types above and is intended to act as a "hashmap" between VALUE_ID and the position it @@ -170,7 +170,7 @@ static const ddog_prof_ValueType all_value_types[] = static const uint8_t all_value_types_positions[] = {CPU_TIME_VALUE_ID, CPU_SAMPLES_VALUE_ID, WALL_TIME_VALUE_ID, ALLOC_SAMPLES_VALUE_ID, ALLOC_SAMPLES_UNSCALED_VALUE_ID, HEAP_SAMPLES_VALUE_ID, HEAP_SIZE_VALUE_ID, TIMELINE_VALUE_ID}; -#define ALL_VALUE_TYPES_COUNT (sizeof(all_value_types) / sizeof(ddog_prof_ValueType)) +#define ALL_VALUE_TYPES_COUNT (sizeof(all_value_types) / sizeof(fixme_ddog_prof_ValueType)) // Struct for storing stats related to a profile in a particular slot. // These stats will share the same lifetime as the data in that profile slot. @@ -242,7 +242,7 @@ typedef struct { static VALUE _native_new(VALUE klass); static void initialize_slot_concurrency_control(stack_recorder_state *state); -static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_ValueType sample_types); +static void initialize_profiles(stack_recorder_state *state, fixme_ddog_prof_Slice_ValueType sample_types); static void stack_recorder_typed_data_free(void *data); static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self); static VALUE _native_serialize(VALUE self, VALUE recorder_instance); @@ -332,7 +332,7 @@ static VALUE _native_new(VALUE klass) { state->heap_clean_after_gc_enabled = false; - ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT}; + fixme_ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT}; initialize_slot_concurrency_control(state); for (uint8_t i = 0; i < ALL_VALUE_TYPES_COUNT; i++) { state->position_for[i] = all_value_types_positions[i]; } @@ -376,7 +376,7 @@ static void initialize_slot_concurrency_control(stack_recorder_state *state) { state->active_slot = 1; } -static void initialize_profiles(stack_recorder_state *state, ddog_prof_Slice_ValueType sample_types) { +static void initialize_profiles(stack_recorder_state *state, fixme_ddog_prof_Slice_ValueType sample_types) { ddog_Timespec start_timestamp = system_epoch_now_timespec(); ddog_prof_Profile_NewResult slot_one_profile_result = @@ -403,10 +403,10 @@ static void stack_recorder_typed_data_free(void *state_ptr) { stack_recorder_state *state = (stack_recorder_state *) state_ptr; pthread_mutex_destroy(&state->mutex_slot_one); - ddog_prof_Profile_drop(&state->profile_slot_one.profile); + fixme_ddog_prof_Profile_drop(&state->profile_slot_one.profile); pthread_mutex_destroy(&state->mutex_slot_two); - ddog_prof_Profile_drop(&state->profile_slot_two.profile); + fixme_ddog_prof_Profile_drop(&state->profile_slot_two.profile); heap_recorder_free(state->heap_recorder); @@ -459,30 +459,30 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel state->enabled_values_count = requested_values_count; - ddog_prof_ValueType enabled_value_types[ALL_VALUE_TYPES_COUNT]; + fixme_ddog_prof_ValueType enabled_value_types[ALL_VALUE_TYPES_COUNT]; uint8_t next_enabled_pos = 0; uint8_t next_disabled_pos = requested_values_count; // CPU_SAMPLES_VALUE is always enabled - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) CPU_SAMPLES_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) CPU_SAMPLES_VALUE; state->position_for[CPU_SAMPLES_VALUE_ID] = next_enabled_pos++; // WALL_TIME_VALUE is always enabled - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) WALL_TIME_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) WALL_TIME_VALUE; state->position_for[WALL_TIME_VALUE_ID] = next_enabled_pos++; if (cpu_time_enabled == Qtrue) { - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) CPU_TIME_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) CPU_TIME_VALUE; state->position_for[CPU_TIME_VALUE_ID] = next_enabled_pos++; } else { state->position_for[CPU_TIME_VALUE_ID] = next_disabled_pos++; } if (alloc_samples_enabled == Qtrue) { - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) ALLOC_SAMPLES_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) ALLOC_SAMPLES_VALUE; state->position_for[ALLOC_SAMPLES_VALUE_ID] = next_enabled_pos++; - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) ALLOC_SAMPLES_UNSCALED_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) ALLOC_SAMPLES_UNSCALED_VALUE; state->position_for[ALLOC_SAMPLES_UNSCALED_VALUE_ID] = next_enabled_pos++; } else { state->position_for[ALLOC_SAMPLES_VALUE_ID] = next_disabled_pos++; @@ -490,14 +490,14 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel } if (heap_samples_enabled == Qtrue) { - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) HEAP_SAMPLES_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) HEAP_SAMPLES_VALUE; state->position_for[HEAP_SAMPLES_VALUE_ID] = next_enabled_pos++; } else { state->position_for[HEAP_SAMPLES_VALUE_ID] = next_disabled_pos++; } if (heap_size_enabled == Qtrue) { - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) HEAP_SIZE_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) HEAP_SIZE_VALUE; state->position_for[HEAP_SIZE_VALUE_ID] = next_enabled_pos++; } else { state->position_for[HEAP_SIZE_VALUE_ID] = next_disabled_pos++; @@ -512,16 +512,16 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel } if (timeline_enabled == Qtrue) { - enabled_value_types[next_enabled_pos] = (ddog_prof_ValueType) TIMELINE_VALUE; + enabled_value_types[next_enabled_pos] = (fixme_ddog_prof_ValueType) TIMELINE_VALUE; state->position_for[TIMELINE_VALUE_ID] = next_enabled_pos++; } else { state->position_for[TIMELINE_VALUE_ID] = next_disabled_pos++; } - ddog_prof_Profile_drop(&state->profile_slot_one.profile); - ddog_prof_Profile_drop(&state->profile_slot_two.profile); + fixme_ddog_prof_Profile_drop(&state->profile_slot_one.profile); + fixme_ddog_prof_Profile_drop(&state->profile_slot_two.profile); - ddog_prof_Slice_ValueType sample_types = {.ptr = enabled_value_types, .len = state->enabled_values_count}; + fixme_ddog_prof_Slice_ValueType sample_types = {.ptr = enabled_value_types, .len = state->enabled_values_count}; initialize_profiles(state, sample_types); return Qtrue; @@ -1059,7 +1059,7 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE rb_raise(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err)); } - ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT}; + fixme_ddog_prof_Slice_ValueType sample_types = {.ptr = all_value_types, .len = ALL_VALUE_TYPES_COUNT}; ddog_prof_Profile_NewResult profile = ddog_prof_Profile_with_string_storage(sample_types, NULL, string_storage.ok); if (profile.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) { @@ -1138,7 +1138,7 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE VALUE encoded_pprof_2 = from_ddog_prof_EncodedProfile(serialize_result.ok); - ddog_prof_Profile_drop(&profile.ok); + fixme_ddog_prof_Profile_drop(&profile.ok); ddog_prof_ManagedStringStorage_drop(string_storage.ok); return rb_ary_new_from_args(2, encoded_pprof_1, encoded_pprof_2); From 009ee1300d19c236e6f953c50bc2813d96ef4afd Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 14:46:31 +0100 Subject: [PATCH 4/9] Stub out advance_gen calls --- .../stack_recorder.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/datadog_profiling_native_extension/stack_recorder.c b/ext/datadog_profiling_native_extension/stack_recorder.c index 66284814bb9..2fdd81dbf26 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.c +++ b/ext/datadog_profiling_native_extension/stack_recorder.c @@ -234,7 +234,7 @@ typedef struct { ddog_prof_Profile_SerializeResult result; long heap_profile_build_time_ns; long serialize_no_gvl_time_ns; - ddog_prof_MaybeError advance_gen_result; + // ddog_prof_MaybeError advance_gen_result; // Set by both bool serialize_ran; @@ -589,10 +589,10 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan state->stats_lifetime.serialization_successes++; VALUE encoded_profile = from_ddog_prof_EncodedProfile(serialized_profile.ok); - ddog_prof_MaybeError result = args.advance_gen_result; - if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { - rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&result.some)); - } + // ddog_prof_MaybeError result = args.advance_gen_result; + // if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { + // rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&result.some)); + // } VALUE start = ruby_time_from(args.slot->start_timestamp); VALUE finish = ruby_time_from(finish_timestamp); @@ -792,7 +792,7 @@ static void *call_serialize_without_gvl(void *call_args) { // Note: The profile gets reset by the serialize call args->result = ddog_prof_Profile_serialize(&args->slot->profile, &args->slot->start_timestamp, &args->finish_timestamp); - args->advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(args->state->string_storage); + // args->advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(args->state->string_storage); args->serialize_ran = true; args->serialize_no_gvl_time_ns = long_max_of(0, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns); @@ -1108,11 +1108,11 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE rb_raise(rb_eRuntimeError, "Failed to serialize: %"PRIsVALUE, get_error_details_and_drop(&serialize_result.err)); } - ddog_prof_MaybeError advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(string_storage.ok); + // ddog_prof_MaybeError advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(string_storage.ok); - if (advance_gen_result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { - rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&advance_gen_result.some)); - } + // if (advance_gen_result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { + // rb_raise(rb_eRuntimeError, "Failed to advance string storage gen: %"PRIsVALUE, get_error_details_and_drop(&advance_gen_result.some)); + // } VALUE encoded_pprof_1 = from_ddog_prof_EncodedProfile(serialize_result.ok); From e082a309266ba1f66d3d5d2536eac1e4d77549af Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 14:49:41 +0100 Subject: [PATCH 5/9] Add more stubs to compile --- .../libdatadog_helpers.h | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/ext/datadog_profiling_native_extension/libdatadog_helpers.h b/ext/datadog_profiling_native_extension/libdatadog_helpers.h index 6ee4755763e..2dbed44ee95 100644 --- a/ext/datadog_profiling_native_extension/libdatadog_helpers.h +++ b/ext/datadog_profiling_native_extension/libdatadog_helpers.h @@ -327,7 +327,93 @@ typedef struct ddog_prof_Sample { struct ddog_prof_Slice_Label labels; } ddog_prof_Sample; -static inline VALUE ruby_string_from_vec_u8(ddog_Vec_U8 string) { +/** + * # Safety + * The `profile` ptr must point to a valid Profile object created by this + * module. All pointers inside the `sample` need to be valid for the duration + * of this call. + * + * If successful, it returns the Ok variant. + * On error, it holds an error message in the error variant. + * + * # Safety + * The `profile` ptr must point to a valid Profile object created by this + * module. + * This call is _NOT_ thread-safe. + */ + DDOG_CHECK_RETURN + struct ddog_prof_Profile_Result ddog_prof_Profile_add(struct ddog_prof_Profile *profile, + struct ddog_prof_Sample sample, + int64_t timestamp); + +/** + * Associate an endpoint to a given local root span id. + * During the serialization of the profile, an endpoint label will be added + * to all samples that contain a matching local root span id label. + * + * Note: calling this API causes the "trace endpoint" and "local root span id" strings + * to be interned, even if no matching sample is found. + * + * # Arguments + * * `profile` - a reference to the profile that will contain the samples. + * * `local_root_span_id` + * * `endpoint` - the value of the endpoint label to add for matching samples. + * + * # Safety + * The `profile` ptr must point to a valid Profile object created by this + * module. + * This call is _NOT_ thread-safe. + */ + DDOG_CHECK_RETURN + struct ddog_prof_Profile_Result ddog_prof_Profile_set_endpoint(struct ddog_prof_Profile *profile, + uint64_t local_root_span_id, + ddog_CharSlice endpoint); + + +/** + * Resets all data in `profile` except the sample types and period. Returns + * true if it successfully reset the profile and false otherwise. The profile + * remains valid if false is returned. + * + * # Arguments + * * `profile` - A mutable reference to the profile to be reset. + * * `start_time` - The time of the profile (after reset). Pass None/null to use the current time. + * + * # Safety + * The `profile` must meet all the requirements of a mutable reference to the profile. Given this + * can be called across an FFI boundary, the compiler cannot enforce this. + * If `time` is not null, it must point to a valid Timespec object. + */ + DDOG_CHECK_RETURN + struct ddog_prof_Profile_Result ddog_prof_Profile_reset(struct ddog_prof_Profile *profile); + + +/** + * Serialize the aggregated profile. + * Drains the data, and then resets the profile for future use. + * + * Don't forget to clean up the ok with `ddog_prof_EncodedProfile_drop` or + * the error variant with `ddog_Error_drop` when you are done with them. + * + * # Arguments + * * `profile` - a reference to the profile being serialized. + * * `start_time` - optional start time for the serialized profile. If None/null is passed, the + * time of profile creation will be used. + * * `end_time` - optional end time of the profile. If None/null is passed, the current time will + * be used. + * + * # Safety + * The `profile` must point to a valid profile object. + * The `start_time` and `end_time` must be null or otherwise point to a valid TimeSpec object. + */ + DDOG_CHECK_RETURN + struct ddog_prof_Profile_SerializeResult ddog_prof_Profile_serialize(struct ddog_prof_Profile *profile, + const struct ddog_Timespec *start_time, + const struct ddog_Timespec *end_time); + + + + static inline VALUE ruby_string_from_vec_u8(ddog_Vec_U8 string) { return rb_str_new((char *) string.ptr, string.len); } From 97e950abe487e8508a8ac0c4331d51577d1a4cc1 Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 14:55:43 +0100 Subject: [PATCH 6/9] Managed string storage related cleanups --- .../libdatadog_helpers.h | 8 ++--- .../stack_recorder.c | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/ext/datadog_profiling_native_extension/libdatadog_helpers.h b/ext/datadog_profiling_native_extension/libdatadog_helpers.h index 2dbed44ee95..596d215960b 100644 --- a/ext/datadog_profiling_native_extension/libdatadog_helpers.h +++ b/ext/datadog_profiling_native_extension/libdatadog_helpers.h @@ -282,9 +282,6 @@ typedef struct ddog_prof_Profile_Result { void fixme_ddog_prof_Profile_drop(struct ddog_prof_Profile *profile); -DDOG_CHECK_RETURN -struct ddog_prof_ManagedStringStorageNewResult ddog_prof_ManagedStringStorage_new(void); - /** * TODO: @ivoanjo Should this take a `*mut ManagedStringStorage` like Profile APIs do? */ @@ -300,10 +297,9 @@ struct ddog_prof_ManagedStringStorageNewResult ddog_prof_ManagedStringStorage_ne * TODO: @ivoanjo Should this take a `*mut ManagedStringStorage` like Profile APIs do? */ DDOG_CHECK_RETURN -struct ddog_prof_Profile_NewResult ddog_prof_Profile_with_string_storage( +struct ddog_prof_Profile_NewResult fixme_ddog_prof_Profile_with_string_storage( struct fixme_ddog_prof_Slice_ValueType sample_types, - const struct ddog_prof_Period *period, - struct ddog_prof_ManagedStringStorage string_storage + const struct ddog_prof_Period *period ); typedef struct ddog_prof_Sample { diff --git a/ext/datadog_profiling_native_extension/stack_recorder.c b/ext/datadog_profiling_native_extension/stack_recorder.c index 2fdd81dbf26..e353cdd6b65 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.c +++ b/ext/datadog_profiling_native_extension/stack_recorder.c @@ -196,7 +196,7 @@ typedef struct { pthread_mutex_t mutex_slot_two; profile_slot profile_slot_two; - ddog_prof_ManagedStringStorage string_storage; + // ddog_prof_ManagedStringStorage string_storage; ddog_prof_ManagedStringId label_key_allocation_class; ddog_prof_ManagedStringId label_key_gc_gen_age; @@ -346,22 +346,22 @@ static VALUE _native_new(VALUE klass) { VALUE stack_recorder = TypedData_Wrap_Struct(klass, &stack_recorder_typed_data, state); - ddog_prof_ManagedStringStorageNewResult string_storage = ddog_prof_ManagedStringStorage_new(); + // ddog_prof_ManagedStringStorageNewResult string_storage = ddog_prof_ManagedStringStorage_new(); - if (string_storage.tag == DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR) { - rb_raise(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err)); - } + // if (string_storage.tag == DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR) { + // rb_raise(rb_eRuntimeError, "Failed to initialize string storage: %"PRIsVALUE, get_error_details_and_drop(&string_storage.err)); + // } - state->string_storage = string_storage.ok; - state->label_key_allocation_class = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("allocation class")); - state->label_key_gc_gen_age = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("gc gen age")); + // state->string_storage = string_storage.ok; + // state->label_key_allocation_class = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("allocation class")); + // state->label_key_gc_gen_age = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("gc gen age")); initialize_profiles(state, sample_types); // NOTE: We initialize this because we want a new recorder to be operational even before #initialize runs and our // default is everything enabled. However, if during recording initialization it turns out we don't want // heap samples, we will free and reset heap_recorder back to NULL. - state->heap_recorder = heap_recorder_new(state->string_storage); + state->heap_recorder = NULL; return stack_recorder; } @@ -380,7 +380,7 @@ static void initialize_profiles(stack_recorder_state *state, fixme_ddog_prof_Sli ddog_Timespec start_timestamp = system_epoch_now_timespec(); ddog_prof_Profile_NewResult slot_one_profile_result = - ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */, state->string_storage); + fixme_ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */); if (slot_one_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) { rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_error_details_and_drop(&slot_one_profile_result.err)); @@ -389,7 +389,7 @@ static void initialize_profiles(stack_recorder_state *state, fixme_ddog_prof_Sli state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_result.ok, .start_timestamp = start_timestamp }; ddog_prof_Profile_NewResult slot_two_profile_result = - ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */, state->string_storage); + fixme_ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */); if (slot_two_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) { // Note: No need to take any special care of slot one, it'll get cleaned up by stack_recorder_typed_data_free @@ -410,7 +410,7 @@ static void stack_recorder_typed_data_free(void *state_ptr) { heap_recorder_free(state->heap_recorder); - ddog_prof_ManagedStringStorage_drop(state->string_storage); + // ddog_prof_ManagedStringStorage_drop(state->string_storage); ruby_xfree(state); } @@ -507,7 +507,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel if (heap_samples_enabled == Qfalse && heap_size_enabled == Qfalse) { // Turns out heap sampling is disabled but we initialized everything in _native_new // assuming all samples were enabled. We need to deinitialize the heap recorder. - heap_recorder_free(state->heap_recorder); + // heap_recorder_free(state->heap_recorder); state->heap_recorder = NULL; } @@ -1053,6 +1053,8 @@ static VALUE _native_benchmark_intern(DDTRACE_UNUSED VALUE _self, VALUE recorder // See comments in rspec test for details on what we're testing here. static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE_UNUSED VALUE _self) { + if (true) return Qnil; + /* ddog_prof_ManagedStringStorageNewResult string_storage = ddog_prof_ManagedStringStorage_new(); if (string_storage.tag == DDOG_PROF_MANAGED_STRING_STORAGE_NEW_RESULT_ERR) { @@ -1142,4 +1144,5 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE ddog_prof_ManagedStringStorage_drop(string_storage.ok); return rb_ary_new_from_args(2, encoded_pprof_1, encoded_pprof_2); + */ } From fbf1dc77193f9b5ac7282f98010b0f68e1fcfe02 Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 15:17:27 +0100 Subject: [PATCH 7/9] Adopt new profile new and drop APIs --- .../datadog_ruby_common.h | 6 ++++ .../libdatadog_helpers.h | 4 +-- .../stack_recorder.c | 32 +++++++++---------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/ext/datadog_profiling_native_extension/datadog_ruby_common.h b/ext/datadog_profiling_native_extension/datadog_ruby_common.h index 9b73df7c417..262fcb21c74 100644 --- a/ext/datadog_profiling_native_extension/datadog_ruby_common.h +++ b/ext/datadog_profiling_native_extension/datadog_ruby_common.h @@ -61,3 +61,9 @@ static inline VALUE get_error_details_and_drop(ddog_Error *error) { ddog_Error_drop(error); return result; } + +static inline VALUE get_status_details_and_drop(ddog_prof_Status *status) { + VALUE result = rb_str_new_cstr(status->err); + ddog_prof_Status_drop(status); + return result; +} diff --git a/ext/datadog_profiling_native_extension/libdatadog_helpers.h b/ext/datadog_profiling_native_extension/libdatadog_helpers.h index 596d215960b..a3453eace5e 100644 --- a/ext/datadog_profiling_native_extension/libdatadog_helpers.h +++ b/ext/datadog_profiling_native_extension/libdatadog_helpers.h @@ -280,8 +280,6 @@ typedef struct ddog_prof_Profile_Result { }; } ddog_prof_Profile_Result; -void fixme_ddog_prof_Profile_drop(struct ddog_prof_Profile *profile); - /** * TODO: @ivoanjo Should this take a `*mut ManagedStringStorage` like Profile APIs do? */ @@ -432,3 +430,5 @@ NORETURN(void intern_all_or_raise( ddog_prof_ManagedStringId *output_ids, uintptr_t output_ids_size )); + +inline bool is_ddog_error(ddog_prof_Status status) { return status.flags != 0; } diff --git a/ext/datadog_profiling_native_extension/stack_recorder.c b/ext/datadog_profiling_native_extension/stack_recorder.c index e353cdd6b65..2cf6e241046 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.c +++ b/ext/datadog_profiling_native_extension/stack_recorder.c @@ -180,7 +180,7 @@ typedef struct { } stats_slot; typedef struct { - ddog_prof_Profile profile; + ddog_prof_ProfileHandle profile; stats_slot stats; ddog_Timespec start_timestamp; } profile_slot; @@ -379,34 +379,34 @@ static void initialize_slot_concurrency_control(stack_recorder_state *state) { static void initialize_profiles(stack_recorder_state *state, fixme_ddog_prof_Slice_ValueType sample_types) { ddog_Timespec start_timestamp = system_epoch_now_timespec(); - ddog_prof_Profile_NewResult slot_one_profile_result = - fixme_ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */); + ddog_prof_ProfileHandle slot_one_profile_handle; + ddog_prof_Status slot_one_profile_status = ddog_prof_Profile_new(&slot_one_profile_handle); - if (slot_one_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) { - rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_error_details_and_drop(&slot_one_profile_result.err)); + if (is_ddog_error(slot_one_profile_status)) { + rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_status_details_and_drop(&slot_one_profile_status)); } - state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_result.ok, .start_timestamp = start_timestamp }; + state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_handle, .start_timestamp = start_timestamp }; - ddog_prof_Profile_NewResult slot_two_profile_result = - fixme_ddog_prof_Profile_with_string_storage(sample_types, NULL /* period is optional */); + ddog_prof_ProfileHandle slot_two_profile_handle; + ddog_prof_Status slot_two_profile_status = ddog_prof_Profile_new(&slot_two_profile_handle); - if (slot_two_profile_result.tag == DDOG_PROF_PROFILE_NEW_RESULT_ERR) { + if (is_ddog_error(slot_two_profile_status)) { // Note: No need to take any special care of slot one, it'll get cleaned up by stack_recorder_typed_data_free - rb_raise(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_error_details_and_drop(&slot_two_profile_result.err)); + rb_raise(rb_eRuntimeError, "Failed to initialize slot two profile: %"PRIsVALUE, get_status_details_and_drop(&slot_two_profile_status)); } - state->profile_slot_two = (profile_slot) { .profile = slot_two_profile_result.ok, .start_timestamp = start_timestamp }; + state->profile_slot_two = (profile_slot) { .profile = slot_two_profile_handle, .start_timestamp = start_timestamp }; } static void stack_recorder_typed_data_free(void *state_ptr) { stack_recorder_state *state = (stack_recorder_state *) state_ptr; pthread_mutex_destroy(&state->mutex_slot_one); - fixme_ddog_prof_Profile_drop(&state->profile_slot_one.profile); + ddog_prof_Profile_drop(&state->profile_slot_one.profile); pthread_mutex_destroy(&state->mutex_slot_two); - fixme_ddog_prof_Profile_drop(&state->profile_slot_two.profile); + ddog_prof_Profile_drop(&state->profile_slot_two.profile); heap_recorder_free(state->heap_recorder); @@ -518,8 +518,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel state->position_for[TIMELINE_VALUE_ID] = next_disabled_pos++; } - fixme_ddog_prof_Profile_drop(&state->profile_slot_one.profile); - fixme_ddog_prof_Profile_drop(&state->profile_slot_two.profile); + ddog_prof_Profile_drop(&state->profile_slot_one.profile); + ddog_prof_Profile_drop(&state->profile_slot_two.profile); fixme_ddog_prof_Slice_ValueType sample_types = {.ptr = enabled_value_types, .len = state->enabled_values_count}; initialize_profiles(state, sample_types); @@ -1140,7 +1140,7 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE VALUE encoded_pprof_2 = from_ddog_prof_EncodedProfile(serialize_result.ok); - fixme_ddog_prof_Profile_drop(&profile.ok); + ddog_prof_Profile_drop(&profile.ok); ddog_prof_ManagedStringStorage_drop(string_storage.ok); return rb_ary_new_from_args(2, encoded_pprof_1, encoded_pprof_2); From 6ede5b7d9a66fd9f38d81801dd8ab20b4e44104c Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 15:30:27 +0100 Subject: [PATCH 8/9] Replace profile serialize --- .../stack_recorder.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ext/datadog_profiling_native_extension/stack_recorder.c b/ext/datadog_profiling_native_extension/stack_recorder.c index 2cf6e241046..4f0ca5c0839 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.c +++ b/ext/datadog_profiling_native_extension/stack_recorder.c @@ -231,7 +231,8 @@ typedef struct { // Set by callee profile_slot *slot; - ddog_prof_Profile_SerializeResult result; + ddog_prof_EncodedProfile encoded_profile; + ddog_prof_Status serialize_status; long heap_profile_build_time_ns; long serialize_no_gvl_time_ns; // ddog_prof_MaybeError advance_gen_result; @@ -576,18 +577,16 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan state->stats_lifetime.serialization_time_ns_min = long_min_of(state->stats_lifetime.serialization_time_ns_min, args.serialize_no_gvl_time_ns); state->stats_lifetime.serialization_time_ns_total += args.serialize_no_gvl_time_ns; - ddog_prof_Profile_SerializeResult serialized_profile = args.result; - - if (serialized_profile.tag == DDOG_PROF_PROFILE_SERIALIZE_RESULT_ERR) { + if (is_ddog_error(args.serialize_status)) { state->stats_lifetime.serialization_failures++; - return rb_ary_new_from_args(2, error_symbol, get_error_details_and_drop(&serialized_profile.err)); + return rb_ary_new_from_args(2, error_symbol, get_status_details_and_drop(&args.serialize_status)); } // Note: If we got here, the profile serialized correctly. // Once we wrap this into a Ruby object, our `EncodedProfile` class will automatically manage memory for it and we // can raise exceptions without worrying about leaking the profile. state->stats_lifetime.serialization_successes++; - VALUE encoded_profile = from_ddog_prof_EncodedProfile(serialized_profile.ok); + VALUE encoded_profile = from_ddog_prof_EncodedProfile(args.encoded_profile); // ddog_prof_MaybeError result = args.advance_gen_result; // if (result.tag == DDOG_PROF_OPTION_ERROR_SOME_ERROR) { @@ -791,7 +790,11 @@ static void *call_serialize_without_gvl(void *call_args) { args->heap_profile_build_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns; // Note: The profile gets reset by the serialize call - args->result = ddog_prof_Profile_serialize(&args->slot->profile, &args->slot->start_timestamp, &args->finish_timestamp); + + // FIXME TODO: Do we need to reset something??? Not sure the previous comment is still true + + args->serialize_status = ddog_prof_ProfileAdapter_build_compressed(&args->encoded_profile, FIXME, &args->slot->start_timestamp, &args->finish_timestamp); + // args->advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(args->state->string_storage); args->serialize_ran = true; args->serialize_no_gvl_time_ns = long_max_of(0, monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - serialize_no_gvl_start_time_ns); From 997740792b66480de4dd3edcc674cc5aae12f39e Mon Sep 17 00:00:00 2001 From: Ivo Anjo Date: Fri, 17 Oct 2025 17:11:16 +0100 Subject: [PATCH 9/9] Continue introducing ProfileAdapter --- .../stack_recorder.c | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/ext/datadog_profiling_native_extension/stack_recorder.c b/ext/datadog_profiling_native_extension/stack_recorder.c index 4f0ca5c0839..9110b7b24e3 100644 --- a/ext/datadog_profiling_native_extension/stack_recorder.c +++ b/ext/datadog_profiling_native_extension/stack_recorder.c @@ -180,7 +180,7 @@ typedef struct { } stats_slot; typedef struct { - ddog_prof_ProfileHandle profile; + ddog_prof_ProfileAdapter profile; stats_slot stats; ddog_Timespec start_timestamp; } profile_slot; @@ -196,6 +196,9 @@ typedef struct { pthread_mutex_t mutex_slot_two; profile_slot profile_slot_two; + ddog_prof_ProfilesDictionaryHandle dictionary; + ddog_prof_ScratchPadHandle scratchpad; + // ddog_prof_ManagedStringStorage string_storage; ddog_prof_ManagedStringId label_key_allocation_class; ddog_prof_ManagedStringId label_key_gc_gen_age; @@ -273,6 +276,15 @@ static VALUE _native_recorder_after_gc_step(DDTRACE_UNUSED VALUE _self, VALUE re static VALUE _native_benchmark_intern(DDTRACE_UNUSED VALUE _self, VALUE recorder_instance, VALUE string, VALUE times, VALUE use_all); static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE_UNUSED VALUE _self); +ddog_prof_StringId insert_into_profiles_dictionary(stack_recorder_state *state, ddog_CharSlice byte_slice) { + ddog_prof_StringId id; + ddog_prof_Status status = ddog_prof_ProfilesDictionary_insert_str(&id, state->dictionary, byte_slice, DDOG_PROF_UTF8_OPTION_ASSUME); + if (is_ddog_error(status)) { + rb_raise(rb_eRuntimeError, "Failed to run ddog_prof_ProfilesDictionary_insert_str: %"PRIsVALUE, get_status_details_and_drop(&status)); + }; + return id; +} + void stack_recorder_init(VALUE profiling_module) { VALUE stack_recorder_class = rb_define_class_under(profiling_module, "StackRecorder", rb_cObject); // Hosts methods used for testing the native code using RSpec @@ -357,6 +369,18 @@ static VALUE _native_new(VALUE klass) { // state->label_key_allocation_class = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("allocation class")); // state->label_key_gc_gen_age = intern_or_raise(state->string_storage, DDOG_CHARSLICE_C("gc gen age")); + ddog_prof_ProfilesDictionaryHandle dictionary; + ddog_prof_Status dictionary_status = ddog_prof_ProfilesDictionary_new(&dictionary); + if (is_ddog_error(dictionary_status)) { + rb_raise(rb_eRuntimeError, "Failed to initialize profiles dictionary: %"PRIsVALUE, get_status_details_and_drop(&dictionary_status)); + } + + ddog_prof_ScratchPadHandle scratchpad; + ddog_prof_Status scratchpad_status = ddog_prof_ScratchPad_new(&scratchpad); + if (is_ddog_error(scratchpad_status)) { + rb_raise(rb_eRuntimeError, "Failed to initialize scratch pad: %"PRIsVALUE, get_status_details_and_drop(&scratchpad_status)); + } + initialize_profiles(state, sample_types); // NOTE: We initialize this because we want a new recorder to be operational even before #initialize runs and our @@ -380,8 +404,19 @@ static void initialize_slot_concurrency_control(stack_recorder_state *state) { static void initialize_profiles(stack_recorder_state *state, fixme_ddog_prof_Slice_ValueType sample_types) { ddog_Timespec start_timestamp = system_epoch_now_timespec(); - ddog_prof_ProfileHandle slot_one_profile_handle; - ddog_prof_Status slot_one_profile_status = ddog_prof_Profile_new(&slot_one_profile_handle); + ddog_prof_ValueType converted_types[sample_types.len]; + int64_t groupings[sample_types.len]; + + for (int i = 0; i < sample_types.len; i++) { + converted_types[i] = (ddog_prof_ValueType) { + .type_id = insert_into_profiles_dictionary(state, sample_types.ptr[i].type_), + .unit_id = insert_into_profiles_dictionary(state, sample_types.ptr[i].unit), + }; + groupings[i] = i; + } + + ddog_prof_ProfileAdapter slot_one_profile_handle; + ddog_prof_Status slot_one_profile_status = ddog_prof_ProfileAdapter_new(&slot_one_profile_handle, state->dictionary, state->scratchpad, (ddog_prof_Slice_ValueType) { .ptr = converted_types, .len = sample_types.len }, (ddog_Slice_I64) { .ptr = groupings, .len = sample_types.len }); if (is_ddog_error(slot_one_profile_status)) { rb_raise(rb_eRuntimeError, "Failed to initialize slot one profile: %"PRIsVALUE, get_status_details_and_drop(&slot_one_profile_status)); @@ -389,8 +424,8 @@ static void initialize_profiles(stack_recorder_state *state, fixme_ddog_prof_Sli state->profile_slot_one = (profile_slot) { .profile = slot_one_profile_handle, .start_timestamp = start_timestamp }; - ddog_prof_ProfileHandle slot_two_profile_handle; - ddog_prof_Status slot_two_profile_status = ddog_prof_Profile_new(&slot_two_profile_handle); + ddog_prof_ProfileAdapter slot_two_profile_handle; + ddog_prof_Status slot_two_profile_status = ddog_prof_ProfileAdapter_new(&slot_two_profile_handle, state->dictionary, state->scratchpad, (ddog_prof_Slice_ValueType) { .ptr = converted_types, .len = sample_types.len }, (ddog_Slice_I64) { .ptr = groupings, .len = sample_types.len }); if (is_ddog_error(slot_two_profile_status)) { // Note: No need to take any special care of slot one, it'll get cleaned up by stack_recorder_typed_data_free @@ -404,10 +439,10 @@ static void stack_recorder_typed_data_free(void *state_ptr) { stack_recorder_state *state = (stack_recorder_state *) state_ptr; pthread_mutex_destroy(&state->mutex_slot_one); - ddog_prof_Profile_drop(&state->profile_slot_one.profile); + ddog_prof_ProfileAdapter_drop(&state->profile_slot_one.profile); pthread_mutex_destroy(&state->mutex_slot_two); - ddog_prof_Profile_drop(&state->profile_slot_two.profile); + ddog_prof_ProfileAdapter_drop(&state->profile_slot_two.profile); heap_recorder_free(state->heap_recorder); @@ -519,8 +554,8 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel state->position_for[TIMELINE_VALUE_ID] = next_disabled_pos++; } - ddog_prof_Profile_drop(&state->profile_slot_one.profile); - ddog_prof_Profile_drop(&state->profile_slot_two.profile); + ddog_prof_ProfileAdapter_drop(&state->profile_slot_one.profile); + ddog_prof_ProfileAdapter_drop(&state->profile_slot_two.profile); fixme_ddog_prof_Slice_ValueType sample_types = {.ptr = enabled_value_types, .len = state->enabled_values_count}; initialize_profiles(state, sample_types); @@ -793,7 +828,17 @@ static void *call_serialize_without_gvl(void *call_args) { // FIXME TODO: Do we need to reset something??? Not sure the previous comment is still true - args->serialize_status = ddog_prof_ProfileAdapter_build_compressed(&args->encoded_profile, FIXME, &args->slot->start_timestamp, &args->finish_timestamp); + args->serialize_status = ddog_prof_ProfileAdapter_build_compressed( + &args->encoded_profile, + &args->slot->profile, + &args->slot->start_timestamp, + &args->finish_timestamp + ); + + if (!is_ddog_error(args->serialize_status)) { + ddog_prof_ProfileAdapter_drop(&args->slot->profile); + args->serialize_status = ddog_prof_ProfileAdapter_new(&args->slot->profile); + } // args->advance_gen_result = ddog_prof_ManagedStringStorage_advance_gen(args->state->string_storage); args->serialize_ran = true; @@ -1143,7 +1188,7 @@ static VALUE _native_test_managed_string_storage_produces_valid_profiles(DDTRACE VALUE encoded_pprof_2 = from_ddog_prof_EncodedProfile(serialize_result.ok); - ddog_prof_Profile_drop(&profile.ok); + ddog_prof_ProfileAdapter_drop(&profile.ok); ddog_prof_ManagedStringStorage_drop(string_storage.ok); return rb_ary_new_from_args(2, encoded_pprof_1, encoded_pprof_2);