acunote-ruby-prof 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/CHANGES +240 -0
  2. data/LICENSE +23 -0
  3. data/README.rdoc +439 -0
  4. data/Rakefile +148 -0
  5. data/bin/ruby-prof +236 -0
  6. data/examples/empty.png +0 -0
  7. data/examples/flat.txt +55 -0
  8. data/examples/graph.dot +106 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.png +0 -0
  11. data/examples/graph.txt +170 -0
  12. data/examples/minus.png +0 -0
  13. data/examples/multi.flat.txt +23 -0
  14. data/examples/multi.graph.html +906 -0
  15. data/examples/multi.grind.dat +194 -0
  16. data/examples/multi.stack.html +573 -0
  17. data/examples/plus.png +0 -0
  18. data/examples/stack.html +573 -0
  19. data/ext/ruby_prof/extconf.rb +43 -0
  20. data/ext/ruby_prof/measure_allocations.h +58 -0
  21. data/ext/ruby_prof/measure_cpu_time.h +152 -0
  22. data/ext/ruby_prof/measure_gc_runs.h +76 -0
  23. data/ext/ruby_prof/measure_gc_time.h +57 -0
  24. data/ext/ruby_prof/measure_memory.h +101 -0
  25. data/ext/ruby_prof/measure_process_time.h +52 -0
  26. data/ext/ruby_prof/measure_wall_time.h +53 -0
  27. data/ext/ruby_prof/mingw/Rakefile +23 -0
  28. data/ext/ruby_prof/mingw/build.rake +38 -0
  29. data/ext/ruby_prof/ruby_prof.c +1834 -0
  30. data/ext/ruby_prof/ruby_prof.h +190 -0
  31. data/ext/ruby_prof/version.h +4 -0
  32. data/lib/ruby-prof.rb +62 -0
  33. data/lib/ruby-prof/abstract_printer.rb +41 -0
  34. data/lib/ruby-prof/aggregate_call_info.rb +68 -0
  35. data/lib/ruby-prof/call_info.rb +112 -0
  36. data/lib/ruby-prof/call_stack_printer.rb +751 -0
  37. data/lib/ruby-prof/call_tree_printer.rb +133 -0
  38. data/lib/ruby-prof/dot_printer.rb +153 -0
  39. data/lib/ruby-prof/empty.png +0 -0
  40. data/lib/ruby-prof/flat_printer.rb +78 -0
  41. data/lib/ruby-prof/flat_printer_with_line_numbers.rb +72 -0
  42. data/lib/ruby-prof/graph_html_printer.rb +278 -0
  43. data/lib/ruby-prof/graph_printer.rb +245 -0
  44. data/lib/ruby-prof/method_info.rb +131 -0
  45. data/lib/ruby-prof/minus.png +0 -0
  46. data/lib/ruby-prof/multi_printer.rb +54 -0
  47. data/lib/ruby-prof/plus.png +0 -0
  48. data/lib/ruby-prof/rack.rb +30 -0
  49. data/lib/ruby-prof/result.rb +70 -0
  50. data/lib/ruby-prof/symbol_to_proc.rb +8 -0
  51. data/lib/ruby-prof/task.rb +146 -0
  52. data/lib/ruby-prof/test.rb +148 -0
  53. data/lib/unprof.rb +8 -0
  54. data/rails/environment/profile.rb +24 -0
  55. data/rails/example/example_test.rb +9 -0
  56. data/rails/profile_test_helper.rb +21 -0
  57. data/test/aggregate_test.rb +136 -0
  58. data/test/basic_test.rb +290 -0
  59. data/test/current_failures_windows +8 -0
  60. data/test/do_nothing.rb +0 -0
  61. data/test/duplicate_names_test.rb +32 -0
  62. data/test/enumerable_test.rb +16 -0
  63. data/test/exceptions_test.rb +15 -0
  64. data/test/exclude_threads_test.rb +54 -0
  65. data/test/exec_test.rb +14 -0
  66. data/test/line_number_test.rb +73 -0
  67. data/test/measurement_test.rb +122 -0
  68. data/test/method_elimination_test.rb +74 -0
  69. data/test/module_test.rb +44 -0
  70. data/test/multi_printer_test.rb +81 -0
  71. data/test/no_method_class_test.rb +13 -0
  72. data/test/prime.rb +55 -0
  73. data/test/prime_test.rb +13 -0
  74. data/test/printers_test.rb +164 -0
  75. data/test/recursive_test.rb +236 -0
  76. data/test/ruby-prof-bin +20 -0
  77. data/test/singleton_test.rb +38 -0
  78. data/test/stack_printer_test.rb +74 -0
  79. data/test/stack_test.rb +138 -0
  80. data/test/start_stop_test.rb +112 -0
  81. data/test/test_suite.rb +32 -0
  82. data/test/thread_test.rb +173 -0
  83. data/test/unique_call_path_test.rb +225 -0
  84. metadata +185 -0
