Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PROF-10241] Extract libdatadog crashtracker telemetry into separate extension #3824

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ NATIVE_EXTS = [

Rake::ExtensionTask.new("datadog_profiling_loader.#{RUBY_VERSION}_#{RUBY_PLATFORM}") do |ext|
ext.ext_dir = 'ext/datadog_profiling_loader'
end,

Rake::ExtensionTask.new("libdatadog_api.#{RUBY_VERSION[/\d+.\d+/]}_#{RUBY_PLATFORM}") do |ext|
ext.ext_dir = 'ext/libdatadog_api'
end
].freeze

Expand Down
9 changes: 6 additions & 3 deletions datadog.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ Gem::Specification.new do |spec|
# Used by appsec
spec.add_dependency 'libddwaf', '~> 1.14.0.0.0'

# Used by profiling (and possibly others in the future)
# When updating the version here, please also update the version in `native_extension_helpers.rb`
# When updating the version here, please also update the version in `libdatadog_extconf_helpers.rb`
# (and yes we have a test for it)
spec.add_dependency 'libdatadog', '~> 11.0.0.1.0'

spec.extensions = ['ext/datadog_profiling_native_extension/extconf.rb', 'ext/datadog_profiling_loader/extconf.rb']
spec.extensions = [
'ext/datadog_profiling_native_extension/extconf.rb',
'ext/datadog_profiling_loader/extconf.rb',
'ext/libdatadog_api/extconf.rb'
]
end
110 changes: 110 additions & 0 deletions ext/datadog_profiling_native_extension/datadog_ruby_common.c
Copy link
Member Author

@ivoanjo ivoanjo Aug 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These things got moved over from other helper files and put together here, they're mostly unchanged.

Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include "datadog_ruby_common.h"

// IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change!

void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name) {
rb_exc_raise(
rb_exc_new_str(
rb_eTypeError,
rb_sprintf("wrong argument %"PRIsVALUE" for '%s' (expected a %s) at %s:%d:in `%s'",
rb_inspect(value),
value_name,
type_name,
file,
line,
function_name
)
)
);
}

VALUE datadog_gem_version(void) {
VALUE ddtrace_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
ENFORCE_TYPE(ddtrace_module, T_MODULE);
VALUE version_module = rb_const_get(ddtrace_module, rb_intern("VERSION"));
ENFORCE_TYPE(version_module, T_MODULE);
VALUE version_string = rb_const_get(version_module, rb_intern("STRING"));
ENFORCE_TYPE(version_string, T_STRING);
return version_string;
}

__attribute__((warn_unused_result))
ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
ENFORCE_TYPE(exporter_configuration, T_ARRAY);

VALUE exporter_working_mode = rb_ary_entry(exporter_configuration, 0);
ENFORCE_TYPE(exporter_working_mode, T_SYMBOL);
ID working_mode = SYM2ID(exporter_working_mode);

ID agentless_id = rb_intern("agentless");
ID agent_id = rb_intern("agent");

if (working_mode != agentless_id && working_mode != agent_id) {
rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
}

if (working_mode == agentless_id) {
VALUE site = rb_ary_entry(exporter_configuration, 1);
VALUE api_key = rb_ary_entry(exporter_configuration, 2);

return ddog_prof_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
} else { // agent_id
VALUE base_url = rb_ary_entry(exporter_configuration, 1);

return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
}
}

static VALUE log_failure_to_process_tag(VALUE err_details) {
VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
VALUE logger = rb_funcall(datadog_module, rb_intern("logger"), 0);

return rb_funcall(logger, rb_intern("warn"), 1, rb_sprintf("Failed to convert tag: %"PRIsVALUE, err_details));
}

__attribute__((warn_unused_result))
ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
ENFORCE_TYPE(tags_as_array, T_ARRAY);

long tags_count = RARRAY_LEN(tags_as_array);
ddog_Vec_Tag tags = ddog_Vec_Tag_new();

