Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
skylight / ext / skylight_native.c
Size: Mime:
#include <dlfcn.h>
#include <ruby.h>
#include <skylight_dlopen.h>
#include <skylight_native.h>

#ifdef HAVE_RUBY_ENCODING_H
#include <ruby/encoding.h>
#endif

#define TO_S(VAL) \
  RSTRING_PTR(rb_funcall(VAL, rb_intern("to_s"), 0))

#define CHECK_TYPE(VAL, T)                        \
  do {                                            \
    if (TYPE(VAL) != T) {                         \
      rb_raise(rb_eArgError, "expected " #VAL " to be " #T " but was '%s' (%s [%i])", \
                TO_S(VAL), rb_obj_classname(VAL), TYPE(VAL)); \
      return Qnil;                                \
    }                                             \
  } while(0)

#define CHECK_NUMERIC(VAL)                        \
  do {                                            \
    if (TYPE(VAL) != T_BIGNUM &&                  \
        TYPE(VAL) != T_FIXNUM) {                  \
      rb_raise(rb_eArgError, "expected " #VAL " to be numeric but was '%s' (%s [%i])", \
                TO_S(VAL), rb_obj_classname(VAL), TYPE(VAL)); \
      return Qnil;                                \
    }                                             \
  } while(0)                                      \

static inline VALUE
BUF2STR(sky_buf_t buf) {
  VALUE str = rb_str_new(buf.data, buf.len);
  rb_enc_associate(str, rb_utf8_encoding());
  return str;
}

static inline sky_buf_t
STR2BUF(VALUE str) {
  return (sky_buf_t) {
    .data = RSTRING_PTR(str),
    .len = RSTRING_LEN(str),
  };
}

#define CHECK_FFI(success, message)               \
  do {                                            \
    if ((success) < 0 ) {                         \
      rb_raise(rb_eRuntimeError, message);        \
      return Qnil;                                \
    }                                             \
  } while(0)

#define My_Struct(name, Type, msg)                \
  Get_Struct(name, self, Type, msg);              \

#define Transfer_My_Struct(name, Type, msg)       \
  My_Struct(name, Type, msg);                     \
  DATA_PTR(self) = NULL;                          \

#define Transfer_Struct(name, obj, Type, msg)     \
  Get_Struct(name, obj, Type, msg);               \
  DATA_PTR(obj) = NULL;                           \

#define Get_Struct(name, obj, Type, msg)          \
  Data_Get_Struct(obj, Type, name);               \
  if (name == NULL) {                             \
    rb_raise(rb_eRuntimeError, "%s", msg);        \
  }

/**
 * Ruby GVL helpers
 */

#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && \
    defined(HAVE_RUBY_THREAD_H)

// Ruby 2.0+
#include <ruby/thread.h>
typedef void* (*blocking_fn_t)(void*);
#define WITHOUT_GVL(fn, a) \
  rb_thread_call_without_gvl((blocking_fn_t)(fn), (a), 0, 0)

// Ruby 1.9
#elif defined(HAVE_RB_THREAD_BLOCKING_REGION)

typedef VALUE (*blocking_fn_t)(void*);
#define WITHOUT_GVL(fn, a) \
  rb_thread_blocking_region((blocking_fn_t)(fn), (a), 0, 0)


#endif


/**
 * Ruby types defined here
 */

VALUE rb_mSkylight;
VALUE rb_mUtil;
VALUE rb_cClock;
VALUE rb_cTrace;
VALUE rb_cInstrumenter;

static const char* no_instrumenter_msg =
  "Instrumenter not currently running";

static const char* consumed_trace_msg =
  "Trace objects cannot be used once it has been submitted to the instrumenter";

static VALUE
load_libskylight(VALUE klass, VALUE path) {
  int res;

  UNUSED(klass);
  CHECK_TYPE(path, T_STRING);

  // Already loaded
  if (sky_hrtime != 0) {
    return Qnil;
  }

  res = sky_load_libskylight(StringValueCStr(path));

  if (res < 0) {
    rb_raise(rb_eRuntimeError, "[SKYLIGHT] dlerror; msg=%s", dlerror());
    return Qnil;
  }

  return Qnil;
}

/*
 *
 * class Skylight::Util::Clock
 *
 */

