ruby-prof 0.11.0.rc1 → 0.11.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/CHANGES +20 -5
  2. data/README.rdoc +10 -3
  3. data/ext/ruby_prof/rp_call_info.c +108 -79
  4. data/ext/ruby_prof/rp_call_info.h +1 -0
  5. data/ext/ruby_prof/rp_measure_cpu_time.c +111 -111
  6. data/ext/ruby_prof/rp_measure_gc_runs.c +1 -1
  7. data/ext/ruby_prof/rp_measure_memory.c +1 -1
  8. data/ext/ruby_prof/rp_measure_process_time.c +71 -71
  9. data/ext/ruby_prof/rp_measure_wall_time.c +1 -1
  10. data/ext/ruby_prof/rp_method.c +143 -73
  11. data/ext/ruby_prof/rp_method.h +7 -4
  12. data/ext/ruby_prof/rp_stack.c +16 -1
  13. data/ext/ruby_prof/rp_stack.h +4 -1
  14. data/ext/ruby_prof/rp_thread.c +165 -35
  15. data/ext/ruby_prof/rp_thread.h +8 -2
  16. data/ext/ruby_prof/ruby_prof.c +164 -171
  17. data/ext/ruby_prof/ruby_prof.h +53 -54
  18. data/ext/ruby_prof/vc/ruby_prof.sln +26 -0
  19. data/ext/ruby_prof/vc/ruby_prof.vcxproj +109 -0
  20. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +105 -0
  21. data/lib/ruby-prof/aggregate_call_info.rb +9 -7
  22. data/lib/ruby-prof/call_info.rb +2 -27
  23. data/lib/ruby-prof/call_info_visitor.rb +42 -0
  24. data/lib/ruby-prof/{empty.png → images/empty.png} +0 -0
  25. data/lib/ruby-prof/{minus.png → images/minus.png} +0 -0
  26. data/lib/ruby-prof/{plus.png → images/plus.png} +0 -0
  27. data/lib/ruby-prof/method_info.rb +13 -15
  28. data/lib/ruby-prof/{abstract_printer.rb → printers/abstract_printer.rb} +36 -2
  29. data/lib/ruby-prof/printers/call_info_printer.rb +40 -0
  30. data/lib/ruby-prof/{call_stack_printer.rb → printers/call_stack_printer.rb} +11 -16
  31. data/lib/ruby-prof/{call_tree_printer.rb → printers/call_tree_printer.rb} +4 -4
  32. data/lib/ruby-prof/{dot_printer.rb → printers/dot_printer.rb} +11 -31
  33. data/lib/ruby-prof/{flat_printer.rb → printers/flat_printer.rb} +26 -35
  34. data/lib/ruby-prof/{flat_printer_with_line_numbers.rb → printers/flat_printer_with_line_numbers.rb} +14 -25
  35. data/lib/ruby-prof/printers/graph_html_printer.rb +248 -0
  36. data/lib/ruby-prof/{graph_printer.rb → printers/graph_printer.rb} +31 -73
  37. data/lib/ruby-prof/{multi_printer.rb → printers/multi_printer.rb} +0 -0
  38. data/lib/ruby-prof/profile.rb +27 -22
  39. data/lib/ruby-prof/rack.rb +22 -12
  40. data/ruby-prof.gemspec +58 -0
  41. data/test/aggregate_test.rb +6 -6
  42. data/test/call_info_visitor_test.rb +31 -0
  43. data/test/duplicate_names_test.rb +1 -1
  44. data/test/dynamic_method_test.rb +1 -1
  45. data/test/enumerable_test.rb +1 -1
  46. data/test/exclude_threads_test.rb +2 -2
  47. data/test/gc_test.rb +35 -0
  48. data/test/line_number_test.rb +2 -2
  49. data/test/measure_cpu_time_test.rb +5 -5
  50. data/test/measure_process_time_test.rb +5 -5
  51. data/test/measure_wall_time_test.rb +5 -5
  52. data/test/method_elimination_test.rb +3 -3
  53. data/test/module_test.rb +1 -1
  54. data/test/no_method_class_test.rb +1 -1
  55. data/test/printers_test.rb +16 -8
  56. data/test/recursive_test.rb +115 -91
  57. data/test/stack_test.rb +1 -1
  58. data/test/start_stop_test.rb +13 -13
  59. data/test/summarize_test.rb +48 -0
  60. data/test/test_suite.rb +1 -0
  61. data/test/thread_test.rb +16 -12
  62. data/test/unique_call_path_test.rb +10 -10
  63. metadata +64 -85
  64. data/lib/1.8/ruby_prof.so +0 -0
  65. data/lib/1.9/ruby_prof.exp +0 -0
  66. data/lib/1.9/ruby_prof.ilk +0 -0
  67. data/lib/1.9/ruby_prof.lib +0 -0
  68. data/lib/1.9/ruby_prof.pdb +0 -0
  69. data/lib/1.9/ruby_prof.so +0 -0
  70. data/lib/ruby-prof.rb +0 -70
  71. data/lib/ruby-prof/graph_html_printer.rb +0 -286
  72. data/lib/ruby-prof/symbol_to_proc.rb +0 -10
  73. data/lib/ruby_prof.exp +0 -0
  74. data/lib/ruby_prof.ilk +0 -0
  75. data/lib/ruby_prof.lib +0 -0
  76. data/lib/ruby_prof.pdb +0 -0
  77. data/lib/ruby_prof.so +0 -0
  78. data/lib/unprof.rb +0 -10
  79. data/test/do_nothing.rb +0 -0