for (long i = 0; i < tags_count; i++) {
VALUE name_value_pair = rb_ary_entry(tags_as_array, i);

if (!RB_TYPE_P(name_value_pair, T_ARRAY)) {
ddog_Vec_Tag_drop(tags);
ENFORCE_TYPE(name_value_pair, T_ARRAY);
}

// Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds
VALUE tag_name = rb_ary_entry(name_value_pair, 0);
VALUE tag_value = rb_ary_entry(name_value_pair, 1);

if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) {
ddog_Vec_Tag_drop(tags);
ENFORCE_TYPE(tag_name, T_STRING);
ENFORCE_TYPE(tag_value, T_STRING);
}

ddog_Vec_Tag_PushResult push_result =
ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value));

if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) {
// libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch.
// We warn users about such tags, and then just ignore them.

int exception_state;
rb_protect(log_failure_to_process_tag, get_error_details_and_drop(&push_result.err), &exception_state);

// Since we are calling into Ruby code, it may raise an exception. Ensure that dynamically-allocated tags
// get cleaned before propagating the exception.
if (exception_state) {
ddog_Vec_Tag_drop(tags);
rb_jump_tag(exception_state); // "Re-raise" exception
}
}
}

return tags;
}
57 changes: 57 additions & 0 deletions ext/datadog_profiling_native_extension/datadog_ruby_common.h
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These things got moved over from other helper files and put together here, they're mostly unchanged.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

// IMPORTANT: Currently this file is copy-pasted between extensions. Make sure to update all versions when doing any change!

#include <ruby.h>
#include <datadog/profiling.h>

// Used to mark symbols to be exported to the outside of the extension.
// Consider very carefully before tagging a function with this.
#define DDTRACE_EXPORT __attribute__ ((visibility ("default")))

// Used to mark function arguments that are deliberately left unused
#ifdef __GNUC__
#define DDTRACE_UNUSED __attribute__((unused))
#else
#define DDTRACE_UNUSED
#endif

#define ADD_QUOTES_HELPER(x) #x
#define ADD_QUOTES(x) ADD_QUOTES_HELPER(x)

// Ruby has a Check_Type(value, type) that is roughly equivalent to this BUT Ruby's version is rather cryptic when it fails
// e.g. "wrong argument type nil (expected String)". This is a replacement that prints more information to help debugging.
#define ENFORCE_TYPE(value, type) \
{ if (RB_UNLIKELY(!RB_TYPE_P(value, type))) raise_unexpected_type(value, ADD_QUOTES(value), ADD_QUOTES(type), __FILE__, __LINE__, __func__); }

#define ENFORCE_BOOLEAN(value) \
{ if (RB_UNLIKELY(value != Qtrue && value != Qfalse)) raise_unexpected_type(value, ADD_QUOTES(value), "true or false", __FILE__, __LINE__, __func__); }

// Called by ENFORCE_TYPE; should not be used directly
NORETURN(void raise_unexpected_type(VALUE value, const char *value_name, const char *type_name, const char *file, int line, const char* function_name));

// Helper to retrieve Datadog::VERSION::STRING
VALUE datadog_gem_version(void);

inline static ddog_CharSlice char_slice_from_ruby_string(VALUE string) {
ENFORCE_TYPE(string, T_STRING);
ddog_CharSlice char_slice = {.ptr = StringValuePtr(string), .len = RSTRING_LEN(string)};
return char_slice;
}

__attribute__((warn_unused_result))
ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration);

__attribute__((warn_unused_result))
ddog_Vec_Tag convert_tags(VALUE tags_as_array);

inline static VALUE ruby_string_from_error(const ddog_Error *error) {
ddog_CharSlice char_slice = ddog_Error_message(error);
return rb_str_new(char_slice.ptr, char_slice.len);
}

inline static VALUE get_error_details_and_drop(ddog_Error *error) {
VALUE result = ruby_string_from_error(error);
ddog_Error_drop(error);
return result;
}
9 changes: 5 additions & 4 deletions ext/datadog_profiling_native_extension/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# rubocop:disable Style/GlobalVars

require_relative 'native_extension_helpers'
require_relative '../libdatadog_extconf_helpers'

SKIPPED_REASON_FILE = "#{__dir__}/skipped_reason.txt".freeze
# Not a problem if the file doesn't exist or we can't delete it
Expand Down Expand Up @@ -114,7 +115,7 @@ def add_compiler_flag(flag)
add_compiler_flag '-Wall'
add_compiler_flag '-Wextra'

