ruby-prof 0.18.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +23 -0
  3. data/LICENSE +2 -2
  4. data/README.rdoc +1 -483
  5. data/Rakefile +3 -6
  6. data/bin/ruby-prof +65 -30
  7. data/ext/ruby_prof/extconf.rb +6 -38
  8. data/ext/ruby_prof/rp_allocation.c +292 -0
  9. data/ext/ruby_prof/rp_allocation.h +31 -0
  10. data/ext/ruby_prof/rp_call_info.c +137 -279
  11. data/ext/ruby_prof/rp_call_info.h +16 -34
  12. data/ext/ruby_prof/rp_measure_allocations.c +25 -49
  13. data/ext/ruby_prof/rp_measure_memory.c +21 -56
  14. data/ext/ruby_prof/rp_measure_process_time.c +28 -36
  15. data/ext/ruby_prof/rp_measure_wall_time.c +36 -19
  16. data/ext/ruby_prof/rp_measurement.c +236 -0
  17. data/ext/ruby_prof/rp_measurement.h +49 -0
  18. data/ext/ruby_prof/rp_method.c +395 -383
  19. data/ext/ruby_prof/rp_method.h +34 -39
  20. data/ext/ruby_prof/rp_profile.c +881 -0
  21. data/ext/ruby_prof/rp_profile.h +36 -0
  22. data/ext/ruby_prof/rp_stack.c +103 -80
  23. data/ext/ruby_prof/rp_stack.h +5 -12
  24. data/ext/ruby_prof/rp_thread.c +149 -88
  25. data/ext/ruby_prof/rp_thread.h +15 -6
  26. data/ext/ruby_prof/ruby_prof.c +11 -757
  27. data/ext/ruby_prof/ruby_prof.h +4 -47
  28. data/ext/ruby_prof/vc/ruby_prof.vcxproj +10 -8
  29. data/lib/ruby-prof.rb +2 -17
  30. data/lib/ruby-prof/assets/graph_printer.html.erb +356 -0
  31. data/lib/ruby-prof/call_info.rb +35 -93
  32. data/lib/ruby-prof/call_info_visitor.rb +19 -21
  33. data/lib/ruby-prof/compatibility.rb +37 -107
  34. data/lib/ruby-prof/exclude_common_methods.rb +198 -0
  35. data/lib/ruby-prof/measurement.rb +14 -0
  36. data/lib/ruby-prof/method_info.rb +52 -83
  37. data/lib/ruby-prof/printers/abstract_printer.rb +66 -52
  38. data/lib/ruby-prof/printers/call_info_printer.rb +13 -3
  39. data/lib/ruby-prof/printers/call_stack_printer.rb +32 -28
  40. data/lib/ruby-prof/printers/call_tree_printer.rb +20 -12
  41. data/lib/ruby-prof/printers/dot_printer.rb +5 -5
  42. data/lib/ruby-prof/printers/flat_printer.rb +6 -24
  43. data/lib/ruby-prof/printers/graph_html_printer.rb +7 -192
  44. data/lib/ruby-prof/printers/graph_printer.rb +13 -15
  45. data/lib/ruby-prof/printers/multi_printer.rb +66 -23
  46. data/lib/ruby-prof/profile.rb +10 -3
  47. data/lib/ruby-prof/rack.rb +0 -3
  48. data/lib/ruby-prof/thread.rb +12 -12
  49. data/lib/ruby-prof/version.rb +1 -1
  50. data/ruby-prof.gemspec +2 -2
  51. data/test/abstract_printer_test.rb +0 -27
  52. data/test/alias_test.rb +129 -0
  53. data/test/basic_test.rb +41 -40
  54. data/test/call_info_visitor_test.rb +3 -3
  55. data/test/dynamic_method_test.rb +0 -2
  56. data/test/line_number_test.rb +120 -39
  57. data/test/marshal_test.rb +119 -0
  58. data/test/measure_allocations.rb +30 -0
  59. data/test/measure_allocations_test.rb +371 -12
  60. data/test/measure_allocations_trace_test.rb +385 -0
  61. data/test/measure_memory_trace_test.rb +756 -0
  62. data/test/measure_process_time_test.rb +821 -33
  63. data/test/measure_times.rb +54 -0
  64. data/test/measure_wall_time_test.rb +349 -145
  65. data/test/multi_printer_test.rb +1 -34
  66. data/test/parser_timings.rb +24 -0
  67. data/test/pause_resume_test.rb +5 -5
  68. data/test/prime.rb +2 -0
  69. data/test/printer_call_tree_test.rb +31 -0
  70. data/test/printer_flat_test.rb +68 -0
  71. data/test/printer_graph_html_test.rb +60 -0
  72. data/test/printer_graph_test.rb +41 -0
  73. data/test/printers_test.rb +32 -166
  74. data/test/printing_recursive_graph_test.rb +26 -72
  75. data/test/recursive_test.rb +72 -77
  76. data/test/stack_printer_test.rb +2 -15
  77. data/test/start_stop_test.rb +22 -25
  78. data/test/test_helper.rb +5 -248
  79. data/test/thread_test.rb +11 -54
  80. data/test/unique_call_path_test.rb +16 -28
  81. data/test/yarv_test.rb +1 -0
  82. metadata +24 -34
  83. data/examples/flat.txt +0 -50
  84. data/examples/graph.dot +0 -84
  85. data/examples/graph.html +0 -823
  86. data/examples/graph.txt +0 -139
  87. data/examples/multi.flat.txt +0 -23
  88. data/examples/multi.graph.html +0 -760
  89. data/examples/multi.grind.dat +0 -114
  90. data/examples/multi.stack.html +0 -547
  91. data/examples/stack.html +0 -547
  92. data/ext/ruby_prof/rp_measure.c +0 -40
  93. data/ext/ruby_prof/rp_measure.h +0 -45
  94. data/ext/ruby_prof/rp_measure_cpu_time.c +0 -136
  95. data/ext/ruby_prof/rp_measure_gc_runs.c +0 -73
  96. data/ext/ruby_prof/rp_measure_gc_time.c +0 -60
  97. data/lib/ruby-prof/aggregate_call_info.rb +0 -76
  98. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +0 -83
  99. data/lib/ruby-prof/profile/exclude_common_methods.rb +0 -207
  100. data/lib/ruby-prof/profile/legacy_method_elimination.rb +0 -50
  101. data/test/aggregate_test.rb +0 -136
  102. data/test/block_test.rb +0 -74
  103. data/test/call_info_test.rb +0 -78
  104. data/test/fiber_test.rb +0 -79
  105. data/test/issue137_test.rb +0 -63
  106. data/test/measure_cpu_time_test.rb +0 -212
  107. data/test/measure_gc_runs_test.rb +0 -32
  108. data/test/measure_gc_time_test.rb +0 -36
  109. data/test/measure_memory_test.rb +0 -33
  110. data/test/method_elimination_test.rb +0 -84
  111. data/test/module_test.rb +0 -45
  112. data/test/stack_test.rb +0 -138