@@ -29,6 +29,8 @@ stack_free(prof_stack_t *stack)
29
29
  prof_frame_t *
30
30
  stack_push(prof_stack_t *stack)
31
31
  {
32
+ prof_frame_t* result = NULL;
33
+
32
34
  /* Is there space on the stack? If not, double
33
35
  its size. */
34
36
  if (stack->ptr == stack->end )
@@ -36,10 +38,23 @@ stack_push(prof_stack_t *stack)
36
38
  size_t len = stack->ptr - stack->start;
37
39
  size_t new_capacity = (stack->end - stack->start) * 2;
38
40
  REALLOC_N(stack->start, prof_frame_t, new_capacity);
41
+ /* Memory just got moved, reset pointers */
39
42
  stack->ptr = stack->start + len;
40
43
  stack->end = stack->start + new_capacity;
41
44
  }
42
- return stack->ptr++;
45
+
46
+ // Setup returned stack pointer to be valid
47
+ result = stack->ptr;
48
+ result->child_time = 0;
49
+ result->switch_time = 0;
50
+ result->wait_time = 0;
51
+ result->depth = (stack->ptr - stack->start);
52
+
53
+ // Increment the stack ptr for next time
54
+ stack->ptr++;
55
+
56
+ // Return the result
57
+ return result;
43
58
  }
44
59
 
45
60
  prof_frame_t *
@@ -11,15 +11,18 @@
11
11
 
12
12
 
13
13
  /* Temporary object that maintains profiling information
14
- for active methods - there is one per method.*/
14
+ for active methods. They are created and destroyed
15
+ as the program moves up and down its stack. */
15
16
  typedef struct
16
17
  {
17
18
  /* Caching prof_method_t values significantly
18
19
  increases performance. */
19
20
  prof_call_info_t *call_info;
20
21
  double start_time;
22
+ double switch_time; /* Time at switch to different thread */
21
23
  double wait_time;
22
24
  double child_time;
25
+ int depth;
23
26
  unsigned int line;
24
27
  } prof_frame_t;
25
28
 
@@ -3,25 +3,96 @@
3
3
 
4
4
  #include "ruby_prof.h"
5
5
 
6
+ VALUE cRpThread;
7
+
6
8
  /* ====== thread_data_t ====== */
7
9
  thread_data_t*
8
- thread_data_create(double measure)
10
+ thread_data_create()
9
11
  {
10
12
  thread_data_t* result = ALLOC(thread_data_t);
11
13
  result->stack = stack_create();
12
14
  result->method_table = method_table_create();
13
- result->last_switch = measure;
15
+ result->top = NULL;
16
+ result->object = Qnil;
17
+ result->methods = Qnil;
14
18
  return result;
15
19
  }
16
20
 
17
- void
21
+ /* The underlying c structures are freed when the parent profile is freed.
22
+ However, on shutdown the Ruby GC frees objects in any will-nilly order.
23
+ That means the ruby thread object wrapping the c thread struct may
24
+ be freed before the parent profile. Thus we add in a free function
25
+ for the garbage collector so that if it does get called will nil
26
+ out our Ruby object reference.*/
27
+ static void
28
+ thread_data_ruby_gc_free(thread_data_t* thread_data)
29
+ {
30
+ /* Has this thread object been accessed by Ruby? If
31
+ yes clean it up so to avoid a segmentation fault. */
32
+ if (thread_data->object != Qnil)
33
+ {
34
+ RDATA(thread_data->object)->data = NULL;
35
+ RDATA(thread_data->object)->dfree = NULL;
36
+ RDATA(thread_data->object)->dmark = NULL;
37
+ }
38
+ thread_data->object = Qnil;
39
+ }
40
+
41
+ static void
18
42
  thread_data_free(thread_data_t* thread_data)
19
43
  {
44
+ thread_data_ruby_gc_free(thread_data);
45
+ thread_data->top = NULL;
20
46
  method_table_free(thread_data->method_table);
21
47
  stack_free(thread_data->stack);
22
- xfree(thread_data);
48
+
49
+ thread_data->thread_id = Qnil;
50
+
51
+ xfree(thread_data);
23
52
  }
24
53
 
54
+ static int
55
+ mark_methods(st_data_t key, st_data_t value, st_data_t result)
56
+ {
57
+ prof_method_t *method = (prof_method_t *) value;
58
+ prof_method_mark(method);
59
+ return ST_CONTINUE;
60
+ }
61
+
62
+ void
63
+ prof_thread_mark(thread_data_t *thread)
64
+ {
65
+ if (thread->object != Qnil)
66
+ rb_gc_mark(thread->object);
67
+
68
+ if (thread->methods != Qnil)
69
+ rb_gc_mark(thread->methods);
70
+
71
+ prof_method_mark(thread->top);
72
+ st_foreach(thread->method_table, mark_methods, 0);
73
+ }
74
+
75
+ VALUE
76
+ prof_thread_wrap(thread_data_t *thread)
77
+ {
78
+ if (thread->object == Qnil)
79
+ {
80
+ thread->object = Data_Wrap_Struct(cRpThread, prof_thread_mark, thread_data_ruby_gc_free, thread);
81
+ }
82
+ return thread->object;
83
+ }
84
+
85
+ static thread_data_t*
86
+ prof_get_thread(VALUE self)
87
+ {
88
+ /* Can't use Data_Get_Struct because that triggers the event hook
89
+ ending up in endless recursion. */
90
+ thread_data_t* result = DATA_PTR(self);
91
+ if (!result)
92
+ rb_raise(rb_eRuntimeError, "This RubyProf::Thread instance has already been freed, likely because its profile has been freed.");
93
+
94
+ return result;
95
+ }
25
96
 
