appsignal/appsignal

View on GitHub
ext/appsignal_extension.c

Summary

Maintainability
Test Coverage
#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include "appsignal.h"

static inline appsignal_string_t make_appsignal_string(VALUE str) {
  return (appsignal_string_t) {
    .len = RSTRING_LEN(str),
    .buf = RSTRING_PTR(str)
  };
}

static inline VALUE make_ruby_string(appsignal_string_t string) {
  VALUE str = rb_str_new(string.buf, string.len);
  rb_enc_associate(str, rb_utf8_encoding());
  return str;
}

VALUE Appsignal;
VALUE Extension;
VALUE Transaction;
VALUE Data;

static VALUE start(VALUE self) {
  appsignal_start();

  return Qnil;
}

static VALUE stop(VALUE self) {
  appsignal_stop();

  return Qnil;
}

static VALUE diagnose(VALUE self) {
  return make_ruby_string(appsignal_diagnose());
}

static VALUE get_server_state(VALUE self, VALUE key) {
  appsignal_string_t string;

  Check_Type(key, T_STRING);

  string = appsignal_get_server_state(make_appsignal_string(key));
  if (string.len > 0) {
    return make_ruby_string(string);
  } else {
    return Qnil;
  }
}

static VALUE start_transaction(VALUE self, VALUE transaction_id, VALUE namespace, VALUE gc_duration_ms) {
  appsignal_transaction_t* transaction;

  Check_Type(transaction_id, T_STRING);
  Check_Type(namespace, T_STRING);
  Check_Type(gc_duration_ms, T_FIXNUM);

  transaction = appsignal_start_transaction(
      make_appsignal_string(transaction_id),
      make_appsignal_string(namespace),
      NUM2LONG(gc_duration_ms)
  );

  if (transaction) {
    return Data_Wrap_Struct(Transaction, NULL, appsignal_free_transaction, transaction);
  } else {
    return Qnil;
  }
}

static VALUE start_event(VALUE self, VALUE gc_duration_ms) {
  appsignal_transaction_t* transaction;

  Check_Type(gc_duration_ms, T_FIXNUM);

  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  appsignal_start_event(transaction, NUM2LONG(gc_duration_ms));

  return Qnil;
}

static VALUE finish_event(VALUE self, VALUE name, VALUE title, VALUE body, VALUE body_format, VALUE gc_duration_ms) {
  appsignal_transaction_t* transaction;
  appsignal_data_t* body_data;
  int body_type;

  Check_Type(name, T_STRING);
  Check_Type(title, T_STRING);
  Check_Type(body_format, T_FIXNUM);

  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  body_type = TYPE(body);
  if (body_type == T_STRING) {
    appsignal_finish_event(
        transaction,
        make_appsignal_string(name),
        make_appsignal_string(title),
        make_appsignal_string(body),
        FIX2INT(body_format),
        FIX2LONG(gc_duration_ms)
    );
  } else if (body_type == RUBY_T_DATA) {
    Data_Get_Struct(body, appsignal_data_t, body_data);
    appsignal_finish_event_data(
        transaction,
        make_appsignal_string(name),
        make_appsignal_string(title),
        body_data,
        FIX2INT(body_format),
        FIX2LONG(gc_duration_ms)
    );
  } else {
      rb_raise(rb_eTypeError, "body should be a String or Appsignal::Extension::Data");
  }

  return Qnil;
}