@@ -4,23 +4,18 @@
4
4
  #ifndef __RP_METHOD_INFO__
5
5
  #define __RP_METHOD_INFO__
6
6
 
7
- #include <ruby.h>
7
+ #include "ruby_prof.h"
8
+ #include "rp_measurement.h"
8
9
 
9
- extern VALUE cMethodInfo;
10
-
11
- /* A key used to identify each method */
12
- typedef struct
13
- {
14
- VALUE klass; /* The method's class. */
15
- ID mid; /* The method id. */
16
- st_index_t key; /* Cache calculated key */
17
- } prof_method_key_t;
10
+ extern VALUE cRpMethodInfo;
18
11
 
19
12
  /* Source relation bit offsets. */
20
13
  enum {
21
- kModuleIncludee = 0, /* Included module */
22
- kModuleSingleton, /* Singleton class of a module */
23
- kObjectSingleton /* Singleton class of an object */
14
+ kModuleIncludee = 0x1, /* Included in module */
15
+ kClassSingleton = 0x2, /* Singleton of a class */
16
+ kModuleSingleton = 0x4, /* Singleton of a module */
17
+ kObjectSingleton = 0x8, /* Singleton of an object */
18
+ kOtherSingleton = 0x10 /* Singleton of unkown object */
24
19
  };
25
20
 
26
21
  /* Forward declaration, see rp_call_info.h */
@@ -30,46 +25,46 @@ struct prof_call_infos_t;
30
25
  /* Excluded methods have no call_infos, source_klass, or source_file. */
31
26
  typedef struct
32
27
  {
33
- /* Hot */
34
-
35
- prof_method_key_t *key; /* Table key */
28
+ st_data_t key; /* Table key */
36
29
 
37
- struct prof_call_infos_t *call_infos; /* Call infos */
38
30
  int visits; /* Current visits on the stack */
31
+ bool excluded; /* Exclude from profile? */
39
32
 
40
- unsigned int excluded : 1; /* Exclude from profile? */
41
- unsigned int recursive : 1; /* Recursive (direct or mutual)? */
33
+ st_table* parent_call_infos; /* Call infos that call this method */
34
+ st_table* child_call_infos; /* Call infos that this method calls */
35
+ st_table* allocations_table; /* Tracks object allocations */
42
36
 
43
- /* Cold */
37
+ unsigned int klass_flags; /* Information about the type of class */
38
+ VALUE klass; /* Resolved klass */
39
+ VALUE klass_name; /* Resolved klass name for this method */
40
+ VALUE method_name; /* Resolved method name for this method */
44
41
 
45
42
  VALUE object; /* Cached ruby object */
46
- VALUE source_klass; /* Source class */
47
- const char *source_file; /* Source file */
48
- int line; /* Line number */
49
43
 
50
- unsigned int resolved : 1; /* Source resolved? */
51
- unsigned int relation : 3; /* Source relation bits */
44
+ bool root; /* Is this a root method */
45
+ bool recursive;
46
+ VALUE source_file; /* Source file */
47
+ int source_line; /* Line number */
48
+
49
+ prof_measurement_t *measurement;
52
50
  } prof_method_t;
53
51
 
54
52
  void rp_init_method_info(void);
55
53
 
56
- void method_key(prof_method_key_t* key, VALUE klass, ID mid);
54
+ st_data_t method_key(VALUE klass, VALUE msym);
57
55
 
58
- st_table * method_table_create();
59
- prof_method_t * method_table_lookup(st_table *table, const prof_method_key_t* key);
60
- size_t method_table_insert(st_table *table, const prof_method_key_t *key, prof_method_t *val);
56
+ st_table *method_table_create(void);
57
+ prof_method_t* prof_method_create_excluded(VALUE klass, VALUE msym);
58
+ prof_method_t *method_table_lookup(st_table *table, st_data_t key);
59
+ size_t method_table_insert(st_table *table, st_data_t key, prof_method_t *val);
61
60
  void method_table_free(st_table *table);
62
-
63
- prof_method_t* prof_method_create(VALUE klass, ID mid, const char* source_file, int line);
64
- prof_method_t* prof_method_create_excluded(VALUE klass, ID mid);
61
+ prof_method_t *prof_method_create(VALUE klass, VALUE msym, VALUE source_file, int source_line);
62
+ prof_method_t *prof_method_get(VALUE self);
65
63
 