26
97
  /* ====== Thread Table ====== */
27
98
  /* The thread table is hash keyed on ruby thread_id that stores instances
@@ -33,6 +104,20 @@ threads_table_create()
33
104
  return st_init_numtable();
34
105
  }
35
106
 
107
+ static int
108
+ thread_table_free_iterator(st_data_t key, st_data_t value, st_data_t dummy)
109
+ {
110
+ thread_data_free((thread_data_t*)value);
111
+ return ST_CONTINUE;
112
+ }
113
+
114
+ void
115
+ threads_table_free(st_table *table)
116
+ {
117
+ st_foreach(table, thread_table_free_iterator, 0);
118
+ st_free_table(table);
119
+ }
120
+
36
121
  size_t
37
122
  threads_table_insert(prof_profile_t* profile, VALUE thread, thread_data_t *thread_data)
38
123
  {
@@ -53,7 +138,7 @@ threads_table_lookup(prof_profile_t* profile, VALUE thread_id)
53
138
  }
54
139
  else
55
140
  {
56
- result = thread_data_create(profile->measurer->measure());
141
+ result = thread_data_create();
57
142
  result->thread_id = thread_id;
58
143
 
59
144
  /* Insert the table */
@@ -62,52 +147,97 @@ threads_table_lookup(prof_profile_t* profile, VALUE thread_id)
62
147
  return result;
63
148
  }
64
149
 
65
- int
66
- free_thread_data(st_data_t key, st_data_t value, st_data_t dummy)
67
- {
68
- thread_data_free((thread_data_t*)value);
69
- return ST_CONTINUE;
70
- }
71
-
72
- void
73
- threads_table_free(st_table *table)
74
- {
75
- st_foreach(table, free_thread_data, 0);
76
- st_free_table(table);
77
- }
78
-
79
150
  thread_data_t *
80
151
  switch_thread(void* prof, VALUE thread_id)
