ruby-prof 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2baeb563dfcb30c82aa1235965228b951b096e333c2367274412ace61ec5fbc6
4
- data.tar.gz: 23840f6343ea4361776e1a01925b829da43e3e45c18717e14dde26eeef8b5858
3
+ metadata.gz: d37fba2001af9994daa9e1c13e316d0a4fe069c769e431915ef573413241203f
4
+ data.tar.gz: f3e9ff45b63fd26c76fd73cca3f20b67a400c39a1183b135364ce89386a51096
5
5
  SHA512:
6
- metadata.gz: 26a1d898c0679b366e57451993117146ea160eb9c8bbcde626306f7deac9ce8760ac1633cccb6058574e352fafd32f4e9abb1b3325c6ac6cf13e2fec11794f82
7
- data.tar.gz: 226493ef8ed88ea3d43ca54925ea43e53558c3a0771bb346992d266de89c2fd4d1b69f8ab1cf2a37e7102eccfbf94340a361f39862171ef90afba58311c488c2
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 thread object been accessed by Ruby? If
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 = TypedData_Wrap_Struct(cRpAllocation, &allocation_type, allocation);
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 thread object been accessed by Ruby? If
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
- RDATA(call_info->object)->data = NULL;
39
- RDATA(call_info->object)->dfree = NULL;
40
- RDATA(call_info->object)->dmark = NULL;
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 = TypedData_Wrap_Struct(cRpCallnfo, &call_info_type, call_info);
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
- ULARGE_INTEGER sysTimeInt;
19
- ULARGE_INTEGER userTimeInt;
18
+ ULARGE_INTEGER sysTimeInt;
19
+ ULARGE_INTEGER userTimeInt;
20
20
 
21
- GetProcessTimes(GetCurrentProcess(), &createTime, &exitTime, &sysTime, &userTime);
21
+ GetProcessTimes(GetCurrentProcess(), &createTime, &exitTime, &sysTime, &userTime);
22
22
 
23
- sysTimeInt.LowPart = sysTime.dwLowDateTime;
24
- sysTimeInt.HighPart = sysTime.dwHighDateTime;
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
- return sysTimeInt.QuadPart + userTimeInt.QuadPart;
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 thread object been accessed by Ruby? If
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
- measurement->object = Qnil;
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 = TypedData_Wrap_Struct(cRpMeasurement, &measurement_type, measurement);
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);
@@ -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 thread object been accessed by Ruby? If
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
- RDATA(method->object)->data = NULL;
245
- RDATA(method->object)->dfree = NULL;
246
- RDATA(method->object)->dmark = NULL;
247
- }
248
- method->object = Qnil;
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 = TypedData_Wrap_Struct(cRpMethodInfo, &method_info_type, method);
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
  }
@@ -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 = rb_fiber_current();
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 = rb_fiber_current();
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 = 0;
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, rb_fiber_current());
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");
@@ -27,6 +27,7 @@ typedef struct
27
27
  thread_data_t* last_thread_data;
28
28
  double measurement_at_pause_resume;
29
29
  bool allow_exceptions;
30
+ bool merge_fibers;
30
31
  } prof_profile_t;
31
32
 
32
33
  void rp_init_profile(void);
@@ -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 = TypedData_Wrap_Struct(cRpThread, &thread_type, thread);
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.3vc\include\ruby-2.6.0\x64-mswin64_140;C:\msys64\usr\local\ruby-2.6.3vc\include\ruby-2.6.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
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.3vc\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
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>
@@ -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>