static VALUE record_event(VALUE self, VALUE name, VALUE title, VALUE body, VALUE body_format, VALUE duration, VALUE gc_duration_ms) {
  appsignal_transaction_t* transaction;
  appsignal_data_t* body_data;
  int body_type;
  int duration_type;

  Check_Type(name, T_STRING);
  Check_Type(title, T_STRING);
  duration_type = TYPE(duration);
  if (duration_type != T_FIXNUM && duration_type != T_BIGNUM) {
      rb_raise(rb_eTypeError, "duration should be an Integer");
  }
  Check_Type(body_format, T_FIXNUM);

  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  body_type = TYPE(body);
  if (body_type == T_STRING) {
    appsignal_record_event(
        transaction,
        make_appsignal_string(name),
        make_appsignal_string(title),
        make_appsignal_string(body),
        FIX2INT(body_format),
        NUM2LONG(duration),
        NUM2LONG(gc_duration_ms)
    );
  } else if (body_type == RUBY_T_DATA) {
    Data_Get_Struct(body, appsignal_data_t, body_data);
    appsignal_record_event_data(
        transaction,
        make_appsignal_string(name),
        make_appsignal_string(title),
        body_data,
        FIX2INT(body_format),
        NUM2LONG(duration),
        NUM2LONG(gc_duration_ms)
    );
  } else {
      rb_raise(rb_eTypeError, "body should be a String or Appsignal::Extension::Data");
  }

  return Qnil;
}

static VALUE set_transaction_error(VALUE self, VALUE name, VALUE message, VALUE backtrace) {
  appsignal_transaction_t* transaction;
  appsignal_data_t* backtrace_data;

  Check_Type(name, T_STRING);
  Check_Type(message, T_STRING);
  Check_Type(backtrace, RUBY_T_DATA);

  Data_Get_Struct(self, appsignal_transaction_t, transaction);
  Data_Get_Struct(backtrace, appsignal_data_t, backtrace_data);

  appsignal_set_transaction_error(
      transaction,
      make_appsignal_string(name),
      make_appsignal_string(message),
      backtrace_data
  );
  return Qnil;
}

static VALUE set_transaction_sample_data(VALUE self, VALUE key, VALUE payload) {
  appsignal_transaction_t* transaction;
  appsignal_data_t* payload_data;

  Check_Type(key, T_STRING);
  Check_Type(payload, RUBY_T_DATA);

  Data_Get_Struct(self, appsignal_transaction_t, transaction);
  Data_Get_Struct(payload, appsignal_data_t, payload_data);

  appsignal_set_transaction_sample_data(
      transaction,
      make_appsignal_string(key),
      payload_data
  );
  return Qnil;
}

static VALUE set_transaction_action(VALUE self, VALUE action) {
  appsignal_transaction_t* transaction;

  Check_Type(action, T_STRING);
  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  appsignal_set_transaction_action(
      transaction,
      make_appsignal_string(action)
  );
  return Qnil;
}

static VALUE set_transaction_namespace(VALUE self, VALUE namespace) {
  appsignal_transaction_t* transaction;

  Check_Type(namespace, T_STRING);
  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  appsignal_set_transaction_namespace(
      transaction,
      make_appsignal_string(namespace)
  );
  return Qnil;
}

static VALUE set_transaction_queue_start(VALUE self, VALUE queue_start) {
  appsignal_transaction_t* transaction;
  int queue_start_type;

  queue_start_type = TYPE(queue_start);
  if (queue_start_type != T_FIXNUM && queue_start_type != T_BIGNUM) {
      rb_raise(rb_eTypeError, "queue_start should be an Integer");
  }

  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  appsignal_set_transaction_queue_start(
      transaction,
      NUM2LONG(queue_start)
  );
  return Qnil;
}

static VALUE set_transaction_metadata(VALUE self, VALUE key, VALUE value) {
  appsignal_transaction_t* transaction;

  Check_Type(key, T_STRING);
  Check_Type(value, T_STRING);
  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  appsignal_set_transaction_metadata(
      transaction,
      make_appsignal_string(key),
      make_appsignal_string(value)
  );
  return Qnil;
}

static VALUE finish_transaction(VALUE self, VALUE gc_duration_ms) {
  appsignal_transaction_t* transaction;
  int sample;

  Check_Type(gc_duration_ms, T_FIXNUM);
  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  sample = appsignal_finish_transaction(transaction, NUM2LONG(gc_duration_ms));
  return sample == 1 ? Qtrue : Qfalse;
}

static VALUE complete_transaction(VALUE self) {
  appsignal_transaction_t* transaction;

  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  appsignal_complete_transaction(transaction);
  return Qnil;
}

