/* Copyright (C) 2005-2013 Shugo Maeda and Charlie Savage Please see the LICENSE file for copyright and distribution information */ /* Document-class: RubyProf::Thread The Thread class contains profile results for a single fiber (note a Ruby thread can run multiple fibers). You cannot create an instance of RubyProf::Thread, instead you access it from a RubyProf::Profile object. profile = RubyProf::Profile.profile do ... end profile.threads.each do |thread| thread.root_methods.sort.each do |method| puts method.total_time end end */ #include "rp_thread.h" #include "rp_profile.h" VALUE cRpThread; // ====== thread_data_t ====== thread_data_t* thread_data_create(void) { thread_data_t* result = ALLOC(thread_data_t); result->stack = prof_stack_create(); result->method_table = method_table_create(); result->call_tree = NULL; result->object = Qnil; result->methods = Qnil; result->fiber_id = Qnil; result->thread_id = Qnil; result->trace = true; result->fiber = Qnil; return result; } static int mark_methods(st_data_t key, st_data_t value, st_data_t result) { prof_method_t* method = (prof_method_t*)value; prof_method_mark(method); return ST_CONTINUE; } size_t prof_thread_size(const void* data) { return sizeof(thread_data_t); } void prof_thread_mark(void* data) { if (!data) return; thread_data_t* thread = (thread_data_t*)data; if (thread->object != Qnil) rb_gc_mark(thread->object); rb_gc_mark(thread->fiber); if (thread->methods != Qnil) rb_gc_mark(thread->methods); if (thread->fiber_id != Qnil) rb_gc_mark(thread->fiber_id); if (thread->thread_id != Qnil) rb_gc_mark(thread->thread_id); if (thread->call_tree) prof_call_tree_mark(thread->call_tree); rb_st_foreach(thread->method_table, mark_methods, 0); } void prof_thread_ruby_gc_free(void* data) { if (data) { thread_data_t* thread_data = (thread_data_t*)data; thread_data->object = Qnil; } } static void prof_thread_free(thread_data_t* thread_data) { /* Has this method object been accessed by Ruby? If yes then set its data to nil to avoid a segmentation fault on the next mark and sweep. */ if (thread_data->object != Qnil) { RTYPEDDATA(thread_data->object)->data = NULL; thread_data->object = Qnil; } method_table_free(thread_data->method_table); if (thread_data->call_tree) prof_call_tree_free(thread_data->call_tree); prof_stack_free(thread_data->stack); xfree(thread_data); } static const rb_data_type_t thread_type = { .wrap_struct_name = "ThreadInfo", .function = { .dmark = prof_thread_mark, .dfree = prof_thread_ruby_gc_free, .dsize = prof_thread_size, }, .data = NULL, .flags = RUBY_TYPED_FREE_IMMEDIATELY }; VALUE prof_thread_wrap(thread_data_t* thread) { if (thread->object == Qnil) { thread->object = TypedData_Wrap_Struct(cRpThread, &thread_type, thread); } return thread->object; } static VALUE prof_thread_allocate(VALUE klass) { thread_data_t* thread_data = thread_data_create(); thread_data->object = prof_thread_wrap(thread_data); return thread_data->object; } thread_data_t* prof_get_thread(VALUE self) { /* Can't use Data_Get_Struct because that triggers the event hook ending up in endless recursion. */ thread_data_t* result = RTYPEDDATA_DATA(self); if (!result) rb_raise(rb_eRuntimeError, "This RubyProf::Thread instance has already been freed, likely because its profile has been freed."); return result; } // ====== Thread Table ====== // The thread table is hash keyed on ruby fiber_id that stores instances of thread_data_t. st_table* threads_table_create() { return rb_st_init_numtable(); } static int thread_table_free_iterator(st_data_t key, st_data_t value, st_data_t dummy) { prof_thread_free((thread_data_t*)value); return ST_CONTINUE; } void threads_table_free(st_table* table) { rb_st_foreach(table, thread_table_free_iterator, 0); rb_st_free_table(table); } thread_data_t* threads_table_lookup(void* prof, VALUE fiber) { prof_profile_t* profile = prof; thread_data_t* result = NULL; st_data_t val; if (rb_st_lookup(profile->threads_tbl, fiber, &val)) { result = (thread_data_t*)val; } return result; } thread_data_t* threads_table_insert(void* prof, VALUE fiber) { prof_profile_t* profile = prof; thread_data_t* result = thread_data_create(); VALUE thread = rb_thread_current(); result->fiber = fiber; result->fiber_id = rb_obj_id(fiber); result->thread_id = rb_obj_id(thread); rb_st_insert(profile->threads_tbl, (st_data_t)fiber, (st_data_t)result); // Are we tracing this thread? if (profile->include_threads_tbl && !rb_st_lookup(profile->include_threads_tbl, thread, 0)) { result->trace = false; } else if (profile->exclude_threads_tbl && rb_st_lookup(profile->exclude_threads_tbl, thread, 0)) { result->trace = false; } else { result->trace = true; } return result; } // ====== Profiling Methods ====== void switch_thread(void* prof, thread_data_t* thread_data, double measurement) { prof_profile_t* profile = prof; /* Get current frame for this thread */ prof_frame_t* frame = prof_frame_current(thread_data->stack); if (frame) { frame->wait_time += measurement - frame->switch_time; frame->switch_time = 0; } /* Save on the last thread the time of the context switch and reset this thread's last context switch to 0.*/ if (profile->last_thread_data) { prof_frame_t* last_frame = prof_frame_current(profile->last_thread_data->stack); if (last_frame) last_frame->switch_time = measurement; } profile->last_thread_data = thread_data; } int pause_thread(st_data_t key, st_data_t value, st_data_t data) { thread_data_t* thread_data = (thread_data_t*)value; prof_profile_t* profile = (prof_profile_t*)data; prof_frame_t* frame = prof_frame_current(thread_data->stack); prof_frame_pause(frame, profile->measurement_at_pause_resume); return ST_CONTINUE; } int unpause_thread(st_data_t key, st_data_t value, st_data_t data) { thread_data_t* thread_data = (thread_data_t*)value; prof_profile_t* profile = (prof_profile_t*)data; prof_frame_t* frame = prof_frame_current(thread_data->stack); prof_frame_unpause(frame, profile->measurement_at_pause_resume); return ST_CONTINUE; } // ====== Helper Methods ====== static int collect_methods(st_data_t key, st_data_t value, st_data_t result) { /* Called for each method stored in a thread's method table. We want to store the method info information into an array.*/ VALUE methods = (VALUE)result; prof_method_t* method = (prof_method_t*)value; rb_ary_push(methods, prof_method_wrap(method)); return ST_CONTINUE; } // ====== RubyProf::Thread ====== /* call-seq: id -> number Returns the thread id of this thread. */ static VALUE prof_thread_id(VALUE self) { thread_data_t* thread = prof_get_thread(self); return thread->thread_id; } /* call-seq: fiber_id -> number Returns the fiber id of this thread. */ static VALUE prof_fiber_id(VALUE self) { thread_data_t* thread = prof_get_thread(self); return thread->fiber_id; } /* call-seq: call_tree -> CallTree Returns the root of the call tree. */ static VALUE prof_call_tree(VALUE self) { thread_data_t* thread = prof_get_thread(self); return prof_call_tree_wrap(thread->call_tree); } /* call-seq: methods -> [RubyProf::MethodInfo] Returns an array of methods that were called from this thread during program execution. */ static VALUE prof_thread_methods(VALUE self) { thread_data_t* thread = prof_get_thread(self); if (thread->methods == Qnil) { thread->methods = rb_ary_new(); rb_st_foreach(thread->method_table, collect_methods, thread->methods); } return thread->methods; } /* :nodoc: */ static VALUE prof_thread_dump(VALUE self) { thread_data_t* thread_data = RTYPEDDATA_DATA(self); VALUE result = rb_hash_new(); rb_hash_aset(result, ID2SYM(rb_intern("fiber_id")), thread_data->fiber_id); rb_hash_aset(result, ID2SYM(rb_intern("methods")), prof_thread_methods(self)); rb_hash_aset(result, ID2SYM(rb_intern("call_tree")), prof_call_tree(self)); return result; } /* :nodoc: */ static VALUE prof_thread_load(VALUE self, VALUE data) { thread_data_t* thread_data = RTYPEDDATA_DATA(self); VALUE call_tree = rb_hash_aref(data, ID2SYM(rb_intern("call_tree"))); thread_data->call_tree = prof_get_call_tree(call_tree); thread_data->fiber_id = rb_hash_aref(data, ID2SYM(rb_intern("fiber_id"))); VALUE methods = rb_hash_aref(data, ID2SYM(rb_intern("methods"))); for (int i = 0; i < rb_array_len(methods); i++) { VALUE method = rb_ary_entry(methods, i); prof_method_t* method_data = RTYPEDDATA_DATA(method); method_table_insert(thread_data->method_table, method_data->key, method_data); } return data; } void rp_init_thread(void) { cRpThread = rb_define_class_under(mProf, "Thread", rb_cObject); rb_undef_method(CLASS_OF(cRpThread), "new"); rb_define_alloc_func(cRpThread, prof_thread_allocate); rb_define_method(cRpThread, "id", prof_thread_id, 0); rb_define_method(cRpThread, "call_tree", prof_call_tree, 0); rb_define_method(cRpThread, "fiber_id", prof_fiber_id, 0); rb_define_method(cRpThread, "methods", prof_thread_methods, 0); rb_define_method(cRpThread, "_dump_data", prof_thread_dump, 0); rb_define_method(cRpThread, "_load_data", prof_thread_load, 1); }