airbnb-ruby-prof 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/CHANGES +483 -0
  2. data/LICENSE +25 -0
  3. data/README.rdoc +426 -0
  4. data/Rakefile +51 -0
  5. data/bin/ruby-prof +279 -0
  6. data/bin/ruby-prof-check-trace +45 -0
  7. data/examples/flat.txt +50 -0
  8. data/examples/graph.dot +84 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.txt +139 -0
  11. data/examples/multi.flat.txt +23 -0
  12. data/examples/multi.graph.html +760 -0
  13. data/examples/multi.grind.dat +114 -0
  14. data/examples/multi.stack.html +547 -0
  15. data/examples/stack.html +547 -0
  16. data/ext/ruby_prof/extconf.rb +67 -0
  17. data/ext/ruby_prof/rp_call_info.c +374 -0
  18. data/ext/ruby_prof/rp_call_info.h +59 -0
  19. data/ext/ruby_prof/rp_fast_call_tree_printer.c +247 -0
  20. data/ext/ruby_prof/rp_fast_call_tree_printer.h +10 -0
  21. data/ext/ruby_prof/rp_measure.c +71 -0
  22. data/ext/ruby_prof/rp_measure.h +56 -0
  23. data/ext/ruby_prof/rp_measure_allocations.c +74 -0
  24. data/ext/ruby_prof/rp_measure_cpu_time.c +134 -0
  25. data/ext/ruby_prof/rp_measure_gc_runs.c +71 -0
  26. data/ext/ruby_prof/rp_measure_gc_time.c +58 -0
  27. data/ext/ruby_prof/rp_measure_memory.c +75 -0
  28. data/ext/ruby_prof/rp_measure_process_time.c +69 -0
  29. data/ext/ruby_prof/rp_measure_wall_time.c +43 -0
  30. data/ext/ruby_prof/rp_method.c +717 -0
  31. data/ext/ruby_prof/rp_method.h +79 -0
  32. data/ext/ruby_prof/rp_stack.c +221 -0
  33. data/ext/ruby_prof/rp_stack.h +81 -0
  34. data/ext/ruby_prof/rp_thread.c +312 -0
  35. data/ext/ruby_prof/rp_thread.h +36 -0
  36. data/ext/ruby_prof/ruby_prof.c +800 -0
  37. data/ext/ruby_prof/ruby_prof.h +64 -0
  38. data/ext/ruby_prof/vc/ruby_prof.sln +32 -0
  39. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +108 -0
  40. data/ext/ruby_prof/vc/ruby_prof_19.vcxproj +110 -0
  41. data/ext/ruby_prof/vc/ruby_prof_20.vcxproj +110 -0
  42. data/lib/ruby-prof.rb +63 -0
  43. data/lib/ruby-prof/aggregate_call_info.rb +76 -0
  44. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  45. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  46. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  47. data/lib/ruby-prof/assets/flame_graph_printer.lib.css.html +149 -0
  48. data/lib/ruby-prof/assets/flame_graph_printer.lib.js.html +707 -0
  49. data/lib/ruby-prof/assets/flame_graph_printer.page.js.html +56 -0
  50. data/lib/ruby-prof/assets/flame_graph_printer.tmpl.html.erb +39 -0
  51. data/lib/ruby-prof/call_info.rb +111 -0
  52. data/lib/ruby-prof/call_info_visitor.rb +40 -0
  53. data/lib/ruby-prof/compatibility.rb +186 -0
  54. data/lib/ruby-prof/method_info.rb +109 -0
  55. data/lib/ruby-prof/printers/abstract_printer.rb +85 -0
  56. data/lib/ruby-prof/printers/call_info_printer.rb +41 -0
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +260 -0
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +130 -0
  59. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  60. data/lib/ruby-prof/printers/fast_call_tree_printer.rb +87 -0
  61. data/lib/ruby-prof/printers/flame_graph_html_printer.rb +59 -0
  62. data/lib/ruby-prof/printers/flame_graph_json_printer.rb +157 -0
  63. data/lib/ruby-prof/printers/flat_printer.rb +70 -0
  64. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +64 -0
  65. data/lib/ruby-prof/printers/graph_html_printer.rb +244 -0
  66. data/lib/ruby-prof/printers/graph_printer.rb +116 -0
  67. data/lib/ruby-prof/printers/multi_printer.rb +58 -0
  68. data/lib/ruby-prof/profile.rb +22 -0
  69. data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
  70. data/lib/ruby-prof/rack.rb +95 -0
  71. data/lib/ruby-prof/task.rb +147 -0
  72. data/lib/ruby-prof/thread.rb +35 -0
  73. data/lib/ruby-prof/version.rb +4 -0
  74. data/lib/ruby-prof/walker.rb +95 -0
  75. data/lib/unprof.rb +10 -0
  76. data/ruby-prof.gemspec +56 -0
  77. data/test/aggregate_test.rb +136 -0
  78. data/test/basic_test.rb +128 -0
  79. data/test/block_test.rb +74 -0
  80. data/test/call_info_test.rb +78 -0
  81. data/test/call_info_visitor_test.rb +31 -0
  82. data/test/duplicate_names_test.rb +32 -0
  83. data/test/dynamic_method_test.rb +55 -0
  84. data/test/enumerable_test.rb +21 -0
  85. data/test/exceptions_test.rb +16 -0
  86. data/test/exclude_methods_test.rb +146 -0
  87. data/test/exclude_threads_test.rb +53 -0
  88. data/test/fiber_test.rb +79 -0
  89. data/test/issue137_test.rb +63 -0
  90. data/test/line_number_test.rb +71 -0
  91. data/test/measure_allocations_test.rb +26 -0
  92. data/test/measure_cpu_time_test.rb +213 -0
  93. data/test/measure_gc_runs_test.rb +32 -0
  94. data/test/measure_gc_time_test.rb +36 -0
  95. data/test/measure_memory_test.rb +33 -0
  96. data/test/measure_process_time_test.rb +63 -0
  97. data/test/measure_wall_time_test.rb +255 -0
  98. data/test/module_test.rb +45 -0
  99. data/test/multi_measure_test.rb +38 -0
  100. data/test/multi_printer_test.rb +83 -0
  101. data/test/no_method_class_test.rb +15 -0
  102. data/test/pause_resume_test.rb +166 -0
  103. data/test/prime.rb +54 -0
  104. data/test/printers_test.rb +255 -0
  105. data/test/printing_recursive_graph_test.rb +127 -0
  106. data/test/rack_test.rb +93 -0
  107. data/test/recursive_test.rb +212 -0
  108. data/test/singleton_test.rb +38 -0
  109. data/test/stack_printer_test.rb +65 -0
  110. data/test/stack_test.rb +138 -0
  111. data/test/start_stop_test.rb +112 -0
  112. data/test/test_helper.rb +264 -0
  113. data/test/thread_test.rb +187 -0
  114. data/test/unique_call_path_test.rb +202 -0
  115. data/test/yarv_test.rb +55 -0
  116. metadata +211 -0