static VALUE transaction_to_json(VALUE self) {
  appsignal_transaction_t* transaction;
  appsignal_string_t json;

  Data_Get_Struct(self, appsignal_transaction_t, transaction);

  json = appsignal_transaction_to_json(transaction);

  if (json.len == 0) {
    return Qnil;
  } else {
    return make_ruby_string(json);
  }
}

static VALUE data_map_new(VALUE self) {
  appsignal_data_t* data;

  data = appsignal_data_map_new();

  if (data) {
    return Data_Wrap_Struct(Data, NULL, appsignal_free_data, data);
  } else {
    return Qnil;
  }
}

static VALUE data_array_new(VALUE self) {
  appsignal_data_t* data;

  data = appsignal_data_array_new();

  if (data) {
    return Data_Wrap_Struct(Data, NULL, appsignal_free_data, data);
  } else {
    return Qnil;
  }
}

static VALUE data_set_string(VALUE self, VALUE key, VALUE value) {
  appsignal_data_t* data;

  Check_Type(key, T_STRING);
  Check_Type(value, T_STRING);

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_map_set_string(
    data,
    make_appsignal_string(key),
    make_appsignal_string(value)
  );

  return Qnil;
}

static VALUE data_set_integer(VALUE self, VALUE key, VALUE value) {
  appsignal_data_t* data;
  VALUE value_type = TYPE(value);

  Check_Type(key, T_STRING);
  if (value_type != T_FIXNUM && value_type != T_BIGNUM) {
    rb_raise(rb_eTypeError, "wrong argument type %s (expected Integer)", rb_obj_classname(value));
  }

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_map_set_integer(
    data,
    make_appsignal_string(key),
    NUM2LONG(value)
  );

  return Qnil;
}

static VALUE data_set_float(VALUE self, VALUE key, VALUE value) {
  appsignal_data_t* data;

  Check_Type(key, T_STRING);
  Check_Type(value, T_FLOAT);

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_map_set_float(
    data,
    make_appsignal_string(key),
    NUM2DBL(value)
  );

  return Qnil;
}

static VALUE data_set_boolean(VALUE self, VALUE key, VALUE value) {
  appsignal_data_t* data;

  Check_Type(key, T_STRING);

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_map_set_boolean(
    data,
    make_appsignal_string(key),
    RTEST(value)
  );

  return Qnil;
}

static VALUE data_set_nil(VALUE self, VALUE key) {
  appsignal_data_t* data;

  Check_Type(key, T_STRING);

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_map_set_null(
    data,
    make_appsignal_string(key)
  );

  return Qnil;
}

static VALUE data_set_data(VALUE self, VALUE key, VALUE value) {
  appsignal_data_t* data;
  appsignal_data_t* value_data;

  Check_Type(key, T_STRING);
  Check_Type(value, RUBY_T_DATA);

  Data_Get_Struct(self, appsignal_data_t, data);
  Data_Get_Struct(value, appsignal_data_t, value_data);

  appsignal_data_map_set_data(
    data,
    make_appsignal_string(key),
    value_data
  );

  return Qnil;
}

static VALUE data_append_string(VALUE self, VALUE value) {
  appsignal_data_t* data;

  Check_Type(value, T_STRING);

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_array_append_string(
    data,
    make_appsignal_string(value)
  );

  return Qnil;
}

static VALUE data_append_integer(VALUE self, VALUE value) {
  appsignal_data_t* data;
  VALUE value_type = TYPE(value);

  if (value_type != T_FIXNUM && value_type != T_BIGNUM) {
    rb_raise(rb_eTypeError, "wrong argument type %s (expected Integer)", rb_obj_classname(value));
  }

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_array_append_integer(
    data,
    NUM2LONG(value)
 );

  return Qnil;
}

static VALUE data_append_float(VALUE self, VALUE value) {
  appsignal_data_t* data;

  Check_Type(value, T_FLOAT);

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_array_append_float(
    data,
    NUM2DBL(value)
 );

  return Qnil;
}

