ruby-prof 0.18.0 → 1.2.0

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