airbnb-ruby-prof 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/CHANGES +483 -0
  2. data/LICENSE +25 -0
  3. data/README.rdoc +426 -0
  4. data/Rakefile +51 -0
  5. data/bin/ruby-prof +279 -0
  6. data/bin/ruby-prof-check-trace +45 -0
  7. data/examples/flat.txt +50 -0
  8. data/examples/graph.dot +84 -0
  9. data/examples/graph.html +823 -0
  10. data/examples/graph.txt +139 -0
  11. data/examples/multi.flat.txt +23 -0
  12. data/examples/multi.graph.html +760 -0
  13. data/examples/multi.grind.dat +114 -0
  14. data/examples/multi.stack.html +547 -0
  15. data/examples/stack.html +547 -0
  16. data/ext/ruby_prof/extconf.rb +67 -0
  17. data/ext/ruby_prof/rp_call_info.c +374 -0
  18. data/ext/ruby_prof/rp_call_info.h +59 -0
  19. data/ext/ruby_prof/rp_fast_call_tree_printer.c +247 -0
  20. data/ext/ruby_prof/rp_fast_call_tree_printer.h +10 -0
  21. data/ext/ruby_prof/rp_measure.c +71 -0
  22. data/ext/ruby_prof/rp_measure.h +56 -0
  23. data/ext/ruby_prof/rp_measure_allocations.c +74 -0
  24. data/ext/ruby_prof/rp_measure_cpu_time.c +134 -0
  25. data/ext/ruby_prof/rp_measure_gc_runs.c +71 -0
  26. data/ext/ruby_prof/rp_measure_gc_time.c +58 -0
  27. data/ext/ruby_prof/rp_measure_memory.c +75 -0
  28. data/ext/ruby_prof/rp_measure_process_time.c +69 -0
  29. data/ext/ruby_prof/rp_measure_wall_time.c +43 -0
  30. data/ext/ruby_prof/rp_method.c +717 -0
  31. data/ext/ruby_prof/rp_method.h +79 -0
  32. data/ext/ruby_prof/rp_stack.c +221 -0
  33. data/ext/ruby_prof/rp_stack.h +81 -0
  34. data/ext/ruby_prof/rp_thread.c +312 -0
  35. data/ext/ruby_prof/rp_thread.h +36 -0
  36. data/ext/ruby_prof/ruby_prof.c +800 -0
  37. data/ext/ruby_prof/ruby_prof.h +64 -0
  38. data/ext/ruby_prof/vc/ruby_prof.sln +32 -0
  39. data/ext/ruby_prof/vc/ruby_prof_18.vcxproj +108 -0
  40. data/ext/ruby_prof/vc/ruby_prof_19.vcxproj +110 -0
  41. data/ext/ruby_prof/vc/ruby_prof_20.vcxproj +110 -0
  42. data/lib/ruby-prof.rb +63 -0
  43. data/lib/ruby-prof/aggregate_call_info.rb +76 -0
  44. data/lib/ruby-prof/assets/call_stack_printer.css.html +117 -0
  45. data/lib/ruby-prof/assets/call_stack_printer.js.html +385 -0
  46. data/lib/ruby-prof/assets/call_stack_printer.png +0 -0
  47. data/lib/ruby-prof/assets/flame_graph_printer.lib.css.html +149 -0
  48. data/lib/ruby-prof/assets/flame_graph_printer.lib.js.html +707 -0
  49. data/lib/ruby-prof/assets/flame_graph_printer.page.js.html +56 -0
  50. data/lib/ruby-prof/assets/flame_graph_printer.tmpl.html.erb +39 -0
  51. data/lib/ruby-prof/call_info.rb +111 -0
  52. data/lib/ruby-prof/call_info_visitor.rb +40 -0
  53. data/lib/ruby-prof/compatibility.rb +186 -0
  54. data/lib/ruby-prof/method_info.rb +109 -0
  55. data/lib/ruby-prof/printers/abstract_printer.rb +85 -0
  56. data/lib/ruby-prof/printers/call_info_printer.rb +41 -0
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +260 -0
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +130 -0
  59. data/lib/ruby-prof/printers/dot_printer.rb +132 -0
  60. data/lib/ruby-prof/printers/fast_call_tree_printer.rb +87 -0
  61. data/lib/ruby-prof/printers/flame_graph_html_printer.rb +59 -0
  62. data/lib/ruby-prof/printers/flame_graph_json_printer.rb +157 -0
  63. data/lib/ruby-prof/printers/flat_printer.rb +70 -0
  64. data/lib/ruby-prof/printers/flat_printer_with_line_numbers.rb +64 -0
  65. data/lib/ruby-prof/printers/graph_html_printer.rb +244 -0
  66. data/lib/ruby-prof/printers/graph_printer.rb +116 -0
  67. data/lib/ruby-prof/printers/multi_printer.rb +58 -0
  68. data/lib/ruby-prof/profile.rb +22 -0
  69. data/lib/ruby-prof/profile/exclude_common_methods.rb +201 -0
  70. data/lib/ruby-prof/rack.rb +95 -0
  71. data/lib/ruby-prof/task.rb +147 -0
  72. data/lib/ruby-prof/thread.rb +35 -0
  73. data/lib/ruby-prof/version.rb +4 -0
  74. data/lib/ruby-prof/walker.rb +95 -0
  75. data/lib/unprof.rb +10 -0
  76. data/ruby-prof.gemspec +56 -0
  77. data/test/aggregate_test.rb +136 -0
  78. data/test/basic_test.rb +128 -0
  79. data/test/block_test.rb +74 -0
  80. data/test/call_info_test.rb +78 -0
  81. data/test/call_info_visitor_test.rb +31 -0
  82. data/test/duplicate_names_test.rb +32 -0
  83. data/test/dynamic_method_test.rb +55 -0
  84. data/test/enumerable_test.rb +21 -0
  85. data/test/exceptions_test.rb +16 -0
  86. data/test/exclude_methods_test.rb +146 -0
  87. data/test/exclude_threads_test.rb +53 -0
  88. data/test/fiber_test.rb +79 -0
  89. data/test/issue137_test.rb +63 -0
  90. data/test/line_number_test.rb +71 -0
  91. data/test/measure_allocations_test.rb +26 -0
  92. data/test/measure_cpu_time_test.rb +213 -0
  93. data/test/measure_gc_runs_test.rb +32 -0
  94. data/test/measure_gc_time_test.rb +36 -0
  95. data/test/measure_memory_test.rb +33 -0
  96. data/test/measure_process_time_test.rb +63 -0
  97. data/test/measure_wall_time_test.rb +255 -0
  98. data/test/module_test.rb +45 -0
  99. data/test/multi_measure_test.rb +38 -0
  100. data/test/multi_printer_test.rb +83 -0
  101. data/test/no_method_class_test.rb +15 -0
  102. data/test/pause_resume_test.rb +166 -0
  103. data/test/prime.rb +54 -0
  104. data/test/printers_test.rb +255 -0
  105. data/test/printing_recursive_graph_test.rb +127 -0
  106. data/test/rack_test.rb +93 -0
  107. data/test/recursive_test.rb +212 -0
  108. data/test/singleton_test.rb +38 -0
  109. data/test/stack_printer_test.rb +65 -0
  110. data/test/stack_test.rb +138 -0
  111. data/test/start_stop_test.rb +112 -0
  112. data/test/test_helper.rb +264 -0
  113. data/test/thread_test.rb +187 -0
  114. data/test/unique_call_path_test.rb +202 -0
  115. data/test/yarv_test.rb +55 -0
  116. metadata +211 -0
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyProf
4
+ class AggregateCallInfo
5
+ attr_reader :call_infos, :method_info
6
+
7
+ def initialize(call_infos, method_info)
8
+ if call_infos.length == 0
9
+ raise(ArgumentError, "Must specify at least one call info.")
10
+ end
11
+ @call_infos = call_infos
12
+ @method_info = method_info
13
+ end
14
+
15
+ def target
16
+ call_infos.first.target
17
+ end
18
+
19
+ def parent
20
+ call_infos.first.parent
21
+ end
22
+
23
+ def line
24
+ call_infos.first.line
25
+ end
26
+
27
+ def children
28
+ call_infos.inject(Array.new) do |result, call_info|
29
+ result.concat(call_info.children)
30
+ end
31
+ end
32
+
33
+ def total_time(i = 0)
34
+ aggregate_roots(:total_time, i)
35
+ end
36
+
37
+ def self_time(i = 0)
38
+ aggregate_roots(:self_time, i)
39
+ end
40
+
41
+ def wait_time(i = 0)
42
+ aggregate_roots(:wait_time, i)
43
+ end
44
+
45
+ def children_time(i = 0)
46
+ aggregate_roots(:children_time, i)
47
+ end
48
+
49
+ def called
50
+ aggregate_all(:called)
51
+ end
52
+
53
+ def to_s
54
+ "#{call_infos.first.target.full_name}"
55
+ end
56
+
57
+ private
58
+
59
+ # return all call_infos which are not (grand) children of any other node in the list of given call_infos
60
+ def roots
61
+ @roots ||= method_info.recursive? ? CallInfo.roots_of(call_infos) : call_infos
62
+ end
63
+
64
+ def aggregate_all(method_name)
65
+ call_infos.inject(0) do |sum, call_info|
66
+ sum + call_info.send(method_name)
67
+ end
68
+ end
69
+
70
+ def aggregate_roots(method_name, *args)
71
+ roots.inject(0) do |sum, call_info|
72
+ sum + call_info.send(method_name, *args)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,117 @@
1
+ <style type="text/css">
2
+ <!--
3
+ body {
4
+ font-size:70%;
5
+ padding:0;
6
+ margin:5px;
7
+ margin-right:0px;
8
+ margin-left:0px;
9
+ background: #ffffff;
10
+ }
11
+ ul {
12
+ margin-left:0px;
13
+ margin-top:0px;
14
+ margin-bottom:0px;
15
+ padding-left:0px;
16
+ list-style-type:none;
17
+ }
18
+ li {
19
+ margin-left:11px;
20
+ padding:0px;
21
+ white-space:nowrap;
22
+ border-top:1px solid #cccccc;
23
+ border-left:1px solid #cccccc;
24
+ border-bottom:none;
25
+ }
26
+ .thread {
27
+ margin-left:11px;
28
+ background:#708090;
29
+ padding-top:3px;
30
+ padding-left:12px;
31
+ padding-bottom:2px;
32
+ border-left:1px solid #CCCCCC;
33
+ border-top:1px solid #CCCCCC;
34
+ font-weight:bold;
35
+ }
36
+ .hidden {
37
+ display:none;
38
+ width:0px;
39
+ height:0px;
40
+ margin:0px;
41
+ padding:0px;
42
+ border-style:none;
43
+ }
44
+ .color01 { background:#adbdeb }
45
+ .color05 { background:#9daddb }
46
+ .color0 { background:#8d9dcb }
47
+ .color1 { background:#89bccb }
48
+ .color2 { background:#56e3e7 }
49
+ .color3 { background:#32cd70 }
50
+ .color4 { background:#a3d53c }
51
+ .color5 { background:#c4cb34 }
52
+ .color6 { background:#dcb66d }
53
+ .color7 { background:#cda59e }
54
+ .color8 { background:#be9d9c }
55
+ .color9 { background:#cf947a }
56
+ #commands {
57
+ font-size:10pt;
58
+ padding:10px;
59
+ margin-left:11px;
60
+ margin-bottom:0px;
61
+ margin-top:0px;
62
+ background:#708090;
63
+ border-top:1px solid #cccccc;
64
+ border-left:1px solid #cccccc;
65
+ border-bottom:none;
66
+ }
67
+ #titlebar {
68
+ font-size:10pt;
69
+ padding:10px;
70
+ margin-left:11px;
71
+ margin-bottom:0px;
72
+ margin-top:10px;
73
+ background:#8090a0;
74
+ border-top:1px solid #cccccc;
75
+ border-left:1px solid #cccccc;
76
+ border-bottom:none;
77
+ }
78
+ #help {
79
+ font-size:10pt;
80
+ padding:10px;
81
+ margin-left:11px;
82
+ margin-bottom:0px;
83
+ margin-top:0px;
84
+ background:#8090a0;
85
+ display:none;
86
+ border-top:1px solid #cccccc;
87
+ border-left:1px solid #cccccc;
88
+ border-bottom:none;
89
+ }
90
+ #sentinel {
91
+ height: 400px;
92
+ margin-left:11px;
93
+ background:#8090a0;
94
+ border-top:1px solid #cccccc;
95
+ border-left:1px solid #cccccc;
96
+ border-bottom:none;
97
+ }
98
+ input { margin-left:10px; }
99
+
100
+ .toggle {
101
+ background: url(data:image/png;base64,%s) no-repeat left center;
102
+ float:left;
103
+ width:9px;
104
+ height:9px;
105
+ margin:2px 1px 1px 1px;
106
+ }
107
+
108
+ .toggle.minus {
109
+ background-position: -9px 0;
110
+ }
111
+
112
+ .toggle.plus {
113
+ background-position: -18px 0;
114
+ }
115
+
116
+ -->
117
+ </style>
@@ -0,0 +1,385 @@
1
+ <script type="text/javascript">
2
+ /*
3
+ Copyright (C) 2005,2009 Stefan Kaes
4
+ skaes@railsexpress.de
5
+ */
6
+
7
+ function rootNode() {
8
+ return currentThread;
9
+ }
10
+
11
+ function showUL(node, show) {
12
+ var lis = node.childNodes;
13
+ var l = lis.length;
14
+ for (var i=0; i < l ; i++ ) {
15
+ toggle(lis[i], show);
16
+ }
17
+ }
18
+
19
+ function findUlChild(li){
20
+ var ul = li.childNodes[2];
21
+ while (ul && ul.nodeName != "UL") {
22
+ ul = ul.nextSibling;
23
+ }
24
+ return ul;
25
+ }
26
+
27
+ function isLeafNode(li) {
28
+ var img = li.firstChild;
29
+ return (img.className.indexOf('empty') > -1);
30
+ }
31
+
32
+ function toggle(li, show) {
33
+ if (isLeafNode(li))
34
+ return;
35
+
36
+ var img = li.firstChild;
37
+ img.className = 'toggle ';
38
+ img.className += show ? 'minus' : 'plus';
39
+
40
+ var ul = findUlChild(li);
41
+ if (ul) {
42
+ ul.style.display = show ? 'block' : 'none';
43
+ showUL(ul, true);
44
+ }
45
+ }
46
+
47
+ function toggleLI(li) {
48
+ var img = li.firstChild;
49
+ if (img.className.indexOf("minus")>-1)
50
+ toggle(li, false);
51
+ else {
52
+ if (img.className.indexOf("plus")>-1)
53
+ toggle(li, true);
54
+ }
55
+ }
56
+
57
+ function aboveThreshold(text, threshold) {
58
+ var match = text.match(/\d+[.,]\d+/);
59
+ return (match && parseFloat(match[0].replace(/,/, '.'))>=threshold);
60
+ }
61
+
62
+ function setThresholdLI(li, threshold) {
63
+ var img = li.firstChild;
64
+ var text = img.nextSibling.firstChild;
65
+ var ul = findUlChild(li);
66
+
67
+ var visible = aboveThreshold(text.nodeValue, threshold) ? 1 : 0;
68
+
69
+ var count = 0;
70
+ if (ul) {
71
+ count = setThresholdUL(ul, threshold);
72
+ }
73
+ if (count>0) {
74
+ img.className = 'toggle minus';
75
+ }
76
+ else {
77
+ img.className = 'toggle empty';
78
+ }
79
+ if (visible) {
80
+ li.style.display = 'block'
81
+ }
82
+ else {
83
+ li.style.display = 'none'
84
+ }
85
+ return visible;
86
+ }
87
+
88
+ function setThresholdUL(node, threshold) {
89
+ var lis = node.childNodes;
90
+ var l = lis.length;
91
+
92
+ var count = 0;
93
+ for ( var i = 0; i < l ; i++ ) {
94
+ count = count + setThresholdLI(lis[i], threshold);
95
+ }
96
+
97
+ var visible = (count > 0) ? 1 : 0;
98
+ if (visible) {
99
+ node.style.display = 'block';
100
+ }
101
+ else {
102
+ node.style.display = 'none';
103
+ }
104
+ return visible;
105
+ }
106
+
107
+ function toggleChildren(img, event) {
108
+ event.cancelBubble=true;
109
+ if (img.className.indexOf('empty') > -1)
110
+ return;
111
+
112
+ var minus = (img.className.indexOf('minus') > -1);
113
+
114
+ if (minus) {
115
+ img.className = 'toggle plus';
116
+ }
117
+ else
118
+ img.className = 'toggle minus';
119
+
120
+ var li = img.parentNode;
121
+ var ul = findUlChild(li);
122
+ if (ul) {
123
+ if (minus)
124
+ ul.style.display = 'none';
125
+ else
126
+ ul.style.display = 'block';
127
+ }
128
+ if (minus)
129
+ moveSelectionIfNecessary(li);
130
+ }
131
+
132
+ function showChildren(li) {
133
+ var img = li.firstChild;
134
+ if (img.className.indexOf('empty') > -1)
135
+ return;
136
+ img.className = 'toggle minus';
137
+
138
+ var ul = findUlChild(li);
139
+ if (ul) {
140
+ ul.style.display = 'block';
141
+ }
142
+ }
143
+
144
+ function setThreshold() {
145
+ var tv = document.getElementById("threshold").value;
146
+ if (tv.match(/[0-9]+([.,][0-9]+)?/)) {
147
+ var f = parseFloat(tv.replace(/,/, '.'));
148
+ var threads = document.getElementsByName("thread");
149
+ var l = threads.length;
150
+ for ( var i = 0; i < l ; i++ ) {
151
+ setThresholdUL(threads[i], f);
152
+ }
153
+ var p = selectedNode;
154
+ while (p && p.style.display=='none')
155
+ p=p.parentNode.parentNode;
156
+ if (p && p.nodeName=="LI")
157
+ selectNode(p);
158
+ }
159
+ else {
160
+ alert("Please specify a decimal number as threshold value!");
161
+ }
162
+ }
163
+
164
+ function expandAll(event) {
165
+ toggleAll(event, true);
166
+ }
167
+
168
+ function collapseAll(event) {
169
+ toggleAll(event, false);
170
+ selectNode(rootNode(), null);
171
+ }
172
+
173
+ function toggleAll(event, show) {
174
+ event.cancelBubble=true;
175
+ var threads = document.getElementsByName("thread");
176
+ var l = threads.length;
177
+ for ( var i = 0; i < l ; i++ ) {
178
+ showUL(threads[i], show);
179
+ }
180
+ }
181
+
182
+ function toggleHelp(node) {
183
+ var help = document.getElementById("help");
184
+ if (node.value == "Show Help") {
185
+ node.value = "Hide Help";
186
+ help.style.display = 'block';
187
+ }
188
+ else {
189
+ node.value = "Show Help";
190
+ help.style.display = 'none';
191
+ }
192
+ }
193
+
194
+ var selectedNode = null;
195
+ var selectedColor = null;
196
+ var selectedThread = null;
197
+
198
+ function descendentOf(a,b){
199
+ while (a!=b && b!=null)
200
+ b=b.parentNode;
201
+ return (a==b);
202
+ }
203
+
204
+ function moveSelectionIfNecessary(node){
205
+ if (descendentOf(node, selectedNode))
206
+ selectNode(node, null);
207
+ }
208
+
209
+ function selectNode(node, event) {
210
+ if (event) {
211
+ event.cancelBubble = true;
212
+ thread = findThread(node);
213
+ selectThread(thread);
214
+ }
215
+ if (selectedNode) {
216
+ selectedNode.style.background = selectedColor;
217
+ }
218
+ selectedNode = node;
219
+ selectedColor = node.style.background;
220
+ selectedNode.style.background = "red";
221
+ selectedNode.scrollIntoView();
222
+ window.scrollBy(0,-400);
223
+ }
224
+
225
+ function moveUp(){
226
+ move(selectedNode.previousSibling);
227
+ }
228
+
229
+ function moveDown(){
230
+ move(selectedNode.nextSibling);
231
+ }
232
+
233
+ function move(p) {
234
+ while (p && p.style.display == 'none')
235
+ p = p.nextSibling;
236
+ if (p && p.nodeName == "LI") {
237
+ selectNode(p, null);
238
+ }
239
+ }
240
+
241
+ function moveLeft(){
242
+ var p = selectedNode.parentNode.parentNode;
243
+ if (p && p.nodeName=="LI") {
244
+ selectNode(p, null);
245
+ }
246
+ }
247
+
248
+ function moveRight(){
249
+ if (!isLeafNode(selectedNode)) {
250
+ showChildren(selectedNode);
251
+ var ul = findUlChild(selectedNode);
252
+ if (ul) {
253
+ selectNode(ul.firstChild, null);
254
+ }
255
+ }
256
+ }
257
+
258
+ function moveForward(){
259
+ if (isLeafNode(selectedNode)) {
260
+ var p = selectedNode;
261
+ while ((p.nextSibling == null || p.nextSibling.style.display=='none') && p.nodeName=="LI") {
262
+ p = p.parentNode.parentNode;
263
+ }
264
+ if (p.nodeName=="LI")
265
+ selectNode(p.nextSibling, null);
266
+ }
267
+ else {
268
+ moveRight();
269
+ }
270
+ }
271
+
272
+ function isExpandedNode(li){
273
+ var img = li.firstChild;
274
+ return(img.className.indexOf('minus')>-1);
275
+ }
276
+
277
+ function moveBackward(){
278
+ var p = selectedNode;
279
+ var q = p.previousSibling;
280
+ while (q != null && q.style.display=='none')
281
+ q = q.previousSibling;
282
+ if (q == null) {
283
+ p = p.parentNode.parentNode;
284
+ } else {
285
+ while (!isLeafNode(q) && isExpandedNode(q)) {
286
+ q = findUlChild(q).lastChild;
287
+ while (q.style.display=='none')
288
+ q = q.previousSibling;
289
+ }
290
+ p = q;
291
+ }
292
+ if (p.nodeName=="LI")
293
+ selectNode(p, null);
294
+ }
295
+
296
+ function moveHome() {
297
+ selectNode(currentThread);
298
+ }
299
+
300
+ var currentThreadIndex = null;
301
+
302
+ function findThread(node){
303
+ while (node && !node.parentNode.nodeName.match(/BODY|DIV/g)) {
304
+ node = node.parentNode;
305
+ }
306
+ return node.firstChild;
307
+ }
308
+
309
+ function selectThread(node){
310
+ var threads = document.getElementsByName("thread");
311
+ currentThread = node;
312
+ for (var i=0; i<threads.length; i++) {
313
+ if (threads[i]==currentThread.parentNode)
314
+ currentThreadIndex = i;
315
+ }
316
+ }
317
+
318
+ function nextThread(){
319
+ var threads = document.getElementsByName("thread");
320
+ if (currentThreadIndex==threads.length-1)
321
+ currentThreadIndex = 0;
322
+ else
323
+ currentThreadIndex += 1
324
+ currentThread = threads[currentThreadIndex].firstChild;
325
+ selectNode(currentThread, null);
326
+ }
327
+
328
+ function previousThread(){
329
+ var threads = document.getElementsByName("thread");
330
+ if (currentThreadIndex==0)
331
+ currentThreadIndex = threads.length-1;
332
+ else
333
+ currentThreadIndex -= 1
334
+ currentThread = threads[currentThreadIndex].firstChild;
335
+ selectNode(currentThread, null);
336
+ }
337
+
338
+ function switchThread(node, event){
339
+ event.cancelBubble = true;
340
+ selectThread(node.nextSibling.firstChild);
341
+ selectNode(currentThread, null);
342
+ }
343
+
344
+ function handleKeyEvent(event){
345
+ var code = event.charCode ? event.charCode : event.keyCode;
346
+ var str = String.fromCharCode(code);
347
+ switch (str) {
348
+ case "a": moveLeft(); break;
349
+ case "s": moveDown(); break;
350
+ case "d": moveRight(); break;
351
+ case "w": moveUp(); break;
352
+ case "f": moveForward(); break;
353
+ case "b": moveBackward(); break;
354
+ case "x": toggleChildren(selectedNode.firstChild, event); break;
355
+ case "*": toggleLI(selectedNode); break;
356
+ case "n": nextThread(); break;
357
+ case "h": moveHome(); break;
358
+ case "p": previousThread(); break;
359
+ }
360
+ }
361
+ document.onkeypress=function(event){ handleKeyEvent(event) };
362
+
363
+ window.onload=function(){
364
+ var images = document.querySelectorAll(".toggle");
365
+ for (var i=0; i<images.length; i++) {
366
+ var img = images[i];
367
+ img.onclick = function(event){ toggleChildren(this, event); return false; };
368
+ }
369
+ var divs = document.getElementsByTagName("div");
370
+ for (i=0; i<divs.length; i++) {
371
+ var div = divs[i];
372
+ if (div.className == "thread")
373
+ div.onclick = function(event){ switchThread(this, event) };
374
+ }
375
+ var lis = document.getElementsByTagName("li");
376
+ for (var i=0; i<lis.length; i++) {
377
+ lis[i].onclick = function(event){ selectNode(this, event); };
378
+ }
379
+ var threads = document.getElementsByName("thread");;
380
+ currentThreadIndex = 0;
381
+ currentThread = threads[0].firstChild;
382
+ selectNode(currentThread, null);
383
+ };
384
+
385
+ </script>