81
152
  {
82
- prof_profile_t* profile = (prof_profile_t*)prof;
83
- prof_frame_t *frame = NULL;
84
- double wait_time = 0;
153
+ prof_profile_t* profile = (prof_profile_t*)prof;
154
+ double measurement = profile->measurer->measure();
85
155
 
86
156
  /* Get new thread information. */
87
157
  thread_data_t *thread_data = threads_table_lookup(profile, thread_id);
88
158
 
89
- /* How long has this thread been waiting? */
90
- wait_time = profile->measurement - thread_data->last_switch;
91
-
92
- thread_data->last_switch = profile->measurement; // XXXX a test that fails if this is 0
93
-
94
- /* Get the frame at the top of the stack. This may represent
95
- the current method (EVENT_LINE, EVENT_RETURN) or the
96
- previous method (EVENT_CALL).*/
97
- frame = stack_peek(thread_data->stack);
159
+ /* Get current frame for this thread */
160
+ prof_frame_t *frame = stack_peek(thread_data->stack);
98
161
 
162
+ /* Update the time this thread waited for another thread */
99
163
  if (frame)
100
- {
101
- frame->wait_time += wait_time;
164
+ {
165
+ frame->wait_time += measurement - frame->switch_time;
166
+ frame->switch_time = measurement;
102
167
  }
103
168
 
104
169
  /* Save on the last thread the time of the context switch
105
170
  and reset this thread's last context switch to 0.*/
106
171
  if (profile->last_thread_data)
107
- {
108
- profile->last_thread_data->last_switch = profile->measurement;
172
+ {
173
+ prof_frame_t *last_frame = stack_peek(profile->last_thread_data->stack);
174
+ if (last_frame)
175
+ last_frame->switch_time = measurement;
109
176
  }
110
177
 
111
178
  profile->last_thread_data = thread_data;
112
179
  return thread_data;
113
180
  }
181
+
182
+ static int
183
+ collect_methods(st_data_t key, st_data_t value, st_data_t result)
184
+ {
185
+ /* Called for each method stored in a thread's method table.
186
+ We want to store the method info information into an array.*/
187
+ VALUE methods = (VALUE) result;
188
+ prof_method_t *method = (prof_method_t *) value;
189
+ rb_ary_push(methods, prof_method_wrap(method));
190
+
191
+ return ST_CONTINUE;
192
+ }
193
+
194
+
195
+ /* call-seq:
196
+ id -> number
197
+
198
+ Returns the id of this thread. */
199
+ static VALUE
200
+ prof_thread_id(VALUE self)
201
+ {
202
+ thread_data_t* thread = prof_get_thread(self);
203
+ return thread->thread_id;
204
+ }
205
+
206
+ /* call-seq:
207
+ methods -> Array of MethodInfo
208
+
209
+ Returns an array of methods that were called from this
210
+ thread during program execution. */
211
+ static VALUE
212
+ prof_thread_methods(VALUE self)
213
+ {
214
+ thread_data_t* thread = prof_get_thread(self);
215
+ if (thread->methods == Qnil)
216
+ {
217
+ thread->methods = rb_ary_new();
218
+ st_foreach(thread->method_table, collect_methods, thread->methods);
219
+ }
220
+ return thread->methods;
221
+ }
222
+
223
+ /* call-seq:
224
+ method -> MethodInfo
225
+
226
+ Returns the top level method for this thread (ie, the starting
227
+ method). */
228
+ static VALUE
229
+ prof_thread_top_method(VALUE self)
230
+ {
231
+ thread_data_t* thread = prof_get_thread(self);
232
+ return prof_method_wrap(thread->top);
233
+ }
234
+
235
+ void rp_init_thread()
236
+ {
237
+ cRpThread = rb_define_class_under(mProf, "Thread", rb_cObject);
238
+ rb_undef_method(CLASS_OF(cRpThread), "new");
239
+
240
+ rb_define_method(cRpThread, "id", prof_thread_id, 0);
241
+ rb_define_method(cRpThread, "methods", prof_thread_methods, 0);
242
+ rb_define_method(cRpThread, "top_method", prof_thread_top_method, 0);
243
+ }
@@ -7,14 +7,20 @@
7
7
  /* Profiling information for a thread. */
8
8
  typedef struct
9
9
  {
10
+ VALUE object; /* Cache to wrapped object */
11
+ VALUE methods; /* Array of RubyProf::MethodInfo */
10
12
  VALUE thread_id; /* Thread id */
11
13
  st_table* method_table; /* Methods called in the thread */
12
- prof_stack_t* stack; /* Active methods */
13
- double last_switch; /* Point of last context switch */
14
+ prof_stack_t* stack; /* Stack of frames */
15
+ prof_method_t* top; /* The top method called in this thread */
14
16
  } thread_data_t;
15
17
 
18
+ void rp_init_thread();
16
19
  st_table * threads_table_create();
17
20
  thread_data_t* switch_thread(void* prof, VALUE thread_id);
18
21
  void threads_table_free(st_table *table);
22
+ VALUE prof_thread_wrap(thread_data_t *thread);
23
+ void prof_thread_mark(thread_data_t *thread);
24
+
19
25
 
20
26
  #endif //__RP_THREAD__
@@ -24,7 +24,6 @@
24
24
  */
25
25
 
26
26
  #include "ruby_prof.h"
27
- #include <stdio.h>
28
27
  #include <assert.h>
29
28
 
30
29
  VALUE mProf;
@@ -44,14 +43,6 @@ prof_get_profile(VALUE self)
44
43
  return (prof_profile_t*)RDATA(self)->data;
45
44
  }
46
45
 
47
- void
48
- method_key(prof_method_key_t* key, VALUE klass, ID mid)
49
- {
50
- key->klass = klass;
51
- key->mid = mid;
52
- key->key = (klass << 4) + (mid << 2);
53
- }
54
-
55
46
  /* support tracing ruby events from ruby-prof. useful for getting at
56
47
  what actually happens inside the ruby interpreter (and ruby-prof).
57
48
  set environment variable RUBY_PROF_TRACE to filename you want to
@@ -91,81 +82,85 @@ get_event_name(rb_event_flag_t event)
91
82
  }
92
83
  }
93
84
 
85
+ static prof_method_t*
86
+ create_method(rb_event_flag_t event, VALUE klass, ID mid, const char* source_file, int line)
87
+ {
88
+ /* Line numbers are not accurate for c method calls */
89
+ if (event == RUBY_EVENT_C_CALL)
90
+ {
91
+ line = 0;
92
+ source_file = NULL;
93
+ }
94
+
95
+ return prof_method_create(klass, mid, source_file, line);
96
+ }
97
+
94
98
 
95
99
  static prof_method_t*
96
100
  #ifdef RUBY_VM
97
- get_method(rb_event_flag_t event, VALUE klass, ID mid, st_table* method_table)
101
+ get_method(rb_event_flag_t event, VALUE klass, ID mid, thread_data_t* thread_data)
98
102
  # else
99
- get_method(rb_event_flag_t event, NODE *node, VALUE klass, ID mid, st_table* method_table)
103
+ get_method(rb_event_flag_t event, NODE *node, VALUE klass, ID mid, thread_data_t* thread_data)
100
104
  #endif
101
105
  {
102
106
  prof_method_key_t key;
103
107
  prof_method_t *method = NULL;
104
108
 
105
109
  method_key(&key, klass, mid);
106
- method = method_table_lookup(method_table, &key);
110
+ method = method_table_lookup(thread_data->method_table, &key);
107
111
 
108
112
  if (!method)
109
113
  {
110
- const char* source_file = rb_sourcefile();
114
+ const char* source_file = rb_sourcefile();
111
115
  int line = rb_sourceline();
112
116
 
113
- /* Line numbers are not accurate for c method calls */
114
- if (event == RUBY_EVENT_C_CALL)
115
- {
116
- line = 0;
117
- source_file = NULL;
118
- }
117
+ method = create_method(event, klass, mid, source_file, line);
118
+ method_table_insert(thread_data->method_table, method->key, method);
119
119
 
120
- method = prof_method_create(&key, source_file, line);
121
- method_table_insert(method_table, method->key, method);
120
+ /* Is this the first method added to the thread? */
121
+ if (!thread_data->top)
122
+ thread_data->top = method;
122
123
  }
123
124
  return method;
124
125
  }