static VALUE data_append_boolean(VALUE self, VALUE value) {
  appsignal_data_t* data;

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_array_append_boolean(
    data,
    RTEST(value)
 );

  return Qnil;
}

static VALUE data_append_nil(VALUE self, VALUE value) {
  appsignal_data_t* data;

  Data_Get_Struct(self, appsignal_data_t, data);

  appsignal_data_array_append_null(data);

  return Qnil;
}

static VALUE data_append_data(VALUE self, VALUE value) {
  appsignal_data_t* data;
  appsignal_data_t* value_data;

  Check_Type(value, RUBY_T_DATA);

  Data_Get_Struct(self, appsignal_data_t, data);
  Data_Get_Struct(value, appsignal_data_t, value_data);

  appsignal_data_array_append_data(
    data,
    value_data
  );

  return Qnil;
}

static VALUE data_equal(VALUE self, VALUE other) {
  appsignal_data_t* data;
  appsignal_data_t* other_data;

  if (TYPE(other) != RUBY_T_DATA) {
    return Qfalse;
  }

  Data_Get_Struct(self, appsignal_data_t, data);
  Data_Get_Struct(other, appsignal_data_t, other_data);

  if (appsignal_data_equal(data, other_data) == 1) {
    return Qtrue;
  } else {
    return Qfalse;
  }
}

static VALUE data_to_s(VALUE self) {
  appsignal_data_t* data;
  appsignal_string_t json;

  Data_Get_Struct(self, appsignal_data_t, data);

  json = appsignal_data_to_json(data);

  if (json.len == 0) {
    return Qnil;
  } else {
    return make_ruby_string(json);
  }
}

static VALUE set_gauge(VALUE self, VALUE key, VALUE value, VALUE tags) {
  appsignal_data_t* tags_data;

  Check_Type(key, T_STRING);
  Check_Type(value, T_FLOAT);
  Check_Type(tags, RUBY_T_DATA);

  Data_Get_Struct(tags, appsignal_data_t, tags_data);

  appsignal_set_gauge(
      make_appsignal_string(key),
      NUM2DBL(value),
      tags_data
  );
  return Qnil;
}

static VALUE set_host_gauge(VALUE self, VALUE key, VALUE value) {
  Check_Type(key, T_STRING);
  Check_Type(value, T_FLOAT);

  appsignal_set_host_gauge(
      make_appsignal_string(key),
      NUM2DBL(value)
  );
  return Qnil;
}

static VALUE set_process_gauge(VALUE self, VALUE key, VALUE value) {
  Check_Type(key, T_STRING);
  Check_Type(value, T_FLOAT);

  appsignal_set_process_gauge(
      make_appsignal_string(key),
      NUM2DBL(value)
  );
  return Qnil;
}

static VALUE increment_counter(VALUE self, VALUE key, VALUE count, VALUE tags) {
  appsignal_data_t* tags_data;

  Check_Type(key, T_STRING);
  Check_Type(count, T_FLOAT);
  Check_Type(tags, RUBY_T_DATA);

  Data_Get_Struct(tags, appsignal_data_t, tags_data);

  appsignal_increment_counter(
      make_appsignal_string(key),
      NUM2DBL(count),
      tags_data
  );
  return Qnil;
}

static VALUE add_distribution_value(VALUE self, VALUE key, VALUE value, VALUE tags) {
  appsignal_data_t* tags_data;

  Check_Type(key, T_STRING);
  Check_Type(value, T_FLOAT);
  Check_Type(tags, RUBY_T_DATA);

  Data_Get_Struct(tags, appsignal_data_t, tags_data);

  appsignal_add_distribution_value(
      make_appsignal_string(key),
      NUM2DBL(value),
      tags_data
  );
  return Qnil;
}

static void track_allocation(rb_event_flag_t flag, VALUE arg1, VALUE arg2, ID arg3, VALUE arg4) {
  appsignal_track_allocation();
}

