Skip to content

Commit

Permalink
flesh out apis so they can take a filename and write some specs
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm1 committed Apr 24, 2010
1 parent 6bffc26 commit aee83d7
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 48 deletions.
105 changes: 82 additions & 23 deletions ext/memprof.c
Expand Up @@ -360,22 +360,63 @@ memprof_track(int argc, VALUE *argv, VALUE self)
return Qnil;
}

static yajl_gen_config fancy_conf = { .beautify = 1, .indentString = " " };
static yajl_gen_config basic_conf = { .beautify = 0, .indentString = " " };

static yajl_gen
json_for_args(int argc, VALUE *argv)
{
FILE *out = NULL;
VALUE str;
rb_scan_args(argc, argv, "01", &str);

if (RTEST(str)) {
out = fopen(StringValueCStr(str), "w");
if (!out)
rb_raise(rb_eArgError, "unable to open output file");
}

if (!out)
out = stderr;

yajl_gen gen = yajl_gen_alloc2((yajl_print_t)&json_print, out == stderr ? &fancy_conf : &basic_conf, NULL, (void*)out);

return gen;
}

static void
json_free(yajl_gen gen)
{
FILE *out = (FILE*)gen->ctx;
if (out != stderr)
fclose(out);
yajl_gen_free(gen);
}

static VALUE
memprof_trace(VALUE self)
memprof_trace(int argc, VALUE *argv, VALUE self)
{
if (!rb_block_given_p())
rb_raise(rb_eArgError, "block required");

trace_set_output(NULL);
yajl_gen gen = trace_get_output();
yajl_gen gen = json_for_args(argc, argv);

trace_set_output(gen);
yajl_gen_map_open(gen);

trace_invoke_all(TRACE_RESET);
trace_invoke_all(TRACE_START);

VALUE ret = rb_yield(Qnil);

trace_invoke_all(TRACE_DUMP);
trace_invoke_all(TRACE_STOP);

yajl_gen_map_close(gen);
yajl_gen_reset(gen);

json_free(gen);
trace_set_output(NULL);

return ret;
}
Expand All @@ -396,6 +437,29 @@ each_request_entry(st_data_t key, st_data_t record, st_data_t arg)
return ST_CONTINUE;
}

static VALUE tracing_json_filename = Qnil;
static yajl_gen tracing_json_gen = NULL;

static VALUE
memprof_trace_filename_set(int argc, VALUE *argv, VALUE self)
{
if (tracing_json_gen)
json_free(tracing_json_gen);

if (!RTEST(*argv)) {
tracing_json_filename = Qnil;
} else {
tracing_json_gen = json_for_args(argc, argv);
tracing_json_filename = *argv;
}
}

static VALUE
memprof_trace_filename_get(VALUE self)
{
return tracing_json_filename;
}

