ext/liballocations/liballocations.c (97 lines of code) (raw):
#include "liballocations.h"
VALUE allocation_tracer;
VALUE free_tracer;
VALUE state_const;
ID id_enabled;
#define IS_SINGLETON(klass) \
RB_TYPE_P(klass, T_CLASS) && FL_TEST(klass, FL_SINGLETON)
/**
* Called whenever a new Ruby object is allocated.
*/
void newobj_callback(VALUE tracepoint, void* data) {
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tracepoint);
st_data_t count = 0;
AllocationState *state = allocation_state_get_struct(state_const);
VALUE klass = rb_tracearg_defined_class(trace_arg);
/* These aren't actually allocated so there's no point in tracking them. */
if ( klass == Qtrue || klass == Qfalse || klass == Qnil ) {
return;
}
// We don't care about sigleton classes since only one of them exists at a
// time. The logic here is stolen from MRI's implementation of
// Class#singleton_class? as MRI sadly provides no public C function for
// this method.
if ( IS_SINGLETON(klass) ) return;
st_lookup(state->object_counts, (st_data_t) klass, &count);
st_insert(state->object_counts, (st_data_t) klass, count + 1);
}
/**
* Called whenever a Ruby object is about to be released.
*
* Important: any Ruby allocations in this function will cause CRuby to
* segfault.
*/
void freeobj_callback(VALUE tracepoint, void* data) {
st_data_t count;
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tracepoint);
AllocationState *state = allocation_state_get_struct(state_const);
VALUE klass = rb_tracearg_defined_class(trace_arg);
if ( IS_SINGLETON(klass) ) return;
if ( st_lookup(state->object_counts, (st_data_t) klass, &count) ) {
if ( count > 0 && (count - 1) > 0 ) {
st_insert(state->object_counts, (st_data_t) klass, count - 1);
}
/* Remove the entry if the count is now 0 */
else {
st_delete(state->object_counts, (st_data_t*) &klass, NULL);
}
}
}
/**
* Copies every value in an st_table to a given Ruby Hash.
*/
static int each_count(st_data_t key, st_data_t value, st_data_t hash_ptr) {
VALUE vkey = (VALUE) key;
rb_hash_aset((VALUE) hash_ptr, vkey, INT2NUM(value));
return ST_CONTINUE;
}
/**
* Returns a Hash containing the current allocation statistics.
*
* The returned Hash contains its own copy of the statistics, any further object
* allocations/frees will not modify said Hash.
*
* This method ignores singleton classes.
*
* call-seq:
* Allocations.to_hash -> Hash
*/
VALUE allocations_to_hash(VALUE self) {
AllocationState *state = allocation_state_get_struct(state_const);
st_table *local_counts;
VALUE hash;
if ( !state->object_counts ) {
return rb_hash_new();
}
local_counts = allocation_state_copy_table(state);
hash = rb_hash_new();
st_foreach(local_counts, each_count, (st_data_t) hash);
st_free_table(local_counts);
return hash;
}
/**
* Starts the counting of object allocations.
*
* call-seq:
* Allocations.start -> nil
*/
VALUE allocations_start(VALUE self) {
AllocationState *state = allocation_state_get_struct(state_const);
if ( rb_ivar_get(self, id_enabled) == Qtrue ) {
return Qnil;
}
allocation_state_allocate_counts(state);
rb_ivar_set(self, id_enabled, Qtrue);
rb_tracepoint_enable(allocation_tracer);
rb_tracepoint_enable(free_tracer);
return Qnil;
}
/**
* Stops the counting of object allocations and clears the current statistics.
*
* call-seq:
* Allocations.stop -> nil
*/
VALUE allocations_stop(VALUE self) {
AllocationState *state = allocation_state_get_struct(state_const);
if ( rb_ivar_get(self, id_enabled) != Qtrue ) {
return Qnil;
}
rb_tracepoint_disable(allocation_tracer);
rb_tracepoint_disable(free_tracer);
allocation_state_reset_counts(state);
rb_ivar_set(self, id_enabled, Qfalse);
return Qnil;
}
/**
* Returns true if tracking allocations has been enabled, false otherwise.
*
* call-seq:
* Allocations.enabled? -> true/false
*/
VALUE allocations_enabled_p(VALUE self) {
VALUE enabled = Qfalse;
if ( rb_ivar_get(self, id_enabled) == Qtrue ) {
enabled = Qtrue;
}
return enabled;
}
void Init_liballocations() {
VALUE mAllocations = rb_define_module_under(rb_cObject, "Allocations");
allocation_tracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ,
newobj_callback, NULL);
free_tracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_FREEOBJ,
freeobj_callback, NULL);
id_enabled = rb_intern("enabled");
rb_define_singleton_method(mAllocations, "to_hash", allocations_to_hash, 0);
rb_define_singleton_method(mAllocations, "start", allocations_start, 0);
rb_define_singleton_method(mAllocations, "stop", allocations_stop, 0);
rb_define_singleton_method(mAllocations, "enabled?", allocations_enabled_p, 0);
rb_define_const(mAllocations, "ALLOCATION_TRACER", allocation_tracer);
rb_define_const(mAllocations, "FREE_TRACER", free_tracer);
Init_allocations_state();
state_const = rb_const_get(mAllocations, rb_intern("STATE"));
}