66
64
  VALUE prof_method_wrap(prof_method_t *result);
67
- void prof_method_mark(prof_method_t *method);
68
-
69
- /* Setup infrastructure to use method keys as hash comparisons */
70
- int method_table_cmp(prof_method_key_t *key1, prof_method_key_t *key2);
71
- st_index_t method_table_hash(prof_method_key_t *key);
65
+ void prof_method_mark(void *data);
72
66
 
73
- extern struct st_hash_type type_method_hash;
67
+ VALUE resolve_klass(VALUE klass, unsigned int *klass_flags);
68
+ VALUE resolve_klass_name(VALUE klass, unsigned int* klass_flags);
74
69
 
75
- #endif
70
+ #endif //__RP_METHOD_INFO__
@@ -0,0 +1,881 @@
1
+ /* Copyright (C) 2005-2019 Shugo Maeda <shugo@ruby-lang.org> and Charlie Savage <cfis@savagexi.com>
2
+ Please see the LICENSE file for copyright and distribution information */
3
+
4
+ /* Document-class: RubyProf::Profile
5
+
6
+ The Profile class represents a single profiling run and provides the main API for using ruby-prof.
7
+ After creating a Profile instance, start profiling code by calling the Profile#start method. To finish profiling,
8
+ call Profile#stop. Once profiling is completed, the Profile instance contains the results.
9
+
10
+ profile = RubyProf::Profile.new
11
+ profile.start
12
+ ...
13
+ result = profile.stop
14
+
15
+ Alternatively, you can use the block syntax:
16
+
17
+ profile = RubyProf::Profile.profile do
18
+ ...
19
+ end
20
+ */
21
+
22
+ #include <assert.h>
23
+
24
+ #include "rp_allocation.h"
25
+ #include "rp_call_info.h"
26
+ #include "rp_profile.h"
27
+ #include "rp_method.h"
28
+
29
+ VALUE cProfile;
30
+
31
+ /* support tracing ruby events from ruby-prof. useful for getting at
32
+ what actually happens inside the ruby interpreter (and ruby-prof).
33
+ set environment variable RUBY_PROF_TRACE to filename you want to
34
+ find the trace in.
35
+ */
36
+ FILE* trace_file = NULL;
37
+
38
+ static int
39
+ excludes_method(st_data_t key, prof_profile_t* profile)
40
+ {
41
+ return (profile->exclude_methods_tbl &&
42
+ method_table_lookup(profile->exclude_methods_tbl, key) != NULL);
43
+ }
44
+
45
+ static prof_method_t*
46
+ create_method(prof_profile_t* profile, st_data_t key, VALUE klass, VALUE msym, VALUE source_file, int source_line)
47
+ {
48
+ prof_method_t* result = NULL;
49
+
50
+ if (excludes_method(key, profile))
51
+ {
52
+ /* We found a exclusion sentinel so propagate it into the thread's local hash table. */
53
+ /* TODO(nelgau): Is there a way to avoid this allocation completely so that all these
54
+ tables share the same exclusion method struct? The first attempt failed due to my
55
+ ignorance of the whims of the GC. */
56
+ result = prof_method_create_excluded(klass, msym);
57
+ }
58
+ else
59
+ {
60
+ result = prof_method_create(klass, msym, source_file, source_line);
61
+ }
62
+
63
+ /* Insert the newly created method, or the exlcusion sentinel. */
64
+ method_table_insert(profile->last_thread_data->method_table, result->key, result);
65
+
66
+ return result;
67
+ }
68
+
69
+ static const char *
70
+ get_event_name(rb_event_flag_t event)
71
+ {
72
+ switch (event) {
73
+ case RUBY_EVENT_LINE:
74
+ return "line";
75
+ case RUBY_EVENT_CLASS:
76
+ return "class";
77
+ case RUBY_EVENT_END:
78
+ return "end";
79
+ case RUBY_EVENT_CALL:
80
+ return "call";
81
+ case RUBY_EVENT_RETURN:
82
+ return "return";
83
+ case RUBY_EVENT_B_CALL:
84
+ return "b-call";
85
+ case RUBY_EVENT_B_RETURN:
86
+ return "b-return";
87
+ case RUBY_EVENT_C_CALL:
88
+ return "c-call";
89
+ case RUBY_EVENT_C_RETURN:
90
+ return "c-return";
91
+ case RUBY_EVENT_THREAD_BEGIN:
92
+ return "thread-begin";
93
+ case RUBY_EVENT_THREAD_END:
94
+ return "thread-end";
95
+ case RUBY_EVENT_FIBER_SWITCH:
96
+ return "fiber-switch";
97
+ case RUBY_EVENT_RAISE:
98
+ return "raise";
99
+ default:
100
+ return "unknown";
101
+ }
102
+ }
103
+
104
+ /* =========== Profiling ================= */
105
+ thread_data_t* check_fiber(prof_profile_t *profile, double measurement)
106
+ {
107
+ thread_data_t* result = NULL;
108
+
109
+ /* Get the current thread and fiber information. */
110
+ VALUE fiber = rb_fiber_current();
111
+
112
+ /* We need to switch the profiling context if we either had none before,
113
+ we don't merge fibers and the fiber ids differ, or the thread ids differ. */
114
+ if (profile->last_thread_data->fiber != fiber)
115
+ {
116
+ result = threads_table_lookup(profile, fiber);
117
+ if (!result)
118
+ {
119
+ result = threads_table_insert(profile, fiber);
120
+ }
121
+ switch_thread(profile, result, measurement);
122
+ }
123
+ else
124
+ {
125
+ result = profile->last_thread_data;
126
+ }
127
+ return result;
128
+ }
129
+
130
+ static void
131
+ prof_trace(prof_profile_t* profile, rb_trace_arg_t *trace_arg, double measurement)
132
+ {
133
+ static VALUE last_fiber = Qnil;
134
+ VALUE fiber = rb_fiber_current();
135
+
136
+ rb_event_flag_t event = rb_tracearg_event_flag(trace_arg);
137
+ const char* event_name = get_event_name(event);
138
+
139
+ VALUE source_file = rb_tracearg_path(trace_arg);
140
+ int source_line = FIX2INT(rb_tracearg_lineno(trace_arg));
141
+
142
+ #ifdef HAVE_RB_TRACEARG_CALLEE_ID
143
+ VALUE msym = rb_tracearg_callee_id(trace_arg);
144
+ #else
145
+ VALUE msym = rb_tracearg_method_id(trace_arg);
146
+ #endif
147
+
148
+ unsigned int klass_flags;
149
+ VALUE klass = rb_tracearg_defined_class(trace_arg);
150
+ VALUE resolved_klass = resolve_klass(klass, &klass_flags);
151
+ const char* class_name = "";
152
+
153
+ if (resolved_klass != Qnil)
154
+ class_name = rb_class2name(resolved_klass);
155
+
156
+ if (last_fiber != fiber)
157
+ {
158
+ fprintf(trace_file, "\n");
159
+ }
160
+
161
+ const char* method_name_char = (msym != Qnil ? rb_id2name(SYM2ID(msym)) : "");
162
+ const char* source_file_char = (source_file != Qnil ? StringValuePtr(source_file) : "");
163
+
164
+ fprintf(trace_file, "%2lu:%2f %-8s %s#%s %s:%2d\n",
165
+ FIX2ULONG(fiber), (double) measurement,
166
+ event_name, class_name, method_name_char, source_file_char, source_line);
167
+ fflush(trace_file);
168
+ last_fiber = fiber;
169
+ }
170
+
171
+ static void
172
+ prof_event_hook(VALUE trace_point, void* data)
173
+ {
174
+ prof_profile_t* profile = (prof_profile_t*)data;
175
+ thread_data_t* thread_data = NULL;
176
+ prof_frame_t *frame = NULL;
177
+ rb_trace_arg_t* trace_arg = rb_tracearg_from_tracepoint(trace_point);
178
+ double measurement = prof_measure(profile->measurer, trace_arg);
179
+ rb_event_flag_t event = rb_tracearg_event_flag(trace_arg);
180
+ VALUE self = rb_tracearg_self(trace_arg);
181
+
182
+ if (trace_file != NULL)
183
+ {
184
+ prof_trace(profile, trace_arg, measurement);
185
+ }
186
+
187
+ /* Special case - skip any methods from the mProf
188
+ module since they clutter the results but aren't important to them results. */
189
+ if (self == mProf)
190
+ return;
191
+
192
+ thread_data = check_fiber(profile, measurement);
193
+
194
+ if (!thread_data->trace)
195
+ return;
196
+
197
+ /* Get the current frame for the current thread. */
198
+ frame = thread_data->stack->ptr;
199
+
200
+ switch (event)
201
+ {
202
+ case RUBY_EVENT_LINE:
203
+ {
204
+ /* Keep track of the current line number in this method. When
205
+ a new method is called, we know what line number it was
206
+ called from. */
207
+ if (frame->call_info)
208
+ {
209
+ if (prof_frame_is_real(frame))
210
+ {
211
+ frame->source_file = rb_tracearg_path(trace_arg);
212
+ frame->source_line = FIX2INT(rb_tracearg_lineno(trace_arg));
213
+ }
214
+ break;
215
+ }
216
+
217
+ /* If we get here there was no frame, which means this is
218
+ the first method seen for this thread, so fall through
219
+ to below to create it. */
220
+ }
221
+ case RUBY_EVENT_CALL:
222
+ case RUBY_EVENT_C_CALL:
223
+ {
224
+ prof_frame_t* next_frame;
225
+ prof_call_info_t* call_info;
226
+ prof_method_t* method;
227
+
228
+ /* Get current measurement */
229
+ measurement = prof_measure(profile->measurer, trace_arg);
230
+
231
+ VALUE klass = rb_tracearg_defined_class(trace_arg);
232
+
233
+ /* Special case - skip any methods from the mProf
234
+ module or cProfile class since they clutter
235
+ the results but aren't important to them results. */
236
+ if (klass == cProfile)
237
+ return;
238
+
239
+ #ifdef HAVE_RB_TRACEARG_CALLEE_ID
240
+ VALUE msym = rb_tracearg_callee_id(trace_arg);
241
+ #else
242
+ VALUE msym = rb_tracearg_method_id(trace_arg);
243
+ #endif
244
+
245
+ st_data_t key = method_key(klass, msym);
246
+
247
+ method = method_table_lookup(thread_data->method_table, key);
248
+
249
+ if (!method)
250
+ {
251
+ VALUE source_file = (event != RUBY_EVENT_C_CALL ? rb_tracearg_path(trace_arg) : Qnil);
252
+ int source_line = (event != RUBY_EVENT_C_CALL ? FIX2INT(rb_tracearg_lineno(trace_arg)) : 0);
253
+ method = create_method(profile, key, klass, msym, source_file, source_line);
254
+ }
255
+
256
+ if (method->excluded)
257
+ {
258
+ prof_stack_pass(thread_data->stack);
259
+ break;
260
+ }
261
+
262
+ if (!frame->call_info)
263
+ {
264
+ method->root = true;
265
+ call_info = prof_call_info_create(method, NULL, method->source_file, method->source_line);
266
+ st_insert(method->parent_call_infos, (st_data_t)&key, (st_data_t)call_info);
267
+ }
268
+ else
269
+ {
270
+ call_info = call_info_table_lookup(method->parent_call_infos, frame->call_info->method->key);
271
+
272
+ if (!call_info)
273
+ {
274
+ /* This call info does not yet exist. So create it, then add
275
+ it to previous callinfo's children and to the current method .*/
276
+ call_info = prof_call_info_create(method, frame->call_info->method, frame->source_file, frame->source_line);
277
+ call_info_table_insert(method->parent_call_infos, frame->call_info->method->key, call_info);
278
+ call_info_table_insert(frame->call_info->method->child_call_infos, method->key, call_info);
279
+ }
280
+ }
281
+
282
+ /* Push a new frame onto the stack for a new c-call or ruby call (into a method) */
283
+ next_frame = prof_stack_push(thread_data->stack, call_info, measurement, RTEST(profile->paused));
284
+ next_frame->source_file = method->source_file;
285
+ next_frame->source_line = method->source_line;
286
+ break;
287
+ }
288
+ case RUBY_EVENT_RETURN:
289
+ case RUBY_EVENT_C_RETURN:
290
+ {
291
+ /* Get current measurement */
292
+ prof_stack_pop(thread_data->stack, measurement);
293
+ break;
294
+ }
295
+ case RUBY_INTERNAL_EVENT_NEWOBJ:
296
+ {
297
+ /* We want to assign the allocations lexically, not the execution context (otherwise all allocations will
298
+ show up under Class#new */
299
+ int source_line = FIX2INT(rb_tracearg_lineno(trace_arg));
300
+ VALUE source_file = rb_tracearg_path(trace_arg);
301
+
302
+ prof_method_t* method = prof_find_method(thread_data->stack, source_file, source_line);
303
+ if (method)
304
+ prof_allocate_increment(method, trace_arg);
305
+
306
+ break;
307
+ }
308
+ }
309
+ }
310
+
311
+ void
312
+ prof_install_hook(VALUE self)
313
+ {
314
+ prof_profile_t* profile = prof_get_profile(self);
315
+
316
+ VALUE event_tracepoint = rb_tracepoint_new(Qnil,
317
+ RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
318
+ RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |
319
+ RUBY_EVENT_LINE,
320
+ prof_event_hook, profile);
321
+ rb_ary_push(profile->tracepoints, event_tracepoint);
322
+
323
+ if (profile->measurer->track_allocations)
324
+ {
325
+ VALUE allocation_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, prof_event_hook, profile);
326
+ rb_ary_push(profile->tracepoints, allocation_tracepoint);
327
+ }
328
+
329
+ for (int i = 0; i < RARRAY_LEN(profile->tracepoints); i++)
330
+ {
331
+ rb_tracepoint_enable(rb_ary_entry(profile->tracepoints, i));
332
+ }
333
+ }
334
+
335
+ void
336
+ prof_remove_hook(VALUE self)
337
+ {
338
+ prof_profile_t* profile = prof_get_profile(self);
339
+
340
+ for (int i = 0; i < RARRAY_LEN(profile->tracepoints); i++)
341
+ {
342
+ rb_tracepoint_disable(rb_ary_entry(profile->tracepoints, i));
343
+ }
344
+ rb_ary_clear(profile->tracepoints);
345
+ }
346
+
347
+ prof_profile_t*
348
+ prof_get_profile(VALUE self)
349
+ {
350
+ /* Can't use Data_Get_Struct because that triggers the event hook
351
+ ending up in endless recursion. */
352
+ return DATA_PTR(self);
353
+ }
354
+
355
+ static int
356
+ collect_threads(st_data_t key, st_data_t value, st_data_t result)
357
+ {
358
+ thread_data_t* thread_data = (thread_data_t*) value;
359
+ if (thread_data->trace)
360
+ {
361
+ VALUE threads_array = (VALUE)result;
362
+ rb_ary_push(threads_array, prof_thread_wrap(thread_data));
363
+ }
364
+ return ST_CONTINUE;
365
+ }
366
+
367
+ /* ======== Profile Class ====== */
368
+ static int
369
+ mark_threads(st_data_t key, st_data_t value, st_data_t result)
370
+ {
371
+ thread_data_t *thread = (thread_data_t *) value;
372
+ prof_thread_mark(thread);
373
+ return ST_CONTINUE;
374
+ }
375
+
376
+ static int
377
+ mark_methods(st_data_t key, st_data_t value, st_data_t result)
378
+ {
379
+ prof_method_t *method = (prof_method_t *) value;
380
+ prof_method_mark(method);
381
+ return ST_CONTINUE;
382
+ }
383
+
384
+ static void
385
+ prof_mark(prof_profile_t *profile)
386
+ {
387
+ rb_gc_mark(profile->tracepoints);
388
+ st_foreach(profile->threads_tbl, mark_threads, 0);
389
+ st_foreach(profile->exclude_methods_tbl, mark_methods, 0);
390
+ }
391
+
392
+ /* Freeing the profile creates a cascade of freeing.
393
+ It fress the thread table, which frees its methods,
394
+ which frees its call infos. */
395
+ static void
396
+ prof_free(prof_profile_t *profile)
397
+ {
398
+ profile->last_thread_data = NULL;
399
+
400
+ threads_table_free(profile->threads_tbl);
401
+ profile->threads_tbl = NULL;
402
+
403
+ if (profile->exclude_threads_tbl)
404
+ {
405
+ st_free_table(profile->exclude_threads_tbl);
406
+ profile->exclude_threads_tbl = NULL;
407
+ }
408
+
409
+ if (profile->include_threads_tbl)
410
+ {
411
+ st_free_table(profile->include_threads_tbl);
412
+ profile->include_threads_tbl = NULL;
413
+ }
414
+
415
+ /* This table owns the excluded sentinels for now. */
416
+ method_table_free(profile->exclude_methods_tbl);
417
+ profile->exclude_methods_tbl = NULL;
418
+
419
+ xfree(profile->measurer);
420
+ profile->measurer = NULL;
421
+
422
+ xfree(profile);
423
+ }
424
+
425
+ static VALUE
426
+ prof_allocate(VALUE klass)
427
+ {
428
+ VALUE result;
429
+ prof_profile_t* profile;
430
+ result = Data_Make_Struct(klass, prof_profile_t, prof_mark, prof_free, profile);
431
+ profile->threads_tbl = threads_table_create();
432
+ profile->exclude_threads_tbl = NULL;
433
+ profile->include_threads_tbl = NULL;
434
+ profile->running = Qfalse;
435
+ profile->allow_exceptions = 0;
436
+ profile->exclude_methods_tbl = method_table_create();
437
+ profile->running = Qfalse;
438
+ profile->tracepoints = rb_ary_new();
439
+ return result;
440
+ }
441
+
442
+ static void
443
+ prof_exclude_common_methods(VALUE profile)
444
+ {
445
+ rb_funcall(profile, rb_intern("exclude_common_methods!"), 0);
446
+ }
447
+
448
+ static int
449
+ pop_frames(VALUE key, st_data_t value, st_data_t data)
450
+ {
451
+ thread_data_t* thread_data = (thread_data_t*)value;
452
+ prof_profile_t* profile = (prof_profile_t*)data;
453
+ double measurement = prof_measure(profile->measurer, NULL);
454
+
455
+ if (profile->last_thread_data->fiber != thread_data->fiber)
456
+ switch_thread(profile, thread_data, measurement);
457
+
458
+ while (prof_stack_pop(thread_data->stack, measurement));
459
+
460
+ return ST_CONTINUE;
461
+ }
462
+
463
+ static void
464
+ prof_stop_threads(prof_profile_t* profile)
465
+ {
466
+ st_foreach(profile->threads_tbl, pop_frames, (st_data_t)profile);
467
+ }
468
+
469
+ /* call-seq:
470
+ new()
471
+ new(options)
472
+
473
+ Returns a new profiler. Possible options for the options hash are:
474
+
475
+ measure_mode:: Measure mode. Specifies the profile measure mode.
476
+ If not specified, defaults to RubyProf::WALL_TIME.
477
+ exclude_threads:: Threads to exclude from the profiling results.
478
+ include_threads:: Focus profiling on only the given threads. This will ignore
479
+ all other threads.
480
+ allow_exceptions:: Whether to raise exceptions encountered during profiling,
481
+ or to suppress all exceptions during profiling
482
+ */
483
+ static VALUE
484
+ prof_initialize(int argc, VALUE *argv, VALUE self)
485
+ {
486
+ prof_profile_t* profile = prof_get_profile(self);
487
+ VALUE mode_or_options;
488
+ VALUE mode = Qnil;
489
+ VALUE exclude_threads = Qnil;
490
+ VALUE include_threads = Qnil;
491
+ VALUE exclude_common = Qnil;
492
+ VALUE allow_exceptions = Qfalse;
493
+ VALUE track_allocations = Qfalse;
494
+
495
+ int i;
496
+
497
+ switch (rb_scan_args(argc, argv, "02", &mode_or_options, &exclude_threads))
498
+ {
499
+ case 0:
500
+ break;
501
+ case 1:
502
+ if (FIXNUM_P(mode_or_options))
503
+ {
504
+ mode = mode_or_options;
505
+ }
506
+ else
507
+ {
508
+ Check_Type(mode_or_options, T_HASH);
509
+ mode = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("measure_mode")));
510
+ track_allocations = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("track_allocations")));
511
+ allow_exceptions = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("allow_exceptions")));
512
+ exclude_common = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("exclude_common")));
513
+ exclude_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("exclude_threads")));
514
+ include_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("include_threads")));
515
+ }
516
+ break;
517
+ case 2:
518
+ Check_Type(exclude_threads, T_ARRAY);
519
+ break;
520
+ }
521
+
522
+ if (mode == Qnil)
523
+ {
524
+ mode = INT2NUM(MEASURE_WALL_TIME);
525
+ }
526
+ else
527
+ {
528
+ Check_Type(mode, T_FIXNUM);
529
+ }
530
+ profile->measurer = prof_get_measurer(NUM2INT(mode), track_allocations == Qtrue);
531
+ profile->allow_exceptions = (allow_exceptions == Qtrue);
532
+
533
+ if (exclude_threads != Qnil)
534
+ {
535
+ Check_Type(exclude_threads, T_ARRAY);
536
+ assert(profile->exclude_threads_tbl == NULL);
537
+ profile->exclude_threads_tbl = threads_table_create();
538
+ for (i = 0; i < RARRAY_LEN(exclude_threads); i++)
539
+ {
540
+ VALUE thread = rb_ary_entry(exclude_threads, i);
541
+ st_insert(profile->exclude_threads_tbl, thread, Qtrue);
542
+ }
543
+ }
544
+
545
+ if (include_threads != Qnil)
546
+ {
547
+ Check_Type(include_threads, T_ARRAY);
548
+ assert(profile->include_threads_tbl == NULL);
549
+ profile->include_threads_tbl = threads_table_create();
550
+ for (i = 0; i < RARRAY_LEN(include_threads); i++)
551
+ {
552
+ VALUE thread = rb_ary_entry(include_threads, i);
553
+ st_insert(profile->include_threads_tbl, thread, Qtrue);
554
+ }
555
+ }
556
+
557
+ if (RTEST(exclude_common))
558
+ {
559
+ prof_exclude_common_methods(self);
560
+ }
561
+
562
+ return self;
563
+ }
564
+
565
+ /* call-seq:
566
+ paused? -> boolean
567
+
568
+ Returns whether a profile is currently paused.*/
569
+ static VALUE
570
+ prof_paused(VALUE self)
571
+ {
572
+ prof_profile_t* profile = prof_get_profile(self);
573
+ return profile->paused;
574
+ }
575
+
576
+ /* call-seq:
577
+ running? -> boolean
578
+
579
+ Returns whether a profile is currently running.*/
580
+ static VALUE
581
+ prof_running(VALUE self)
582
+ {
583
+ prof_profile_t* profile = prof_get_profile(self);
584
+ return profile->running;
585
+ }
586
+
587
+ /* call-seq:
588
+ mode -> measure_mode
589
+
590
+ Returns the measure mode used in this profile.*/
591
+ static VALUE
592
+ prof_profile_measure_mode(VALUE self)
593
+ {
594
+ prof_profile_t* profile = prof_get_profile(self);
595
+ return INT2NUM(profile->measurer->mode);
596
+ }
597
+
598
+ /* call-seq:
599
+ track_allocations -> boolean
600
+
601
+ Returns if object allocations were tracked in this profile.*/
602
+ static VALUE
603
+ prof_profile_track_allocations(VALUE self)
604
+ {
605
+ prof_profile_t* profile = prof_get_profile(self);
606
+ return profile->measurer->track_allocations ? Qtrue : Qfalse;
607
+ }
608
+
609
+ /* call-seq:
610
+ start -> self
611
+
612
+ Starts recording profile data.*/
613
+ static VALUE
614
+ prof_start(VALUE self)
615
+ {
616
+ char* trace_file_name;
617
+
618
+ prof_profile_t* profile = prof_get_profile(self);
619
+
620
+ if (profile->running == Qtrue)
621
+ {
622
+ rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
623
+ }
624
+
625
+ profile->running = Qtrue;
626
+ profile->paused = Qfalse;
627
+ profile->last_thread_data = threads_table_insert(profile, rb_fiber_current());
628
+
629
+ /* open trace file if environment wants it */
630
+ trace_file_name = getenv("RUBY_PROF_TRACE");
631
+ if (trace_file_name != NULL)
632
+ {
633
+ if (strcmp(trace_file_name, "stdout") == 0)
634
+ {
635
+ trace_file = stdout;
636
+ }
637
+ else if (strcmp(trace_file_name, "stderr") == 0)
638
+ {
639
+ trace_file = stderr;
640
+ }
641
+ else
642
+ {
643
+ trace_file = fopen(trace_file_name, "w");
644
+ }
645
+ }
646
+
647
+ prof_install_hook(self);
648
+ return self;
649
+ }
650
+
651
+ /* call-seq:
652
+ pause -> self
653
+
654
+ Pauses collecting profile data. */
655
+ static VALUE
656
+ prof_pause(VALUE self)
657
+ {
658
+ prof_profile_t* profile = prof_get_profile(self);
659
+ if (profile->running == Qfalse)
660
+ {
661
+ rb_raise(rb_eRuntimeError, "RubyProf is not running.");
662
+ }
663
+
664
+ if (profile->paused == Qfalse)
665
+ {
666
+ profile->paused = Qtrue;
667
+ profile->measurement_at_pause_resume = prof_measure(profile->measurer, NULL);
668
+ st_foreach(profile->threads_tbl, pause_thread, (st_data_t) profile);
669
+ }
670
+
671
+ return self;
672
+ }
673
+
674
+ /* call-seq:
675
+ resume -> self
676
+ resume(&block) -> self
677
+
678
+ Resumes recording profile data.*/
679
+ static VALUE
680
+ prof_resume(VALUE self)
681
+ {
682
+ prof_profile_t* profile = prof_get_profile(self);
683
+ if (profile->running == Qfalse)
684
+ {
685
+ rb_raise(rb_eRuntimeError, "RubyProf is not running.");
686
+ }
687
+
688
+ if (profile->paused == Qtrue)
689
+ {
690
+ profile->paused = Qfalse;
691
+ profile->measurement_at_pause_resume = prof_measure(profile->measurer, NULL);
692
+ st_foreach(profile->threads_tbl, unpause_thread, (st_data_t) profile);
693
+ }
694
+
695
+ return rb_block_given_p() ? rb_ensure(rb_yield, self, prof_pause, self) : self;
696
+ }
697
+
698
+ /* call-seq:
699
+ stop -> self
700
+
701
+ Stops collecting profile data.*/
702
+ static VALUE
703
+ prof_stop(VALUE self)
704
+ {
705
+ prof_profile_t* profile = prof_get_profile(self);
706
+
707
+ if (profile->running == Qfalse)
708
+ {
709
+ rb_raise(rb_eRuntimeError, "RubyProf.start was not yet called");
710
+ }
711
+
712
+ prof_remove_hook(self);
713
+
714
+ /* close trace file if open */
715
+ if (trace_file != NULL)
716
+ {
717
+ if (trace_file !=stderr && trace_file != stdout)
718
+ {
719
+ #ifdef _MSC_VER
720
+ _fcloseall();
721
+ #else
722
+ fclose(trace_file);
723
+ #endif
724
+ }
725
+ trace_file = NULL;
726
+ }
727
+
728
+ prof_stop_threads(profile);
729
+
730
+ /* Unset the last_thread_data (very important!)
731
+ and the threads table */
732
+ profile->running = profile->paused = Qfalse;
733
+ profile->last_thread_data = NULL;
734
+
735
+ return self;
736
+ }
737
+
738
+ /* call-seq:
739
+ threads -> Array of RubyProf::Thread
740
+
741
+ Returns an array of RubyProf::Thread instances that were profiled. */
742
+ static VALUE
743
+ prof_threads(VALUE self)
744
+ {
745
+ VALUE result = rb_ary_new();
746
+ prof_profile_t* profile = prof_get_profile(self);
747
+ st_foreach(profile->threads_tbl, collect_threads, result);
748
+ return result;
749
+ }
750
+
751
+ /* Document-method: RubyProf::Profile#Profile
752
+ call-seq:
753
+ profile(&block) -> self
754
+
755
+ Profiles the specified block.
756
+
757
+ profile = RubyProf::Profile.new
758
+ profile.profile do
759
+ ..
760
+ end
761
+ */
762
+ static VALUE
763
+ prof_profile_object(VALUE self)
764
+ {
765
+ int result;
766
+ prof_profile_t* profile = prof_get_profile(self);
767
+
768
+ if (!rb_block_given_p())
769
+ {
770
+ rb_raise(rb_eArgError, "A block must be provided to the profile method.");
771
+ }
772
+
773
+ prof_start(self);
774
+ rb_protect(rb_yield, self, &result);
775
+ self = prof_stop(self);
776
+
777
+ if (profile->allow_exceptions && result != 0)
778
+ {
779
+ rb_jump_tag(result);
780
+ }
781
+
782
+ return self;
783
+
784
+ }
785
+
786
+ /* Document-method: RubyProf::Profile::Profile
787
+ call-seq:
788
+ profile(&block) -> RubyProf::Profile
789
+ profile(options, &block) -> RubyProf::Profile
790
+
791
+ Profiles the specified block and returns a RubyProf::Profile
792
+ object. Arguments are passed to Profile initialize method.
793
+
794
+ profile = RubyProf::Profile.profile do
795
+ ..
796
+ end
797
+ */
798
+ static VALUE
799
+ prof_profile_class(int argc, VALUE *argv, VALUE klass)
800
+ {
801
+ return prof_profile_object(rb_class_new_instance(argc, argv, cProfile));
802
+ }
803
+
804
+ /* call-seq:
805
+ exclude_method!(module, method_name) -> self
806
+
807
+ Excludes the method from profiling results.
808
+ */
809
+ static VALUE
810
+ prof_exclude_method(VALUE self, VALUE klass, VALUE msym)
811
+ {
812
+ prof_profile_t* profile = prof_get_profile(self);
813
+
814
+ st_data_t key = method_key(klass, msym);
815
+ prof_method_t *method;
816
+
817
+ if (profile->running == Qtrue)
818
+ {
819
+ rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
820
+ }
821
+
822
+ method = method_table_lookup(profile->exclude_methods_tbl, key);
823
+
824
+ if (!method)
825
+ {
826
+ method = prof_method_create_excluded(klass, msym);
827
+ method_table_insert(profile->exclude_methods_tbl, method->key, method);
828
+ }
829
+
830
+ return self;
831
+ }
832
+
833
+ /* :nodoc: */
834
+ VALUE prof_profile_dump(VALUE self)
835
+ {
836
+ VALUE result = rb_hash_new();
837
+ rb_hash_aset(result, ID2SYM(rb_intern("threads")), prof_threads(self));
838
+ return result;
839
+ }
840
+
841
+ /* :nodoc: */
842
+ VALUE prof_profile_load(VALUE self, VALUE data)
843
+ {
844
+ prof_profile_t* profile = prof_get_profile(self);
845
+
846
+ VALUE threads = rb_hash_aref(data, ID2SYM(rb_intern("threads")));
847
+ for (int i = 0; i < rb_array_len(threads); i++)
848
+ {
849
+ VALUE thread = rb_ary_entry(threads, i);
850
+ thread_data_t* thread_data = DATA_PTR(thread);
851
+ st_insert(profile->threads_tbl, (st_data_t)thread_data->fiber_id, (st_data_t)thread_data);
852
+ }
853
+
854
+ return data;
855
+ }
856
+
857
+ void rp_init_profile(void)
858
+ {
859
+ mProf = rb_define_module("RubyProf");
860
+
861
+ cProfile = rb_define_class_under(mProf, "Profile", rb_cObject);
862
+ rb_define_alloc_func(cProfile, prof_allocate);
863
+
864
+ rb_define_singleton_method(cProfile, "profile", prof_profile_class, -1);
865
+ rb_define_method(cProfile, "initialize", prof_initialize, -1);
866
+ rb_define_method(cProfile, "start", prof_start, 0);
867
+ rb_define_method(cProfile, "stop", prof_stop, 0);
868
+ rb_define_method(cProfile, "resume", prof_resume, 0);
869
+ rb_define_method(cProfile, "pause", prof_pause, 0);
870
+ rb_define_method(cProfile, "running?", prof_running, 0);
871
+ rb_define_method(cProfile, "paused?", prof_paused, 0);
872
+ rb_define_method(cProfile, "threads", prof_threads, 0);
873
+ rb_define_method(cProfile, "exclude_method!", prof_exclude_method, 2);
874
+ rb_define_method(cProfile, "profile", prof_profile_object, 0);
875
+
876
+ rb_define_method(cProfile, "measure_mode", prof_profile_measure_mode, 0);
877
+ rb_define_method(cProfile, "track_allocations?", prof_profile_track_allocations, 0);
878
+
879
+ rb_define_method(cProfile, "_dump_data", prof_profile_dump, 0);
880
+ rb_define_method(cProfile, "_load_data", prof_profile_load, 1);
881
+ }