125
126
 
126
- static void
127
- update_result(double total_time,
128
- prof_frame_t *parent_frame,
129
- prof_frame_t *frame)
130
- {
131
- double self_time = total_time - frame->child_time - frame->wait_time;
132
- prof_call_info_t *call_info = frame->call_info;
133
-
134
- /* Update information about the current method */
135
- call_info->called++;
136
- call_info->total_time += total_time;
137
- call_info->self_time += self_time;
138
- call_info->wait_time += frame->wait_time;
139
-
140
- /* Note where the current method was called from */
141
- if (parent_frame)
142
- call_info->line = parent_frame->line;
143
- }
144
-
145
127
  static prof_frame_t*
146
128
  pop_frame(prof_profile_t* profile, thread_data_t *thread_data)
147
129
  {
148
130
  prof_frame_t *frame = NULL;
149
131
  prof_frame_t* parent_frame = NULL;
132
+ prof_call_info_t *call_info;
133
+ double measurement = profile->measurer->measure();
150
134
  double total_time;
135
+ double self_time;
151
136
 
152
137
  frame = stack_pop(thread_data->stack); // only time it's called
138
+
153
139
  /* Frame can be null. This can happen if RubProf.start is called from
154
140
  a method that exits. And it can happen if an exception is raised
155
141
  in code that is being profiled and the stack unwinds (RubyProf is
156
142
  not notified of that by the ruby runtime. */
157
- if (frame == NULL) return NULL;
143
+ if (frame == NULL)
144
+ return NULL;
158
145
 
159
146
  /* Calculate the total time this method took */
160
- total_time = profile->measurement - frame->start_time;
147
+ total_time = measurement - frame->start_time;
148
+ self_time = total_time - frame->child_time - frame->wait_time;
149
+
150
+ /* Update information about the current method */
151
+ call_info = frame->call_info;
152
+ call_info->called++;
153
+ call_info->total_time += total_time;
154
+ call_info->self_time += self_time;
155
+ call_info->wait_time += frame->wait_time;
161
156
 
162
157
  parent_frame = stack_peek(thread_data->stack);
163
158
  if (parent_frame)
164
159
  {
165
- parent_frame->child_time += total_time;
160
+ parent_frame->child_time += total_time;
161
+ call_info->line = parent_frame->line;
166
162
  }
167
163
 
168
- update_result(total_time, parent_frame, frame); // only time it's called
169
164
  return frame;
170
165
  }
171
166
 
@@ -194,6 +189,42 @@ prof_pop_threads(prof_profile_t* profile)
194
189
  st_foreach(profile->threads_tbl, pop_frames, (st_data_t) profile);
195
190
  }
196
191
 
192
+ /* =========== Profiling ================= */
193
+ #ifdef RUBY_VM
194
+ static void
195
+ prof_trace(prof_profile_t* profile, rb_event_flag_t event, ID mid, VALUE klass, double measurement)
196
+ #else
197
+ static void
198
+ prof_trace(prof_profile_t* profile, rb_event_flag_t event, NODE *node, ID mid, VALUE klass, double measurement)
199
+ #endif
200
+ {
201
+ static VALUE last_thread_id = Qnil;
202
+
203
+ VALUE thread = rb_thread_current();
204
+ VALUE thread_id = rb_obj_id(thread);
205
+ const char* class_name = NULL;
206
+ const char* method_name = rb_id2name(mid);
207
+ const char* source_file = rb_sourcefile();
208
+ unsigned int source_line = rb_sourceline();
209
+
210
+ const char* event_name = get_event_name(event);
211
+
212
+ if (klass != 0)
213
+ klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
214
+
215
+ class_name = rb_class2name(klass);
216
+
217
+ if (last_thread_id != thread_id)
218
+ {
219
+ fprintf(trace_file, "\n");
220
+ }
221
+
222
+ fprintf(trace_file, "%2u:%2ums %-8s %s:%2d %s#%s\n",
223
+ (unsigned int) thread_id, (unsigned int) measurement, event_name, source_file, source_line, class_name, method_name);
224
+ fflush(trace_file);
225
+ last_thread_id = thread_id;
226
+ }
227
+
197
228
  #ifdef RUBY_VM
198
229
  static void
199
230
  prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
@@ -207,11 +238,12 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
207
238
  #else
208
239
  prof_profile_t* profile = prof_get_profile(data);
209
240
  #endif
210
-
211
- VALUE thread = Qnil;
241
+
242
+ VALUE thread = Qnil;
212
243
  VALUE thread_id = Qnil;
213
244
  thread_data_t* thread_data = NULL;
214
245
  prof_frame_t *frame = NULL;
246
+ double measurement;
215
247
 
216
248
  #ifdef RUBY_VM
