ext/stackprof.c in stackprof-0.1.0 vs ext/stackprof.c in stackprof-0.2.0
- old
+ new
@@ -13,11 +13,13 @@
**********************************************************************/
#include <ruby/ruby.h>
#include <ruby/debug.h>
#include <ruby/st.h>
+#include <signal.h>
#include <sys/time.h>
+#include <pthread.h>
#define BUF_SIZE 2048
typedef struct {
size_t total_samples;
@@ -25,80 +27,116 @@
st_table *edges;
st_table *lines;
} frame_data_t;
static struct {
- enum {
- PROF_NONE = 0,
- PROF_CPU,
- PROF_WALL,
- PROF_OBJECT
- } type;
+ int running;
+ VALUE mode;
+ VALUE interval;
+ size_t overall_signals;
size_t overall_samples;
+ size_t during_gc;
st_table *frames;
VALUE frames_buffer[BUF_SIZE];
int lines_buffer[BUF_SIZE];
-} _results;
+} _stackprof;
-static VALUE sym_object, sym_wall, sym_name, sym_file, sym_line;
-static VALUE sym_samples, sym_total_samples, sym_edges, sym_lines;
-static VALUE sym_version, sym_mode, sym_frames;
-static VALUE objtracer;
+static VALUE sym_object, sym_wall, sym_cpu, sym_custom, sym_name, sym_file, sym_line;
+static VALUE sym_samples, sym_total_samples, sym_missed_samples, sym_edges, sym_lines;
+static VALUE sym_version, sym_mode, sym_interval, sym_frames;
+static VALUE sym_gc_samples, objtracer;
static VALUE gc_hook;
+static VALUE rb_mStackProf;
static void stackprof_newobj_handler(VALUE, void*);
static void stackprof_signal_handler(int sig, siginfo_t* sinfo, void* ucontext);
static VALUE
-stackprof_start(VALUE self, VALUE type, VALUE usec)
+stackprof_start(int argc, VALUE *argv, VALUE self)
{
- if (type == sym_object) {
- _results.type = PROF_OBJECT;
+ struct sigaction sa;
+ struct itimerval timer;
+ VALUE opts = Qnil, mode = Qnil, interval = Qnil;
+
+ if (_stackprof.running)
+ return Qfalse;
+
+ rb_scan_args(argc, argv, "0:", &opts);
+
+ if (RTEST(opts)) {
+ mode = rb_hash_aref(opts, sym_mode);
+ interval = rb_hash_aref(opts, sym_interval);
+ }
+ if (!RTEST(mode)) mode = sym_wall;
+
+ if (!_stackprof.frames) {
+ _stackprof.frames = st_init_numtable();
+ _stackprof.overall_signals = 0;
+ _stackprof.overall_samples = 0;
+ _stackprof.during_gc = 0;
+ }
+
+ if (mode == sym_object) {
+ if (!RTEST(interval)) interval = INT2FIX(1);
+
objtracer = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, stackprof_newobj_handler, 0);
rb_tracepoint_enable(objtracer);
- } else {
- if (type == sym_wall)
- _results.type = PROF_WALL;
- else
- _results.type = PROF_CPU;
+ } else if (mode == sym_wall || mode == sym_cpu) {
+ if (!RTEST(interval)) interval = INT2FIX(1000);
- struct sigaction sa;
sa.sa_sigaction = stackprof_signal_handler;
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigemptyset(&sa.sa_mask);
- sigaction(_results.type == PROF_WALL ? SIGALRM : SIGPROF, &sa, NULL);
+ sigaction(mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
- struct itimerval timer;
timer.it_interval.tv_sec = 0;
- timer.it_interval.tv_usec = NUM2LONG(usec);
+ timer.it_interval.tv_usec = NUM2LONG(interval);
timer.it_value = timer.it_interval;
- setitimer(_results.type == PROF_WALL ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
+ setitimer(mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
+ } else if (mode == sym_custom) {
+ /* sampled manually */
+ interval = Qnil;
+ } else {
+ rb_raise(rb_eArgError, "unknown profiler mode");
}
- return Qnil;
+ _stackprof.running = 1;
+ _stackprof.mode = mode;
+ _stackprof.interval = interval;
+
+ return Qtrue;
}
static VALUE
stackprof_stop(VALUE self)
{
- if (_results.type == PROF_OBJECT) {
+ struct sigaction sa;
+ struct itimerval timer;
+
+ if (!_stackprof.running)
+ return Qfalse;
+ _stackprof.running = 0;
+
+ if (_stackprof.mode == sym_object) {
rb_tracepoint_disable(objtracer);
- } else {
- struct itimerval timer;
+ } else if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
memset(&timer, 0, sizeof(timer));
- setitimer(_results.type == PROF_WALL ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
- struct sigaction sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
- sigaction(_results.type == PROF_WALL ? SIGALRM : SIGPROF, &sa, NULL);
+ sigaction(_stackprof.mode == sym_wall ? SIGALRM : SIGPROF, &sa, NULL);
+ } else if (_stackprof.mode == sym_custom) {
+ /* sampled manually */
+ } else {
+ rb_raise(rb_eArgError, "unknown profiler mode");
}
- return Qnil;
+ return Qtrue;
}
static int
frame_edges_i(st_data_t key, st_data_t val, st_data_t arg)
{
@@ -112,12 +150,15 @@
static int
frame_lines_i(st_data_t key, st_data_t val, st_data_t arg)
{
VALUE lines = (VALUE)arg;
- intptr_t weight = (intptr_t)val;
- rb_hash_aset(lines, INT2FIX(key), INT2FIX(weight));
+ size_t weight = (size_t)val;
+ size_t total = weight & (~(size_t)0 << (8*SIZEOF_SIZE_T/2));
+ weight -= total;
+ total = total >> (8*SIZEOF_SIZE_T/2);
+ rb_hash_aset(lines, INT2FIX(key), rb_ary_new3(2, ULONG2NUM(total), ULONG2NUM(weight)));
return ST_CONTINUE;
}
static int
frame_i(st_data_t key, st_data_t val, st_data_t arg)
@@ -125,11 +166,10 @@
VALUE frame = (VALUE)key;
frame_data_t *frame_data = (frame_data_t *)val;
VALUE results = (VALUE)arg;
VALUE details = rb_hash_new();
VALUE name, file, edges, lines;
- VALUE label, method_name;
VALUE line;
rb_hash_aset(results, rb_obj_id(frame), details);
name = rb_profile_frame_full_label(frame);
@@ -165,151 +205,243 @@
xfree(frame_data);
return ST_DELETE;
}
static VALUE
-stackprof_run(VALUE self, VALUE type, VALUE usec)
+stackprof_results(VALUE self)
{
VALUE results, frames;
- rb_need_block();
- if (!_results.frames)
- _results.frames = st_init_numtable();
- _results.overall_samples = 0;
- stackprof_start(self, type, usec);
- rb_yield(Qundef);
- stackprof_stop(self);
+ if (!_stackprof.frames || _stackprof.running)
+ return Qnil;
results = rb_hash_new();
- rb_hash_aset(results, sym_version, DBL2NUM(1.0));
- rb_hash_aset(results, sym_mode, rb_sprintf("%"PRIsVALUE"(%"PRIsVALUE")", type, usec));
- rb_hash_aset(results, sym_samples, SIZET2NUM(_results.overall_samples));
+ rb_hash_aset(results, sym_version, DBL2NUM(1.1));
+ rb_hash_aset(results, sym_mode, _stackprof.mode);
+ rb_hash_aset(results, sym_interval, _stackprof.interval);
+ rb_hash_aset(results, sym_samples, SIZET2NUM(_stackprof.overall_samples));
+ rb_hash_aset(results, sym_gc_samples, SIZET2NUM(_stackprof.during_gc));
+ rb_hash_aset(results, sym_missed_samples, SIZET2NUM(_stackprof.overall_signals - _stackprof.overall_samples));
frames = rb_hash_new();
rb_hash_aset(results, sym_frames, frames);
- st_foreach(_results.frames, frame_i, (st_data_t)frames);
+ st_foreach(_stackprof.frames, frame_i, (st_data_t)frames);
+ st_free_table(_stackprof.frames);
+ _stackprof.frames = NULL;
+
return results;
}
+static VALUE
+stackprof_run(int argc, VALUE *argv, VALUE self)
+{
+ rb_need_block();
+ stackprof_start(argc, argv, self);
+ rb_ensure(rb_yield, Qundef, stackprof_stop, self);
+ return stackprof_results(self);
+}
+
+static VALUE
+stackprof_running_p(VALUE self)
+{
+ return _stackprof.running ? Qtrue : Qfalse;
+}
+
static inline frame_data_t *
sample_for(VALUE frame)
{
st_data_t key = (st_data_t)frame, val = 0;
frame_data_t *frame_data;
- if (st_lookup(_results.frames, key, &val)) {
+ if (st_lookup(_stackprof.frames, key, &val)) {
frame_data = (frame_data_t *)val;
} else {
frame_data = ALLOC_N(frame_data_t, 1);
MEMZERO(frame_data, frame_data_t, 1);
val = (st_data_t)frame_data;
- st_insert(_results.frames, key, val);
+ st_insert(_stackprof.frames, key, val);
}
return frame_data;
}
+static int
+numtable_increment_callback(st_data_t *key, st_data_t *value, st_data_t arg, int existing)
+{
+ size_t *weight = (size_t *)value;
+ size_t increment = (size_t)arg;
+
+ if (existing)
+ (*weight) += increment;
+ else
+ *weight = increment;
+
+ return ST_CONTINUE;
+}
+
void
-st_numtable_increment(st_table *table, st_data_t key)
+st_numtable_increment(st_table *table, st_data_t key, size_t increment)
{
- intptr_t weight = 0;
- st_lookup(table, key, (st_data_t *)&weight);
- weight++;
- st_insert(table, key, weight);
+ st_update(table, key, numtable_increment_callback, (st_data_t)increment);
}
-static void
-stackprof_sample()
+void
+stackprof_record_sample()
{
int num, i;
- VALUE prev_frame;
- st_data_t key;
+ VALUE prev_frame = Qnil;
- _results.overall_samples++;
- num = rb_profile_frames(0, sizeof(_results.frames_buffer), _results.frames_buffer, _results.lines_buffer);
+ _stackprof.overall_samples++;
+ num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer), _stackprof.frames_buffer, _stackprof.lines_buffer);
for (i = 0; i < num; i++) {
- int line = _results.lines_buffer[i];
- VALUE frame = _results.frames_buffer[i];
+ int line = _stackprof.lines_buffer[i];
+ VALUE frame = _stackprof.frames_buffer[i];
frame_data_t *frame_data = sample_for(frame);
frame_data->total_samples++;
if (i == 0) {
frame_data->caller_samples++;
- if (line > 0) {
- if (!frame_data->lines)
- frame_data->lines = st_init_numtable();
- st_numtable_increment(frame_data->lines, (st_data_t)line);
- }
} else {
if (!frame_data->edges)
frame_data->edges = st_init_numtable();
- st_numtable_increment(frame_data->edges, (st_data_t)prev_frame);
+ st_numtable_increment(frame_data->edges, (st_data_t)prev_frame, 1);
}
+ if (line > 0) {
+ if (!frame_data->lines)
+ frame_data->lines = st_init_numtable();
+ size_t half = (size_t)1<<(8*SIZEOF_SIZE_T/2);
+ size_t increment = i == 0 ? half + 1 : half;
+ st_numtable_increment(frame_data->lines, (st_data_t)line, increment);
+ }
+
prev_frame = frame;
}
}
static void
stackprof_job_handler(void *data)
{
static int in_signal_handler = 0;
if (in_signal_handler) return;
+ if (!_stackprof.running) return;
in_signal_handler++;
- stackprof_sample();
+ stackprof_record_sample();
in_signal_handler--;
}
static void
stackprof_signal_handler(int sig, siginfo_t *sinfo, void *ucontext)
{
- rb_postponed_job_register_one(0, stackprof_job_handler, 0);
+ _stackprof.overall_signals++;
+ if (rb_during_gc())
+ _stackprof.during_gc++, _stackprof.overall_samples++;
+ else
+ rb_postponed_job_register_one(0, stackprof_job_handler, 0);
}
static void
stackprof_newobj_handler(VALUE tpval, void *data)
{
+ _stackprof.overall_signals++;
stackprof_job_handler(0);
}
+static VALUE
+stackprof_sample(VALUE self)
+{
+ if (!_stackprof.running)
+ return Qfalse;
+
+ _stackprof.overall_signals++;
+ stackprof_job_handler(0);
+ return Qtrue;
+}
+
static int
frame_mark_i(st_data_t key, st_data_t val, st_data_t arg)
{
VALUE frame = (VALUE)key;
- rb_gc_mark_maybe(frame);
+ rb_gc_mark(frame);
return ST_CONTINUE;
}
static void
-stackprof_gc_mark()
+stackprof_gc_mark(void *data)
{
- if (_results.frames)
- st_foreach(_results.frames, frame_mark_i, 0);
+ if (_stackprof.frames)
+ st_foreach(_stackprof.frames, frame_mark_i, 0);
}
+static void
+stackprof_atfork_prepare(void)
+{
+ struct itimerval timer;
+ if (_stackprof.running) {
+ if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
+ memset(&timer, 0, sizeof(timer));
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
+ }
+ }
+}
+
+static void
+stackprof_atfork_parent(void)
+{
+ struct itimerval timer;
+ if (_stackprof.running) {
+ if (_stackprof.mode == sym_wall || _stackprof.mode == sym_cpu) {
+ timer.it_interval.tv_sec = 0;
+ timer.it_interval.tv_usec = NUM2LONG(_stackprof.interval);
+ timer.it_value = timer.it_interval;
+ setitimer(_stackprof.mode == sym_wall ? ITIMER_REAL : ITIMER_PROF, &timer, 0);
+ }
+ }
+}
+
+static void
+stackprof_atfork_child(void)
+{
+ stackprof_stop(rb_mStackProf);
+}
+
void
Init_stackprof(void)
{
sym_object = ID2SYM(rb_intern("object"));
- sym_name = ID2SYM(rb_intern("name"));
+ sym_custom = ID2SYM(rb_intern("custom"));
sym_wall = ID2SYM(rb_intern("wall"));
+ sym_cpu = ID2SYM(rb_intern("cpu"));
+ sym_name = ID2SYM(rb_intern("name"));
sym_file = ID2SYM(rb_intern("file"));
sym_line = ID2SYM(rb_intern("line"));
sym_total_samples = ID2SYM(rb_intern("total_samples"));
+ sym_gc_samples = ID2SYM(rb_intern("gc_samples"));
+ sym_missed_samples = ID2SYM(rb_intern("missed_samples"));
sym_samples = ID2SYM(rb_intern("samples"));
sym_edges = ID2SYM(rb_intern("edges"));
sym_lines = ID2SYM(rb_intern("lines"));
sym_version = ID2SYM(rb_intern("version"));
sym_mode = ID2SYM(rb_intern("mode"));
+ sym_interval = ID2SYM(rb_intern("interval"));
sym_frames = ID2SYM(rb_intern("frames"));
gc_hook = Data_Wrap_Struct(rb_cObject, stackprof_gc_mark, NULL, NULL);
rb_global_variable(&gc_hook);
- VALUE rb_mStackProf = rb_define_module("StackProf");
- rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, 2);
+ rb_mStackProf = rb_define_module("StackProf");
+ rb_define_singleton_method(rb_mStackProf, "running?", stackprof_running_p, 0);
+ rb_define_singleton_method(rb_mStackProf, "run", stackprof_run, -1);
+ rb_define_singleton_method(rb_mStackProf, "start", stackprof_start, -1);
+ rb_define_singleton_method(rb_mStackProf, "stop", stackprof_stop, 0);
+ rb_define_singleton_method(rb_mStackProf, "results", stackprof_results, 0);
+ rb_define_singleton_method(rb_mStackProf, "sample", stackprof_sample, 0);
+
rb_autoload(rb_mStackProf, rb_intern_const("Report"), "stackprof/report.rb");
+ rb_autoload(rb_mStackProf, rb_intern_const("Middleware"), "stackprof/middleware.rb");
+
+ pthread_atfork(stackprof_atfork_prepare, stackprof_atfork_parent, stackprof_atfork_child);
}