ruby-prof 1.0.0 → 1.1.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.
- checksums.yaml +4 -4
- data/CHANGES +9 -0
- data/ext/ruby_prof/rp_allocation.c +5 -18
- data/ext/ruby_prof/rp_call_info.c +7 -19
- data/ext/ruby_prof/rp_measure_process_time.c +10 -6
- data/ext/ruby_prof/rp_measurement.c +12 -18
- data/ext/ruby_prof/rp_measurement.h +1 -0
- data/ext/ruby_prof/rp_method.c +8 -20
- data/ext/ruby_prof/rp_profile.c +18 -4
- data/ext/ruby_prof/rp_profile.h +1 -0
- data/ext/ruby_prof/rp_thread.c +12 -13
- data/ext/ruby_prof/vc/ruby_prof.vcxproj +2 -2
- data/lib/ruby-prof.rb +0 -1
- data/lib/ruby-prof/assets/call_stack_printer.html.erb +713 -0
- data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
- data/lib/ruby-prof/printers/abstract_printer.rb +9 -0
- data/lib/ruby-prof/printers/call_stack_printer.rb +43 -130
- data/lib/ruby-prof/printers/graph_html_printer.rb +1 -2
- data/lib/ruby-prof/version.rb +1 -1
- data/test/fiber_test.rb +73 -0
- data/test/gc_test.rb +96 -0
- data/test/printer_call_stack_test.rb +28 -0
- data/test/printer_graph_html_test.rb +1 -1
- metadata +7 -5
- data/lib/ruby-prof/assets/call_stack_printer.css.html +0 -117
- data/lib/ruby-prof/assets/call_stack_printer.js.html +0 -385
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d37fba2001af9994daa9e1c13e316d0a4fe069c769e431915ef573413241203f
|
4
|
+
data.tar.gz: f3e9ff45b63fd26c76fd73cca3f20b67a400c39a1183b135364ce89386a51096
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e4e378eef6217b03f62497c38fc765275aab3176e0559449b62e68b2f0e38931dde75ffafb28c01bebf44dc9bfed86a3664c891bdb9dc49f10bb4a1228c28d3
|
7
|
+
data.tar.gz: 4bb4a9a4c41549e5618e3b0cb4e8d7aa14163dfa6b742c739b0c1be16196eb7035bd0f9d9e8ceaf0be7b5d9127acbaec4811094835661a2583bcdf4a5c0adbfa
|
data/CHANGES
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
1.1.0 (2019-12-14)
|
2
|
+
=====================
|
3
|
+
Changes
|
4
|
+
|
5
|
+
* Reimplement merge_fibers (Charlie Savage)
|
6
|
+
* Fix crash caused by threads being freed when profiles are freed (Charlie Savage)
|
7
|
+
|
1
8
|
1.0.0 (2019-07-29)
|
2
9
|
=====================
|
3
10
|
ruby-prof's development stretches all the way back to 2005. Fourteen years later, it seems time for version 1.0!
|
@@ -7,6 +14,8 @@ Changes (Charlie Savage):
|
|
7
14
|
|
8
15
|
* Profiling is significantly faster - 5x in some cases
|
9
16
|
* Recursive profiles are finally handled correctly. Yeah!!!
|
17
|
+
* Redesigned reports (Chirs Whitefield)
|
18
|
+
* New documentation website (https://ruby-prof.github.io)
|
10
19
|
* The ability to measure allocations and memory usage using a standard (unpatched) version of ruby
|
11
20
|
* The ability to save and reload profiling results for later analysis
|
12
21
|
* The ability track object allocations
|
@@ -83,15 +83,15 @@ prof_allocation_ruby_gc_free(void *data)
|
|
83
83
|
{
|
84
84
|
prof_allocation_t* allocation = (prof_allocation_t*)data;
|
85
85
|
|
86
|
-
/* Has this
|
86
|
+
/* Has this allocation object been accessed by Ruby? If
|
87
87
|
yes clean it up so to avoid a segmentation fault. */
|
88
88
|
if (allocation->object != Qnil)
|
89
89
|
{
|
90
|
-
RDATA(allocation->object)->data = NULL;
|
91
|
-
RDATA(allocation->object)->dfree = NULL;
|
92
90
|
RDATA(allocation->object)->dmark = NULL;
|
91
|
+
RDATA(allocation->object)->dfree = NULL;
|
92
|
+
RDATA(allocation->object)->data = NULL;
|
93
|
+
allocation->object = Qnil;
|
93
94
|
}
|
94
|
-
allocation->object = Qnil;
|
95
95
|
}
|
96
96
|
|
97
97
|
void
|
@@ -124,25 +124,12 @@ prof_allocation_mark(void *data)
|
|
124
124
|
rb_gc_mark(allocation->source_file);
|
125
125
|
}
|
126
126
|
|
127
|
-
static const rb_data_type_t allocation_type =
|
128
|
-
{
|
129
|
-
.wrap_struct_name = "Allocation",
|
130
|
-
.function =
|
131
|
-
{
|
132
|
-
.dmark = prof_allocation_mark,
|
133
|
-
.dfree = prof_allocation_ruby_gc_free,
|
134
|
-
.dsize = prof_allocation_size,
|
135
|
-
},
|
136
|
-
.data = NULL,
|
137
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
138
|
-
};
|
139
|
-
|
140
127
|
VALUE
|
141
128
|
prof_allocation_wrap(prof_allocation_t *allocation)
|
142
129
|
{
|
143
130
|
if (allocation->object == Qnil)
|
144
131
|
{
|
145
|
-
allocation->object =
|
132
|
+
allocation->object = Data_Wrap_Struct(cRpAllocation, prof_allocation_mark , prof_allocation_ruby_gc_free, allocation);
|
146
133
|
}
|
147
134
|
return allocation->object;
|
148
135
|
}
|
@@ -31,20 +31,21 @@ prof_call_info_ruby_gc_free(void *data)
|
|
31
31
|
{
|
32
32
|
prof_call_info_t *call_info = (prof_call_info_t*)data;
|
33
33
|
|
34
|
-
/* Has this
|
34
|
+
/* Has this call info object been accessed by Ruby? If
|
35
35
|
yes clean it up so to avoid a segmentation fault. */
|
36
36
|
if (call_info->object != Qnil)
|
37
37
|
{
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
RDATA(call_info->object)->dmark = NULL;
|
39
|
+
RDATA(call_info->object)->dfree = NULL;
|
40
|
+
RDATA(call_info->object)->data = NULL;
|
41
|
+
call_info->object = Qnil;
|
41
42
|
}
|
42
|
-
call_info->object = Qnil;
|
43
43
|
}
|
44
44
|
|
45
45
|
void
|
46
46
|
prof_call_info_free(prof_call_info_t *call_info)
|
47
47
|
{
|
48
|
+
prof_measurement_free(call_info->measurement);
|
48
49
|
prof_call_info_ruby_gc_free(call_info);
|
49
50
|
xfree(call_info);
|
50
51
|
}
|
@@ -75,25 +76,12 @@ prof_call_info_mark(void *data)
|
|
75
76
|
prof_measurement_mark(call_info->measurement);
|
76
77
|
}
|
77
78
|
|
78
|
-
static const rb_data_type_t call_info_type =
|
79
|
-
{
|
80
|
-
.wrap_struct_name = "CallInfo",
|
81
|
-
.function =
|
82
|
-
{
|
83
|
-
.dmark = prof_call_info_mark,
|
84
|
-
.dfree = prof_call_info_ruby_gc_free,
|
85
|
-
.dsize = prof_call_info_size,
|
86
|
-
},
|
87
|
-
.data = NULL,
|
88
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
89
|
-
};
|
90
|
-
|
91
79
|
VALUE
|
92
80
|
prof_call_info_wrap(prof_call_info_t *call_info)
|
93
81
|
{
|
94
82
|
if (call_info->object == Qnil)
|
95
83
|
{
|
96
|
-
call_info->object =
|
84
|
+
call_info->object = Data_Wrap_Struct(cRpCallnfo, prof_call_info_mark, prof_call_info_ruby_gc_free, call_info);
|
97
85
|
}
|
98
86
|
return call_info->object;
|
99
87
|
}
|
@@ -15,17 +15,21 @@ measure_process_time(rb_trace_arg_t* trace_arg)
|
|
15
15
|
FILETIME sysTime;
|
16
16
|
FILETIME userTime;
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
ULARGE_INTEGER sysTimeInt;
|
19
|
+
ULARGE_INTEGER userTimeInt;
|
20
20
|
|
21
|
-
|
21
|
+
GetProcessTimes(GetCurrentProcess(), &createTime, &exitTime, &sysTime, &userTime);
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
sysTimeInt.LowPart = sysTime.dwLowDateTime;
|
24
|
+
sysTimeInt.HighPart = sysTime.dwHighDateTime;
|
25
25
|
userTimeInt.LowPart = userTime.dwLowDateTime;
|
26
26
|
userTimeInt.HighPart = userTime.dwHighDateTime;
|
27
27
|
|
28
|
-
|
28
|
+
return sysTimeInt.QuadPart + userTimeInt.QuadPart;
|
29
|
+
#elif !defined(CLOCK_PROCESS_CPUTIME_ID)
|
30
|
+
struct rusage usage;
|
31
|
+
getrusage(RUSAGE_SELF, &usage);
|
32
|
+
return usage.ru_stime.tv_sec + usage.ru_utime.tv_sec + ((usage.ru_stime.tv_usec + usage.ru_utime.tv_usec)/1000000.0);
|
29
33
|
#else
|
30
34
|
struct timespec clock;
|
31
35
|
clock_gettime(CLOCK_PROCESS_CPUTIME_ID , &clock);
|
@@ -56,15 +56,22 @@ prof_measurement_ruby_gc_free(void *data)
|
|
56
56
|
{
|
57
57
|
prof_measurement_t* measurement = (prof_measurement_t*)data;
|
58
58
|
|
59
|
-
/* Has this
|
59
|
+
/* Has this measurement object been accessed by Ruby? If
|
60
60
|
yes clean it up so to avoid a segmentation fault. */
|
61
61
|
if (measurement->object != Qnil)
|
62
62
|
{
|
63
|
-
RDATA(measurement->object)->data = NULL;
|
64
|
-
RDATA(measurement->object)->dfree = NULL;
|
65
63
|
RDATA(measurement->object)->dmark = NULL;
|
64
|
+
RDATA(measurement->object)->dfree = NULL;
|
65
|
+
RDATA(measurement->object)->data = NULL;
|
66
|
+
measurement->object = Qnil;
|
66
67
|
}
|
67
|
-
|
68
|
+
}
|
69
|
+
|
70
|
+
void
|
71
|
+
prof_measurement_free(prof_measurement_t* measurement)
|
72
|
+
{
|
73
|
+
prof_measurement_ruby_gc_free(measurement);
|
74
|
+
xfree(measurement);
|
68
75
|
}
|
69
76
|
|
70
77
|
size_t
|
@@ -81,25 +88,12 @@ prof_measurement_mark(void *data)
|
|
81
88
|
rb_gc_mark(measurement->object);
|
82
89
|
}
|
83
90
|
|
84
|
-
static const rb_data_type_t measurement_type =
|
85
|
-
{
|
86
|
-
.wrap_struct_name = "Measurement",
|
87
|
-
.function =
|
88
|
-
{
|
89
|
-
.dmark = prof_measurement_mark,
|
90
|
-
.dfree = prof_measurement_ruby_gc_free,
|
91
|
-
.dsize = prof_measurement_size,
|
92
|
-
},
|
93
|
-
.data = NULL,
|
94
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
95
|
-
};
|
96
|
-
|
97
91
|
VALUE
|
98
92
|
prof_measurement_wrap(prof_measurement_t* measurement)
|
99
93
|
{
|
100
94
|
if (measurement->object == Qnil)
|
101
95
|
{
|
102
|
-
measurement->object =
|
96
|
+
measurement->object = Data_Wrap_Struct(cRpMeasurement, prof_measurement_mark, prof_measurement_ruby_gc_free, measurement);
|
103
97
|
}
|
104
98
|
return measurement->object;
|
105
99
|
}
|
@@ -40,6 +40,7 @@ prof_measurer_t *prof_get_measurer(prof_measure_mode_t measure, bool track_alloc
|
|
40
40
|
double prof_measure(prof_measurer_t *measurer, rb_trace_arg_t* trace_arg);
|
41
41
|
|
42
42
|
prof_measurement_t *prof_measurement_create(void);
|
43
|
+
void prof_measurement_free(prof_measurement_t* measurement);
|
43
44
|
VALUE prof_measurement_wrap(prof_measurement_t *measurement);
|
44
45
|
prof_measurement_t* prof_get_measurement(VALUE self);
|
45
46
|
void prof_measurement_mark(void *data);
|
data/ext/ruby_prof/rp_method.c
CHANGED
@@ -237,15 +237,15 @@ prof_method_ruby_gc_free(void *data)
|
|
237
237
|
{
|
238
238
|
prof_method_t* method = (prof_method_t*)data;
|
239
239
|
|
240
|
-
/* Has this
|
240
|
+
/* Has this method object been accessed by Ruby? If
|
241
241
|
yes clean it up so to avoid a segmentation fault. */
|
242
242
|
if (method->object != Qnil)
|
243
243
|
{
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
244
|
+
RDATA(method->object)->dmark = NULL;
|
245
|
+
RDATA(method->object)->dfree = NULL;
|
246
|
+
RDATA(method->object)->data = NULL;
|
247
|
+
method->object = Qnil;
|
248
|
+
}
|
249
249
|
method->klass_name = Qnil;
|
250
250
|
method->method_name = Qnil;
|
251
251
|
}
|
@@ -261,6 +261,7 @@ prof_method_free(prof_method_t* method)
|
|
261
261
|
call_info_table_free(method->parent_call_infos);
|
262
262
|
xfree(method->child_call_infos);
|
263
263
|
|
264
|
+
prof_measurement_free(method->measurement);
|
264
265
|
xfree(method);
|
265
266
|
}
|
266
267
|
|
@@ -290,19 +291,6 @@ prof_method_mark(void *data)
|
|
290
291
|
st_foreach(method->allocations_table, prof_method_mark_allocations, 0);
|
291
292
|
}
|
292
293
|
|
293
|
-
static const rb_data_type_t method_info_type =
|
294
|
-
{
|
295
|
-
.wrap_struct_name = "MethodInfo",
|
296
|
-
.function =
|
297
|
-
{
|
298
|
-
.dmark = prof_method_mark,
|
299
|
-
.dfree = prof_method_ruby_gc_free,
|
300
|
-
.dsize = prof_method_size,
|
301
|
-
},
|
302
|
-
.data = NULL,
|
303
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
304
|
-
};
|
305
|
-
|
306
294
|
static VALUE
|
307
295
|
prof_method_allocate(VALUE klass)
|
308
296
|
{
|
@@ -316,7 +304,7 @@ prof_method_wrap(prof_method_t *method)
|
|
316
304
|
{
|
317
305
|
if (method->object == Qnil)
|
318
306
|
{
|
319
|
-
method->object =
|
307
|
+
method->object = Data_Wrap_Struct(cRpMethodInfo, prof_method_mark, prof_method_ruby_gc_free, method);
|
320
308
|
}
|
321
309
|
return method->object;
|
322
310
|
}
|
data/ext/ruby_prof/rp_profile.c
CHANGED
@@ -101,13 +101,20 @@ get_event_name(rb_event_flag_t event)
|
|
101
101
|
}
|
102
102
|
}
|
103
103
|
|
104
|
+
VALUE get_fiber(prof_profile_t* profile)
|
105
|
+
{
|
106
|
+
if (profile->merge_fibers)
|
107
|
+
return rb_thread_current();
|
108
|
+
else
|
109
|
+
return rb_fiber_current();
|
110
|
+
}
|
104
111
|
/* =========== Profiling ================= */
|
105
112
|
thread_data_t* check_fiber(prof_profile_t *profile, double measurement)
|
106
113
|
{
|
107
114
|
thread_data_t* result = NULL;
|
108
115
|
|
109
116
|
/* Get the current thread and fiber information. */
|
110
|
-
VALUE fiber =
|
117
|
+
VALUE fiber = get_fiber(profile);
|
111
118
|
|
112
119
|
/* We need to switch the profiling context if we either had none before,
|
113
120
|
we don't merge fibers and the fiber ids differ, or the thread ids differ. */
|
@@ -131,7 +138,7 @@ static void
|
|
131
138
|
prof_trace(prof_profile_t* profile, rb_trace_arg_t *trace_arg, double measurement)
|
132
139
|
{
|
133
140
|
static VALUE last_fiber = Qnil;
|
134
|
-
VALUE fiber =
|
141
|
+
VALUE fiber = get_fiber(profile);
|
135
142
|
|
136
143
|
rb_event_flag_t event = rb_tracearg_event_flag(trace_arg);
|
137
144
|
const char* event_name = get_event_name(event);
|
@@ -432,7 +439,8 @@ prof_allocate(VALUE klass)
|
|
432
439
|
profile->exclude_threads_tbl = NULL;
|
433
440
|
profile->include_threads_tbl = NULL;
|
434
441
|
profile->running = Qfalse;
|
435
|
-
profile->allow_exceptions =
|
442
|
+
profile->allow_exceptions = false;
|
443
|
+
profile->merge_fibers = false;
|
436
444
|
profile->exclude_methods_tbl = method_table_create();
|
437
445
|
profile->running = Qfalse;
|
438
446
|
profile->tracepoints = rb_ary_new();
|
@@ -479,6 +487,9 @@ prof_stop_threads(prof_profile_t* profile)
|
|
479
487
|
all other threads.
|
480
488
|
allow_exceptions:: Whether to raise exceptions encountered during profiling,
|
481
489
|
or to suppress all exceptions during profiling
|
490
|
+
merge_fibers:: Whether profiling data for a given thread's fibers should all be
|
491
|
+
subsumed under a single entry. Basically only useful to produce
|
492
|
+
callgrind profiles.
|
482
493
|
*/
|
483
494
|
static VALUE
|
484
495
|
prof_initialize(int argc, VALUE *argv, VALUE self)
|
@@ -490,6 +501,7 @@ prof_initialize(int argc, VALUE *argv, VALUE self)
|
|
490
501
|
VALUE include_threads = Qnil;
|
491
502
|
VALUE exclude_common = Qnil;
|
492
503
|
VALUE allow_exceptions = Qfalse;
|
504
|
+
VALUE merge_fibers = Qfalse;
|
493
505
|
VALUE track_allocations = Qfalse;
|
494
506
|
|
495
507
|
int i;
|
@@ -509,6 +521,7 @@ prof_initialize(int argc, VALUE *argv, VALUE self)
|
|
509
521
|
mode = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("measure_mode")));
|
510
522
|
track_allocations = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("track_allocations")));
|
511
523
|
allow_exceptions = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("allow_exceptions")));
|
524
|
+
merge_fibers = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("merge_fibers")));
|
512
525
|
exclude_common = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("exclude_common")));
|
513
526
|
exclude_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("exclude_threads")));
|
514
527
|
include_threads = rb_hash_aref(mode_or_options, ID2SYM(rb_intern("include_threads")));
|
@@ -529,6 +542,7 @@ prof_initialize(int argc, VALUE *argv, VALUE self)
|
|
529
542
|
}
|
530
543
|
profile->measurer = prof_get_measurer(NUM2INT(mode), track_allocations == Qtrue);
|
531
544
|
profile->allow_exceptions = (allow_exceptions == Qtrue);
|
545
|
+
profile->merge_fibers = (merge_fibers == Qtrue);
|
532
546
|
|
533
547
|
if (exclude_threads != Qnil)
|
534
548
|
{
|
@@ -624,7 +638,7 @@ prof_start(VALUE self)
|
|
624
638
|
|
625
639
|
profile->running = Qtrue;
|
626
640
|
profile->paused = Qfalse;
|
627
|
-
profile->last_thread_data = threads_table_insert(profile,
|
641
|
+
profile->last_thread_data = threads_table_insert(profile, get_fiber(profile));
|
628
642
|
|
629
643
|
/* open trace file if environment wants it */
|
630
644
|
trace_file_name = getenv("RUBY_PROF_TRACE");
|
data/ext/ruby_prof/rp_profile.h
CHANGED
data/ext/ruby_prof/rp_thread.c
CHANGED
@@ -41,9 +41,20 @@ thread_data_create(void)
|
|
41
41
|
static void
|
42
42
|
prof_thread_free(thread_data_t* thread_data)
|
43
43
|
{
|
44
|
+
/* Has this method object been accessed by Ruby? If
|
45
|
+
yes then set its data to nil to avoid a segmentation fault on the next mark and sweep. */
|
46
|
+
if (thread_data->object != Qnil)
|
47
|
+
{
|
48
|
+
RDATA(thread_data->object)->dmark = NULL;
|
49
|
+
RDATA(thread_data->object)->dfree = NULL;
|
50
|
+
RDATA(thread_data->object)->data = NULL;
|
51
|
+
thread_data->object = Qnil;
|
52
|
+
}
|
53
|
+
|
44
54
|
method_table_free(thread_data->method_table);
|
45
55
|
prof_stack_free(thread_data->stack);
|
46
56
|
|
57
|
+
|
47
58
|
xfree(thread_data);
|
48
59
|
}
|
49
60
|
|
@@ -81,25 +92,13 @@ prof_thread_mark(void *data)
|
|
81
92
|
st_foreach(thread->method_table, mark_methods, 0);
|
82
93
|
}
|
83
94
|
|
84
|
-
static const rb_data_type_t thread_type =
|
85
|
-
{
|
86
|
-
.wrap_struct_name = "ThreadInfo",
|
87
|
-
.function =
|
88
|
-
{
|
89
|
-
.dmark = prof_thread_mark,
|
90
|
-
.dfree = NULL, /* The profile class frees its thread table which frees each underlying thread_data instance */
|
91
|
-
.dsize = prof_thread_size,
|
92
|
-
},
|
93
|
-
.data = NULL,
|
94
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY
|
95
|
-
};
|
96
95
|
|
97
96
|
VALUE
|
98
97
|
prof_thread_wrap(thread_data_t *thread)
|
99
98
|
{
|
100
99
|
if (thread->object == Qnil)
|
101
100
|
{
|
102
|
-
thread->object =
|
101
|
+
thread->object = Data_Wrap_Struct(cRpThread, prof_thread_mark, NULL, thread);
|
103
102
|
}
|
104
103
|
return thread->object;
|
105
104
|
}
|
@@ -102,12 +102,12 @@
|
|
102
102
|
</ItemDefinitionGroup>
|
103
103
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
104
104
|
<ClCompile>
|
105
|
-
<AdditionalIncludeDirectories>C:\msys64\usr\local\ruby-2.6.
|
105
|
+
<AdditionalIncludeDirectories>C:\msys64\usr\local\ruby-2.6.5vc\include\ruby-2.6.0\x64-mswin64_140;C:\msys64\usr\local\ruby-2.6.5vc\include\ruby-2.6.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
106
106
|
<Optimization>Disabled</Optimization>
|
107
107
|
<PreprocessorDefinitions>HAVE_RB_TRACEARG_CALLEE_ID;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
108
108
|
</ClCompile>
|
109
109
|
<Link>
|
110
|
-
<AdditionalLibraryDirectories>C:\msys64\usr\local\ruby-2.6.
|
110
|
+
<AdditionalLibraryDirectories>C:\msys64\usr\local\ruby-2.6.5vc\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
111
111
|
<AdditionalDependencies>x64-vcruntime140-ruby260.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
112
112
|
<ModuleDefinitionFile>ruby_prof.def</ModuleDefinitionFile>
|
113
113
|
</Link>
|
data/lib/ruby-prof.rb
CHANGED
@@ -18,7 +18,6 @@ require 'ruby-prof/rack'
|
|
18
18
|
require 'ruby-prof/thread'
|
19
19
|
|
20
20
|
module RubyProf
|
21
|
-
autoload :AggregateCallInfo, 'ruby-prof/aggregate_call_info'
|
22
21
|
autoload :CallInfoVisitor, 'ruby-prof/call_info_visitor'
|
23
22
|
|
24
23
|
autoload :AbstractPrinter, 'ruby-prof/printers/abstract_printer'
|
@@ -0,0 +1,713 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
5
|
+
<title>ruby-prof call tree</title>
|
6
|
+
<style type="text/css">
|
7
|
+
body {
|
8
|
+
font-size: 70%;
|
9
|
+
padding: 0;
|
10
|
+
margin: 5px;
|
11
|
+
margin-right: 0px;
|
12
|
+
margin-left: 0px;
|
13
|
+
background: #ffffff;
|
14
|
+
}
|
15
|
+
|
16
|
+
ul {
|
17
|
+
margin-left: 0px;
|
18
|
+
margin-top: 0px;
|
19
|
+
margin-bottom: 0px;
|
20
|
+
padding-left: 0px;
|
21
|
+
list-style-type: none;
|
22
|
+
}
|
23
|
+
|
24
|
+
li {
|
25
|
+
margin-left: 11px;
|
26
|
+
padding: 0px;
|
27
|
+
white-space: nowrap;
|
28
|
+
border-top: 1px solid #cccccc;
|
29
|
+
border-left: 1px solid #cccccc;
|
30
|
+
border-bottom: none;
|
31
|
+
}
|
32
|
+
|
33
|
+
.thread {
|
34
|
+
margin-left: 11px;
|
35
|
+
background: #708090;
|
36
|
+
padding-top: 3px;
|
37
|
+
padding-left: 12px;
|
38
|
+
padding-bottom: 2px;
|
39
|
+
border-left: 1px solid #CCCCCC;
|
40
|
+
border-top: 1px solid #CCCCCC;
|
41
|
+
font-weight: bold;
|
42
|
+
}
|
43
|
+
|
44
|
+
.hidden {
|
45
|
+
display: none;
|
46
|
+
width: 0px;
|
47
|
+
height: 0px;
|
48
|
+
margin: 0px;
|
49
|
+
padding: 0px;
|
50
|
+
border-style: none;
|
51
|
+
}
|
52
|
+
|
53
|
+
.color01 {
|
54
|
+
background: #adbdeb
|
55
|
+
}
|
56
|
+
|
57
|
+
.color05 {
|
58
|
+
background: #9daddb
|
59
|
+
}
|
60
|
+
|
61
|
+
.color0 {
|
62
|
+
background: #8d9dcb
|
63
|
+
}
|
64
|
+
|
65
|
+
.color1 {
|
66
|
+
background: #89bccb
|
67
|
+
}
|
68
|
+
|
69
|
+
.color2 {
|
70
|
+
background: #56e3e7
|
71
|
+
}
|
72
|
+
|
73
|
+
.color3 {
|
74
|
+
background: #32cd70
|
75
|
+
}
|
76
|
+
|
77
|
+
.color4 {
|
78
|
+
background: #a3d53c
|
79
|
+
}
|
80
|
+
|
81
|
+
.color5 {
|
82
|
+
background: #c4cb34
|
83
|
+
}
|
84
|
+
|
85
|
+
.color6 {
|
86
|
+
background: #dcb66d
|
87
|
+
}
|
88
|
+
|
89
|
+
.color7 {
|
90
|
+
background: #cda59e
|
91
|
+
}
|
92
|
+
|
93
|
+
.color8 {
|
94
|
+
background: #be9d9c
|
95
|
+
}
|
96
|
+
|
97
|
+
.color9 {
|
98
|
+
background: #cf947a
|
99
|
+
}
|
100
|
+
|
101
|
+
#commands {
|
102
|
+
font-size: 10pt;
|
103
|
+
padding: 10px;
|
104
|
+
margin-left: 11px;
|
105
|
+
margin-bottom: 0px;
|
106
|
+
margin-top: 0px;
|
107
|
+
background: #708090;
|
108
|
+
border-top: 1px solid #cccccc;
|
109
|
+
border-left: 1px solid #cccccc;
|
110
|
+
border-bottom: none;
|
111
|
+
}
|
112
|
+
|
113
|
+
#titlebar {
|
114
|
+
font-size: 10pt;
|
115
|
+
padding: 10px;
|
116
|
+
margin-left: 11px;
|
117
|
+
margin-bottom: 0px;
|
118
|
+
margin-top: 10px;
|
119
|
+
background: #8090a0;
|
120
|
+
border-top: 1px solid #cccccc;
|
121
|
+
border-left: 1px solid #cccccc;
|
122
|
+
border-bottom: none;
|
123
|
+
}
|
124
|
+
|
125
|
+
#help {
|
126
|
+
font-size: 10pt;
|
127
|
+
padding: 10px;
|
128
|
+
margin-left: 11px;
|
129
|
+
margin-bottom: 0px;
|
130
|
+
margin-top: 0px;
|
131
|
+
background: #8090a0;
|
132
|
+
display: none;
|
133
|
+
border-top: 1px solid #cccccc;
|
134
|
+
border-left: 1px solid #cccccc;
|
135
|
+
border-bottom: none;
|
136
|
+
}
|
137
|
+
|
138
|
+
#sentinel {
|
139
|
+
height: 400px;
|
140
|
+
margin-left: 11px;
|
141
|
+
background: #8090a0;
|
142
|
+
border-top: 1px solid #cccccc;
|
143
|
+
border-left: 1px solid #cccccc;
|
144
|
+
border-bottom: none;
|
145
|
+
}
|
146
|
+
|
147
|
+
input {
|
148
|
+
margin-left: 10px;
|
149
|
+
}
|
150
|
+
|
151
|
+
.toggle {
|
152
|
+
background: url(data:image/png;base64,<%= base64_image %>) no-repeat left center;
|
153
|
+
float: left;
|
154
|
+
width: 9px;
|
155
|
+
height: 9px;
|
156
|
+
margin: 2px 1px 1px 1px;
|
157
|
+
}
|
158
|
+
|
159
|
+
.toggle.minus {
|
160
|
+
background-position: -9px 0;
|
161
|
+
}
|
162
|
+
|
163
|
+
.toggle.plus {
|
164
|
+
background-position: -18px 0;
|
165
|
+
}
|
166
|
+
</style>
|
167
|
+
|
168
|
+
<script type="text/javascript">
|
169
|
+
function rootNode()
|
170
|
+
{
|
171
|
+
return currentThread
|
172
|
+
}
|
173
|
+
|
174
|
+
function showUL(node, show)
|
175
|
+
{
|
176
|
+
Array.prototype.forEach.call(node.childNodes, function(child)
|
177
|
+
{
|
178
|
+
if (child.nodeName == 'LI')
|
179
|
+
toggle(child, show)
|
180
|
+
})
|
181
|
+
}
|
182
|
+
|
183
|
+
function findUlChild(li)
|
184
|
+
{
|
185
|
+
var ul = li.childNodes[2]
|
186
|
+
while (ul && ul.nodeName != "UL")
|
187
|
+
{
|
188
|
+
ul = ul.nextSibling
|
189
|
+
}
|
190
|
+
return ul
|
191
|
+
}
|
192
|
+
|
193
|
+
function isLeafNode(li)
|
194
|
+
{
|
195
|
+
var element = li.querySelector('a')
|
196
|
+
return element.classList.contains('empty')
|
197
|
+
}
|
198
|
+
|
199
|
+
function toggle(li, show)
|
200
|
+
{
|
201
|
+
if (isLeafNode(li))
|
202
|
+
return
|
203
|
+
|
204
|
+
var img = li.firstChild
|
205
|
+
img.className = 'toggle '
|
206
|
+
img.className += show ? 'minus' : 'plus'
|
207
|
+
|
208
|
+
var ul = findUlChild(li)
|
209
|
+
if (ul)
|
210
|
+
{
|
211
|
+
ul.style.display = show ? 'block' : 'none'
|
212
|
+
showUL(ul, true)
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
function toggleLI(li)
|
217
|
+
{
|
218
|
+
var img = li.firstChild
|
219
|
+
if (img.className.indexOf("minus") > -1)
|
220
|
+
toggle(li, false)
|
221
|
+
else
|
222
|
+
{
|
223
|
+
if (img.className.indexOf("plus") > -1)
|
224
|
+
toggle(li, true)
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
228
|
+
function aboveThreshold(text, threshold)
|
229
|
+
{
|
230
|
+
var match = text.match(/\d+[.,]\d+%/)
|
231
|
+
if (!match)
|
232
|
+
{
|
233
|
+
return true
|
234
|
+
}
|
235
|
+
else
|
236
|
+
{
|
237
|
+
var value = parseFloat(match[0].replace(/,/, '.'))
|
238
|
+
return value >= threshold
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
function setThresholdLI(li, threshold)
|
243
|
+
{
|
244
|
+
var a = li.querySelector('a')
|
245
|
+
var span = li.querySelector('span')
|
246
|
+
var ul = li.querySelector('ul')
|
247
|
+
|
248
|
+
var visible = aboveThreshold(span.textContent, threshold) ? 1 : 0
|
249
|
+
|
250
|
+
var count = 0
|
251
|
+
if (ul)
|
252
|
+
{
|
253
|
+
count = setThresholdUL(ul, threshold)
|
254
|
+
}
|
255
|
+
|
256
|
+
if (count > 0)
|
257
|
+
{
|
258
|
+
a.className = 'toggle minus'
|
259
|
+
}
|
260
|
+
else
|
261
|
+
{
|
262
|
+
a.className = 'toggle empty'
|
263
|
+
}
|
264
|
+
|
265
|
+
if (visible)
|
266
|
+
{
|
267
|
+
li.style.display = 'block'
|
268
|
+
} else
|
269
|
+
{
|
270
|
+
li.style.display = 'none'
|
271
|
+
}
|
272
|
+
return visible
|
273
|
+
}
|
274
|
+
|
275
|
+
function setThresholdUL(node, threshold)
|
276
|
+
{
|
277
|
+
var count = 0
|
278
|
+
Array.prototype.forEach.call(node.childNodes, function(child)
|
279
|
+
{
|
280
|
+
if (child.nodeName == 'LI')
|
281
|
+
count = count + setThresholdLI(child, threshold)
|
282
|
+
})
|
283
|
+
|
284
|
+
var visible = (count > 0) ? 1 : 0
|
285
|
+
if (visible)
|
286
|
+
{
|
287
|
+
node.style.display = 'block'
|
288
|
+
} else
|
289
|
+
{
|
290
|
+
node.style.display = 'none'
|
291
|
+
}
|
292
|
+
return visible
|
293
|
+
}
|
294
|
+
|
295
|
+
function toggleChildren(img, event)
|
296
|
+
{
|
297
|
+
event.cancelBubble = true
|
298
|
+
if (img.className.indexOf('empty') > -1)
|
299
|
+
return
|
300
|
+
|
301
|
+
var minus = (img.className.indexOf('minus') > -1)
|
302
|
+
|
303
|
+
if (minus)
|
304
|
+
{
|
305
|
+
img.className = 'toggle plus'
|
306
|
+
} else
|
307
|
+
img.className = 'toggle minus'
|
308
|
+
|
309
|
+
var li = img.parentNode
|
310
|
+
var ul = findUlChild(li)
|
311
|
+
if (ul)
|
312
|
+
{
|
313
|
+
if (minus)
|
314
|
+
ul.style.display = 'none'
|
315
|
+
else
|
316
|
+
ul.style.display = 'block'
|
317
|
+
}
|
318
|
+
if (minus)
|
319
|
+
moveSelectionIfNecessary(li)
|
320
|
+
}
|
321
|
+
|
322
|
+
function showChildren(li)
|
323
|
+
{
|
324
|
+
var img = li.firstChild
|
325
|
+
if (img.className.indexOf('empty') > -1)
|
326
|
+
return
|
327
|
+
img.className = 'toggle minus'
|
328
|
+
|
329
|
+
var ul = findUlChild(li)
|
330
|
+
if (ul)
|
331
|
+
{
|
332
|
+
ul.style.display = 'block'
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
function setThreshold()
|
337
|
+
{
|
338
|
+
var tv = document.getElementById("threshold").value
|
339
|
+
if (tv.match(/[0-9]+([.,][0-9]+)?/))
|
340
|
+
{
|
341
|
+
var f = parseFloat(tv.replace(/,/, '.'))
|
342
|
+
var threads = document.getElementsByName("thread")
|
343
|
+
var l = threads.length
|
344
|
+
for (var i = 0; i < l; i++)
|
345
|
+
{
|
346
|
+
setThresholdUL(threads[i], f)
|
347
|
+
}
|
348
|
+
var p = selectedNode
|
349
|
+
while (p && p.style.display == 'none')
|
350
|
+
p = p.parentNode.parentNode
|
351
|
+
if (p && p.nodeName == "LI")
|
352
|
+
selectNode(p)
|
353
|
+
} else
|
354
|
+
{
|
355
|
+
alert("Please specify a decimal number as threshold value!")
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
function expandAll(event)
|
360
|
+
{
|
361
|
+
toggleAll(event, true)
|
362
|
+
}
|
363
|
+
|
364
|
+
function collapseAll(event)
|
365
|
+
{
|
366
|
+
toggleAll(event, false)
|
367
|
+
selectNode(rootNode(), null)
|
368
|
+
}
|
369
|
+
|
370
|
+
function toggleAll(event, show)
|
371
|
+
{
|
372
|
+
event.cancelBubble = true
|
373
|
+
var threads = document.getElementsByName("thread")
|
374
|
+
var l = threads.length
|
375
|
+
for (var i = 0; i < l; i++)
|
376
|
+
{
|
377
|
+
showUL(threads[i], show)
|
378
|
+
}
|
379
|
+
}
|
380
|
+
|
381
|
+
function toggleHelp(node)
|
382
|
+
{
|
383
|
+
var help = document.getElementById("help")
|
384
|
+
if (node.value == "Show Help")
|
385
|
+
{
|
386
|
+
node.value = "Hide Help"
|
387
|
+
help.style.display = 'block'
|
388
|
+
} else
|
389
|
+
{
|
390
|
+
node.value = "Show Help"
|
391
|
+
help.style.display = 'none'
|
392
|
+
}
|
393
|
+
}
|
394
|
+
|
395
|
+
var selectedNode = null
|
396
|
+
var selectedColor = null
|
397
|
+
var selectedThread = null
|
398
|
+
|
399
|
+
function descendentOf(a, b)
|
400
|
+
{
|
401
|
+
while (a != b && b != null)
|
402
|
+
b = b.parentNode
|
403
|
+
return (a == b)
|
404
|
+
}
|
405
|
+
|
406
|
+
function moveSelectionIfNecessary(node)
|
407
|
+
{
|
408
|
+
if (descendentOf(node, selectedNode))
|
409
|
+
selectNode(node, null)
|
410
|
+
}
|
411
|
+
|
412
|
+
function selectNode(node, event)
|
413
|
+
{
|
414
|
+
if (event)
|
415
|
+
{
|
416
|
+
event.cancelBubble = true
|
417
|
+
thread = findThread(node)
|
418
|
+
selectThread(thread)
|
419
|
+
}
|
420
|
+
if (selectedNode)
|
421
|
+
{
|
422
|
+
selectedNode.style.background = selectedColor
|
423
|
+
}
|
424
|
+
selectedNode = node
|
425
|
+
selectedColor = node.style.background
|
426
|
+
selectedNode.style.background = "red"
|
427
|
+
selectedNode.scrollIntoView()
|
428
|
+
window.scrollBy(0, -400)
|
429
|
+
}
|
430
|
+
|
431
|
+
function moveUp()
|
432
|
+
{
|
433
|
+
move(selectedNode.previousSibling)
|
434
|
+
}
|
435
|
+
|
436
|
+
function moveDown()
|
437
|
+
{
|
438
|
+
move(selectedNode.nextSibling)
|
439
|
+
}
|
440
|
+
|
441
|
+
function move(p)
|
442
|
+
{
|
443
|
+
while (p && p.style.display == 'none')
|
444
|
+
p = p.nextSibling
|
445
|
+
if (p && p.nodeName == "LI")
|
446
|
+
{
|
447
|
+
selectNode(p, null)
|
448
|
+
}
|
449
|
+
}
|
450
|
+
|
451
|
+
function moveLeft()
|
452
|
+
{
|
453
|
+
var p = selectedNode.parentNode.parentNode
|
454
|
+
if (p && p.nodeName == "LI")
|
455
|
+
{
|
456
|
+
selectNode(p, null)
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
function moveRight()
|
461
|
+
{
|
462
|
+
if (!isLeafNode(selectedNode))
|
463
|
+
{
|
464
|
+
showChildren(selectedNode)
|
465
|
+
var ul = findUlChild(selectedNode)
|
466
|
+
if (ul)
|
467
|
+
{
|
468
|
+
selectNode(ul.firstChild, null)
|
469
|
+
}
|
470
|
+
}
|
471
|
+
}
|
472
|
+
|
473
|
+
function moveForward()
|
474
|
+
{
|
475
|
+
if (isLeafNode(selectedNode))
|
476
|
+
{
|
477
|
+
var p = selectedNode
|
478
|
+
while ((p.nextSibling == null || p.nextSibling.style.display == 'none') && p.nodeName == "LI")
|
479
|
+
{
|
480
|
+
p = p.parentNode.parentNode
|
481
|
+
}
|
482
|
+
if (p.nodeName == "LI")
|
483
|
+
selectNode(p.nextSibling, null)
|
484
|
+
} else
|
485
|
+
{
|
486
|
+
moveRight()
|
487
|
+
}
|
488
|
+
}
|
489
|
+
|
490
|
+
function isExpandedNode(li)
|
491
|
+
{
|
492
|
+
var img = li.firstChild
|
493
|
+
return (img.className.indexOf('minus') > -1)
|
494
|
+
}
|
495
|
+
|
496
|
+
function moveBackward()
|
497
|
+
{
|
498
|
+
var p = selectedNode
|
499
|
+
var q = p.previousSibling
|
500
|
+
while (q != null && q.style.display == 'none')
|
501
|
+
q = q.previousSibling
|
502
|
+
if (q == null)
|
503
|
+
{
|
504
|
+
p = p.parentNode.parentNode
|
505
|
+
} else
|
506
|
+
{
|
507
|
+
while (!isLeafNode(q) && isExpandedNode(q))
|
508
|
+
{
|
509
|
+
q = findUlChild(q).lastChild
|
510
|
+
while (q.style.display == 'none')
|
511
|
+
q = q.previousSibling
|
512
|
+
}
|
513
|
+
p = q
|
514
|
+
}
|
515
|
+
if (p.nodeName == "LI")
|
516
|
+
selectNode(p, null)
|
517
|
+
}
|
518
|
+
|
519
|
+
function moveHome()
|
520
|
+
{
|
521
|
+
selectNode(currentThread)
|
522
|
+
}
|
523
|
+
|
524
|
+
var currentThreadIndex = null
|
525
|
+
|
526
|
+
function findThread(node)
|
527
|
+
{
|
528
|
+
while (node && !node.parentNode.nodeName.match(/BODY|DIV/g))
|
529
|
+
{
|
530
|
+
node = node.parentNode
|
531
|
+
}
|
532
|
+
return node.firstChild
|
533
|
+
}
|
534
|
+
|
535
|
+
function selectThread(node)
|
536
|
+
{
|
537
|
+
var threads = document.getElementsByName("thread")
|
538
|
+
currentThread = node
|
539
|
+
for (var i = 0; i < threads.length; i++)
|
540
|
+
{
|
541
|
+
if (threads[i] == currentThread.parentNode)
|
542
|
+
currentThreadIndex = i
|
543
|
+
}
|
544
|
+
}
|
545
|
+
|
546
|
+
function nextThread()
|
547
|
+
{
|
548
|
+
var threads = document.getElementsByName("thread")
|
549
|
+
if (currentThreadIndex == threads.length - 1)
|
550
|
+
currentThreadIndex = 0
|
551
|
+
else
|
552
|
+
currentThreadIndex += 1
|
553
|
+
currentThread = threads[currentThreadIndex].firstChild
|
554
|
+
selectNode(currentThread, null)
|
555
|
+
}
|
556
|
+
|
557
|
+
function previousThread()
|
558
|
+
{
|
559
|
+
var threads = document.getElementsByName("thread")
|
560
|
+
if (currentThreadIndex == 0)
|
561
|
+
currentThreadIndex = threads.length - 1
|
562
|
+
else
|
563
|
+
currentThreadIndex -= 1
|
564
|
+
currentThread = threads[currentThreadIndex].firstChild
|
565
|
+
selectNode(currentThread, null)
|
566
|
+
}
|
567
|
+
|
568
|
+
function switchThread(node, event)
|
569
|
+
{
|
570
|
+
event.cancelBubble = true
|
571
|
+
selectThread(node.nextSibling.firstChild)
|
572
|
+
selectNode(currentThread, null)
|
573
|
+
}
|
574
|
+
|
575
|
+
function handleKeyEvent(event)
|
576
|
+
{
|
577
|
+
var code = event.charCode ? event.charCode : event.keyCode
|
578
|
+
var str = String.fromCharCode(code)
|
579
|
+
switch (str)
|
580
|
+
{
|
581
|
+
case "a":
|
582
|
+
moveLeft()
|
583
|
+
break
|
584
|
+
case "s":
|
585
|
+
moveDown()
|
586
|
+
break
|
587
|
+
case "d":
|
588
|
+
moveRight()
|
589
|
+
break
|
590
|
+
case "w":
|
591
|
+
moveUp()
|
592
|
+
break
|
593
|
+
case "f":
|
594
|
+
moveForward()
|
595
|
+
break
|
596
|
+
case "b":
|
597
|
+
moveBackward()
|
598
|
+
break
|
599
|
+
case "x":
|
600
|
+
toggleChildren(selectedNode.firstChild, event)
|
601
|
+
break
|
602
|
+
case "*":
|
603
|
+
toggleLI(selectedNode)
|
604
|
+
break
|
605
|
+
case "n":
|
606
|
+
nextThread()
|
607
|
+
break
|
608
|
+
case "h":
|
609
|
+
moveHome()
|
610
|
+
break
|
611
|
+
case "p":
|
612
|
+
previousThread()
|
613
|
+
break
|
614
|
+
}
|
615
|
+
}
|
616
|
+
|
617
|
+
document.onkeypress = function (event)
|
618
|
+
{
|
619
|
+
handleKeyEvent(event)
|
620
|
+
}
|
621
|
+
|
622
|
+
window.onload = function ()
|
623
|
+
{
|
624
|
+
var images = document.querySelectorAll(".toggle")
|
625
|
+
for (var i = 0; i < images.length; i++)
|
626
|
+
{
|
627
|
+
var img = images[i]
|
628
|
+
img.onclick = function (event)
|
629
|
+
{
|
630
|
+
toggleChildren(this, event)
|
631
|
+
return false
|
632
|
+
}
|
633
|
+
}
|
634
|
+
var divs = document.getElementsByTagName("div")
|
635
|
+
for (i = 0; i < divs.length; i++)
|
636
|
+
{
|
637
|
+
var div = divs[i]
|
638
|
+
if (div.className == "thread")
|
639
|
+
div.onclick = function (event)
|
640
|
+
{
|
641
|
+
switchThread(this, event)
|
642
|
+
}
|
643
|
+
}
|
644
|
+
var lis = document.getElementsByTagName("li")
|
645
|
+
for (var i = 0; i < lis.length; i++)
|
646
|
+
{
|
647
|
+
lis[i].onclick = function (event)
|
648
|
+
{
|
649
|
+
selectNode(this, event)
|
650
|
+
}
|
651
|
+
}
|
652
|
+
|
653
|
+
var threads = document.getElementsByName("thread")
|
654
|
+
currentThreadIndex = 0
|
655
|
+
currentThread = threads[0].querySelector('li')
|
656
|
+
selectNode(currentThread, null)
|
657
|
+
}
|
658
|
+
</script>
|
659
|
+
|
660
|
+
<% @overall_time = @result.threads.reduce(0) do |val, thread|
|
661
|
+
val += thread.total_time
|
662
|
+
end %>
|
663
|
+
</head>
|
664
|
+
<body>
|
665
|
+
<div style="display: inline-block;">
|
666
|
+
<div id="titlebar">
|
667
|
+
Call tree for application <strong><%= application %> <%= arguments %></strong><br/> Generated on <%= Time.now %>
|
668
|
+
with options <%= @options.inspect %><br/>
|
669
|
+
</div>
|
670
|
+
<div id="commands">
|
671
|
+
<span style="font-size: 11pt; font-weight: bold;">Threshold:</span>
|
672
|
+
<input value="1.0" size="3" id="threshold" type="text">
|
673
|
+
<input value="Apply" onclick="setThreshold();" type="submit">
|
674
|
+
<input value="Expand All" onclick="expandAll(event);" type="submit">
|
675
|
+
<input value="Collapse All" onclick="collapseAll(event);" type="submit">
|
676
|
+
<input value="Show Help" onclick="toggleHelp(this);" type="submit">
|
677
|
+
</div>
|
678
|
+
<ul style="display: none;" id="help">
|
679
|
+
<li>* indicates recursively called methods</li>
|
680
|
+
<li>Enter a decimal value <i>d</i> into the threshold field and click "Apply" to hide all nodes marked with time
|
681
|
+
values lower than <em>d</em>.
|
682
|
+
</li>
|
683
|
+
<li>Click on "Expand All" for full tree expansion.</li>
|
684
|
+
<li>Click on "Collapse All" to show only top level nodes.</li>
|
685
|
+
<li>Use a, s, d, w as in Quake or Urban Terror to navigate the tree.</li>
|
686
|
+
<li>Use f and b to navigate the tree in preorder forward and backwards.</li>
|
687
|
+
<li>Use x to toggle visibility of a subtree.</li>
|
688
|
+
<li>Use * to expand/collapse a whole subtree.</li>
|
689
|
+
<li>Use h to navigate to thread root.</li>
|
690
|
+
<li>Use n and p to navigate between threads.</li>
|
691
|
+
<li>Click on background to move focus to a subtree.</li>
|
692
|
+
</ul>
|
693
|
+
|
694
|
+
<% @result.threads.each do |thread| %>
|
695
|
+
<% thread_percent = 100 * (thread.total_time / @overall_time)
|
696
|
+
thread_info = "#{"%4.2f%%" % thread_percent} ~ #{@overall_time}" %>
|
697
|
+
<div class="thread">
|
698
|
+
<span>Thread: <%= thread.id %>, Fiber: <%= thread.fiber_id %> (<%= thread_info %>)</span>
|
699
|
+
<ul name="thread">
|
700
|
+
<% call_infos = thread.root_methods.map(&:callers).flatten %>
|
701
|
+
<% call_infos.each do |call_info| %>
|
702
|
+
<% visited = Set.new
|
703
|
+
output = StringIO.new('')
|
704
|
+
print_stack(output, visited, call_info, call_info.total_time) %>
|
705
|
+
<%= output.string %>
|
706
|
+
<% end %>
|
707
|
+
</ul>
|
708
|
+
</div>
|
709
|
+
<% end %>
|
710
|
+
<div id="sentinel"></div>
|
711
|
+
</div>
|
712
|
+
</body>
|
713
|
+
</html>
|