217
249
  if (event != RUBY_EVENT_C_CALL && event != RUBY_EVENT_C_RETURN) {
@@ -221,34 +253,15 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
221
253
  #endif
222
254
 
223
255
  /* Get current measurement */
224
- profile->measurement = profile->measurer->measure();
256
+ measurement = profile->measurer->measure();
225
257
 
226
258
  if (trace_file != NULL)
227
259
  {
228
- static VALUE last_thread_id = Qnil;
229
-
230
- VALUE thread = rb_thread_current();
231
- VALUE thread_id = rb_obj_id(thread);
232
- const char* class_name = NULL;
233
- const char* method_name = rb_id2name(mid);
234
- const char* source_file = rb_sourcefile();
235
- unsigned int source_line = rb_sourceline();
236
-
237
- const char* event_name = get_event_name(event);
238
-
239
- if (klass != 0)
240
- klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
241
-
242
- class_name = rb_class2name(klass);
243
-
244
- if (last_thread_id != thread_id) {
245
- fprintf(trace_file, "\n");
246
- }
247
-
248
- fprintf(trace_file, "%2u:%2ums %-8s %s:%2d %s#%s\n",
249
- (unsigned int) thread_id, (unsigned int) profile->measurement, event_name, source_file, source_line, class_name, method_name);
250
- /* fflush(trace_file); */
251
- last_thread_id = thread_id;
260
+ #ifdef RUBY_VM
261
+ prof_trace(profile, event, mid, klass, measurement);
262
+ #else
263
+ prof_trace(profile, event, node, mid, klass, measurement);
264
+ #endif
252
265
  }
253
266
 
254
267
  /* Special case - skip any methods from the mProf
@@ -271,6 +284,8 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
271
284
  else
272
285
  thread_data = profile->last_thread_data;
273
286
 
287
+ /* Get the current frame for the current thread. */
288
+ frame = stack_peek(thread_data->stack);
274
289
 
275
290
  switch (event) {
276
291
  case RUBY_EVENT_LINE:
@@ -279,9 +294,6 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
279
294
  a new method is called, we know what line number it was
280
295
  called from. */
281
296
 
282
- /* Get the current frame for the current thread. */
283
- frame = stack_peek(thread_data->stack);
284
-
285
297
  if (frame)
286
298
  {
287
299
  frame->line = rb_sourceline();
@@ -298,20 +310,10 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
298
310
  prof_call_info_t *call_info = NULL;
299
311
  prof_method_t *method = NULL;
300
312
 
301
- /* Get the current frame for the current thread. */
302
- frame = stack_peek(thread_data->stack);
303
-
304
- /* Is this an include for a module? If so get the actual
305
- module class since we want to combine all profiling
306
- results for that module. */
307
-
308
- if (klass != 0)
309
- klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
310
-
311
313
  #ifdef RUBY_VM
312
- method = get_method(event, klass, mid, thread_data->method_table);
314
+ method = get_method(event, klass, mid, thread_data);
313
315
  #else
314
- method = get_method(event, node, klass, mid, thread_data->method_table);
316
+ method = get_method(event, node, klass, mid, thread_data);
315
317
  #endif
316
318
 
317
319
  if (!frame)
@@ -325,6 +327,8 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
325
327
 
326
328
  if (!call_info)
327
329
  {
330
+ /* This call info does not yet exist. So create it, then add
331
+ it to previous callinfo's children and to the current method .*/
328
332
  call_info = prof_call_info_create(method, frame->call_info);
329
333
  call_info_table_insert(frame->call_info->call_infos, method->key, call_info);
330
334
  prof_add_call_info(method->call_infos, call_info);
@@ -334,23 +338,20 @@ prof_event_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE kla
334
338
  /* Push a new frame onto the stack for a new c-call or ruby call (into a method) */
335
339
  frame = stack_push(thread_data->stack);
336
340
  frame->call_info = call_info;
337
- frame->start_time = profile->measurement;
338
- frame->wait_time = 0;
339
- frame->child_time = 0;
341
+ frame->call_info->depth = frame->depth;
342
+ frame->start_time = measurement;
340
343
  frame->line = rb_sourceline();
341
344
  break;
342
345
  }
343
346
  case RUBY_EVENT_RETURN:
344
347
  case RUBY_EVENT_C_RETURN:
345
348
  {
346
- frame = pop_frame(profile, thread_data);
349
+ pop_frame(profile, thread_data);
347
350
  break;
348
351
  }
349
352
  }
350
353
  }
351
354
 
352
-
353
- /* =========== Profiling ================= */
354
355
  void
355
356
  prof_install_hook(VALUE self)
356
357
  {
@@ -365,7 +366,7 @@ prof_install_hook(VALUE self)
365
366
  RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN
366
367
  | RUBY_EVENT_LINE);
367
368
 
368
- pCurrentProfile = prof_get_profile(self);
369
+ pCurrentProfile = prof_get_profile(self);
369
370
  #endif
370
371
 
371
372
  #if defined(TOGGLE_GC_STATS)
@@ -381,65 +382,53 @@ prof_remove_hook()
381
382
  #endif
382
383
 
383
384
  #ifndef RUBY_VM
384
- pCurrentProfile = NULL;
385
+ pCurrentProfile = NULL;
385
386
  #endif
386
387
 
387
388
  /* Now unregister from event */
388
389
  rb_remove_event_hook(prof_event_hook);
389
390
  }
390
391
 
391
-
392
392
  static int
