ext/datadog_cov/datadog_cov.c in datadog-ci-1.0.0 vs ext/datadog_cov/datadog_cov.c in datadog-ci-1.0.1
- old
+ new
@@ -1,66 +1,64 @@
#include <ruby.h>
#include <ruby/debug.h>
-// constants
-#define DD_COV_TARGET_FILES 1
-#define DD_COV_TARGET_LINES 2
+#define PROFILE_FRAMES_BUFFER_SIZE 1
-static int is_prefix(VALUE prefix, const char *str)
+// threading modes
+#define SINGLE_THREADED_COVERAGE_MODE 0
+#define MULTI_THREADED_COVERAGE_MODE 1
+
+char *ruby_strndup(const char *str, size_t size)
{
- if (prefix == Qnil)
- {
- return 0;
- }
+ char *dup;
- const char *c_prefix = RSTRING_PTR(prefix);
- if (c_prefix == NULL)
- {
- return 0;
- }
+ dup = xmalloc(size + 1);
+ memcpy(dup, str, size);
+ dup[size] = '\0';
- long prefix_len = RSTRING_LEN(prefix);
- if (strncmp(c_prefix, str, prefix_len) == 0)
- {
- return 1;
- }
- else
- {
- return 0;
- }
+ return dup;
}
// Data structure
struct dd_cov_data
{
- VALUE root;
- VALUE ignored_path;
- int mode;
+ char *root;
+ long root_len;
+
+ char *ignored_path;
+ long ignored_path_len;
+
VALUE coverage;
+
+ uintptr_t last_filename_ptr;
+
+ // for single threaded mode: thread that is being covered
+ VALUE th_covered;
+
+ int threading_mode;
};
static void dd_cov_mark(void *ptr)
{
struct dd_cov_data *dd_cov_data = ptr;
rb_gc_mark_movable(dd_cov_data->coverage);
- rb_gc_mark_movable(dd_cov_data->root);
- rb_gc_mark_movable(dd_cov_data->ignored_path);
+ rb_gc_mark_movable(dd_cov_data->th_covered);
}
static void dd_cov_free(void *ptr)
{
struct dd_cov_data *dd_cov_data = ptr;
-
+ xfree(dd_cov_data->root);
+ xfree(dd_cov_data->ignored_path);
xfree(dd_cov_data);
}
static void dd_cov_compact(void *ptr)
{
struct dd_cov_data *dd_cov_data = ptr;
dd_cov_data->coverage = rb_gc_location(dd_cov_data->coverage);
- dd_cov_data->root = rb_gc_location(dd_cov_data->root);
- dd_cov_data->ignored_path = rb_gc_location(dd_cov_data->ignored_path);
+ dd_cov_data->th_covered = rb_gc_location(dd_cov_data->th_covered);
}
const rb_data_type_t dd_cov_data_type = {
.wrap_struct_name = "dd_cov",
.function = {
@@ -72,135 +70,167 @@
static VALUE dd_cov_allocate(VALUE klass)
{
struct dd_cov_data *dd_cov_data;
VALUE obj = TypedData_Make_Struct(klass, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
+
dd_cov_data->coverage = rb_hash_new();
- dd_cov_data->root = Qnil;
- dd_cov_data->ignored_path = Qnil;
- dd_cov_data->mode = DD_COV_TARGET_FILES;
+ dd_cov_data->root = NULL;
+ dd_cov_data->root_len = 0;
+ dd_cov_data->ignored_path = NULL;
+ dd_cov_data->ignored_path_len = 0;
+ dd_cov_data->last_filename_ptr = 0;
+ dd_cov_data->threading_mode = MULTI_THREADED_COVERAGE_MODE;
+
return obj;
}
// DDCov methods
static VALUE dd_cov_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE opt;
- int mode;
rb_scan_args(argc, argv, "10", &opt);
VALUE rb_root = rb_hash_lookup(opt, ID2SYM(rb_intern("root")));
if (!RTEST(rb_root))
{
rb_raise(rb_eArgError, "root is required");
}
-
VALUE rb_ignored_path = rb_hash_lookup(opt, ID2SYM(rb_intern("ignored_path")));
- VALUE rb_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("mode")));
- if (!RTEST(rb_mode) || rb_mode == ID2SYM(rb_intern("files")))
+ VALUE rb_threading_mode = rb_hash_lookup(opt, ID2SYM(rb_intern("threading_mode")));
+ int threading_mode;
+ if (rb_threading_mode == ID2SYM(rb_intern("multi")))
{
- mode = DD_COV_TARGET_FILES;
+ threading_mode = MULTI_THREADED_COVERAGE_MODE;
}
- else if (rb_mode == ID2SYM(rb_intern("lines")))
+ else if (rb_threading_mode == ID2SYM(rb_intern("single")))
{
- mode = DD_COV_TARGET_LINES;
+ threading_mode = SINGLE_THREADED_COVERAGE_MODE;
}
else
{
- rb_raise(rb_eArgError, "mode is invalid");
+ rb_raise(rb_eArgError, "threading mode is invalid");
}
struct dd_cov_data *dd_cov_data;
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
- dd_cov_data->root = rb_root;
- dd_cov_data->ignored_path = rb_ignored_path;
- dd_cov_data->mode = mode;
+ dd_cov_data->threading_mode = threading_mode;
+ dd_cov_data->root_len = RSTRING_LEN(rb_root);
+ dd_cov_data->root = ruby_strndup(RSTRING_PTR(rb_root), dd_cov_data->root_len);
+ if (RTEST(rb_ignored_path))
+ {
+ dd_cov_data->ignored_path_len = RSTRING_LEN(rb_ignored_path);
+ dd_cov_data->ignored_path = ruby_strndup(RSTRING_PTR(rb_ignored_path), dd_cov_data->ignored_path_len);
+ }
+
return Qnil;
}
-static void dd_cov_update_line_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
+static void dd_cov_update_coverage(rb_event_flag_t event, VALUE data, VALUE self, ID id, VALUE klass)
{
struct dd_cov_data *dd_cov_data;
TypedData_Get_Struct(data, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
- const char *filename = rb_sourcefile();
- if (filename == NULL)
+ const char *c_filename = rb_sourcefile();
+
+ // skip if we cover the same file again
+ uintptr_t current_filename_ptr = (uintptr_t)c_filename;
+ if (dd_cov_data->last_filename_ptr == current_filename_ptr)
{
return;
}
+ dd_cov_data->last_filename_ptr = current_filename_ptr;
- // if given filename is not located under the root, we skip it
- if (is_prefix(dd_cov_data->root, filename) == 0)
+ VALUE top_frame;
+ int captured_frames = rb_profile_frames(
+ 0 /* stack starting depth */,
+ PROFILE_FRAMES_BUFFER_SIZE,
+ &top_frame,
+ NULL);
+
+ if (captured_frames != PROFILE_FRAMES_BUFFER_SIZE)
{
return;
}
- // if ignored_path is provided and given filename is located under the ignored_path, we skip it too
- // this is useful for ignoring bundled gems location
- if (RTEST(dd_cov_data->ignored_path) && is_prefix(dd_cov_data->ignored_path, filename) == 1)
+ VALUE filename = rb_profile_frame_path(top_frame);
+ if (filename == Qnil)
{
return;
}
- VALUE rb_str_source_file = rb_str_new2(filename);
-
- if (dd_cov_data->mode == DD_COV_TARGET_FILES)
+ char *filename_ptr = RSTRING_PTR(filename);
+ // if the current filename is not located under the root, we skip it
+ if (strncmp(dd_cov_data->root, filename_ptr, dd_cov_data->root_len) != 0)
{
- rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, Qtrue);
return;
}
- // this isn't optimized yet, this is a POC to show that lines coverage is possible
- // ITR beta is going to use files coverage, we'll get back to this part when
- // we need to implement lines coverage
- if (dd_cov_data->mode == DD_COV_TARGET_LINES)
+ // if ignored_path is provided and the current filename is located under the ignored_path, we skip it too
+ // this is useful for ignoring bundled gems location
+ if (dd_cov_data->ignored_path_len != 0 && strncmp(dd_cov_data->ignored_path, filename_ptr, dd_cov_data->ignored_path_len) == 0)
{
- int line_number = rb_sourceline();
- if (line_number <= 0)
- {
- return;
- }
-
- VALUE rb_lines = rb_hash_aref(dd_cov_data->coverage, rb_str_source_file);
- if (rb_lines == Qnil)
- {
- rb_lines = rb_hash_new();
- rb_hash_aset(dd_cov_data->coverage, rb_str_source_file, rb_lines);
- }
-
- rb_hash_aset(rb_lines, INT2FIX(line_number), Qtrue);
+ return;
}
+
+ rb_hash_aset(dd_cov_data->coverage, filename, Qtrue);
}
static VALUE dd_cov_start(VALUE self)
{
- // get current thread
- VALUE thval = rb_thread_current();
- // add event hook
- rb_thread_add_event_hook(thval, dd_cov_update_line_coverage, RUBY_EVENT_LINE, self);
+ struct dd_cov_data *dd_cov_data;
+ TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
+ if (dd_cov_data->root_len == 0)
+ {
+ rb_raise(rb_eRuntimeError, "root is required");
+ }
+
+ if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
+ {
+ VALUE thval = rb_thread_current();
+ rb_thread_add_event_hook(thval, dd_cov_update_coverage, RUBY_EVENT_LINE, self);
+ dd_cov_data->th_covered = thval;
+ }
+ else
+ {
+ rb_add_event_hook(dd_cov_update_coverage, RUBY_EVENT_LINE, self);
+ }
+
return self;
}
static VALUE dd_cov_stop(VALUE self)
{
- // get current thread
- VALUE thval = rb_thread_current();
- // remove event hook for the current thread
- rb_thread_remove_event_hook(thval, dd_cov_update_line_coverage);
-
struct dd_cov_data *dd_cov_data;
TypedData_Get_Struct(self, struct dd_cov_data, &dd_cov_data_type, dd_cov_data);
- VALUE cov = dd_cov_data->coverage;
+ if (dd_cov_data->threading_mode == SINGLE_THREADED_COVERAGE_MODE)
+ {
+ VALUE thval = rb_thread_current();
+ if (!rb_equal(thval, dd_cov_data->th_covered))
+ {
+ rb_raise(rb_eRuntimeError, "Coverage was not started by this thread");
+ }
+ rb_thread_remove_event_hook(dd_cov_data->th_covered, dd_cov_update_coverage);
+ dd_cov_data->th_covered = Qnil;
+ }
+ else
+ {
+ rb_remove_event_hook(dd_cov_update_coverage);
+ }
+
+ VALUE res = dd_cov_data->coverage;
+
dd_cov_data->coverage = rb_hash_new();
+ dd_cov_data->last_filename_ptr = 0;
- return cov;
+ return res;
}
void Init_datadog_cov(void)
{
VALUE mDatadog = rb_define_module("Datadog");