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 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>