static VALUE
clock_high_res_time(VALUE self) {
  UNUSED(self);
  return ULL2NUM(sky_hrtime());
}

/*
 *
 * class Skylight::Instrumenter
 *
 */

static VALUE
instrumenter_new(VALUE klass, VALUE rb_env) {
  sky_instrumenter_t* instrumenter;
  sky_buf_t env[256];
  int i, envc;

  UNUSED(klass);
  CHECK_TYPE(rb_env, T_ARRAY);

  if (RARRAY_LEN(rb_env) >= 256) {
    rb_raise(rb_eArgError, "environment array too long");
    return Qnil;
  }

  envc = (int) RARRAY_LEN(rb_env);

  for (i = 0; i < envc; ++i) {
    VALUE val = rb_ary_entry(rb_env, i);

    // Make sure it is a string
    CHECK_TYPE(val, T_STRING);

    env[i] = STR2BUF(val);
  }

  CHECK_FFI(
      sky_instrumenter_new(env, envc, &instrumenter),
      "failed to initialize instrumenter");

  return Data_Wrap_Struct(rb_cInstrumenter, NULL, sky_instrumenter_free, instrumenter);
}

static void*
instrumenter_start_nogvl(sky_instrumenter_t* instrumenter) {
  /*
   * Cannot use CHECK_FFI in here
   */

  if (sky_instrumenter_start(instrumenter) == 0) {
    sky_activate_memprof();

    return (void*) Qtrue;
  }
  else {
    return (void*) Qfalse;
  }
}

static VALUE
instrumenter_start(VALUE self) {
  sky_instrumenter_t* instrumenter;

  My_Struct(instrumenter, sky_instrumenter_t, no_instrumenter_msg);

  return (VALUE) WITHOUT_GVL(instrumenter_start_nogvl, instrumenter);
}

static VALUE
instrumenter_stop(VALUE self) {
  sky_instrumenter_t* instrumenter;

  My_Struct(instrumenter, sky_instrumenter_t, no_instrumenter_msg);

  CHECK_FFI(
      sky_instrumenter_stop(instrumenter),
      "native Instrumenter#stop failed");

  sky_deactivate_memprof();

  return Qnil;
}

static VALUE
instrumenter_submit_trace(VALUE self, VALUE rb_trace) {
  sky_instrumenter_t* instrumenter;
  sky_trace_t* trace;

  My_Struct(instrumenter, sky_instrumenter_t, no_instrumenter_msg);
  Transfer_Struct(trace, rb_trace, sky_trace_t, consumed_trace_msg);

  CHECK_FFI(
      sky_instrumenter_submit_trace(instrumenter, trace),
      "native Instrumenter#submit_trace failed");

  return Qnil;
}

/*
 *
 * class Skylight::Trace
 *
 */

static VALUE
trace_new(VALUE klass, VALUE start, VALUE uuid, VALUE endpoint) {
  sky_trace_t* trace;

  UNUSED(klass);
  CHECK_NUMERIC(start);
  CHECK_TYPE(uuid, T_STRING);
  CHECK_TYPE(endpoint, T_STRING);

  CHECK_FFI(
      sky_trace_new(NUM2ULL(start), STR2BUF(uuid), STR2BUF(endpoint), &trace),
      "native Trace#new failed");

  sky_clear_allocation_count();

  return Data_Wrap_Struct(rb_cTrace, NULL, sky_trace_free, trace);
}

static VALUE
trace_get_started_at(VALUE self) {
  uint64_t start;
  sky_trace_t* trace;

  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_FFI(
      sky_trace_start(trace, &start),
      "native Trace#started_at failed");

  return ULL2NUM(start);
}

static VALUE
trace_get_endpoint(VALUE self) {
  sky_trace_t* trace;
  sky_buf_t endpoint;

  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_FFI(
      sky_trace_endpoint(trace, &endpoint),
      "native Trace#endpoint failed");

  return BUF2STR(endpoint);
}

static VALUE
trace_set_endpoint(VALUE self, VALUE endpoint) {
  sky_trace_t* trace;

  CHECK_TYPE(endpoint, T_STRING);
  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_FFI(
      sky_trace_set_endpoint(trace, STR2BUF(endpoint)),
      "native Trace#set_endpoint failed");

  return Qnil;
}