393
- collect_methods(st_data_t key, st_data_t value, st_data_t result)
393
+ collect_threads(st_data_t key, st_data_t value, st_data_t result)
394
394
  {
395
- /* Called for each method stored in a thread's method table.
396
- We want to store the method info information into an array.*/
397
- VALUE methods = (VALUE) result;
398
- prof_method_t *method = (prof_method_t *) value;
399
- rb_ary_push(methods, prof_method_wrap(method));
400
-
401
- /* Wrap call info objects */
402
- prof_call_infos_wrap(method->call_infos);
403
-
395
+ thread_data_t* thread_data = (thread_data_t*) value;
396
+ VALUE threads_array = (VALUE) result;
397
+ rb_ary_push(threads_array, prof_thread_wrap(thread_data));
404
398
  return ST_CONTINUE;
405
399
  }
406
400
 
401
+ /* ======== Profile Class ====== */
407
402
  static int
408
- collect_threads(st_data_t key, st_data_t value, st_data_t result)
403
+ mark_threads(st_data_t key, st_data_t value, st_data_t result)
409
404
  {
410
- /* Although threads are keyed on an id, that is actually a
411
- pointer to the VALUE object of the thread. So its bogus.
412
- However, in thread_data is the real thread id stored
413
- as an int. */
414
- thread_data_t* thread_data = (thread_data_t*) value;
415
- VALUE threads_hash = (VALUE) result;
416
-
417
- VALUE methods = rb_ary_new();
418
-
419
- /* Now collect an array of all the called methods */
420
- st_table* method_table = thread_data->method_table;
421
- st_foreach(method_table, collect_methods, methods);
422
-
423
- /* Store the results in the threads hash keyed on the thread id. */
424
- rb_hash_aset(threads_hash, thread_data->thread_id, methods);
425
-
405
+ thread_data_t *thread = (thread_data_t *) value;
406
+ prof_thread_mark(thread);
426
407
  return ST_CONTINUE;
427
408
  }
428
409
 
429
- /* ======== Profile Class ====== */
430
410
  static void
431
411
  prof_mark(prof_profile_t *profile)
432
412
  {
433
- VALUE threads = profile->threads;
434
- rb_gc_mark(threads);
413
+ st_foreach(profile->threads_tbl, mark_threads, 0);
435
414
  }
436
415
 
416
+ /* Freeing the profile creates a cascade of freeing.
417
+ It fress the thread table, which frees its methods,
418
+ which frees its call infos. */
437
419
  static void
438
420
  prof_free(prof_profile_t *profile)
439
421
  {
440
- profile->threads = Qnil;
422
+ profile->last_thread_data = NULL;
423
+
424
+ threads_table_free(profile->threads_tbl);
425
+ profile->threads_tbl = NULL;
426
+
441
427
  st_free_table(profile->exclude_threads_tbl);
442
- profile->exclude_threads_tbl = NULL;
428
+ profile->exclude_threads_tbl = NULL;
429
+
430
+ xfree(profile->measurer);
431
+ profile->measurer = NULL;
443
432
 
444
433
  xfree(profile);
445
434
  }
@@ -450,7 +439,8 @@ prof_allocate(VALUE klass)
450
439
  VALUE result;
451
440
  prof_profile_t* profile;
452
441
  result = Data_Make_Struct(klass, prof_profile_t, prof_mark, prof_free, profile);
453
- profile->exclude_threads_tbl = threads_table_create();
442
+ profile->threads_tbl = threads_table_create();
443
+ profile->exclude_threads_tbl = threads_table_create();
454
444
  profile->running = Qfalse;
455
445
  return result;
456
446
  }
@@ -469,9 +459,9 @@ prof_initialize(int argc, VALUE *argv, VALUE self)
469
459
  {
470
460
  prof_profile_t* profile = prof_get_profile(self);
471
461
  VALUE mode;
472
- prof_measure_mode_t measurer;
462
+ prof_measure_mode_t measurer;
473
463
  VALUE exclude_threads;
474
- int i;
464
+ int i;
475
465
 
476
466
  switch (rb_scan_args(argc, argv, "02", &mode, &exclude_threads))
477
467
  {
@@ -479,32 +469,30 @@ prof_initialize(int argc, VALUE *argv, VALUE self)
479
469
  {
480
470
  measurer = MEASURE_WALL_TIME;
481
471
  exclude_threads = rb_ary_new();
482
- break;
472
+ break;
483
473
  }
484
474
  case 1:
485
475
  {
486
476
  measurer = (prof_measure_mode_t)NUM2INT(mode);
487
477
  exclude_threads = rb_ary_new();
488
- break;
478
+ break;
489
479
  }
490
480
  case 2:
491
481
  {
492
- Check_Type(exclude_threads, T_ARRAY);
482
+ Check_Type(exclude_threads, T_ARRAY);
493
483
  measurer = (prof_measure_mode_t)NUM2INT(mode);
494
- break;
484
+ break;
495
485
  }
496
486
  }
497
487
 
498
488
  profile->measurer = prof_get_measurer(measurer);
499
- profile->threads = rb_hash_new();
500
-
501
489
 
502
- for (i = 0; i < RARRAY_LEN(exclude_threads); i++)
503
- {
504
- VALUE thread = rb_ary_entry(exclude_threads, i);
505
- VALUE thread_id = rb_obj_id(thread);
506
- st_insert(profile->exclude_threads_tbl, thread_id, Qtrue);
507
- }
490
+ for (i = 0; i < RARRAY_LEN(exclude_threads); i++)
491
+ {
492
+ VALUE thread = rb_ary_entry(exclude_threads, i);
493
+ VALUE thread_id = rb_obj_id(thread);
494
+ st_insert(profile->exclude_threads_tbl, thread_id, Qtrue);
495
+ }
508
496
 
509
497
  return self;
510
498
  }