if ENV['DDTRACE_DEBUG']
if ENV['DDTRACE_DEBUG'] == 'true'
$defs << '-DDD_DEBUG'
CONFIG['optflags'] = '-O0'
CONFIG['debugflags'] = '-ggdb3'
Expand Down Expand Up @@ -201,7 +202,7 @@ def add_compiler_flag(flag)
Logging.message("[datadog] Ruby detected the pkg-config command is #{$PKGCONFIG.inspect}\n")

skip_building_extension!(
if Datadog::Profiling::NativeExtensionHelpers::Supported.pkg_config_missing?
if Datadog::LibdatadogExtconfHelpers.pkg_config_missing?
Datadog::Profiling::NativeExtensionHelpers::Supported::PKG_CONFIG_IS_MISSING
else
# Less specific error message
Expand All @@ -218,8 +219,8 @@ def add_compiler_flag(flag)
# The extremely excessive escaping around ORIGIN below seems to be correct and was determined after a lot of
# experimentation. We need to get these special characters across a lot of tools untouched...
extra_relative_rpaths = [
Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_native_lib_folder,
*Datadog::Profiling::NativeExtensionHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_native_lib_folder(current_folder: __dir__),
*Datadog::LibdatadogExtconfHelpers.libdatadog_folder_relative_to_ruby_extensions_folders,
]
extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
Expand Down
11 changes: 0 additions & 11 deletions ext/datadog_profiling_native_extension/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,6 @@

#include <stdint.h>

// Used to mark symbols to be exported to the outside of the extension.
// Consider very carefully before tagging a function with this.
#define DDTRACE_EXPORT __attribute__ ((visibility ("default")))

// Used to mark function arguments that are deliberately left unused
#ifdef __GNUC__
#define DDTRACE_UNUSED __attribute__((unused))
#else
#define DDTRACE_UNUSED
#endif

Comment on lines -5 to -15
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to datadog_ruby_common.h

// @ivoanjo: After trying to read through https://stackoverflow.com/questions/3437404/min-and-max-in-c I decided I
// don't like C and I just implemented this as a function.
inline static uint64_t uint64_max_of(uint64_t a, uint64_t b) { return a > b ? a : b; }
Expand Down
2 changes: 1 addition & 1 deletion ext/datadog_profiling_native_extension/http_transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ void http_transport_init(VALUE profiling_module) {
ok_symbol = ID2SYM(rb_intern_const("ok"));
error_symbol = ID2SYM(rb_intern_const("error"));

library_version_string = ddtrace_version();
library_version_string = datadog_gem_version();
rb_global_variable(&library_version_string);
}

Expand Down
86 changes: 0 additions & 86 deletions ext/datadog_profiling_native_extension/libdatadog_helpers.c
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to datadog_ruby_common.c

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

#include <ruby.h>

static VALUE log_failure_to_process_tag(VALUE err_details);

const char *ruby_value_type_to_string(enum ruby_value_type type) {
return ruby_value_type_to_char_slice(type).ptr;
}
Expand Down Expand Up @@ -62,87 +60,3 @@ size_t read_ddogerr_string_and_drop(ddog_Error *error, char *string, size_t capa
ddog_Error_drop(error);
return error_msg_size;
}

__attribute__((warn_unused_result))
ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration) {
ENFORCE_TYPE(exporter_configuration, T_ARRAY);

VALUE exporter_working_mode = rb_ary_entry(exporter_configuration, 0);
ENFORCE_TYPE(exporter_working_mode, T_SYMBOL);
ID working_mode = SYM2ID(exporter_working_mode);

ID agentless_id = rb_intern("agentless");
ID agent_id = rb_intern("agent");

if (working_mode != agentless_id && working_mode != agent_id) {
rb_raise(rb_eArgError, "Failed to initialize transport: Unexpected working mode, expected :agentless or :agent");
}

if (working_mode == agentless_id) {
VALUE site = rb_ary_entry(exporter_configuration, 1);
VALUE api_key = rb_ary_entry(exporter_configuration, 2);
ENFORCE_TYPE(site, T_STRING);
ENFORCE_TYPE(api_key, T_STRING);

return ddog_prof_Endpoint_agentless(char_slice_from_ruby_string(site), char_slice_from_ruby_string(api_key));
} else { // agent_id
VALUE base_url = rb_ary_entry(exporter_configuration, 1);
ENFORCE_TYPE(base_url, T_STRING);

return ddog_prof_Endpoint_agent(char_slice_from_ruby_string(base_url));
}
}

