ruby-prof 1.4.4-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +608 -0
  3. data/LICENSE +25 -0
  4. data/README.md +5 -0
  5. data/Rakefile +98 -0
  6. data/bin/ruby-prof +328 -0
  7. data/bin/ruby-prof-check-trace +45 -0
  8. data/ext/ruby_prof/extconf.rb +22 -0
  9. data/ext/ruby_prof/rp_aggregate_call_tree.c +59 -0
  10. data/ext/ruby_prof/rp_aggregate_call_tree.h +13 -0
  11. data/ext/ruby_prof/rp_allocation.c +287 -0
  12. data/ext/ruby_prof/rp_allocation.h +31 -0
  13. data/ext/ruby_prof/rp_call_tree.c +367 -0
  14. data/ext/ruby_prof/rp_call_tree.h +43 -0
  15. data/ext/ruby_prof/rp_call_trees.c +288 -0
  16. data/ext/ruby_prof/rp_call_trees.h +28 -0
  17. data/ext/ruby_prof/rp_measure_allocations.c +47 -0
  18. data/ext/ruby_prof/rp_measure_memory.c +46 -0
  19. data/ext/ruby_prof/rp_measure_process_time.c +66 -0
  20. data/ext/ruby_prof/rp_measure_wall_time.c +64 -0
  21. data/ext/ruby_prof/rp_measurement.c +237 -0
  22. data/ext/ruby_prof/rp_measurement.h +50 -0
  23. data/ext/ruby_prof/rp_method.c +491 -0
  24. data/ext/ruby_prof/rp_method.h +62 -0
  25. data/ext/ruby_prof/rp_profile.c +915 -0
  26. data/ext/ruby_prof/rp_profile.h +35 -0
  27. data/ext/ruby_prof/rp_stack.c +212 -0
  28. data/ext/ruby_prof/rp_stack.h +53 -0
  29. data/ext/ruby_prof/rp_thread.c +362 -0
  30. data/ext/ruby_prof/rp_thread.h +39 -0
  31. data/ext/ruby_prof/ruby_prof.c +52 -0
  32. data/ext/ruby_prof/ruby_prof.h +26 -0
  33. data/ext/ruby_prof/vc/ruby_prof.sln +39 -0
  34. data/ext/ruby_prof/vc/ruby_prof.vcxproj +160 -0
  35. data/lib/3.1/ruby_prof.so +0 -0
  36. data/lib/ruby-prof/assets/call_stack_printer.html.erb +711 -0
  37. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  38. data/lib/ruby-prof/assets/graph_printer.html.erb +355 -0
  39. data/lib/ruby-prof/call_tree.rb +57 -0
  40. data/lib/ruby-prof/call_tree_visitor.rb +36 -0
  41. data/lib/ruby-prof/compatibility.rb +99 -0
  42. data/lib/ruby-prof/exclude_common_methods.rb +198 -0
  43. data/lib/ruby-prof/measurement.rb +17 -0
  44. data/lib/ruby-prof/method_info.rb +78 -0
  45. data/lib/ruby-prof/printers/abstract_printer.rb +137 -0
  46. data/lib/ruby-prof/printers/call_info_printer.rb +53 -0
  47. data/lib/ruby-prof/printers/call_stack_printer.rb +180 -0
  48. data/lib/ruby-prof/printers/call_tree_printer.rb +147 -0
  49. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  50. data/lib/ruby-prof/printers/flat_printer.rb +53 -0
  51. data/lib/ruby-prof/printers/graph_html_printer.rb +63 -0
  52. data/lib/ruby-prof/printers/graph_printer.rb +113 -0
  53. data/lib/ruby-prof/printers/multi_printer.rb +127 -0
  54. data/lib/ruby-prof/profile.rb +37 -0
  55. data/lib/ruby-prof/rack.rb +95 -0
  56. data/lib/ruby-prof/task.rb +147 -0
  57. data/lib/ruby-prof/thread.rb +20 -0
  58. data/lib/ruby-prof/version.rb +3 -0
  59. data/lib/ruby-prof.rb +52 -0
  60. data/lib/unprof.rb +10 -0
  61. data/ruby-prof.gemspec +64 -0
  62. data/test/abstract_printer_test.rb +26 -0
  63. data/test/alias_test.rb +122 -0
  64. data/test/basic_test.rb +43 -0
  65. data/test/call_tree_visitor_test.rb +32 -0
  66. data/test/call_trees_test.rb +66 -0
  67. data/test/duplicate_names_test.rb +32 -0
  68. data/test/dynamic_method_test.rb +67 -0
  69. data/test/enumerable_test.rb +21 -0
  70. data/test/exceptions_test.rb +24 -0
  71. data/test/exclude_methods_test.rb +151 -0
  72. data/test/exclude_threads_test.rb +53 -0
  73. data/test/fiber_test.rb +129 -0
  74. data/test/gc_test.rb +100 -0
  75. data/test/inverse_call_tree_test.rb +175 -0
  76. data/test/line_number_test.rb +158 -0
  77. data/test/marshal_test.rb +145 -0
  78. data/test/measure_allocations.rb +26 -0
  79. data/test/measure_allocations_test.rb +333 -0
  80. data/test/measure_memory_test.rb +688 -0
  81. data/test/measure_process_time_test.rb +1614 -0
  82. data/test/measure_times.rb +56 -0
  83. data/test/measure_wall_time_test.rb +426 -0
  84. data/test/multi_printer_test.rb +71 -0
  85. data/test/no_method_class_test.rb +15 -0
  86. data/test/pause_resume_test.rb +175 -0
  87. data/test/prime.rb +54 -0
  88. data/test/prime_script.rb +6 -0
  89. data/test/printer_call_stack_test.rb +27 -0
  90. data/test/printer_call_tree_test.rb +30 -0
  91. data/test/printer_flat_test.rb +99 -0
  92. data/test/printer_graph_html_test.rb +59 -0
  93. data/test/printer_graph_test.rb +40 -0
  94. data/test/printers_test.rb +141 -0
  95. data/test/printing_recursive_graph_test.rb +81 -0
  96. data/test/profile_test.rb +16 -0
  97. data/test/rack_test.rb +93 -0
  98. data/test/recursive_test.rb +430 -0
  99. data/test/singleton_test.rb +38 -0
  100. data/test/stack_printer_test.rb +64 -0
  101. data/test/start_stop_test.rb +109 -0
  102. data/test/test_helper.rb +13 -0
  103. data/test/thread_test.rb +144 -0
  104. data/test/unique_call_path_test.rb +136 -0
  105. data/test/yarv_test.rb +60 -0
  106. metadata +187 -0