@@ -527,7 +515,7 @@ prof_running(VALUE self)
527
515
  static VALUE
528
516
  prof_start(VALUE self)
529
517
  {
530
- char* trace_file_name;
518
+ char* trace_file_name;
531
519
  prof_profile_t* profile = prof_get_profile(self);
532
520
 
533
521
  if (profile->running == Qtrue)
@@ -537,17 +525,23 @@ prof_start(VALUE self)
537
525
 
538
526
  profile->running = Qtrue;
539
527
  profile->last_thread_data = NULL;
540
- profile->threads_tbl = threads_table_create();
528
+
541
529
 
542
530
  /* open trace file if environment wants it */
543
531
  trace_file_name = getenv("RUBY_PROF_TRACE");
544
- if (trace_file_name != NULL) {
545
- if (0==strcmp(trace_file_name, "stdout")) {
532
+ if (trace_file_name != NULL)
533
+ {
534
+ if (strcmp(trace_file_name, "stdout") == 0)
535
+ {
546
536
  trace_file = stdout;
547
- } else if (0==strcmp(trace_file_name, "stderr")) {
537
+ }
538
+ else if (strcmp(trace_file_name, "stderr") == 0)
539
+ {
548
540
  trace_file = stderr;
549
- } else {
550
- trace_file = fopen(trace_file_name, "a");
541
+ }
542
+ else
543
+ {
544
+ trace_file = fopen(trace_file_name, "w");
551
545
  }
552
546
  }
553
547
 
@@ -609,23 +603,27 @@ prof_stop(VALUE self)
609
603
  {
610
604
  prof_profile_t* profile = prof_get_profile(self);
611
605
 
612
- /* get 'now' before prof emove hook because it calls GC.disable_stats
613
- which makes the call within prof_pop_threads of now return 0, which is wrong
614
- */
615
- profile->measurement = profile->measurer->measure();
616
606
  if (profile->running == Qfalse)
617
607
  {
618
608
  rb_raise(rb_eRuntimeError, "RubyProf.start was not yet called");
619
609
  }
620
610
 
611
+ prof_remove_hook();
612
+
621
613
  /* close trace file if open */
622
- if (trace_file != NULL) {
623
- if (trace_file!=stderr && trace_file!=stdout)
614
+ if (trace_file != NULL)
615
+ {
616
+ if (trace_file !=stderr && trace_file != stdout)
617
+ {
618
+ #ifdef _MSC_VER
619
+ _fcloseall();
620
+ #else
624
621
  fclose(trace_file);
622
+ #endif
623
+ }
625
624
  trace_file = NULL;
626
625
  }
627
626
 
628
- prof_remove_hook();
629
627
  prof_pop_threads(profile);
630
628
 
631
629
  /* Unset the last_thread_data (very important!)
@@ -633,13 +631,8 @@ prof_stop(VALUE self)
633
631
  profile->running = Qfalse;
634
632
  profile->last_thread_data = NULL;
635
633
 
636
- /* Save the result */
637
- st_foreach(profile->threads_tbl, collect_threads, profile->threads);
638
- threads_table_free(profile->threads_tbl);
639
- profile->threads_tbl = NULL;
640
-
641
- /* compute minimality of call_infos */
642
- rb_funcall(self, rb_intern("compute_minimality") , 0);
634
+ /* Post process result */
635
+ rb_funcall(self, rb_intern("post_process") , 0);
643
636
 
644
637
  return self;
645
638
  }
@@ -665,18 +658,17 @@ prof_profile(int argc, VALUE *argv, VALUE klass)
665
658
  }
666
659
 
667
660
  /* call-seq:
668
- threads -> Hash
661
+ threads -> Array of RubyProf::Thread
669
662
 
670
- Returns a hash table keyed on thread ID. For each thread id,
671
- the hash table stores another hash table that contains profiling
672
- information for each method called during the threads execution.
673
- That hash table is keyed on method name and contains
674
- RubyProf::MethodInfo objects. */
663
+ Returns an array of RubyProf::Thread instances that were executed
664
+ while the the program was being run. */
675
665
  static VALUE
676
666
  prof_threads(VALUE self)
677
667
  {
668
+ VALUE result = rb_ary_new();
678
669
  prof_profile_t* profile = prof_get_profile(self);
679
- return profile->threads;
670
+ st_foreach(profile->threads_tbl, collect_threads, result);
671
+ return result;
680
672
  }
681
673
 
682
674
  void Init_ruby_prof()
@@ -687,8 +679,9 @@ void Init_ruby_prof()
687
679
  rp_init_measure();
688
680
  rp_init_method_info();
689
681
  rp_init_call_info();
682
+ rp_init_thread();
690
683
 
691
- cProfile = rb_define_class_under(mProf, "Profile", rb_cObject);
684
+ cProfile = rb_define_class_under(mProf, "Profile", rb_cObject);
692
685
  rb_define_singleton_method(cProfile, "profile", prof_profile, -1);
693
686
  rb_define_alloc_func (cProfile, prof_allocate);
694
687
  rb_define_method(cProfile, "initialize", prof_initialize, -1);