@@ -0,0 +1,751 @@
1
+ require 'ruby-prof/abstract_printer'
2
+ require 'erb'
3
+ require 'fileutils'
4
+
5
+ module RubyProf
6
+ # prints a HTML visualization of the call tree
7
+ class CallStackPrinter < AbstractPrinter
8
+ include ERB::Util
9
+
10
+ def initialize(result)
11
+ super(result)
12
+ end
13
+
14
+ def print(output = STDOUT, options = {})
15
+ @output = output
16
+ setup_options(options)
17
+ if @graph_html = options.delete(:graph)
18
+ @graph_html = "file://" + @graph_html if @graph_html[0]=="/"
19
+ end
20
+
21
+ @overall_threads_time = 0.0
22
+ @threads_totals = Hash.new
23
+ @result.threads.each do |thread_id, methods|
24
+ roots = methods.select{|m| m.root?}
25
+ thread_total_time = sum(roots.map{|r| self.total_time(r.call_infos)})
26
+ @overall_threads_time += thread_total_time
27
+ @threads_totals[thread_id] = thread_total_time
28
+ end
29
+
30
+ print_header
31
+
32
+ @result.threads.keys.sort.each do |thread_id|
33
+ @current_thread_id = thread_id
34
+ @overall_time = @threads_totals[thread_id]
35
+ @output.print "<div class=\"thread\">Thread: #{thread_id} (#{"%4.2f%%" % ((@overall_time/@overall_threads_time)*100)} ~ #{@overall_time})</div>"
36
+ @output.print "<ul name=\"thread\">"
37
+ @result.threads[thread_id].each do |m|
38
+ # $stderr.print m.dump
39
+ next unless m.root?
40
+ m.call_infos.each do |ci|
41
+ next unless ci.root?
42
+ print_stack ci, @threads_totals[thread_id]
43
+ end
44
+ end
45
+ @output.print "</ul>"
46
+ end
47
+
48
+ print_footer
49
+
50
+ copy_image_files
51
+ end
52
+
53
+ def print_stack(call_info, parent_time)
54
+ total_time = call_info.total_time
55
+ percent_parent = (total_time/parent_time)*100
56
+ percent_total = (total_time/@overall_time)*100
57
+ return unless percent_total > min_percent
58
+ color = self.color(percent_total)
59
+ kids = call_info.children
60
+ visible = percent_total >= threshold
61
+ expanded = percent_total >= expansion
62
+ display = visible ? "block" : "none"
63
+ @output.print "<li class=\"color#{color}\" style=\"display:#{display}\">"
64
+ if kids.empty?
65
+ @output.print "<img src=\"empty.png\">"
66
+ else
67
+ visible_children = kids.any?{|ci| (ci.total_time/@overall_time)*100 >= threshold}
68
+ image = visible_children ? (expanded ? "minus" : "plus") : "empty"
69
+ @output.print "<img class=\"toggle\" src=\"#{image}.png\">"
70
+ end
71
+ @output.printf " %4.2f%% (%4.2f%%) %s %s\n", percent_total, percent_parent, link(call_info), graph_link(call_info)
72
+ unless kids.empty?
73
+ if expanded
74
+ @output.print "<ul>"
75
+ else
76
+ @output.print '<ul style="display:none">'
77
+ end
78
+ kids.sort_by{|c| -c.total_time}.each do |callinfo|
79
+ print_stack callinfo, total_time
80
+ end
81
+ @output.print "</ul>"
82
+ end
83
+ @output.print "</li>"
84
+ end
85
+
86
+ def name(call_info)
87
+ method = call_info.target
88
+ method.full_name
89
+ end
90
+
91
+ def link(call_info)
92
+ method = call_info.target
93
+ file = File.expand_path(method.source_file)
94
+ if file =~ /\/ruby_runtime$/
95
+ h(name(call_info))
96
+ else
97
+ if RUBY_PLATFORM =~ /darwin/
98
+ "<a href=\"txmt://open?url=file://#{file}&line=#{method.line}\">#{h(name(call_info))}</a>"
99
+ else
100
+ "<a href=\"file://#{file}?line=#{method.line}\">#{h(name(call_info))}</a>"
101
+ end
102
+ end
103
+ end
104
+
105
+ def graph_link(call_info)
106
+ total_calls = call_info.target.call_infos.inject(0){|t, ci| t += ci.called}
107
+ href = "#{@graph_html}##{method_href(call_info.target)}"
108
+ totals = @graph_html ? "<a href='#{href}'>#{total_calls}</a>" : total_calls.to_s
109
+ "[#{call_info.called} calls, #{totals} total]"
110
+ end
111
+
112
+ def method_href(method)
113
+ h(method.full_name.gsub(/[><#\.\?=:]/,"_") + "_" + @current_thread_id.to_s)
114
+ end
115
+
116
+ def total_time(call_infos)
117
+ sum(call_infos.map{|ci| ci.total_time})
118
+ end
119
+
120
+ def sum(a)
121
+ a.inject(0.0){|s,t| s+=t}
122
+ end
123
+
124
+ def dump(ci)
125
+ $stderr.printf "%s/%d t:%f s:%f w:%f \n", ci, ci.object_id, ci.total_time, ci.self_time, ci.wait_time
126
+ end
127
+
128
+ def color(p)
129
+ case i = p.to_i
130
+ when 0..5
131
+ "01"
132
+ when 5..10
133
+ "05"
134
+ when 100
135
+ "9"
136
+ else
137
+ "#{i/10}"
138
+ end
139
+ end
140
+
141
+ def application
142
+ @options[:application] || $PROGRAM_NAME
143
+ end
144
+
145
+ def arguments
146
+ ARGV.join(' ')
147
+ end
148
+
149
+ def title
150
+ @title ||= @options.delete(:title) || "ruby-prof call tree"
151
+ end
152
+
153
+ def threshold
154
+ @options[:threshold] || 1.0
155
+ end
156
+
157
+ def expansion
158
+ @options[:expansion] || 10.0
159
+ end
160
+
161
+ def copy_image_files
162
+ if @output.is_a?(File)
163
+ target_dir = File.dirname(@output.path)
164
+ image_dir = File.dirname(__FILE__)
165
+ %w(empty plus minus).each do |img|
166
+ source_file = "#{image_dir}/#{img}.png"
167
+ target_file = "#{target_dir}/#{img}.png"
168
+ FileUtils.cp(source_file, target_file) unless File.exist?(target_file)
169
+ end
170
+ end
171
+ end
172
+
173
+ def print_header
174
+ @output.puts "<html><head>"
175
+ @output.puts '<meta http-equiv="content-type" content="text/html; charset=utf-8">'
176
+ @output.puts "<title>#{h title}</title>"
177
+ print_css
178
+ print_java_script
179
+ @output.puts '</head><body>'
180
+ print_title_bar
181
+ print_commands
182
+ print_help
183
+ end
184
+
185
+ def print_footer
186
+ @output.puts '<div id="sentinel"></div></body></html>'
187
+ end
188
+
189
+ def print_css
190
+ @output.puts <<-'end_css'
191
+ <style type="text/css">
192
+ <!--
193
+ body {
194
+ font-size:70%;
195
+ padding:0px;
196
+ margin:5px;
197
+ margin-right:0px;
198
+ margin-left:0px;
199
+ background: #ffffff;
200
+ }
201
+ ul {
202
+ margin-left:0px;
203
+ margin-top:0px;
204
+ margin-bottom:0px;
205
+ padding-left:0px;
206
+ list-style-type:none;
207
+ }
208
+ li {
209
+ margin-left:11px;
210
+ padding:0px;
211
+ white-space:nowrap;
212
+ border-top:1px solid #cccccc;
213
+ border-left:1px solid #cccccc;
214
+ border-bottom:none;
215
+ }
216
+ .thread {
217
+ margin-left:11px;
218
+ background:#708090;
219
+ padding-top:3px;
220
+ padding-left:12px;
221
+ padding-bottom:2px;
222
+ border-left:1px solid #CCCCCC;
223
+ border-top:1px solid #CCCCCC;
224
+ font-weight:bold;
225
+ }
226
+ .hidden {
227
+ display:none;
228
+ width:0px;
229
+ height:0px;
230
+ margin:0px;
231
+ padding:0px;
232
+ border-style:none;
233
+ }
234
+ .color01 { background:#adbdeb }
235
+ .color05 { background:#9daddb }
236
+ .color0 { background:#8d9dcb }
237
+ .color1 { background:#89bccb }
238
+ .color2 { background:#56e3e7 }
239
+ .color3 { background:#32cd70 }
240
+ .color4 { background:#a3d53c }
241
+ .color5 { background:#c4cb34 }
242
+ .color6 { background:#dcb66d }
243
+ .color7 { background:#cda59e }
244
+ .color8 { background:#be9d9c }
245
+ .color9 { background:#cf947a }
246
+ #commands {
247
+ font-size:10pt;
248
+ padding:10px;
249
+ margin-left:11px;
250
+ margin-bottom:0px;
251
+ margin-top:0px;
252
+ background:#708090;
253
+ border-top:1px solid #cccccc;
254
+ border-left:1px solid #cccccc;
255
+ border-bottom:none;
256
+ }
257
+ #titlebar {
258
+ font-size:10pt;
259
+ padding:10px;
260
+ margin-left:11px;
261
+ margin-bottom:0px;
262
+ margin-top:10px;
263
+ background:#8090a0;
264
+ border-top:1px solid #cccccc;
265
+ border-left:1px solid #cccccc;
266
+ border-bottom:none;
267
+ }
268
+ #help {
269
+ font-size:10pt;
270
+ padding:10px;
271
+ margin-left:11px;
272
+ margin-bottom:0px;
273
+ margin-top:0px;
274
+ background:#8090a0;
275
+ display:none;
276
+ border-top:1px solid #cccccc;
277
+ border-left:1px solid #cccccc;
278
+ border-bottom:none;
279
+ }
280
+ #sentinel {
281
+ height: 400px;
282
+ margin-left:11px;
283
+ background:#8090a0;
284
+ border-top:1px solid #cccccc;
285
+ border-left:1px solid #cccccc;
286
+ border-bottom:none;
287
+ }
288
+ input { margin-left:10px; }
289
+ -->
290
+ </style>
291
+ end_css
292
+ end
293
+
294
+ def print_java_script
295
+ @output.puts <<-'end_java_script'
296
+ <script type="text/javascript">
297
+ /*
298
+ Copyright (C) 2005,2009 Stefan Kaes
299
+ skaes@railsexpress.de
300
+ */
301
+
302
+ function rootNode() {
303
+ return currentThread;
304
+ }
305
+
306
+ function hideUL(node) {
307
+ var lis = node.childNodes
308
+ var l = lis.length;
309
+ for (var i=0; i < l ; i++ ) {
310
+ hideLI(lis[i]);
311
+ }
312
+ }
313
+
314
+ function showUL(node) {
315
+ var lis = node.childNodes;
316
+ var l = lis.length;
317
+ for (var i=0; i < l ; i++ ) {
318
+ showLI(lis[i]);
319
+ }
320
+ }
321
+
322
+ function findUlChild(li){
323
+ var ul = li.childNodes[2];
324
+ while (ul && ul.nodeName != "UL") {
325
+ ul = ul.nextSibling;
326
+ }
327
+ return ul;
328
+ }
329
+
330
+ function isLeafNode(li) {
331
+ var img = li.firstChild;
332
+ return (img.src.indexOf('empty.png') > -1);
333
+ }
334
+
335
+ function hideLI(li) {
336
+ if (isLeafNode(li))
337
+ return;
338
+
339
+ var img = li.firstChild;
340
+ img.src = 'plus.png';
341
+
342
+ var ul = findUlChild(li);
343
+ if (ul) {
344
+ ul.style.display = 'none';
345
+ hideUL(ul);
346
+ }
347
+ }
348
+
349
+ function showLI(li) {
350
+ if (isLeafNode(li))
351
+ return;
352
+
353
+ var img = li.firstChild;
354
+ img.src = 'minus.png';
355
+
356
+ var ul = findUlChild(li);
357
+ if (ul) {
358
+ ul.style.display = 'block';
359
+ showUL(ul);
360
+ }
361
+ }
362
+
363
+ function toggleLI(li) {
364
+ var img = li.firstChild;
365
+ if (img.src.indexOf("minus.png")>-1)
366
+ hideLI(li);
367
+ else {
368
+ if (img.src.indexOf("plus.png")>-1)
369
+ showLI(li);
370
+ }
371
+ }
372
+
373
+ function aboveThreshold(text, threshold) {
374
+ var match = text.match(/\d+[.,]\d+/);
375
+ return (match && parseFloat(match[0].replace(/,/, '.'))>=threshold);
376
+ }
377
+
378
+ function setThresholdLI(li, threshold) {
379
+ var img = li.firstChild;
380
+ var text = img.nextSibling;
381
+ var ul = findUlChild(li);
382
+
383
+ var visible = aboveThreshold(text.nodeValue, threshold) ? 1 : 0;
384
+
385
+ var count = 0;
386
+ if (ul) {
387
+ count = setThresholdUL(ul, threshold);
388
+ }
389
+ if (count>0) {
390
+ img.src = 'minus.png';
391
+ }
392
+ else {
393
+ img.src = 'empty.png';
394
+ }
395
+ if (visible) {
396
+ li.style.display = 'block'
397
+ }
398
+ else {
399
+ li.style.display = 'none'
400
+ }
401
+ return visible;
402
+ }
403
+
404
+ function setThresholdUL(node, threshold) {
405
+ var lis = node.childNodes;
406
+ var l = lis.length;
407
+
408
+ var count = 0;
409
+ for ( var i = 0; i < l ; i++ ) {
410
+ count = count + setThresholdLI(lis[i], threshold);
411
+ }
412
+
413
+ var visible = (count > 0) ? 1 : 0;
414
+ if (visible) {
415
+ node.style.display = 'block';
416
+ }
417
+ else {
418
+ node.style.display = 'none';
419
+ }
420
+ return visible;
421
+ }
422
+
423
+ function toggleChildren(img, event) {
424
+ event.cancelBubble=true;
425
+
426
+ if (img.src.indexOf('empty.png') > -1)
427
+ return;
428
+
429
+ var minus = (img.src.indexOf('minus.png') > -1);
430
+
431
+ if (minus) {
432
+ img.src = 'plus.png';
433
+ }
434
+ else
435
+ img.src = 'minus.png';
436
+
437
+ var li = img.parentNode;
438
+ var ul = findUlChild(li);
439
+ if (ul) {
440
+ if (minus)
441
+ ul.style.display = 'none';
442
+ else
443
+ ul.style.display = 'block';
444
+ }
445
+ if (minus)
446
+ moveSelectionIfNecessary(li);
447
+ }
448
+
449
+ function showChildren(li) {
450
+ var img = li.firstChild;
451
+ if (img.src.indexOf('empty.png') > -1)
452
+ return;
453
+ img.src = 'minus.png';
454
+
455
+ var ul = findUlChild(li);
456
+ if (ul) {
457
+ ul.style.display = 'block';
458
+ }
459
+ }
460
+
461
+ function setThreshold() {
462
+ var tv = document.getElementById("threshold").value;
463
+ if (tv.match(/[0-9]+([.,][0-9]+)?/)) {
464
+ var f = parseFloat(tv.replace(/,/, '.'));
465
+ var threads = document.getElementsByName("thread");
466
+ var l = threads.length;
467
+ for ( var i = 0; i < l ; i++ ) {
468
+ setThresholdUL(threads[i], f);
469
+ }
470
+ var p = selectedNode;
471
+ while (p && p.style.display=='none')
472
+ p=p.parentNode.parentNode;
473
+ if (p && p.nodeName=="LI")
474
+ selectNode(p);
475
+ }
476
+ else {
477
+ alert("Please specify a decimal number as threshold value!");
478
+ }
479
+ }
480
+
481
+ function collapseAll(event) {
482
+ event.cancelBubble=true;
483
+ var threads = document.getElementsByName("thread");
484
+ var l = threads.length;
485
+ for ( var i = 0; i < l ; i++ ) {
486
+ hideUL(threads[i]);
487
+ }
488
+ selectNode(rootNode(), null);
489
+ }
490
+
491
+ function expandAll(event) {
492
+ event.cancelBubble=true;
493
+ var threads = document.getElementsByName("thread");
494
+ var l = threads.length;
495
+ for ( var i = 0; i < l ; i++ ) {
496
+ showUL(threads[i]);
497
+ }
498
+ }
499
+
500
+ function toggleHelp(node) {
501
+ var help = document.getElementById("help");
502
+ if (node.value == "Show Help") {
503
+ node.value = "Hide Help";
504
+ help.style.display = 'block';
505
+ }
506
+ else {
507
+ node.value = "Show Help";
508
+ help.style.display = 'none';
509
+ }
510
+ }
511
+
512
+ var selectedNode = null;
513
+ var selectedColor = null;
514
+ var selectedThread = null;
515
+
516
+ function descendentOf(a,b){
517
+ while (a!=b && b!=null)
518
+ b=b.parentNode;
519
+ return (a==b);
520
+ }
521
+
522
+ function moveSelectionIfNecessary(node){
523
+ if (descendentOf(node, selectedNode))
524
+ selectNode(node, null);
525
+ }
526
+
527
+ function selectNode(node, event) {
528
+ if (event) {
529
+ event.cancelBubble = true;
530
+ thread = findThread(node);
531
+ selectThread(thread);
532
+ }
533
+ if (selectedNode) {
534
+ selectedNode.style.background = selectedColor;
535
+ }
536
+ selectedNode = node;
537
+ selectedColor = node.style.background;
538
+ selectedNode.style.background = "red";
539
+ selectedNode.scrollIntoView();
540
+ window.scrollBy(0,-400);
541
+ }
542
+
543
+ function moveUp(){
544
+ var p = selectedNode.previousSibling;
545
+ while (p && p.style.display == 'none')
546
+ p = p.previousSibling;
547
+ if (p && p.nodeName == "LI") {
548
+ selectNode(p, null);
549
+ }
550
+ }
551
+
552
+ function moveDown(){
553
+ var p = selectedNode.nextSibling;
554
+ while (p && p.style.display == 'none')
555
+ p = p.nextSibling;
556
+ if (p && p.nodeName == "LI") {
557
+ selectNode(p, null);
558
+ }
559
+ }
560
+
561
+ function moveLeft(){
562
+ var p = selectedNode.parentNode.parentNode;
563
+ if (p && p.nodeName=="LI") {
564
+ selectNode(p, null);
565
+ }
566
+ }
567
+
568
+ function moveRight(){
569
+ if (!isLeafNode(selectedNode)) {
570
+ showChildren(selectedNode);
571
+ var ul = findUlChild(selectedNode);
572
+ if (ul) {
573
+ selectNode(ul.firstChild, null);
574
+ }
575
+ }
576
+ }
577
+
578
+ function moveForward(){
579
+ if (isLeafNode(selectedNode)) {
580
+ var p = selectedNode;
581
+ while ((p.nextSibling == null || p.nextSibling.style.display=='none') && p.nodeName=="LI") {
582
+ p = p.parentNode.parentNode;
583
+ }
584
+ if (p.nodeName=="LI")
585
+ selectNode(p.nextSibling, null);
586
+ }
587
+ else {
588
+ moveRight();
589
+ }
590
+ }
591
+
592
+ function isExpandedNode(li){
593
+ var img = li.firstChild;
594
+ return(img.src.indexOf('minus.png')>-1);
595
+ }
596
+
597
+ function moveBackward(){
598
+ var p = selectedNode;
599
+ var q = p.previousSibling;
600
+ while (q != null && q.style.display=='none')
601
+ q = q.previousSibling;
602
+ if (q == null) {
603
+ p = p.parentNode.parentNode;
604
+ } else {
605
+ while (!isLeafNode(q) && isExpandedNode(q)) {
606
+ q = findUlChild(q).lastChild;
607
+ while (q.style.display=='none')
608
+ q = q.previousSibling;
609
+ }
610
+ p = q;
611
+ }
612
+ if (p.nodeName=="LI")
613
+ selectNode(p, null);
614
+ }
615
+
616
+ function moveHome() {
617
+ selectNode(currentThread);
618
+ }
619
+
620
+ var currentThreadIndex = null;
621
+
622
+ function findThread(node){
623
+ while (node && node.parentNode.nodeName!="BODY") {
624
+ node = node.parentNode;
625
+ }
626
+ return node.firstChild;
627
+ }
628
+
629
+ function selectThread(node){
630
+ var threads = document.getElementsByName("thread");
631
+ currentThread = node;
632
+ for (var i=0; i<threads.length; i++) {
633
+ if (threads[i]==currentThread.parentNode)
634
+ currentThreadIndex = i;
635
+ }
636
+ }
637
+
638
+ function nextThread(){
639
+ var threads = document.getElementsByName("thread");
640
+ if (currentThreadIndex==threads.length-1)
641
+ currentThreadIndex = 0;
642
+ else
643
+ currentThreadIndex += 1
644
+ currentThread = threads[currentThreadIndex].firstChild;
645
+ selectNode(currentThread, null);
646
+ }
647
+
648
+ function previousThread(){
649
+ var threads = document.getElementsByName("thread");
650
+ if (currentThreadIndex==0)
651
+ currentThreadIndex = threads.length-1;
652
+ else
653
+ currentThreadIndex -= 1
654
+ currentThread = threads[currentThreadIndex].firstChild;
655
+ selectNode(currentThread, null);
656
+ }
657
+
658
+ function switchThread(node, event){
659
+ event.cancelBubble = true;
660
+ selectThread(node.nextSibling.firstChild);
661
+ selectNode(currentThread, null);
662
+ }
663
+
664
+ function handleKeyEvent(event){
665
+ var code = event.charCode ? event.charCode : event.keyCode;
666
+ var str = String.fromCharCode(code);
667
+ switch (str) {
668
+ case "a": moveLeft(); break;
669
+ case "s": moveDown(); break;
670
+ case "d": moveRight(); break;
671
+ case "w": moveUp(); break;
672
+ case "f": moveForward(); break;
673
+ case "b": moveBackward(); break;
674
+ case "x": toggleChildren(selectedNode.firstChild, event); break;
675
+ case "*": toggleLI(selectedNode); break;
676
+ case "n": nextThread(); break;
677
+ case "h": moveHome(); break;
678
+ case "p": previousThread(); break;
679
+ }
680
+ }
681
+ document.onkeypress=function(event){ handleKeyEvent(event) };
682
+
683
+ window.onload=function(){
684
+ var images = document.getElementsByTagName("img");
685
+ for (var i=0; i<images.length; i++) {
686
+ var img = images[i];
687
+ if (img.className == "toggle") {
688
+ img.onclick = function(event){ toggleChildren(this, event); };
689
+ }
690
+ }
691
+ var divs = document.getElementsByTagName("div");
692
+ for (i=0; i<divs.length; i++) {
693
+ var div = divs[i];
694
+ if (div.className == "thread")
695
+ div.onclick = function(event){ switchThread(this, event) };
696
+ }
697
+ var lis = document.getElementsByTagName("li");
698
+ for (var i=0; i<lis.length; i++) {
699
+ lis[i].onclick = function(event){ selectNode(this, event); };
700
+ }
701
+ var threads = document.getElementsByName("thread");
702
+ currentThreadIndex = 0;
703
+ currentThread = threads[0].firstChild;
704
+ selectNode(currentThread, null);
705
+ }
706
+ </script>
707
+ end_java_script
708
+ end
709
+
710
+ def print_title_bar
711
+ @output.puts <<-"end_title_bar"
712
+ <div id="titlebar">
713
+ Call tree for application <b>#{h application} #{h arguments}</b><br/>
714
+ Generated on #{Time.now} with options #{h @options.inspect}<br/>
715
+ </div>
716
+ end_title_bar
717
+ end
718
+
719
+ def print_commands
720
+ @output.puts <<-"end_commands"
721
+ <div id=\"commands\">
722
+ <span style=\"font-size: 11pt; font-weight: bold;\">Threshold:</span>
723
+ <input value=\"#{h threshold}\" size=\"3\" id=\"threshold\" type=\"text\">
724
+ <input value=\"Apply\" onclick=\"setThreshold();\" type=\"submit\">
725
+ <input value=\"Expand All\" onclick=\"expandAll(event);\" type=\"submit\">
726
+ <input value=\"Collapse All\" onclick=\"collapseAll(event);\" type=\"submit\">
727
+ <input value=\"Show Help\" onclick=\"toggleHelp(this);\" type=\"submit\">
728
+ </div>
729
+ end_commands
730
+ end
731
+
732
+ def print_help
733
+ @output.puts <<-'end_help'
734
+ <div style="display: none;" id="help">
735
+ <img src="empty.png"> Enter a decimal value <i>d</i> into the threshold field and click "Apply"
736
+ to hide all nodes marked with time values lower than <i>d</i>.<br>
737
+ <img src="empty.png"> Click on "Expand All" for full tree expansion.<br>
738
+ <img src="empty.png"> Click on "Collapse All" to show only top level nodes.<br>
739
+ <img src="empty.png"> Use a, s, d, w as in Quake or Urban Terror to navigate the tree.<br>
740
+ <img src="empty.png"> Use f and b to navigate the tree in preorder forward and backwards.<br>
741
+ <img src="empty.png"> Use x to toggle visibility of a subtree.<br>
742
+ <img src="empty.png"> Use * to expand/collapse a whole subtree.<br>
743
+ <img src="empty.png"> Use h to navigate to thread root.<br>
744
+ <img src="empty.png"> Use n and p to navigate between threads.<br>
745
+ <img src="empty.png"> Click on background to move focus to a subtree.<br>
746
+ </div>
747
+ end_help
748
+ end
749
+ end
750
+ end
751
+