static VALUE install_allocation_event_hook() {
  // This event hook is only available on Ruby 2.1 and 2.2
  #if defined(RUBY_INTERNAL_EVENT_NEWOBJ)
  rb_add_event_hook(
      track_allocation,
      RUBY_INTERNAL_EVENT_NEWOBJ,
      Qnil
  );
  #endif

  return Qnil;
}

static VALUE running_in_container() {
  return appsignal_running_in_container() == 1 ? Qtrue : Qfalse;
}

void Init_appsignal_extension(void) {
  Appsignal = rb_define_module("Appsignal");
  Extension = rb_define_class_under(Appsignal, "Extension", rb_cObject);
  Transaction = rb_define_class_under(Extension, "Transaction", rb_cObject);
  Data = rb_define_class_under(Extension, "Data", rb_cObject);

  // Starting and stopping
  rb_define_singleton_method(Extension, "start",    start,    0);
  rb_define_singleton_method(Extension, "stop",     stop,     0);
  // Diagnostics
  rb_define_singleton_method(Extension, "diagnose", diagnose, 0);

  // Server state
  rb_define_singleton_method(Extension, "get_server_state", get_server_state, 1);

  // Start transaction
  rb_define_singleton_method(Extension, "start_transaction", start_transaction, 3);

  // Transaction instance methods
  rb_define_method(Transaction, "start_event",     start_event,                 1);
  rb_define_method(Transaction, "finish_event",    finish_event,                5);
  rb_define_method(Transaction, "record_event",    record_event,                6);
  rb_define_method(Transaction, "set_error",       set_transaction_error,       3);
  rb_define_method(Transaction, "set_sample_data", set_transaction_sample_data, 2);
  rb_define_method(Transaction, "set_action",      set_transaction_action,      1);
  rb_define_method(Transaction, "set_namespace",   set_transaction_namespace,   1);
  rb_define_method(Transaction, "set_queue_start", set_transaction_queue_start, 1);
  rb_define_method(Transaction, "set_metadata",    set_transaction_metadata,    2);
  rb_define_method(Transaction, "finish",          finish_transaction,          1);
  rb_define_method(Transaction, "complete",        complete_transaction,        0);
  rb_define_method(Transaction, "to_json",         transaction_to_json,         0);

  // Create a data map or array
  rb_define_singleton_method(Extension, "data_map_new", data_map_new, 0);
  rb_define_singleton_method(Extension, "data_array_new", data_array_new, 0);

  // Add content to a data map
  rb_define_method(Data, "set_string",  data_set_string,  2);
  rb_define_method(Data, "set_integer", data_set_integer, 2);
  rb_define_method(Data, "set_float",   data_set_float,   2);
  rb_define_method(Data, "set_boolean", data_set_boolean, 2);
  rb_define_method(Data, "set_nil",     data_set_nil,     1);
  rb_define_method(Data, "set_data",    data_set_data,    2);

  // Add content to a data array
  rb_define_method(Data, "append_string",  data_append_string,  1);
  rb_define_method(Data, "append_integer", data_append_integer, 1);
  rb_define_method(Data, "append_float",   data_append_float,   1);
  rb_define_method(Data, "append_boolean", data_append_boolean, 1);
  rb_define_method(Data, "append_nil",     data_append_nil,     0);
  rb_define_method(Data, "append_data",    data_append_data,    1);

  // Data equality
  rb_define_method(Data, "==", data_equal, 1);

  // Get JSON content of a data
  rb_define_method(Data, "to_s", data_to_s, 0);

  // Event hook installation
  rb_define_singleton_method(Extension, "install_allocation_event_hook", install_allocation_event_hook, 0);
  rb_define_singleton_method(Extension, "running_in_container?", running_in_container, 0);

  // Metrics
  rb_define_singleton_method(Extension, "set_gauge",              set_gauge,              3);
  rb_define_singleton_method(Extension, "set_host_gauge",         set_host_gauge,         2);
  rb_define_singleton_method(Extension, "set_process_gauge",      set_process_gauge,      2);
  rb_define_singleton_method(Extension, "increment_counter",      increment_counter,      3);
  rb_define_singleton_method(Extension, "add_distribution_value", add_distribution_value, 3);
}