static VALUE
trace_get_uuid(VALUE self) {
  sky_trace_t* trace;
  sky_buf_t uuid;

  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_FFI(
      sky_trace_uuid(trace, &uuid),
      "native Trace#uuid failed");

  return BUF2STR(uuid);
}

static VALUE
trace_start_span(VALUE self, VALUE time, VALUE category) {
  sky_trace_t* trace;
  uint32_t span;

  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_NUMERIC(time);
  CHECK_TYPE(category, T_STRING);

  CHECK_FFI(
      sky_trace_instrument(trace, NUM2ULL(time), STR2BUF(category), &span),
      "native Trace#start_span failed");

  if (sky_have_memprof()) {
    sky_trace_span_add_uint_annotation(trace, span, 2, sky_consume_allocations());
  }

  return UINT2NUM(span);
}

static VALUE
trace_stop_span(VALUE self, VALUE span, VALUE time) {
  sky_trace_t* trace;

  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_NUMERIC(time);
  CHECK_TYPE(span, T_FIXNUM);

  if (sky_have_memprof()) {
    sky_trace_span_add_uint_annotation(trace, FIX2UINT(span), 1, sky_consume_allocations());
  }

  CHECK_FFI(
      sky_trace_span_done(trace, FIX2UINT(span), NUM2ULL(time)),
      "native Trace#stop_span failed");

  return Qnil;
}

static VALUE
trace_span_set_title(VALUE self, VALUE span, VALUE title) {
  sky_trace_t* trace;

  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_TYPE(span, T_FIXNUM);
  CHECK_TYPE(title, T_STRING);

  CHECK_FFI(
      sky_trace_span_set_title(trace, FIX2UINT(span), STR2BUF(title)),
      "native Trace#span_set_title failed");

  return Qnil;
}

static VALUE
trace_span_set_description(VALUE self, VALUE span, VALUE desc) {
  sky_trace_t* trace;

  My_Struct(trace, sky_trace_t, consumed_trace_msg);

  CHECK_TYPE(span, T_FIXNUM);
  CHECK_TYPE(desc, T_STRING);

  CHECK_FFI(
      sky_trace_span_set_desc(trace, FIX2UINT(span), STR2BUF(desc)),
      "native Trace#span_set_description failed");

  return Qnil;
}

void Init_skylight_native() {
  rb_mSkylight = rb_define_module("Skylight");
  rb_define_singleton_method(rb_mSkylight, "load_libskylight", load_libskylight, 1);

  rb_mUtil  = rb_define_module_under(rb_mSkylight, "Util");
  rb_cClock = rb_define_class_under(rb_mUtil, "Clock", rb_cObject);
  rb_define_method(rb_cClock, "native_hrtime", clock_high_res_time, 0);

  rb_cTrace = rb_define_class_under(rb_mSkylight, "Trace", rb_cObject);
  rb_define_singleton_method(rb_cTrace, "native_new", trace_new, 3);
  rb_define_method(rb_cTrace, "native_get_started_at", trace_get_started_at, 0);
  rb_define_method(rb_cTrace, "native_get_endpoint", trace_get_endpoint, 0);
  rb_define_method(rb_cTrace, "native_set_endpoint", trace_set_endpoint, 1);
  rb_define_method(rb_cTrace, "native_get_uuid", trace_get_uuid, 0);
  rb_define_method(rb_cTrace, "native_start_span", trace_start_span, 2);
  rb_define_method(rb_cTrace, "native_stop_span", trace_stop_span, 2);
  rb_define_method(rb_cTrace, "native_span_set_title", trace_span_set_title, 2);
  rb_define_method(rb_cTrace, "native_span_set_description", trace_span_set_description, 2);

  rb_cInstrumenter = rb_define_class_under(rb_mSkylight, "Instrumenter", rb_cObject);
  rb_define_singleton_method(rb_cInstrumenter, "native_new", instrumenter_new, 1);
  rb_define_method(rb_cInstrumenter, "native_start", instrumenter_start, 0);
  rb_define_method(rb_cInstrumenter, "native_stop", instrumenter_stop, 0);
  rb_define_method(rb_cInstrumenter, "native_submit_trace", instrumenter_submit_trace, 1);
}