@@ -0,0 +1,915 @@
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_trees.h"
26
+ #include "rp_call_tree.h"
27
+ #include "rp_profile.h"
28
+ #include "rp_method.h"
29
+
30
+ VALUE cProfile;
31
+
32
+ /* support tracing ruby events from ruby-prof. useful for getting at
33
+ what actually happens inside the ruby interpreter (and ruby-prof).
34
+ set environment variable RUBY_PROF_TRACE to filename you want to
35
+ find the trace in.
36
+ */
37
+ FILE* trace_file = NULL;
38
+
39
+ static const char* get_event_name(rb_event_flag_t event)
40
+ {
41
+ switch (event) {
42
+ case RUBY_EVENT_LINE:
43
+ return "line";
44
+ case RUBY_EVENT_CLASS:
45
+ return "class";
46
+ case RUBY_EVENT_END:
47
+ return "end";
48
+ case RUBY_EVENT_CALL:
49
+ return "call";
50
+ case RUBY_EVENT_RETURN:
51
+ return "return";
52
+ case RUBY_EVENT_B_CALL:
53
+ return "b-call";
54
+ case RUBY_EVENT_B_RETURN:
55
+ return "b-return";
56
+ case RUBY_EVENT_C_CALL:
57
+ return "c-call";
58
+ case RUBY_EVENT_C_RETURN:
59
+ return "c-return";
60
+ case RUBY_EVENT_THREAD_BEGIN:
61
+ return "thread-begin";
62
+ case RUBY_EVENT_THREAD_END:
63
+ return "thread-end";
64
+ case RUBY_EVENT_FIBER_SWITCH:
65
+ return "fiber-switch";
66
+ case RUBY_EVENT_RAISE:
67
+ return "raise";
68
+ case RUBY_INTERNAL_EVENT_NEWOBJ:
69
+ return "newobj";
70
+ default:
71
+ return "unknown";
72
+ }
73
+ }
74
+
75
+ thread_data_t* check_fiber(prof_profile_t* profile, double measurement)
76
+ {
77
+ thread_data_t* result = NULL;
78
+
79
+ // Get the current fiber
80
+ VALUE fiber = rb_fiber_current();
81
+
82
+ /* We need to switch the profiling context if we either had none before,
83
+ we don't merge fibers and the fiber ids differ, or the thread ids differ. */
84
+ if (profile->last_thread_data->fiber != fiber)
85
+ {
86
+ result = threads_table_lookup(profile, fiber);
87
+ if (!result)
88
+ {
89
+ result = threads_table_insert(profile, fiber);
90
+ }
91
+ switch_thread(profile, result, measurement);
92
+ }
93
+ else
94
+ {
95
+ result = profile->last_thread_data;
96
+ }
97
+ return result;
98
+ }
99
+
100
+ static int excludes_method(st_data_t key, prof_profile_t* profile)
101
+ {
102
+ return (profile->exclude_methods_tbl &&
103
+ method_table_lookup(profile->exclude_methods_tbl, key) != NULL);
104
+ }
105
+
106
+ static prof_method_t* create_method(VALUE profile, st_data_t key, VALUE klass, VALUE msym, VALUE source_file, int source_line)
107
+ {
108
+ prof_method_t* result = prof_method_create(profile, klass, msym, source_file, source_line);
109
+
110
+ prof_profile_t* profile_t = prof_get_profile(profile);
111
+ method_table_insert(profile_t->last_thread_data->method_table, result->key, result);
112
+
113
+ return result;
114
+ }
115
+
116
+ static prof_method_t* check_parent_method(VALUE profile, thread_data_t* thread_data)
117
+ {
118
+ VALUE msym = ID2SYM(rb_intern("_inserted_parent_"));
119
+ st_data_t key = method_key(cProfile, msym);
120
+
121
+ prof_method_t* result = method_table_lookup(thread_data->method_table, key);
122
+
123
+ if (!result)
124
+ {
125
+ result = create_method(profile, key, cProfile, msym, Qnil, 0);
126
+ }
127
+
128
+ return result;
129
+ }
130
+
131
+ prof_method_t* check_method(VALUE profile, rb_trace_arg_t* trace_arg, rb_event_flag_t event, thread_data_t* thread_data)
132
+ {
133
+ VALUE klass = rb_tracearg_defined_class(trace_arg);
134
+
135
+ /* Special case - skip any methods from the mProf
136
+ module or cProfile class since they clutter
137
+ the results but aren't important to them results. */
138
+ if (klass == cProfile)
139
+ return NULL;
140
+
141
+ VALUE msym = rb_tracearg_callee_id(trace_arg);
142
+
143
+ st_data_t key = method_key(klass, msym);
144
+
145
+ prof_profile_t* profile_t = prof_get_profile(profile);
146
+ if (excludes_method(key, profile_t))
147
+ return NULL;
148
+
149
+ prof_method_t* result = method_table_lookup(thread_data->method_table, key);
150
+
151
+ if (!result)
152
+ {
153
+ VALUE source_file = (event != RUBY_EVENT_C_CALL ? rb_tracearg_path(trace_arg) : Qnil);
154
+ int source_line = (event != RUBY_EVENT_C_CALL ? FIX2INT(rb_tracearg_lineno(trace_arg)) : 0);
155
+ result = create_method(profile, key, klass, msym, source_file, source_line);
156
+ }
157
+
158
+ return result;
159
+ }
160
+
161
+ /* =========== Profiling ================= */
162
+ static void prof_trace(prof_profile_t* profile, rb_trace_arg_t* trace_arg, double measurement)
163
+ {
164
+ static VALUE last_fiber = Qnil;
165
+ VALUE fiber = rb_fiber_current();
166
+
167
+ rb_event_flag_t event = rb_tracearg_event_flag(trace_arg);
168
+ const char* event_name = get_event_name(event);
169
+
170
+ VALUE source_file = rb_tracearg_path(trace_arg);
171
+ int source_line = FIX2INT(rb_tracearg_lineno(trace_arg));
172
+
173
+ VALUE msym = rb_tracearg_callee_id(trace_arg);
174
+
175
+ unsigned int klass_flags;
176
+ VALUE klass = rb_tracearg_defined_class(trace_arg);
177
+ VALUE resolved_klass = resolve_klass(klass, &klass_flags);
178
+ const char* class_name = "";
179
+
180
+ if (resolved_klass != Qnil)
181
+ class_name = rb_class2name(resolved_klass);
182
+
183
+ if (last_fiber != fiber)
184
+ {
185
+ fprintf(trace_file, "\n");
186
+ }
187
+
188
+ const char* method_name_char = (msym != Qnil ? rb_id2name(SYM2ID(msym)) : "");
189
+ const char* source_file_char = (source_file != Qnil ? StringValuePtr(source_file) : "");
190
+
191
+ fprintf(trace_file, "%2lu:%2f %-8s %s#%s %s:%2d\n",
192
+ FIX2ULONG(fiber), (double)measurement,
193
+ event_name, class_name, method_name_char, source_file_char, source_line);
194
+ fflush(trace_file);
195
+ last_fiber = fiber;
196
+ }
197
+
198
+ static void prof_event_hook(VALUE trace_point, void* data)
199
+ {
200
+ VALUE profile = (VALUE)data;
201
+ prof_profile_t* profile_t = prof_get_profile(profile);
202
+
203
+ rb_trace_arg_t* trace_arg = rb_tracearg_from_tracepoint(trace_point);
204
+ double measurement = prof_measure(profile_t->measurer, trace_arg);
205
+ rb_event_flag_t event = rb_tracearg_event_flag(trace_arg);
206
+ VALUE self = rb_tracearg_self(trace_arg);
207
+
208
+ if (trace_file != NULL)
209
+ {
210
+ prof_trace(profile_t, trace_arg, measurement);
211
+ }
212
+
213
+ /* Special case - skip any methods from the mProf
214
+ module since they clutter the results but aren't important. */
215
+ if (self == mProf)
216
+ return;
217
+
218
+ thread_data_t* thread_data = check_fiber(profile_t, measurement);
219
+
220
+ if (!thread_data->trace)
221
+ return;
222
+
223
+ switch (event)
224
+ {
225
+ case RUBY_EVENT_LINE:
226
+ {
227
+ prof_frame_t* frame = prof_frame_current(thread_data->stack);
228
+
229
+ /* If there is no frame then this is either the first method being profiled or we have climbed the
230
+ call stack higher than where we started. */
231
+ if (!frame)
232
+ {
233
+ prof_method_t* method = check_method(profile, trace_arg, event, thread_data);
234
+
235
+ if (!method)
236
+ break;
237
+
238
+ prof_call_tree_t* call_tree = prof_call_tree_create(method, NULL, method->source_file, method->source_line);
239
+ prof_add_call_tree(method->call_trees, call_tree);
240
+
241
+ // We have climbed higher in the stack then where we started
242
+ if (thread_data->call_tree)
243
+ {
244
+ prof_call_tree_add_parent(thread_data->call_tree, call_tree);
245
+ frame = prof_frame_unshift(thread_data->stack, call_tree, thread_data->call_tree, measurement);
246
+ }
247
+ // This is the first method to be profiled
248
+ else
249
+ {
250
+ frame = prof_frame_push(thread_data->stack, call_tree, measurement, RTEST(profile_t->paused));
251
+ }
252
+
253
+ thread_data->call_tree = call_tree;
254
+ }
255
+
256
+ frame->source_file = rb_tracearg_path(trace_arg);
257
+ frame->source_line = FIX2INT(rb_tracearg_lineno(trace_arg));
258
+
259
+ break;
260
+ }
261
+ case RUBY_EVENT_CALL:
262
+ case RUBY_EVENT_C_CALL:
263
+ {
264
+ prof_method_t* method = check_method(profile, trace_arg, event, thread_data);
265
+
266
+ if (!method)
267
+ break;
268
+
269
+ prof_frame_t* frame = prof_frame_current(thread_data->stack);
270
+ prof_call_tree_t* parent_call_tree = NULL;
271
+ prof_call_tree_t* call_tree = NULL;
272
+
273
+ // Frame can be NULL if we are switching from one fiber to another (see FiberTest#fiber_test)
274
+ if (frame)
275
+ {
276
+ parent_call_tree = frame->call_tree;
277
+ call_tree = call_tree_table_lookup(parent_call_tree->children, method->key);
278
+ }
279
+ else if (!frame && thread_data->call_tree)
280
+ {
281
+ // There is no current parent - likely we have returned out of the highest level method we have profiled so far.
282
+ // This can happen with enumerators (see fiber_test.rb). So create a new dummy parent.
283
+ prof_method_t* parent_method = check_parent_method(profile, thread_data);
284
+ parent_call_tree = prof_call_tree_create(parent_method, NULL, Qnil, 0);
285
+ prof_add_call_tree(parent_method->call_trees, parent_call_tree);
286
+ prof_call_tree_add_parent(thread_data->call_tree, parent_call_tree);
287
+ frame = prof_frame_unshift(thread_data->stack, parent_call_tree, thread_data->call_tree, measurement);
288
+ thread_data->call_tree = parent_call_tree;
289
+ }
290
+
291
+ if (!call_tree)
292
+ {
293
+ // This call info does not yet exist. So create it and add it to previous CallTree's children and the current method.
294
+ call_tree = prof_call_tree_create(method, parent_call_tree, frame ? frame->source_file : Qnil, frame? frame->source_line : 0);
295
+ prof_add_call_tree(method->call_trees, call_tree);
296
+ if (parent_call_tree)
297
+ prof_call_tree_add_child(parent_call_tree, call_tree);
298
+ }
299
+
300
+ if (!thread_data->call_tree)
301
+ thread_data->call_tree = call_tree;
302
+
303
+ // Push a new frame onto the stack for a new c-call or ruby call (into a method)
304
+ prof_frame_t* next_frame = prof_frame_push(thread_data->stack, call_tree, measurement, RTEST(profile_t->paused));
305
+ next_frame->source_file = method->source_file;
306
+ next_frame->source_line = method->source_line;
307
+ break;
308
+ }
309
+ case RUBY_EVENT_RETURN:
310
+ case RUBY_EVENT_C_RETURN:
311
+ {
312
+ // We need to check for excluded methods so that we don't pop them off the stack
313
+ prof_method_t* method = check_method(profile, trace_arg, event, thread_data);
314
+
315
+ if (!method)
316
+ break;
317
+
318
+ prof_frame_pop(thread_data->stack, measurement);
319
+ break;
320
+ }
321
+ case RUBY_INTERNAL_EVENT_NEWOBJ:
322
+ {
323
+ /* We want to assign the allocations lexically, not the execution context (otherwise all allocations will
324
+ show up under Class#new */
325
+ int source_line = FIX2INT(rb_tracearg_lineno(trace_arg));
326
+ VALUE source_file = rb_tracearg_path(trace_arg);
327
+
328
+ prof_method_t* method = prof_find_method(thread_data->stack, source_file, source_line);
329
+ if (method)
330
+ prof_allocate_increment(method, trace_arg);
331
+
332
+ break;
333
+ }
334
+ }
335
+ }
336
+
337
+ void prof_install_hook(VALUE self)
338
+ {
339
+ prof_profile_t* profile = prof_get_profile(self);
340
+
341
+ VALUE event_tracepoint = rb_tracepoint_new(Qnil,
342
+ RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
343
+ RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |
344
+ RUBY_EVENT_LINE,
345
+ prof_event_hook, (void*)self);
346
+ rb_ary_push(profile->tracepoints, event_tracepoint);
347
+
348
+ if (profile->measurer->track_allocations)
349
+ {
350
+ VALUE allocation_tracepoint = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, prof_event_hook, (void*)self);
351
+ rb_ary_push(profile->tracepoints, allocation_tracepoint);
352
+ }
353
+
354
+ for (int i = 0; i < RARRAY_LEN(profile->tracepoints); i++)
355
+ {
356
+ rb_tracepoint_enable(rb_ary_entry(profile->tracepoints, i));
357
+ }
358
+ }
359
+
360
+ void prof_remove_hook(VALUE self)
361
+ {
362
+ prof_profile_t* profile = prof_get_profile(self);
363
+
364
+ for (int i = 0; i < RARRAY_LEN(profile->tracepoints); i++)
365
+ {
366
+ rb_tracepoint_disable(rb_ary_entry(profile->tracepoints, i));
367
+ }
368
+ rb_ary_clear(profile->tracepoints);
369
+ }
370
+
371
+ prof_profile_t* prof_get_profile(VALUE self)
372
+ {
373
+ /* Can't use Data_Get_Struct because that triggers the event hook
374
+ ending up in endless recursion. */
375
+ return RTYPEDDATA_DATA(self);
376
+ }
377
+
378
+ static int collect_threads(st_data_t key, st_data_t value, st_data_t result)
379
+ {
380
+ thread_data_t* thread_data = (thread_data_t*)value;
381
+ if (thread_data->trace && thread_data->call_tree)
382
+ {
383
+ VALUE threads_array = (VALUE)result;
384
+ rb_ary_push(threads_array, prof_thread_wrap(thread_data));
385
+ }
386
+ return ST_CONTINUE;
387
+ }
388
+
389
+ /* ======== Profile Class ====== */
390
+ static int mark_threads(st_data_t key, st_data_t value, st_data_t result)
391
+ {
392
+ thread_data_t* thread = (thread_data_t*)value;
393
+ prof_thread_mark(thread);
394
+ return ST_CONTINUE;
395
+ }
396
+
397
+ static int prof_profile_mark_methods(st_data_t key, st_data_t value, st_data_t result)
398
+ {
399
+ prof_method_t* method = (prof_method_t*)value;
400
+ prof_method_mark(method);
401
+ return ST_CONTINUE;
402
+ }
403
+
404
+ static void prof_profile_mark(void* data)
405
+ {
406
+ prof_profile_t* profile = (prof_profile_t*)data;
407
+ rb_gc_mark(profile->tracepoints);
408
+ rb_gc_mark(profile->running);
409
+ rb_gc_mark(profile->paused);
410
+
411
+ // If GC stress is true (useful for debugging), when threads_table_create is called in the
412
+ // allocate method Ruby will immediately call this mark method. Thus the threads_tbl will be NULL.
413
+ if (profile->threads_tbl)
414
+ rb_st_foreach(profile->threads_tbl, mark_threads, 0);
415
+
416
+ if (profile->exclude_methods_tbl)
417
+ rb_st_foreach(profile->exclude_methods_tbl, prof_profile_mark_methods, 0);
418
+ }
419
+
420
+ /* Freeing the profile creates a cascade of freeing. It frees its threads table, which frees
421
+ each thread and its associated call treee and methods. */
422
+ static void prof_profile_ruby_gc_free(void* data)
423
+ {
424
+ prof_profile_t* profile = (prof_profile_t*)data;
425
+ profile->last_thread_data = NULL;
426
+
427
+ threads_table_free(profile->threads_tbl);
428
+ profile->threads_tbl = NULL;
429
+
430
+ if (profile->exclude_threads_tbl)
431
+ {
432
+ rb_st_free_table(profile->exclude_threads_tbl);
433
+ profile->exclude_threads_tbl = NULL;
434
+ }
435
+
436
+ if (profile->include_threads_tbl)
437
+ {
438
+ rb_st_free_table(profile->include_threads_tbl);
439
+ profile->include_threads_tbl = NULL;
440
+ }
441
+
442
+ /* This table owns the excluded sentinels for now. */
443
+ method_table_free(profile->exclude_methods_tbl);
444
+ profile->exclude_methods_tbl = NULL;
445
+
446
+ xfree(profile->measurer);
447
+ profile->measurer = NULL;
448
+
449
+ xfree(profile);
450
+ }
451
+
452
+ size_t prof_profile_size(const void* data)
453
+ {
454
+ return sizeof(prof_profile_t);
455
+ }
456
+
457
+ static const rb_data_type_t profile_type =
458
+ {
459
+ .wrap_struct_name = "Profile",
460
+ .function =
461
+ {
462
+ .dmark = prof_profile_mark,
463
+ .dfree = prof_profile_ruby_gc_free,
464
+ .dsize = prof_profile_size,
465
+ },
466
+ .data = NULL,
467
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY
468
+ };
469
+
470
+ static VALUE prof_allocate(VALUE klass)
471
+ {
472
+ VALUE result;
473
+ prof_profile_t* profile;
474
+ result = TypedData_Make_Struct(klass, prof_profile_t, &profile_type, profile);
475
+ profile->threads_tbl = threads_table_create();
476
+ profile->exclude_threads_tbl = NULL;
477
+ profile->include_threads_tbl = NULL;
478
+ profile->running = Qfalse;
479
+ profile->allow_exceptions = false;
480
+ profile->exclude_methods_tbl = method_table_create();
481
+ profile->running = Qfalse;
482
+ profile->tracepoints = rb_ary_new();
483
+ return result;
484
+ }
485
+
486
+ static void prof_exclude_common_methods(VALUE profile)
487
+ {
488
+ rb_funcall(profile, rb_intern("exclude_common_methods!"), 0);
489
+ }
490
+
491
+ static int pop_frames(VALUE key, st_data_t value, st_data_t data)
492
+ {
493
+ thread_data_t* thread_data = (thread_data_t*)value;
494
+ prof_profile_t* profile = (prof_profile_t*)data;
495
+ double measurement = prof_measure(profile->measurer, NULL);
496
+
497
+ if (profile->last_thread_data->fiber != thread_data->fiber)
498
+ switch_thread(profile, thread_data, measurement);
499
+
500
+ while (prof_frame_pop(thread_data->stack, measurement));
501
+
502
+ return ST_CONTINUE;
503
+ }
504
+
505
+ static void
506
+ prof_stop_threads(prof_profile_t* profile)
507
+ {
508
+ rb_st_foreach(profile->threads_tbl, pop_frames, (st_data_t)profile);
509
+ }
510
+
511
+ /* call-seq:
512
+ new()
513
+ new(options)
514
+
515
+ Returns a new profiler. Possible options for the options hash are:
516
+
517
+ measure_mode: Measure mode. Specifies the profile measure mode.
518
+ If not specified, defaults to RubyProf::WALL_TIME.
519
+ allow_exceptions: Whether to raise exceptions encountered during profiling,
520
+ or to suppress all exceptions during profiling
521
+ track_allocations: Whether to track object allocations while profiling. True or false.
522
+ exclude_common: Exclude common methods from the profile. True or false.
523
+ exclude_threads: Threads to exclude from the profiling results.
524
+ include_threads: Focus profiling on only the given threads. This will ignore
525
+ all other threads. */
526
+ static VALUE prof_initialize(int argc, VALUE* argv, VALUE self)
527
+ {
528
+ prof_profile_t* profile = prof_get_profile(self);
529
+ VALUE mode_or_options;
530
+ VALUE mode = Qnil;
531
+ VALUE exclude_threads = Qnil;
532
+ VALUE include_threads = Qnil;
533
+ VALUE exclude_common = Qnil;
534
+ VALUE allow_exceptions = Qfalse;
535
+ VALUE track_allocations = Qfalse;
536
+
537
+ int i;
538
+
539
+ switch (rb_scan_args(argc, argv, "02", &mode_or_options, &exclude_threads))
540
+ {
541
+ case 0:
542
+ break;
543
+ case 1:
544
+ if (FIXNUM_P(mode_or_options))
545
+ {
546
+ mode = mode_or_options;
547
+ }
548
+ else
549
+ {
550
+ Check_Type(mode_or_options, T_HASH);
551
+ mode = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("measure_mode")));
552
+ track_allocations = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("track_allocations")));
553
+ allow_exceptions = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("allow_exceptions")));
554
+ exclude_common = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("exclude_common")));
555
+ exclude_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("exclude_threads")));
556
+ include_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("include_threads")));
557
+ }
558
+ break;
559
+ case 2:
560
+ Check_Type(exclude_threads, T_ARRAY);
561
+ break;
562
+ }
563
+
564
+ if (mode == Qnil)
565
+ {
566
+ mode = INT2NUM(MEASURE_WALL_TIME);
567
+ }
568
+ else
569
+ {
570
+ Check_Type(mode, T_FIXNUM);
571
+ }
572
+ profile->measurer = prof_measurer_create(NUM2INT(mode), track_allocations == Qtrue);
573
+ profile->allow_exceptions = (allow_exceptions == Qtrue);
574
+
575
+ if (exclude_threads != Qnil)
576
+ {
577
+ Check_Type(exclude_threads, T_ARRAY);
578
+ assert(profile->exclude_threads_tbl == NULL);
579
+ profile->exclude_threads_tbl = threads_table_create();
580
+ for (i = 0; i < RARRAY_LEN(exclude_threads); i++)
581
+ {
582
+ VALUE thread = rb_ary_entry(exclude_threads, i);
583
+ rb_st_insert(profile->exclude_threads_tbl, thread, Qtrue);
584
+ }
585
+ }
586
+
587
+ if (include_threads != Qnil)
588
+ {
589
+ Check_Type(include_threads, T_ARRAY);
590
+ assert(profile->include_threads_tbl == NULL);
591
+ profile->include_threads_tbl = threads_table_create();
592
+ for (i = 0; i < RARRAY_LEN(include_threads); i++)
593
+ {
594
+ VALUE thread = rb_ary_entry(include_threads, i);
595
+ rb_st_insert(profile->include_threads_tbl, thread, Qtrue);
596
+ }
597
+ }
598
+
599
+ if (RTEST(exclude_common))
600
+ {
601
+ prof_exclude_common_methods(self);
602
+ }
603
+
604
+ return self;
605
+ }
606
+
607
+ /* call-seq:
608
+ paused? -> boolean
609
+
610
+ Returns whether a profile is currently paused.*/
611
+ static VALUE prof_paused(VALUE self)
612
+ {
613
+ prof_profile_t* profile = prof_get_profile(self);
614
+ return profile->paused;
615
+ }
616
+
617
+ /* call-seq:
618
+ running? -> boolean
619
+
620
+ Returns whether a profile is currently running.*/
621
+ static VALUE
622
+ prof_running(VALUE self)
623
+ {
624
+ prof_profile_t* profile = prof_get_profile(self);
625
+ return profile->running;
626
+ }
627
+
628
+ /* call-seq:
629
+ mode -> measure_mode
630
+
631
+ Returns the measure mode used in this profile.*/
632
+ static VALUE prof_profile_measure_mode(VALUE self)
633
+ {
634
+ prof_profile_t* profile = prof_get_profile(self);
635
+ return INT2NUM(profile->measurer->mode);
636
+ }
637
+
638
+ /* call-seq:
639
+ track_allocations -> boolean
640
+
641
+ Returns if object allocations were tracked in this profile.*/
642
+ static VALUE prof_profile_track_allocations(VALUE self)
643
+ {
644
+ prof_profile_t* profile = prof_get_profile(self);
645
+ return profile->measurer->track_allocations ? Qtrue : Qfalse;
646
+ }
647
+
648
+ /* call-seq:
649
+ start -> self
650
+
651
+ Starts recording profile data.*/
652
+ static VALUE prof_start(VALUE self)
653
+ {
654
+ char* trace_file_name;
655
+
656
+ prof_profile_t* profile = prof_get_profile(self);
657
+
658
+ if (profile->running == Qtrue)
659
+ {
660
+ rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
661
+ }
662
+
663
+ profile->running = Qtrue;
664
+ profile->paused = Qfalse;
665
+ profile->last_thread_data = threads_table_insert(profile, rb_fiber_current());
666
+
667
+ /* open trace file if environment wants it */
668
+ trace_file_name = getenv("RUBY_PROF_TRACE");
669
+
670
+ if (trace_file_name != NULL)
671
+ {
672
+ if (strcmp(trace_file_name, "stdout") == 0)
673
+ {
674
+ trace_file = stdout;
675
+ }
676
+ else if (strcmp(trace_file_name, "stderr") == 0)
677
+ {
678
+ trace_file = stderr;
679
+ }
680
+ else
681
+ {
682
+ trace_file = fopen(trace_file_name, "w");
683
+ }
684
+ }
685
+
686
+ prof_install_hook(self);
687
+ return self;
688
+ }
689
+
690
+ /* call-seq:
691
+ pause -> self
692
+
693
+ Pauses collecting profile data. */
694
+ static VALUE prof_pause(VALUE self)
695
+ {
696
+ prof_profile_t* profile = prof_get_profile(self);
697
+ if (profile->running == Qfalse)
698
+ {
699
+ rb_raise(rb_eRuntimeError, "RubyProf is not running.");
700
+ }
701
+
702
+ if (profile->paused == Qfalse)
703
+ {
704
+ profile->paused = Qtrue;
705
+ profile->measurement_at_pause_resume = prof_measure(profile->measurer, NULL);
706
+ rb_st_foreach(profile->threads_tbl, pause_thread, (st_data_t)profile);
707
+ }
708
+
709
+ return self;
710
+ }
711
+
712
+ /* call-seq:
713
+ resume -> self
714
+ resume(&block) -> self
715
+
716
+ Resumes recording profile data.*/
717
+ static VALUE prof_resume(VALUE self)
718
+ {
719
+ prof_profile_t* profile = prof_get_profile(self);
720
+ if (profile->running == Qfalse)
721
+ {
722
+ rb_raise(rb_eRuntimeError, "RubyProf is not running.");
723
+ }
724
+
725
+ if (profile->paused == Qtrue)
726
+ {
727
+ profile->paused = Qfalse;
728
+ profile->measurement_at_pause_resume = prof_measure(profile->measurer, NULL);
729
+ rb_st_foreach(profile->threads_tbl, unpause_thread, (st_data_t)profile);
730
+ }
731
+
732
+ return rb_block_given_p() ? rb_ensure(rb_yield, self, prof_pause, self) : self;
733
+ }
734
+
735
+ /* call-seq:
736
+ stop -> self
737
+
738
+ Stops collecting profile data.*/
739
+ static VALUE prof_stop(VALUE self)
740
+ {
741
+ prof_profile_t* profile = prof_get_profile(self);
742
+
743
+ if (profile->running == Qfalse)
744
+ {
745
+ rb_raise(rb_eRuntimeError, "RubyProf.start was not yet called");
746
+ }
747
+
748
+ prof_remove_hook(self);
749
+
750
+ /* close trace file if open */
751
+ if (trace_file != NULL)
752
+ {
753
+ if (trace_file != stderr && trace_file != stdout)
754
+ {
755
+ fclose(trace_file);
756
+ }
757
+ trace_file = NULL;
758
+ }
759
+
760
+ prof_stop_threads(profile);
761
+
762
+ /* Unset the last_thread_data (very important!)
763
+ and the threads table */
764
+ profile->running = profile->paused = Qfalse;
765
+ profile->last_thread_data = NULL;
766
+
767
+ return self;
768
+ }
769
+
770
+ /* call-seq:
771
+ threads -> Array of RubyProf::Thread
772
+
773
+ Returns an array of RubyProf::Thread instances that were profiled. */
774
+ static VALUE prof_threads(VALUE self)
775
+ {
776
+ VALUE result = rb_ary_new();
777
+ prof_profile_t* profile = prof_get_profile(self);
778
+ rb_st_foreach(profile->threads_tbl, collect_threads, result);
779
+ return result;
780
+ }
781
+
782
+ /* Document-method: RubyProf::Profile#Profile
783
+ call-seq:
784
+ profile(&block) -> self
785
+
786
+ Profiles the specified block.
787
+
788
+ profile = RubyProf::Profile.new
789
+ profile.profile do
790
+ ..
791
+ end
792
+ */
793
+ static VALUE prof_profile_object(VALUE self)
794
+ {
795
+ int result;
796
+ prof_profile_t* profile = prof_get_profile(self);
797
+
798
+ if (!rb_block_given_p())
799
+ {
800
+ rb_raise(rb_eArgError, "A block must be provided to the profile method.");
801
+ }
802
+
803
+ prof_start(self);
804
+ rb_protect(rb_yield, self, &result);
805
+ self = prof_stop(self);
806
+
807
+ if (profile->allow_exceptions && result != 0)
808
+ {
809
+ rb_jump_tag(result);
810
+ }
811
+
812
+ return self;
813
+ }
814
+
815
+ /* Document-method: RubyProf::Profile::Profile
816
+ call-seq:
817
+ profile(&block) -> RubyProf::Profile
818
+ profile(options, &block) -> RubyProf::Profile
819
+
820
+ Profiles the specified block and returns a RubyProf::Profile
821
+ object. Arguments are passed to Profile initialize method.
822
+
823
+ profile = RubyProf::Profile.profile do
824
+ ..
825
+ end
826
+ */
827
+ static VALUE prof_profile_class(int argc, VALUE* argv, VALUE klass)
828
+ {
829
+ return prof_profile_object(rb_class_new_instance(argc, argv, cProfile));
830
+ }
831
+
832
+ /* call-seq:
833
+ exclude_method!(module, method_name) -> self
834
+
835
+ Excludes the method from profiling results.
836
+ */
837
+ static VALUE prof_exclude_method(VALUE self, VALUE klass, VALUE msym)
838
+ {
839
+ prof_profile_t* profile = prof_get_profile(self);
840
+
841
+ if (profile->running == Qtrue)
842
+ {
843
+ rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
844
+ }
845
+
846
+ st_data_t key = method_key(klass, msym);
847
+ prof_method_t* method = method_table_lookup(profile->exclude_methods_tbl, key);
848
+
849
+ if (!method)
850
+ {
851
+ method = prof_method_create(self, klass, msym, Qnil, 0);
852
+ method_table_insert(profile->exclude_methods_tbl, method->key, method);
853
+ }
854
+
855
+ return self;
856
+ }
857
+
858
+ /* :nodoc: */
859
+ VALUE prof_profile_dump(VALUE self)
860
+ {
861
+ prof_profile_t* profile = prof_get_profile(self);
862
+
863
+ VALUE result = rb_hash_new();
864
+ rb_hash_aset(result, ID2SYM(rb_intern("threads")), prof_threads(self));
865
+ rb_hash_aset(result, ID2SYM(rb_intern("measurer_mode")), INT2NUM(profile->measurer->mode));
866
+ rb_hash_aset(result, ID2SYM(rb_intern("measurer_track_allocations")),
867
+ profile->measurer->track_allocations ? Qtrue : Qfalse);
868
+
869
+ return result;
870
+ }
871
+
872
+ /* :nodoc: */
873
+ VALUE prof_profile_load(VALUE self, VALUE data)
874
+ {
875
+ prof_profile_t* profile = prof_get_profile(self);
876
+
877
+ VALUE measurer_mode = rb_hash_aref(data, ID2SYM(rb_intern("measurer_mode")));
878
+ VALUE measurer_track_allocations = rb_hash_aref(data, ID2SYM(rb_intern("measurer_track_allocations")));
879
+ profile->measurer = prof_measurer_create((prof_measure_mode_t)(NUM2INT(measurer_mode)),
880
+ measurer_track_allocations == Qtrue ? true : false);
881
+
882
+ VALUE threads = rb_hash_aref(data, ID2SYM(rb_intern("threads")));
883
+ for (int i = 0; i < rb_array_len(threads); i++)
884
+ {
885
+ VALUE thread = rb_ary_entry(threads, i);
886
+ thread_data_t* thread_data = prof_get_thread(thread);
887
+ rb_st_insert(profile->threads_tbl, (st_data_t)thread_data->fiber_id, (st_data_t)thread_data);
888
+ }
889
+
890
+ return data;
891
+ }
892
+
893
+ void rp_init_profile(void)
894
+ {
895
+ cProfile = rb_define_class_under(mProf, "Profile", rb_cObject);
896
+ rb_define_alloc_func(cProfile, prof_allocate);
897
+
898
+ rb_define_singleton_method(cProfile, "profile", prof_profile_class, -1);
899
+ rb_define_method(cProfile, "initialize", prof_initialize, -1);
900
+ rb_define_method(cProfile, "start", prof_start, 0);
901
+ rb_define_method(cProfile, "stop", prof_stop, 0);
902
+ rb_define_method(cProfile, "resume", prof_resume, 0);
903
+ rb_define_method(cProfile, "pause", prof_pause, 0);
904
+ rb_define_method(cProfile, "running?", prof_running, 0);
905
+ rb_define_method(cProfile, "paused?", prof_paused, 0);
906
+ rb_define_method(cProfile, "threads", prof_threads, 0);
907
+ rb_define_method(cProfile, "exclude_method!", prof_exclude_method, 2);
908
+ rb_define_method(cProfile, "profile", prof_profile_object, 0);
909
+
910
+ rb_define_method(cProfile, "measure_mode", prof_profile_measure_mode, 0);
911
+ rb_define_method(cProfile, "track_allocations?", prof_profile_track_allocations, 0);
912
+
913
+ rb_define_method(cProfile, "_dump_data", prof_profile_dump, 0);
914
+ rb_define_method(cProfile, "_load_data", prof_profile_load, 1);
915
+ }