static VALUE
memprof_trace_request(VALUE self, VALUE env)
{
Expand All @@ -405,8 +469,11 @@ memprof_trace_request(VALUE self, VALUE env)
struct timeval now;
gettimeofday(&now, NULL);

yajl_gen_config conf = { .beautify = 1, .indentString = " " };
yajl_gen gen = yajl_gen_alloc2((yajl_print_t)&json_print, &conf, NULL, (void*)stderr);
yajl_gen gen;
if (tracing_json_gen)
gen = tracing_json_gen;
else
gen = json_for_args(0, NULL);

yajl_gen_map_open(gen);

Expand Down Expand Up @@ -434,7 +501,11 @@ memprof_trace_request(VALUE self, VALUE env)

yajl_gen_map_close(gen);
yajl_gen_map_close(gen);
yajl_gen_free(gen);

if (gen != tracing_json_gen)
json_free(gen);
else
yajl_gen_reset(gen);

return ret;
}
Expand Down Expand Up @@ -1485,32 +1556,18 @@ memprof_dump(int argc, VALUE *argv, VALUE self)
{
VALUE str, ret = Qnil;
int old = track_objs;
FILE *out = NULL;

if (rb_block_given_p()) {
memprof_start(self);
ret = rb_yield(Qnil);
} else if (!track_objs)
rb_raise(rb_eRuntimeError, "object tracking disabled, call Memprof.start first");

rb_scan_args(argc, argv, "01", &str);

if (RTEST(str)) {
out = fopen(StringValueCStr(str), "w");
if (!out)
rb_raise(rb_eArgError, "unable to open output file");
}

yajl_gen_config conf = { .beautify = 0, .indentString = " " };
yajl_gen gen = yajl_gen_alloc2((yajl_print_t)&json_print, &conf, NULL, (void*)out);

track_objs = 0;

yajl_gen gen = json_for_args(argc, argv);
st_foreach(objs, objs_each_dump, (st_data_t)gen);
yajl_gen_free(gen);

if (out)
fclose(out);
json_free(gen);

if (rb_block_given_p())
memprof_stop(self);
Expand Down Expand Up @@ -1828,8 +1885,10 @@ Init_memprof()
rb_define_singleton_method(memprof, "track", memprof_track, -1);
rb_define_singleton_method(memprof, "dump", memprof_dump, -1);
rb_define_singleton_method(memprof, "dump_all", memprof_dump_all, -1);
rb_define_singleton_method(memprof, "trace", memprof_trace, 0);
rb_define_singleton_method(memprof, "trace", memprof_trace, -1);
rb_define_singleton_method(memprof, "trace_request", memprof_trace_request, 1);
rb_define_singleton_method(memprof, "trace_filename", memprof_trace_filename_get, 0);
rb_define_singleton_method(memprof, "trace_filename=", memprof_trace_filename_set, -1);

objs = st_init_numtable();
init_memprof_config_base();
Expand Down
22 changes: 1 addition & 21 deletions ext/tracer.c
Expand Up @@ -8,8 +8,6 @@
#include "tracer.h"
#include "util.h"

static yajl_gen_config json_conf = { .beautify = 0, .indentString = " " };
static yajl_gen default_json = NULL;
static yajl_gen json_gen = NULL;

/*
Expand Down Expand Up @@ -107,28 +105,10 @@ trace_invoke(const char *id, trace_fn fn)
return 0;
}

static void
json_print(void *ctx, const char * str, unsigned int len)
{
FILE *out = (FILE *)ctx;
size_t written = 0;
while(1) {
written += fwrite(str + written, sizeof(char), len - written, out ? out : stdout);
if (written == len) break;
}
}

void
trace_set_output(yajl_gen gen)
{
if (gen) {
json_gen = gen;
} else {
if (!default_json)
default_json = yajl_gen_alloc2((yajl_print_t)&json_print, &json_conf, NULL, (void*)stderr);

json_gen = default_json;
}
json_gen = gen;
}

yajl_gen
Expand Down
16 changes: 12 additions & 4 deletions spec/memprof_spec.rb
Expand Up @@ -61,17 +61,25 @@ def filedata
1.23+1
Memprof.dump(filename)

filedata.should =~ /"file": "#{__FILE__}"/
filedata.should =~ /"line": #{__LINE__-4}/
filedata.should =~ /"type": "float"/
filedata.should =~ /"data": 2\.23/
filedata.should =~ /"file":"#{__FILE__}"/
filedata.should =~ /"line":#{__LINE__-4}/
filedata.should =~ /"type":"float"/
filedata.should =~ /"data":2\.23/
end

should 'raise error when calling ::stats or ::dump without ::start' do
lambda{ Memprof.stats }.should.raise(RuntimeError).message.should =~ /Memprof.start/
lambda{ Memprof.dump }.should.raise(RuntimeError).message.should =~ /Memprof.start/
end

should 'dump objects created for block' do
Memprof.dump(filename) do
2.45+1
end

filedata.should =~ /"data":3\.45/
end

should 'dump out the entire heap' do
Memprof.stop
Memprof.dump_all(filename)
Expand Down
82 changes: 82 additions & 0 deletions spec/tracing_spec.rb
@@ -0,0 +1,82 @@
require File.dirname(__FILE__) + "/../ext/memprof"

require 'rubygems'
require 'bacon'
Bacon.summary_on_exit

require 'tempfile'

describe 'Memprof tracers' do
@tempfile = Tempfile.new('tracing_spec')

def filename
@tempfile.path
end

def filedata
File.read(filename)
end

should 'trace i/o for block of code' do
require 'open-uri'
Memprof.trace(filename) do
open("http://google.com").read
end

filedata.should =~ /"read":\{"calls":\d+/
filedata.should =~ /"write":\{"calls":\d+/
filedata.should =~ /"connect":\{"calls":\d+/
end

should 'trace objects created for block of code' do
Memprof.trace(filename) do
10.times{1.1+1.2}
end

filedata.should =~ /"float":10/
end

should 'trace gc runs for block of code' do
Memprof.trace(filename) do
10.times{GC.start}
end

filedata.should =~ /"gc":\{"calls":10,"time":[\d.]+/
end

should 'trace memory allocation for block of code' do
Memprof.trace(filename) do
10.times{ "abc" << "def" }
end

filedata.should =~ /"malloc":\{"calls":10/
filedata.should =~ /"realloc":\{"calls":10/
end
end

describe 'Memprof request tracing' do
@tempfile = Tempfile.new('tracing_spec')

def filename
@tempfile.path
end

def filedata
File.read(filename)
end

should 'trace request env' do
env = {"HEADER" => "value"}

Memprof.trace_filename = filename
Memprof.trace_filename.should == filename

Memprof.trace_request(env) do
end

Memprof.trace_filename = nil
Memprof.trace_filename.should.be.nil

filedata.should =~ /"HEADER":"value"/
end
end

0 comments on commit aee83d7

Please sign in to comment.