ext/ruby_prof/rp_method.c in ruby-prof-0.16.2 vs ext/ruby_prof/rp_method.c in ruby-prof-0.17.0

- old
+ new

@@ -1,167 +1,265 @@ /* Copyright (C) 2005-2013 Shugo Maeda <shugo@ruby-lang.org> and Charlie Savage <cfis@savagexi.com> Please see the LICENSE file for copyright and distribution information */ #include "ruby_prof.h" +#define RP_REL_GET(r, off) ((r) & (1 << (off))) +#define RP_REL_SET(r, off) \ +do { \ + r |= (1 << (off)); \ +} while (0) + VALUE cMethodInfo; /* ================ Helper Functions =================*/ static VALUE figure_singleton_name(VALUE klass) { - VALUE result = Qnil; + volatile VALUE attached, super; + volatile VALUE attached_str, super_str; + volatile VALUE result = Qnil; /* We have come across a singleton object. First figure out what it is attached to.*/ - VALUE attached = rb_iv_get(klass, "__attached__"); + attached = rb_iv_get(klass, "__attached__"); /* Is this a singleton class acting as a metaclass? */ if (BUILTIN_TYPE(attached) == T_CLASS) { + attached_str = rb_class_name(attached); result = rb_str_new2("<Class::"); - rb_str_append(result, rb_inspect(attached)); + rb_str_append(result, attached_str); rb_str_cat2(result, ">"); } /* Is this for singleton methods on a module? */ else if (BUILTIN_TYPE(attached) == T_MODULE) { + attached_str = rb_class_name(attached); result = rb_str_new2("<Module::"); - rb_str_append(result, rb_inspect(attached)); + rb_str_append(result, attached_str); rb_str_cat2(result, ">"); } /* Is this for singleton methods on an object? */ else if (BUILTIN_TYPE(attached) == T_OBJECT) { /* Make sure to get the super class so that we don't mistakenly grab a T_ICLASS which would lead to unknown method errors. */ - VALUE super = rb_class_superclass(klass); + super = rb_class_superclass(klass); + super_str = rb_class_name(super); result = rb_str_new2("<Object::"); - rb_str_append(result, rb_inspect(super)); + rb_str_append(result, super_str); rb_str_cat2(result, ">"); } /* Ok, this could be other things like an array made put onto a singleton object (yeah, it happens, see the singleton objects test case). */ else { - result = rb_inspect(klass); + result = rb_any_to_s(klass); } return result; } static VALUE klass_name(VALUE klass) { - VALUE result = Qnil; + volatile VALUE result = Qnil; if (klass == 0 || klass == Qnil) { - result = rb_str_new2("Global"); + result = rb_str_new2("[global]"); } else if (BUILTIN_TYPE(klass) == T_MODULE) { - result = rb_inspect(klass); + result = rb_class_name(klass); } else if (BUILTIN_TYPE(klass) == T_CLASS && FL_TEST(klass, FL_SINGLETON)) { result = figure_singleton_name(klass); } else if (BUILTIN_TYPE(klass) == T_CLASS) { - result = rb_inspect(klass); + result = rb_class_name(klass); } else { /* Should never happen. */ - result = rb_str_new2("Unknown"); + result = rb_str_new2("[unknown]"); } return result; } static VALUE method_name(ID mid) { - VALUE result; + volatile VALUE name = Qnil; - if (mid == 0) - result = rb_str_new2("[No method]"); #ifdef ID_ALLOCATOR - else if (mid == ID_ALLOCATOR) - result = rb_str_new2("allocate"); + if (mid == ID_ALLOCATOR) { + return rb_str_new2("allocate"); + } #endif - else - result = rb_String(ID2SYM(mid)); - return result; + if (RTEST(mid)) { + name = rb_id2str(mid); + return rb_str_dup(name); + } else { + return rb_str_new2("[no method]"); + } } static VALUE full_name(VALUE klass, ID mid) { - VALUE result = rb_str_dup(klass_name(klass)); + volatile VALUE klass_str, method_str; + volatile VALUE result = Qnil; + + klass_str = klass_name(klass); + method_str = method_name(mid); + + result = rb_str_dup(klass_str); rb_str_cat2(result, "#"); - rb_str_append(result, method_name(mid)); + rb_str_append(result, method_str); return result; } +static VALUE +source_klass_name(VALUE source_klass) +{ + volatile VALUE klass_str; + volatile VALUE result = Qnil; + + if (RTEST(source_klass)) { + klass_str = rb_class_name(source_klass); + result = rb_str_dup(klass_str); + } else { + result = rb_str_new2("[global]"); + } + + return result; +} + +static VALUE +calltree_name(VALUE source_klass, int relation, ID mid) +{ + volatile VALUE klass_str, klass_path, joiner; + volatile VALUE method_str; + volatile VALUE result = Qnil; + + klass_str = source_klass_name(source_klass); + method_str = method_name(mid); + + klass_path = rb_str_split(klass_str, "::"); + joiner = rb_str_new2("/"); + result = rb_ary_join(klass_path, joiner); + + rb_str_cat2(result, "::"); + + if (RP_REL_GET(relation, kObjectSingleton)) { + rb_str_cat2(result, "*"); + } + + if (RP_REL_GET(relation, kModuleSingleton)) { + rb_str_cat2(result, "^"); + } + + rb_str_append(result, method_str); + + return result; +} + void method_key(prof_method_key_t* key, VALUE klass, ID mid) { /* Is this an include for a module? If so get the actual module class since we want to combine all profiling results for that module. */ - if (klass != 0) - klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass); + if (klass != 0 && BUILTIN_TYPE(klass) == T_ICLASS) { + klass = RBASIC(klass)->klass; + } key->klass = klass; key->mid = mid; key->key = (klass << 4) + (mid << 2); } /* ================ prof_method_t =================*/ + prof_method_t* prof_method_create(VALUE klass, ID mid, const char* source_file, int line) { prof_method_t *result = ALLOC(prof_method_t); - result->object = Qnil; - result->call_infos = prof_call_infos_create(); result->key = ALLOC(prof_method_key_t); method_key(result->key, klass, mid); - //result->call_info_table = call_info_table_create(); + result->excluded = 0; + result->recursive = 0; + result->call_infos = prof_call_infos_create(); + result->visits = 0; + + result->object = Qnil; + if (source_file != NULL) { size_t len = strlen(source_file) + 1; char *buffer = ALLOC_N(char, len); MEMCPY(buffer, source_file, char, len); result->source_file = buffer; - } - else - { + } else { result->source_file = source_file; } + + result->source_klass = Qnil; result->line = line; + result->resolved = 0; + result->relation = 0; + return result; } -/* The underlying c structures are freed when the parent profile is freed. +prof_method_t* +prof_method_create_excluded(VALUE klass, ID mid) +{ + prof_method_t *result = ALLOC(prof_method_t); + + result->key = ALLOC(prof_method_key_t); + method_key(result->key, klass, mid); + + /* Invisible with this flag set. */ + result->excluded = 1; + result->recursive = 0; + + result->call_infos = 0; + result->visits = 0; + + result->object = Qnil; + result->source_klass = Qnil; + result->line = 0; + + result->resolved = 0; + result->relation = 0; + + return result; +} + +/* The underlying c structures are freed when the parent profile is freed. However, on shutdown the Ruby GC frees objects in any will-nilly order. That means the ruby thread object wrapping the c thread struct may be freed before the parent profile. Thus we add in a free function - for the garbage collector so that if it does get called will nil + for the garbage collector so that if it does get called will nil out our Ruby object reference.*/ static void prof_method_ruby_gc_free(prof_method_t* method) { /* Has this thread object been accessed by Ruby? If @@ -169,41 +267,134 @@ if (method->object != Qnil) { RDATA(method->object)->data = NULL; RDATA(method->object)->dfree = NULL; RDATA(method->object)->dmark = NULL; - } + } method->object = Qnil; } static void prof_method_free(prof_method_t* method) { prof_method_ruby_gc_free(method); - prof_call_infos_free(method->call_infos); - xfree(method->call_infos); + if (method->call_infos) { + prof_call_infos_free(method->call_infos); + xfree(method->call_infos); + } + xfree(method->key); + method->key = NULL; + method->source_klass = Qnil; xfree(method); } void prof_method_mark(prof_method_t *method) { - if (method->object) + if (method->key->klass) { + rb_gc_mark(method->key->klass); + } + + if (method->source_klass) { + rb_gc_mark(method->source_klass); + } + + if (method->object) { rb_gc_mark(method->object); + } - prof_call_infos_mark(method->call_infos); + if (method->call_infos) { + prof_call_infos_mark(method->call_infos); + } } +static VALUE +resolve_source_klass(prof_method_t* method) +{ + volatile VALUE klass, next_klass; + volatile VALUE attached; + unsigned int relation; + + /* We want to group methods according to their source-level + definitions, not their implementation class. Follow module + inclusions and singleton classes back to a meaningful root + while keeping track of these relationships. */ + + if (method->resolved) { + return method->source_klass; + } + + klass = method->key->klass; + relation = 0; + + while (1) + { + /* This is a global/unknown class */ + if (klass == 0 || klass == Qnil) + break; + + /* Is this a singleton class? (most common case) */ + if (BUILTIN_TYPE(klass) == T_CLASS && FL_TEST(klass, FL_SINGLETON)) + { + /* We have come across a singleton object. First + figure out what it is attached to.*/ + attached = rb_iv_get(klass, "__attached__"); + + /* Is this a singleton class acting as a metaclass? + Or for singleton methods on a module? */ + if (BUILTIN_TYPE(attached) == T_CLASS || + BUILTIN_TYPE(attached) == T_MODULE) + { + RP_REL_SET(relation, kModuleSingleton); + klass = attached; + } + /* Is this for singleton methods on an object? */ + else if (BUILTIN_TYPE(attached) == T_OBJECT) + { + RP_REL_SET(relation, kObjectSingleton); + next_klass = rb_class_superclass(klass); + klass = next_klass; + } + /* This is a singleton of an instance of a builtin type. */ + else + { + RP_REL_SET(relation, kObjectSingleton); + next_klass = rb_class_superclass(klass); + klass = next_klass; + } + } + /* Is this an include for a module? If so get the actual + module class since we want to combine all profiling + results for that module. */ + else if (BUILTIN_TYPE(klass) == T_ICLASS) + { + RP_REL_SET(relation, kModuleIncludee); + next_klass = RBASIC(klass)->klass; + klass = next_klass; + } + /* No transformations apply; so bail. */ + else + { + break; + } + } + + method->resolved = 1; + method->relation = relation; + method->source_klass = klass; + + return klass; +} + VALUE prof_method_wrap(prof_method_t *result) { - if (result->object == Qnil) - { + if (result->object == Qnil) { result->object = Data_Wrap_Struct(cMethodInfo, prof_method_mark, prof_method_ruby_gc_free, result); } return result->object; } @@ -212,12 +403,13 @@ { /* Can't use Data_Get_Struct because that triggers the event hook ending up in endless recursion. */ prof_method_t* result = DATA_PTR(self); - if (!result) + if (!result) { rb_raise(rb_eRuntimeError, "This RubyProf::MethodInfo instance has already been freed, likely because its profile has been freed."); + } return result; } /* ================ Method Table =================*/ @@ -245,38 +437,34 @@ } static int method_table_free_iterator(st_data_t key, st_data_t value, st_data_t dummy) { - prof_method_free((prof_method_t*)value); + prof_method_free((prof_method_t *)value); return ST_CONTINUE; } void method_table_free(st_table *table) { st_foreach(table, method_table_free_iterator, 0); st_free_table(table); } - size_t method_table_insert(st_table *table, const prof_method_key_t *key, prof_method_t *val) { return st_insert(table, (st_data_t) key, (st_data_t) val); } prof_method_t * method_table_lookup(st_table *table, const prof_method_key_t* key) { st_data_t val; - if (st_lookup(table, (st_data_t)key, &val)) - { + if (st_lookup(table, (st_data_t)key, &val)) { return (prof_method_t *) val; - } - else - { + } else { return NULL; } } /* ================ Method Info =================*/ @@ -294,27 +482,25 @@ returns the line number of the method */ static VALUE prof_method_line(VALUE self) { - return rb_int_new(get_prof_method(self)->line); + int line = get_prof_method(self)->line; + return rb_int_new(line); } /* call-seq: source_file => string return the source file of the method */ static VALUE prof_method_source_file(VALUE self) { const char* sf = get_prof_method(self)->source_file; - if(!sf) - { + if(!sf) { return rb_str_new2("ruby_runtime"); - } - else - { + } else { return rb_str_new2(sf); } } @@ -385,17 +571,51 @@ about the current method.*/ static VALUE prof_method_call_infos(VALUE self) { prof_method_t *method = get_prof_method(self); - if (method->call_infos->object == Qnil) - { + if (method->call_infos->object == Qnil) { method->call_infos->object = prof_call_infos_wrap(method->call_infos); } return method->call_infos->object; } +/* call-seq: + recursive? -> boolean + + Returns the true if this method is recursive */ +static VALUE +prof_method_recursive(VALUE self) +{ + prof_method_t *method = get_prof_method(self); + return method->recursive ? Qtrue : Qfalse; +} + +/* call-seq: + source_klass -> klass + +Returns the Ruby klass of the natural source-level definition. */ +static VALUE +prof_source_klass(VALUE self) +{ + prof_method_t *method = get_prof_method(self); + return resolve_source_klass(method); +} + +/* call-seq: + calltree_name -> string + +Returns the full name of this method in the calltree format.*/ + +static VALUE +prof_calltree_name(VALUE self) +{ + prof_method_t *method = get_prof_method(self); + volatile VALUE source_klass = resolve_source_klass(method); + return calltree_name(source_klass, method->relation, method->key->mid); +} + void rp_init_method_info() { /* MethodInfo */ cMethodInfo = rb_define_class_under(mProf, "MethodInfo", rb_cObject); rb_undef_method(CLASS_OF(cMethodInfo), "new"); @@ -403,9 +623,14 @@ rb_define_method(cMethodInfo, "klass", prof_method_klass, 0); rb_define_method(cMethodInfo, "klass_name", prof_klass_name, 0); rb_define_method(cMethodInfo, "method_name", prof_method_name, 0); rb_define_method(cMethodInfo, "full_name", prof_full_name, 0); rb_define_method(cMethodInfo, "method_id", prof_method_id, 0); - rb_define_method(cMethodInfo, "source_file", prof_method_source_file,0); - rb_define_method(cMethodInfo, "line", prof_method_line, 0); + rb_define_method(cMethodInfo, "call_infos", prof_method_call_infos, 0); + rb_define_method(cMethodInfo, "source_klass", prof_source_klass, 0); + rb_define_method(cMethodInfo, "source_file", prof_method_source_file, 0); + rb_define_method(cMethodInfo, "line", prof_method_line, 0); + + rb_define_method(cMethodInfo, "recursive?", prof_method_recursive, 0); + rb_define_method(cMethodInfo, "calltree_name", prof_calltree_name, 0); }