ruby-prof 1.6.1-x64-mingw-ucrt → 1.6.3-x64-mingw-ucrt

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