@@ -0,0 +1,36 @@
1
+ /* Copyright (C) 2005-2013 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
+ #ifndef __RP_THREAD__
5
+ #define __RP_THREAD__
6
+
7
+ /* Profiling information for a thread. */
8
+ typedef struct
9
+ {
10
+ uintptr_t thread_index; /* Thread index (main is 0) */
11
+ VALUE thread_id; /* Thread id */
12
+ VALUE fiber_id; /* Fiber id */
13
+
14
+ st_table* method_table; /* Methods called in the thread */
15
+ prof_stack_t* stack; /* Stack of frames */
16
+
17
+ VALUE object; /* Cache to wrapped object */
18
+ VALUE methods; /* Array of RubyProf::MethodInfo */
19
+ } thread_data_t;
20
+
21
+ void rp_init_thread();
22
+
23
+ st_table * threads_table_create();
24
+ void threads_table_free(st_table *table);
25
+
26
+ thread_data_t* switch_thread(void* prof, VALUE thread_id, VALUE fiber_id);
27
+
28
+ VALUE prof_thread_wrap(thread_data_t *thread);
29
+ void prof_thread_mark(thread_data_t *thread);
30
+
31
+ int pause_thread(st_data_t key, st_data_t value, st_data_t data);
32
+ int unpause_thread(st_data_t key, st_data_t value, st_data_t data);
33
+
34
+ thread_data_t* prof_get_thread(VALUE self);
35
+
36
+ #endif //__RP_THREAD__
@@ -0,0 +1,800 @@
1
+ /* Copyright (C) 2005-2013 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
+ /* ruby-prof tracks the time spent executing every method in ruby programming.
5
+ The main players are:
6
+
7
+ profile_t - This represents 1 profile.
8
+ thread_data_t - Stores data about a single thread.
9
+ prof_stack_t - The method call stack in a particular thread
10
+ prof_method_t - Profiling information about each method
11
+ prof_call_info_t - Keeps track a method's callers and callees.
12
+
13
+ The final result is an instance of a profile object which has a hash table of
14
+ thread_data_t, keyed on the thread id. Each thread in turn has a hash table
15
+ of prof_method_t, keyed on the method id. A hash table is used for quick
16
+ look up when doing a profile. However, it is exposed to Ruby as an array.
17
+
18
+ Each prof_method_t has two hash tables, parent and children, of prof_call_info_t.
19
+ These objects keep track of a method's callers (who called the method) and its
20
+ callees (who the method called). These are keyed the method id, but once again,
21
+ are exposed to Ruby as arrays. Each prof_call_into_t maintains a pointer to the
22
+ caller or callee method, thereby making it easy to navigate through the call
23
+ hierarchy in ruby - which is very helpful for creating call graphs.
24
+ */
25
+
26
+ #include "ruby_prof.h"
27
+ #include <assert.h>
28
+
29
+ VALUE mProf;
30
+ VALUE cProfile;
31
+ VALUE cExcludeCommonMethods;
32
+
33
+ static prof_profile_t*
34
+ prof_get_profile(VALUE self)
35
+ {
36
+ /* Can't use Data_Get_Struct because that triggers the event hook
37
+ ending up in endless recursion. */
38
+ return (prof_profile_t*)RDATA(self)->data;
39
+ }
40
+
41
+ /* support tracing ruby events from ruby-prof. useful for getting at
42
+ what actually happens inside the ruby interpreter (and ruby-prof).
43
+ set environment variable RUBY_PROF_TRACE to filename you want to
44
+ find the trace in.
45
+ */
46
+ static FILE* trace_file = NULL;
47
+
48
+ /* Copied from thread.c (1.9.3) */
49
+ static const char *
50
+ get_event_name(rb_event_flag_t event)
51
+ {
52
+ switch (event) {
53
+ case RUBY_EVENT_LINE:
54
+ return "line";
55
+ case RUBY_EVENT_CLASS:
56
+ return "class";
57
+ case RUBY_EVENT_END:
58
+ return "end";
59
+ case RUBY_EVENT_CALL:
60
+ return "call";
61
+ case RUBY_EVENT_RETURN:
62
+ return "return";
63
+ case RUBY_EVENT_C_CALL:
64
+ return "c-call";
65
+ case RUBY_EVENT_C_RETURN:
66
+ return "c-return";
67
+ case RUBY_EVENT_RAISE:
68
+ return "raise";
69
+ default:
70
+ return "unknown";
71
+ }
72
+ }
73
+
74
+ static int
75
+ excludes_method(prof_method_key_t *key, prof_profile_t *profile)
76
+ {
77
+ return (profile->exclude_methods_tbl &&
78
+ method_table_lookup(profile->exclude_methods_tbl, key) != NULL);
79
+ }
80
+
81
+ static void
82
+ prof_exclude_common_methods(VALUE profile)
83
+ {
84
+ rb_funcall(cExcludeCommonMethods, rb_intern("apply!"), 1, profile);
85
+ }
86
+
87
+ static prof_method_t*
88
+ create_method(rb_event_flag_t event, VALUE klass, ID mid, const char* source_file, int line)
89
+ {
90
+ /* Line numbers are not accurate for c method calls */
91
+ if (event == RUBY_EVENT_C_CALL)
92
+ {
93
+ line = 0;
94
+ source_file = NULL;
95
+ }
96
+
97
+ return prof_method_create(klass, mid, source_file, line);
98
+ }
99
+
100
+ static prof_method_t*
101
+ get_method(rb_event_flag_t event, VALUE klass, ID mid, thread_data_t *thread_data, prof_profile_t *profile)
102
+ {
103
+ prof_method_key_t key;
104
+ prof_method_t *method = NULL;
105
+
106
+ /* Probe the local table. */
107
+ method_key(&key, klass, mid);
108
+ method = method_table_lookup(thread_data->method_table, &key);
109
+
110
+ if (!method)
111
+ {
112
+ /* Didn't find it. Is it the top level, or are we excluding it specifically? */
113
+ if (!RTEST(klass) || !RTEST(mid) || excludes_method(&key, profile)) {
114
+ /* We found a exclusion sentinel so propagate it into the thread's local hash table. */
115
+ /* TODO(nelgau): Is there a way to avoid this allocation completely so that all these
116
+ tables share the same exclusion method struct? The first attempt failed due to my
117
+ ignorance of the whims of the GC. */
118
+ method = prof_method_create_excluded(klass, mid);
119
+ } else {
120
+ /* This method has no entry for this thread/fiber and isn't specifically excluded. */
121
+ const char* source_file = rb_sourcefile();
122
+ int line = rb_sourceline();
123
+ method = create_method(event, klass, mid, source_file, line);
124
+ }
125
+
126
+ /* Insert the newly created method, or the exlcusion sentinel. */
127
+ method_table_insert(thread_data->method_table, method->key, method);
128
+ }
129
+
130
+ return method;
131
+ }
132
+
133
+ static int
134
+ pop_frames(st_data_t key, st_data_t value, st_data_t data)
135
+ {
136
+ thread_data_t* thread_data = (thread_data_t *) value;
137
+ prof_profile_t* profile = (prof_profile_t*) data;
138
+ VALUE thread_id = thread_data->thread_id;
139
+ VALUE fiber_id = thread_data->fiber_id;
140
+
141
+ prof_measurer_take_measurements(profile->measurer, profile->measurements);
142
+
143
+ if (!profile->last_thread_data
144
+ || (!profile->merge_fibers && profile->last_thread_data->fiber_id != fiber_id)
145
+ || profile->last_thread_data->thread_id != thread_id
146
+ )
147
+ thread_data = switch_thread(profile, thread_id, fiber_id);
148
+ else
149
+ thread_data = profile->last_thread_data;
150
+
151
+ while (prof_stack_pop(thread_data->stack, profile->measurements));
152
+
153
+ return ST_CONTINUE;
154
+ }
155
+
156
+ static void
157
+ prof_pop_threads(prof_profile_t* profile)
158
+ {
159
+ st_foreach(profile->threads_tbl, pop_frames, (st_data_t) profile);
160
+ }
161
+
162
+ /* =========== Profiling ================= */
163
+ static void
164
+ prof_trace(prof_profile_t* profile, rb_event_flag_t event, ID mid, VALUE klass, double measurement)
165
+ {
166
+ static VALUE last_fiber_id = Qnil;
167
+
168
+ VALUE thread = rb_thread_current();
169
+ VALUE thread_id = rb_obj_id(thread);
170
+ VALUE fiber = rb_fiber_current();
171
+ VALUE fiber_id = rb_obj_id(fiber);
172
+ const char* class_name = NULL;
173
+ const char* method_name = rb_id2name(mid);
174
+ const char* source_file = rb_sourcefile();
175
+ unsigned int source_line = rb_sourceline();
176
+
177
+ const char* event_name = get_event_name(event);
178
+
179
+ if (klass != 0)
180
+ klass = (BUILTIN_TYPE(klass) == T_ICLASS ? RBASIC(klass)->klass : klass);
181
+
182
+ class_name = rb_class2name(klass);
183
+
184
+ if (last_fiber_id != fiber_id)
185
+ {
186
+ fprintf(trace_file, "\n");
187
+ }
188
+
189
+ fprintf(trace_file, "%2lu:%2lu:%2ums %-8s %s:%2d %s#%s\n",
190
+ (unsigned long) thread_id, (unsigned long) fiber_id, (unsigned int) measurement*1000,
191
+ event_name, source_file, source_line, class_name, method_name);
192
+ fflush(trace_file);
193
+ last_fiber_id = fiber_id;
194
+ }
195
+
196
+ static void
197
+ prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
198
+ {
199
+ prof_profile_t* profile = prof_get_profile(data);
200
+ VALUE thread = Qnil;
201
+ VALUE thread_id = Qnil;
202
+ VALUE fiber = Qnil;
203
+ VALUE fiber_id = Qnil;
204
+ thread_data_t* thread_data = NULL;
205
+ prof_frame_t *frame = NULL;
206
+
207
+ /* if we don't have a valid method id, try to retrieve one */
208
+ if (mid == 0) {
209
+ rb_frame_method_id_and_class(&mid, &klass);
210
+ }
211
+
212
+ /* Get current measurement */
213
+ prof_measurer_take_measurements(profile->measurer, profile->measurements);
214
+
215
+ /* Special case - skip any methods from the mProf
216
+ module or cProfile class since they clutter
217
+ the results but aren't important to them results. */
218
+ if (self == mProf || klass == cProfile) {
219
+ return;
220
+ }
221
+
222
+ if (trace_file != NULL)
223
+ {
224
+ prof_trace(profile, event, mid, klass, profile->measurements->values[0]);
225
+ }
226
+
227
+ /* Get the current thread and fiber information. */
228
+ thread = rb_thread_current();
229
+ thread_id = rb_obj_id(thread);
230
+ fiber = rb_fiber_current();
231
+ fiber_id = rb_obj_id(fiber);
232
+
233
+ /* Don't measure anything if the include_threads option has been specified
234
+ and the current thread is not in the list
235
+ */
236
+ if (profile->include_threads_tbl && !st_lookup(profile->include_threads_tbl, (st_data_t) thread_id, 0))
237
+ {
238
+ return;
239
+ }
240
+
241
+ /* Don't measure anything if the current thread is in the excluded thread table
242
+ */
243
+ if (profile->exclude_threads_tbl && st_lookup(profile->exclude_threads_tbl, (st_data_t) thread_id, 0))
244
+ {
245
+ return;
246
+ }
247
+
248
+ /* We need to switch the profiling context if we either had none before,
249
+ we don't merge fibers and the fiber ids differ, or the thread ids differ.
250
+ */
251
+ if (!profile->last_thread_data
252
+ || (!profile->merge_fibers && profile->last_thread_data->fiber_id != fiber_id)
253
+ || profile->last_thread_data->thread_id != thread_id
254
+ )
255
+ thread_data = switch_thread(profile, thread_id, fiber_id);
256
+ else
257
+ thread_data = profile->last_thread_data;
258
+
259
+ /* Get the current frame for the current thread. */
260
+ frame = prof_stack_peek(thread_data->stack);
261
+
262
+ switch (event) {
263
+ case RUBY_EVENT_LINE:
264
+ {
265
+ /* Keep track of the current line number in this method. When
266
+ a new method is called, we know what line number it was
267
+ called from. */
268
+ if (frame)
269
+ {
270
+ if (prof_frame_is_real(frame)) {
271
+ frame->line = rb_sourceline();
272
+ }
273
+ break;
274
+ }
275
+
276
+ /* If we get here there was no frame, which means this is
277
+ the first method seen for this thread, so fall through
278
+ to below to create it. */
279
+ }
280
+ case RUBY_EVENT_CALL:
281
+ case RUBY_EVENT_C_CALL:
282
+ {
283
+ prof_frame_t *next_frame;
284
+ prof_call_info_t *call_info;
285
+ prof_method_t *method;
286
+
287
+ method = get_method(event, klass, mid, thread_data, profile);
288
+
289
+ if (method->excluded) {
290
+ prof_stack_pass(thread_data->stack);
291
+ break;
292
+ }
293
+
294
+ if (!frame)
295
+ {
296
+ call_info = prof_call_info_create(method, NULL, profile->measurer->len);
297
+ prof_add_call_info(method->call_infos, call_info);
298
+ }
299
+ else
300
+ {
301
+ call_info = call_info_table_lookup(frame->call_info->call_infos, method->key);
302
+
303
+ if (!call_info)
304
+ {
305
+ /* This call info does not yet exist. So create it, then add
306
+ it to previous callinfo's children and to the current method .*/
307
+ call_info = prof_call_info_create(method, frame->call_info, profile->measurer->len);
308
+ call_info_table_insert(frame->call_info->call_infos, method->key, call_info);
309
+ prof_add_call_info(method->call_infos, call_info);
310
+ }
311
+ }
312
+
313
+ /* Push a new frame onto the stack for a new c-call or ruby call (into a method) */
314
+ next_frame = prof_stack_push(thread_data->stack, call_info, profile->measurements, RTEST(profile->paused));
315
+ next_frame->line = rb_sourceline();
316
+ break;
317
+ }
318
+ case RUBY_EVENT_RETURN:
319
+ case RUBY_EVENT_C_RETURN:
320
+ {
321
+ prof_stack_pop(thread_data->stack, profile->measurements);
322
+ break;
323
+ }
324
+ }
325
+ }
326
+
327
+ void
328
+ prof_install_hook(VALUE self)
329
+ {
330
+ rb_add_event_hook(prof_event_hook,
331
+ RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
332
+ RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |
333
+ RUBY_EVENT_LINE, self);
334
+ }
335
+
336
+ void
337
+ prof_remove_hook()
338
+ {
339
+ rb_remove_event_hook(prof_event_hook);
340
+ }
341
+
342
+ static int
343
+ collect_threads(st_data_t key, st_data_t value, st_data_t result)
344
+ {
345
+ thread_data_t* thread_data = (thread_data_t*) value;
346
+ VALUE threads_array = (VALUE) result;
347
+ rb_ary_push(threads_array, prof_thread_wrap(thread_data));
348
+ return ST_CONTINUE;
349
+ }
350
+
351
+ /* ======== Profile Class ====== */
352
+ static int
353
+ mark_threads(st_data_t key, st_data_t value, st_data_t result)
354
+ {
355
+ thread_data_t *thread = (thread_data_t *) value;
356
+ prof_thread_mark(thread);
357
+ return ST_CONTINUE;
358
+ }
359
+
360
+ static int
361
+ mark_methods(st_data_t key, st_data_t value, st_data_t result)
362
+ {
363
+ prof_method_t *method = (prof_method_t *) value;
364
+ prof_method_mark(method);
365
+ return ST_CONTINUE;
366
+ }
367
+
368
+ static void
369
+ prof_mark(prof_profile_t *profile)
370
+ {
371
+ st_foreach(profile->threads_tbl, mark_threads, 0);
372
+ st_foreach(profile->exclude_methods_tbl, mark_methods, 0);
373
+ }
374
+
375
+ /* Freeing the profile creates a cascade of freeing.
376
+ It fress the thread table, which frees its methods,
377
+ which frees its call infos. */
378
+ static void
379
+ prof_free(prof_profile_t *profile)
380
+ {
381
+ profile->last_thread_data = NULL;
382
+
383
+ threads_table_free(profile->threads_tbl);
384
+ profile->threads_tbl = NULL;
385
+
386
+ if (profile->exclude_threads_tbl) {
387
+ st_free_table(profile->exclude_threads_tbl);
388
+ profile->exclude_threads_tbl = NULL;
389
+ }
390
+
391
+ if (profile->include_threads_tbl) {
392
+ st_free_table(profile->include_threads_tbl);
393
+ profile->include_threads_tbl = NULL;
394
+ }
395
+
396
+ /* This table owns the excluded sentinels for now. */
397
+ method_table_free(profile->exclude_methods_tbl);
398
+ profile->exclude_methods_tbl = NULL;
399
+
400
+ if (profile->measurer) {
401
+ xfree(profile->measurer->measure_modes);
402
+ xfree(profile->measurer);
403
+ profile->measurer = NULL;
404
+ }
405
+
406
+ xfree(profile->measurements);
407
+ xfree(profile->measurements_at_pause_resume);
408
+
409
+ xfree(profile);
410
+ }
411
+
412
+ static VALUE
413
+ prof_allocate(VALUE klass)
414
+ {
415
+ VALUE result;
416
+ prof_profile_t* profile;
417
+ VALUE main_thread;
418
+ VALUE main_thread_id;
419
+
420
+ result = Data_Make_Struct(klass, prof_profile_t, prof_mark, prof_free, profile);
421
+
422
+ profile->running = Qfalse;
423
+ profile->paused = Qfalse;
424
+
425
+ main_thread = rb_funcall(rb_cThread, rb_intern("main"), 0);
426
+ main_thread_id = rb_obj_id(main_thread);
427
+ profile->main_thread_id = main_thread_id;
428
+
429
+ profile->threads_tbl = threads_table_create();
430
+ profile->exclude_threads_tbl = NULL;
431
+ profile->include_threads_tbl = NULL;
432
+ profile->merge_fibers = 0;
433
+ profile->exclude_methods_tbl = method_table_create();
434
+ return result;
435
+ }
436
+
437
+ /* call-seq:
438
+ new(options)
439
+
440
+ Returns a new profiler. Possible options for the options hash are:
441
+
442
+ measure_modes:: Measure mode. Specifies the profile measure mode.
443
+ If not specified, defaults to RubyProf::WALL_TIME.
444
+ exclude_threads:: Threads to exclude from the profiling results.
445
+ include_threads:: Focus profiling on only the given threads. This will ignore
446
+ all other threads.
447
+ merge_fibers:: Whether to merge all fibers under a given thread. This should be
448
+ used when profiling for a callgrind printer.
449
+ */
450
+ static VALUE
451
+ prof_initialize(VALUE self, VALUE options)
452
+ {
453
+ prof_profile_t* profile = prof_get_profile(self);
454
+ VALUE modes = Qnil;
455
+ VALUE exclude_threads = Qnil;
456
+ VALUE include_threads = Qnil;
457
+ VALUE merge_fibers = Qnil;
458
+ VALUE exclude_common = Qnil;
459
+ int i;
460
+ prof_measure_mode_t* measure_modes = NULL;
461
+ size_t measure_modes_len = 0;
462
+
463
+ Check_Type(options, T_HASH);
464
+ modes = rb_hash_aref(options, ID2SYM(rb_intern("measure_modes")));
465
+ merge_fibers = rb_hash_aref(options, ID2SYM(rb_intern("merge_fibers")));
466
+ exclude_common = rb_hash_aref(options, ID2SYM(rb_intern("exclude_common")));
467
+ exclude_threads = rb_hash_aref(options, ID2SYM(rb_intern("exclude_threads")));
468
+ include_threads = rb_hash_aref(options, ID2SYM(rb_intern("include_threads")));
469
+
470
+ if (modes != Qnil) {
471
+ Check_Type(modes, T_ARRAY);
472
+ measure_modes_len = RARRAY_LEN(modes);
473
+
474
+ if (measure_modes_len > 0) {
475
+ measure_modes = ALLOC_N(prof_measure_mode_t, measure_modes_len);
476
+
477
+ for (size_t j = 0; j < measure_modes_len; j++) {
478
+ measure_modes[j] = NUM2INT(rb_ary_entry(modes, j));
479
+ }
480
+ }
481
+ }
482
+
483
+ if (measure_modes == NULL) {
484
+ measure_modes_len = 1;
485
+ measure_modes = ALLOC_N(prof_measure_mode_t, measure_modes_len);
486
+ measure_modes[0] = MEASURE_WALL_TIME;
487
+ }
488
+
489
+ profile->measurer = prof_get_measurer(measure_modes, measure_modes_len);
490
+ profile->measurements = ruby_xmalloc(sizeof(prof_measurements_t) + profile->measurer->len * sizeof(double));
491
+ profile->measurements->len = profile->measurer->len;
492
+ profile->measurements_at_pause_resume = ruby_xmalloc(sizeof(prof_measurements_t) + profile->measurer->len * sizeof(double));
493
+ profile->measurements_at_pause_resume->len = profile->measurer->len;
494
+
495
+ profile->merge_fibers = merge_fibers != Qnil && merge_fibers != Qfalse;
496
+
497
+ if (exclude_threads != Qnil) {
498
+ Check_Type(exclude_threads, T_ARRAY);
499
+ assert(profile->exclude_threads_tbl == NULL);
500
+ profile->exclude_threads_tbl = threads_table_create();
501
+ for (i = 0; i < RARRAY_LEN(exclude_threads); i++) {
502
+ VALUE thread = rb_ary_entry(exclude_threads, i);
503
+ VALUE thread_id = rb_obj_id(thread);
504
+ st_insert(profile->exclude_threads_tbl, thread_id, Qtrue);
505
+ }
506
+ }
507
+
508
+ if (include_threads != Qnil) {
509
+ Check_Type(include_threads, T_ARRAY);
510
+ assert(profile->include_threads_tbl == NULL);
511
+ profile->include_threads_tbl = threads_table_create();
512
+ for (i = 0; i < RARRAY_LEN(include_threads); i++) {
513
+ VALUE thread = rb_ary_entry(include_threads, i);
514
+ VALUE thread_id = rb_obj_id(thread);
515
+ st_insert(profile->include_threads_tbl, thread_id, Qtrue);
516
+ }
517
+ }
518
+
519
+ if (RTEST(exclude_common)) {
520
+ prof_exclude_common_methods(self);
521
+ }
522
+
523
+ return self;
524
+ }
525
+
526
+ /* call-seq:
527
+ paused? -> boolean
528
+
529
+ Returns whether a profile is currently paused.*/
530
+ static VALUE
531
+ prof_paused(VALUE self)
532
+ {
533
+ prof_profile_t* profile = prof_get_profile(self);
534
+ return profile->paused;
535
+ }
536
+
537
+ /* call-seq:
538
+ running? -> boolean
539
+
540
+ Returns whether a profile is currently running.*/
541
+ static VALUE
542
+ prof_running(VALUE self)
543
+ {
544
+ prof_profile_t* profile = prof_get_profile(self);
545
+ return profile->running;
546
+ }
547
+
548
+ /* call-seq:
549
+ start -> self
550
+
551
+ Starts recording profile data.*/
552
+ static VALUE
553
+ prof_start(VALUE self)
554
+ {
555
+ char* trace_file_name;
556
+
557
+ prof_profile_t* profile = prof_get_profile(self);
558
+
559
+ if (profile->running == Qtrue)
560
+ {
561
+ rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
562
+ }
563
+
564
+ profile->running = Qtrue;
565
+ profile->paused = Qfalse;
566
+ profile->last_thread_data = NULL;
567
+
568
+
569
+ /* open trace file if environment wants it */
570
+ trace_file_name = getenv("RUBY_PROF_TRACE");
571
+ if (trace_file_name != NULL)
572
+ {
573
+ if (strcmp(trace_file_name, "stdout") == 0)
574
+ {
575
+ trace_file = stdout;
576
+ }
577
+ else if (strcmp(trace_file_name, "stderr") == 0)
578
+ {
579
+ trace_file = stderr;
580
+ }
581
+ else
582
+ {
583
+ trace_file = fopen(trace_file_name, "w");
584
+ }
585
+ }
586
+
587
+ prof_install_hook(self);
588
+ return self;
589
+ }
590
+
591
+ /* call-seq:
592
+ pause -> self
593
+
594
+ Pauses collecting profile data. */
595
+ static VALUE
596
+ prof_pause(VALUE self)
597
+ {
598
+ prof_profile_t* profile = prof_get_profile(self);
599
+ if (profile->running == Qfalse)
600
+ {
601
+ rb_raise(rb_eRuntimeError, "RubyProf is not running.");
602
+ }
603
+
604
+ if (profile->paused == Qfalse)
605
+ {
606
+ profile->paused = Qtrue;
607
+ prof_measurer_take_measurements(profile->measurer, profile->measurements_at_pause_resume);
608
+ st_foreach(profile->threads_tbl, pause_thread, (st_data_t) profile);
609
+ }
610
+
611
+ return self;
612
+ }
613
+
614
+ /* call-seq:
615
+ resume -> self
616
+ resume(&block) -> self
617
+
618
+ Resumes recording profile data.*/
619
+ static VALUE
620
+ prof_resume(VALUE self)
621
+ {
622
+ prof_profile_t* profile = prof_get_profile(self);
623
+ if (profile->running == Qfalse)
624
+ {
625
+ rb_raise(rb_eRuntimeError, "RubyProf is not running.");
626
+ }
627
+
628
+ if (profile->paused == Qtrue)
629
+ {
630
+ profile->paused = Qfalse;
631
+ prof_measurer_take_measurements(profile->measurer, profile->measurements_at_pause_resume);
632
+ st_foreach(profile->threads_tbl, unpause_thread, (st_data_t) profile);
633
+ }
634
+
635
+ return rb_block_given_p() ? rb_ensure(rb_yield, self, prof_pause, self) : self;
636
+ }
637
+
638
+ /* call-seq:
639
+ stop -> self
640
+
641
+ Stops collecting profile data.*/
642
+ static VALUE
643
+ prof_stop(VALUE self)
644
+ {
645
+ prof_profile_t* profile = prof_get_profile(self);
646
+
647
+ if (profile->running == Qfalse)
648
+ {
649
+ rb_raise(rb_eRuntimeError, "RubyProf.start was not yet called");
650
+ }
651
+
652
+ prof_remove_hook();
653
+
654
+ /* close trace file if open */
655
+ if (trace_file != NULL)
656
+ {
657
+ if (trace_file !=stderr && trace_file != stdout)
658
+ {
659
+ #ifdef _MSC_VER
660
+ _fcloseall();
661
+ #else
662
+ fclose(trace_file);
663
+ #endif
664
+ }
665
+ trace_file = NULL;
666
+ }
667
+
668
+ prof_pop_threads(profile);
669
+
670
+ /* Unset the last_thread_data (very important!)
671
+ and the threads table */
672
+ profile->running = profile->paused = Qfalse;
673
+ profile->last_thread_data = NULL;
674
+
675
+ return self;
676
+ }
677
+
678
+ /* call-seq:
679
+ threads -> Array of RubyProf::Thread
680
+
681
+ Returns an array of RubyProf::Thread instances that were executed
682
+ while the the program was being run. */
683
+ static VALUE
684
+ prof_threads(VALUE self)
685
+ {
686
+ VALUE result = rb_ary_new();
687
+ prof_profile_t* profile = prof_get_profile(self);
688
+ st_foreach(profile->threads_tbl, collect_threads, result);
689
+ return result;
690
+ }
691
+
692
+ /* call-seq:
693
+ profile(&block) -> self
694
+ profile(options, &block) -> self
695
+
696
+ Profiles the specified block and returns a RubyProf::Profile
697
+ object. Arguments are passed to Profile initialize method.
698
+ */
699
+ static VALUE
700
+ prof_profile_class(int argc, VALUE *argv, VALUE klass)
701
+ {
702
+ int result;
703
+ VALUE profile = rb_class_new_instance(argc, argv, cProfile);
704
+
705
+ if (!rb_block_given_p())
706
+ {
707
+ rb_raise(rb_eArgError, "A block must be provided to the profile method.");
708
+ }
709
+
710
+ prof_start(profile);
711
+ rb_protect(rb_yield, profile, &result);
712
+ return prof_stop(profile);
713
+ }
714
+
715
+ /* call-seq:
716
+ profile {block} -> RubyProf::Result
717
+
718
+ Profiles the specified block and returns a RubyProf::Result object. */
719
+ static VALUE
720
+ prof_profile_object(VALUE self)
721
+ {
722
+ int result;
723
+ if (!rb_block_given_p())
724
+ {
725
+ rb_raise(rb_eArgError, "A block must be provided to the profile method.");
726
+ }
727
+
728
+ prof_start(self);
729
+ rb_protect(rb_yield, self, &result);
730
+ return prof_stop(self);
731
+
732
+ }
733
+
734
+ static VALUE
735
+ prof_exclude_method(VALUE self, VALUE klass, VALUE sym)
736
+ {
737
+ prof_profile_t* profile = prof_get_profile(self);
738
+ ID mid = SYM2ID(sym);
739
+
740
+ prof_method_key_t key;
741
+ prof_method_t *method;
742
+
743
+ if (profile->running == Qtrue)
744
+ {
745
+ rb_raise(rb_eRuntimeError, "RubyProf.start was already called");
746
+ }
747
+
748
+ method_key(&key, klass, mid);
749
+ method = method_table_lookup(profile->exclude_methods_tbl, &key);
750
+
751
+ if (!method) {
752
+ method = prof_method_create_excluded(klass, mid);
753
+ method_table_insert(profile->exclude_methods_tbl, method->key, method);
754
+ }
755
+
756
+ return self;
757
+ }
758
+
759
+ static VALUE
760
+ prof_measure_modes(VALUE self)
761
+ {
762
+ prof_profile_t* profile = prof_get_profile(self);
763
+ VALUE ary = rb_ary_new2(profile->measurer->len);
764
+ for(size_t i = 0; i < profile->measurer->len; i++) {
765
+ rb_ary_store(ary, i, INT2NUM(profile->measurer->measure_modes[i]));
766
+ }
767
+
768
+ return ary;
769
+ }
770
+
771
+ void Init_ruby_prof()
772
+ {
773
+ mProf = rb_define_module("RubyProf");
774
+
775
+ rp_init_measure();
776
+ rp_init_method_info();
777
+ rp_init_call_info();
778
+ rp_init_thread();
779
+ rp_init_fast_call_tree_printer();
780
+
781
+ cProfile = rb_define_class_under(mProf, "Profile", rb_cObject);
782
+ rb_define_alloc_func (cProfile, prof_allocate);
783
+
784
+ rb_define_method(cProfile, "initialize", prof_initialize, 1);
785
+ rb_define_method(cProfile, "start", prof_start, 0);
786
+ rb_define_method(cProfile, "stop", prof_stop, 0);
787
+ rb_define_method(cProfile, "resume", prof_resume, 0);
788
+ rb_define_method(cProfile, "pause", prof_pause, 0);
789
+ rb_define_method(cProfile, "running?", prof_running, 0);
790
+ rb_define_method(cProfile, "paused?", prof_paused, 0);
791
+ rb_define_method(cProfile, "threads", prof_threads, 0);
792
+ rb_define_method(cProfile, "measure_modes", prof_measure_modes, 0);
793
+
794
+ rb_define_singleton_method(cProfile, "profile", prof_profile_class, -1);
795
+ rb_define_method(cProfile, "profile", prof_profile_object, 0);
796
+
797
+ rb_define_method(cProfile, "exclude_method!", prof_exclude_method, 2);
798
+
799
+ cExcludeCommonMethods = rb_define_class_under(cProfile, "ExcludeCommonMethods", rb_cObject);
800
+ }