__attribute__((warn_unused_result))
ddog_Vec_Tag convert_tags(VALUE tags_as_array) {
ENFORCE_TYPE(tags_as_array, T_ARRAY);

long tags_count = RARRAY_LEN(tags_as_array);
ddog_Vec_Tag tags = ddog_Vec_Tag_new();

for (long i = 0; i < tags_count; i++) {
VALUE name_value_pair = rb_ary_entry(tags_as_array, i);

if (!RB_TYPE_P(name_value_pair, T_ARRAY)) {
ddog_Vec_Tag_drop(tags);
ENFORCE_TYPE(name_value_pair, T_ARRAY);
}

// Note: We can index the array without checking its size first because rb_ary_entry returns Qnil if out of bounds
VALUE tag_name = rb_ary_entry(name_value_pair, 0);
VALUE tag_value = rb_ary_entry(name_value_pair, 1);

if (!(RB_TYPE_P(tag_name, T_STRING) && RB_TYPE_P(tag_value, T_STRING))) {
ddog_Vec_Tag_drop(tags);
ENFORCE_TYPE(tag_name, T_STRING);
ENFORCE_TYPE(tag_value, T_STRING);
}

ddog_Vec_Tag_PushResult push_result =
ddog_Vec_Tag_push(&tags, char_slice_from_ruby_string(tag_name), char_slice_from_ruby_string(tag_value));

if (push_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) {
// libdatadog validates tags and may catch invalid tags that ddtrace didn't actually catch.
// We warn users about such tags, and then just ignore them.

int exception_state;
rb_protect(log_failure_to_process_tag, get_error_details_and_drop(&push_result.err), &exception_state);

// Since we are calling into Ruby code, it may raise an exception. Ensure that dynamically-allocated tags
// get cleaned before propagating the exception.
if (exception_state) {
ddog_Vec_Tag_drop(tags);
rb_jump_tag(exception_state); // "Re-raise" exception
}
}
}

return tags;
}

static VALUE log_failure_to_process_tag(VALUE err_details) {
VALUE datadog_module = rb_const_get(rb_cObject, rb_intern("Datadog"));
VALUE logger = rb_funcall(datadog_module, rb_intern("logger"), 0);

return rb_funcall(logger, rb_intern("warn"), 1, rb_sprintf("Failed to add tag to profiling request: %"PRIsVALUE, err_details));
}
21 changes: 0 additions & 21 deletions ext/datadog_profiling_native_extension/libdatadog_helpers.h
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to datadog_ruby_common.h

Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,10 @@
#include <datadog/profiling.h>
#include "ruby_helpers.h"

inline static ddog_CharSlice char_slice_from_ruby_string(VALUE string) {
ENFORCE_TYPE(string, T_STRING);
ddog_CharSlice char_slice = {.ptr = StringValuePtr(string), .len = RSTRING_LEN(string)};
return char_slice;
}

inline static VALUE ruby_string_from_vec_u8(ddog_Vec_U8 string) {
return rb_str_new((char *) string.ptr, string.len);
}

inline static VALUE ruby_string_from_error(const ddog_Error *error) {
ddog_CharSlice char_slice = ddog_Error_message(error);
return rb_str_new(char_slice.ptr, char_slice.len);
}

inline static VALUE get_error_details_and_drop(ddog_Error *error) {
VALUE result = ruby_string_from_error(error);
ddog_Error_drop(error);
return result;
}

// Utility function to be able to extract an error cstring from a ddog_Error.
// Returns the amount of characters written to string (which are necessarily
// bounded by capacity - 1 since the string will be null-terminated).
Expand All @@ -40,7 +23,3 @@ ddog_CharSlice ruby_value_type_to_char_slice(enum ruby_value_type type);
inline static char* string_from_char_slice(ddog_CharSlice slice) {
return ruby_strndup(slice.ptr, slice.len);
}

ddog_prof_Endpoint endpoint_from(VALUE exporter_configuration);

ddog_Vec_Tag convert_tags(VALUE tags_as_array);
Loading
Loading