ruby-prof 1.7.2 → 2.0.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.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGES → CHANGELOG.md} +112 -178
  3. data/README.md +5 -5
  4. data/bin/ruby-prof +1 -4
  5. data/docs/advanced-usage.md +132 -0
  6. data/docs/alternatives.md +98 -0
  7. data/docs/architecture.md +122 -0
  8. data/docs/best-practices.md +27 -0
  9. data/docs/getting-started.md +130 -0
  10. data/docs/history.md +11 -0
  11. data/docs/index.md +45 -0
  12. data/docs/profiling-rails.md +64 -0
  13. data/docs/public/examples/example.rb +33 -0
  14. data/docs/public/examples/generate_reports.rb +92 -0
  15. data/docs/public/examples/reports/call_info.txt +27 -0
  16. data/docs/public/examples/reports/call_stack.html +835 -0
  17. data/docs/public/examples/reports/callgrind.out +150 -0
  18. data/docs/public/examples/reports/flame_graph.html +408 -0
  19. data/docs/public/examples/reports/flat.txt +45 -0
  20. data/docs/public/examples/reports/graph.dot +129 -0
  21. data/docs/public/examples/reports/graph.html +1319 -0
  22. data/docs/public/examples/reports/graph.txt +100 -0
  23. data/docs/public/examples/reports/graphviz_viewer.html +1 -0
  24. data/docs/public/images/call_stack.png +0 -0
  25. data/docs/public/images/class_diagram.png +0 -0
  26. data/docs/public/images/dot_printer.png +0 -0
  27. data/docs/public/images/flame_graph.png +0 -0
  28. data/docs/public/images/flat.png +0 -0
  29. data/docs/public/images/graph.png +0 -0
  30. data/docs/public/images/graph_html.png +0 -0
  31. data/docs/public/images/ruby-prof-logo.svg +1 -0
  32. data/docs/reports.md +150 -0
  33. data/docs/stylesheets/extra.css +80 -0
  34. data/ext/ruby_prof/rp_allocation.c +0 -15
  35. data/ext/ruby_prof/rp_allocation.h +29 -33
  36. data/ext/ruby_prof/rp_call_tree.c +3 -0
  37. data/ext/ruby_prof/rp_call_tree.h +1 -4
  38. data/ext/ruby_prof/rp_call_trees.h +1 -4
  39. data/ext/ruby_prof/rp_measurement.c +0 -5
  40. data/ext/ruby_prof/rp_measurement.h +49 -53
  41. data/ext/ruby_prof/rp_method.c +3 -0
  42. data/ext/ruby_prof/rp_method.h +1 -4
  43. data/ext/ruby_prof/rp_profile.c +1 -1
  44. data/ext/ruby_prof/rp_profile.h +1 -5
  45. data/ext/ruby_prof/rp_stack.h +50 -53
  46. data/ext/ruby_prof/rp_thread.h +1 -4
  47. data/ext/ruby_prof/ruby_prof.h +1 -4
  48. data/ext/ruby_prof/vc/ruby_prof.vcxproj +7 -8
  49. data/lib/ruby-prof/assets/call_stack_printer.html.erb +746 -711
  50. data/lib/ruby-prof/assets/flame_graph_printer.html.erb +412 -0
  51. data/lib/ruby-prof/assets/graph_printer.html.erb +355 -355
  52. data/lib/ruby-prof/call_tree.rb +57 -57
  53. data/lib/ruby-prof/call_tree_visitor.rb +36 -36
  54. data/lib/ruby-prof/measurement.rb +17 -17
  55. data/lib/ruby-prof/printers/abstract_printer.rb +19 -33
  56. data/lib/ruby-prof/printers/call_info_printer.rb +53 -53
  57. data/lib/ruby-prof/printers/call_stack_printer.rb +168 -180
  58. data/lib/ruby-prof/printers/call_tree_printer.rb +132 -145
  59. data/lib/ruby-prof/printers/dot_printer.rb +177 -132
  60. data/lib/ruby-prof/printers/flame_graph_printer.rb +79 -0
  61. data/lib/ruby-prof/printers/flat_printer.rb +52 -52
  62. data/lib/ruby-prof/printers/graph_html_printer.rb +62 -63
  63. data/lib/ruby-prof/printers/graph_printer.rb +112 -113
  64. data/lib/ruby-prof/printers/multi_printer.rb +134 -127
  65. data/lib/ruby-prof/profile.rb +13 -0
  66. data/lib/ruby-prof/rack.rb +114 -105
  67. data/lib/ruby-prof/task.rb +147 -147
  68. data/lib/ruby-prof/thread.rb +20 -20
  69. data/lib/ruby-prof/version.rb +1 -1
  70. data/lib/ruby-prof.rb +50 -52
  71. data/lib/unprof.rb +10 -10
  72. data/ruby-prof.gemspec +5 -5
  73. data/test/abstract_printer_test.rb +25 -27
  74. data/test/alias_test.rb +203 -117
  75. data/test/call_tree_builder.rb +126 -126
  76. data/test/call_tree_visitor_test.rb +27 -27
  77. data/test/call_trees_test.rb +66 -66
  78. data/test/duplicate_names_test.rb +32 -32
  79. data/test/dynamic_method_test.rb +50 -50
  80. data/test/exceptions_test.rb +24 -24
  81. data/test/exclude_threads_test.rb +48 -48
  82. data/test/fiber_test.rb +72 -72
  83. data/test/inverse_call_tree_test.rb +174 -174
  84. data/test/line_number_test.rb +138 -1
  85. data/test/marshal_test.rb +144 -145
  86. data/test/measure_allocations.rb +26 -26
  87. data/test/measure_allocations_test.rb +340 -1
  88. data/test/measure_process_time_test.rb +3098 -3142
  89. data/test/measure_times.rb +56 -56
  90. data/test/measure_wall_time_test.rb +511 -372
  91. data/test/measurement_test.rb +82 -82
  92. data/test/merge_test.rb +48 -48
  93. data/test/multi_printer_test.rb +52 -66
  94. data/test/no_method_class_test.rb +15 -15
  95. data/test/pause_resume_test.rb +171 -171
  96. data/test/prime.rb +54 -54
  97. data/test/prime_script.rb +5 -5
  98. data/test/printer_call_stack_test.rb +28 -27
  99. data/test/printer_call_tree_test.rb +30 -30
  100. data/test/printer_flame_graph_test.rb +82 -0
  101. data/test/printer_flat_test.rb +99 -99
  102. data/test/printer_graph_html_test.rb +62 -59
  103. data/test/printer_graph_test.rb +42 -40
  104. data/test/printers_test.rb +28 -44
  105. data/test/printing_recursive_graph_test.rb +81 -81
  106. data/test/profile_test.rb +101 -101
  107. data/test/rack_test.rb +103 -93
  108. data/test/recursive_test.rb +139 -139
  109. data/test/scheduler.rb +4 -0
  110. data/test/singleton_test.rb +39 -38
  111. data/test/stack_printer_test.rb +61 -61
  112. data/test/start_stop_test.rb +106 -106
  113. data/test/test_helper.rb +4 -0
  114. data/test/thread_test.rb +29 -29
  115. data/test/unique_call_path_test.rb +123 -123
  116. data/test/yarv_test.rb +56 -56
  117. metadata +53 -11
  118. data/ext/ruby_prof/rp_measure_memory.c +0 -46
  119. data/lib/ruby-prof/compatibility.rb +0 -113
  120. data/test/compatibility_test.rb +0 -49
  121. data/test/measure_memory_test.rb +0 -1193
@@ -1,711 +1,746 @@
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>
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
- font-weight: normal;
23
- }
24
-
25
- li {
26
- margin-left: 11px;
27
- padding: 0px;
28
- white-space: nowrap;
29
- border-top: 1px solid #cccccc;
30
- border-left: 1px solid #cccccc;
31
- border-bottom: none;
32
- }
33
-
34
- .thread {
35
- margin-left: 11px;
36
- background: #708090;
37
- padding-top: 3px;
38
- padding-left: 12px;
39
- padding-bottom: 2px;
40
- border-left: 1px solid #CCCCCC;
41
- border-top: 1px solid #CCCCCC;
42
- font-weight: bold;
43
- }
44
-
45
- .hidden {
46
- display: none;
47
- width: 0px;
48
- height: 0px;
49
- margin: 0px;
50
- padding: 0px;
51
- border-style: none;
52
- }
53
-
54
- .color01 {
55
- background: #adbdeb
56
- }
57
-
58
- .color05 {
59
- background: #9daddb
60
- }
61
-
62
- .color0 {
63
- background: #8d9dcb
64
- }
65
-
66
- .color1 {
67
- background: #89bccb
68
- }
69
-
70
- .color2 {
71
- background: #56e3e7
72
- }
73
-
74
- .color3 {
75
- background: #32cd70
76
- }
77
-
78
- .color4 {
79
- background: #a3d53c
80
- }
81
-
82
- .color5 {
83
- background: #c4cb34
84
- }
85
-
86
- .color6 {
87
- background: #dcb66d
88
- }
89
-
90
- .color7 {
91
- background: #cda59e
92
- }
93
-
94
- .color8 {
95
- background: #be9d9c
96
- }
97
-
98
- .color9 {
99
- background: #cf947a
100
- }
101
-
102
- #commands {
103
- font-size: 10pt;
104
- padding: 10px;
105
- margin-left: 11px;
106
- margin-bottom: 0px;
107
- margin-top: 0px;
108
- background: #708090;
109
- border-top: 1px solid #cccccc;
110
- border-left: 1px solid #cccccc;
111
- border-bottom: none;
112
- }
113
-
114
- #titlebar {
115
- font-size: 10pt;
116
- padding: 10px;
117
- margin-left: 11px;
118
- margin-bottom: 0px;
119
- margin-top: 10px;
120
- background: #8090a0;
121
- border-top: 1px solid #cccccc;
122
- border-left: 1px solid #cccccc;
123
- border-bottom: none;
124
- }
125
-
126
- #help {
127
- font-size: 10pt;
128
- padding: 10px;
129
- margin-left: 11px;
130
- margin-bottom: 0px;
131
- margin-top: 0px;
132
- background: #8090a0;
133
- display: none;
134
- border-top: 1px solid #cccccc;
135
- border-left: 1px solid #cccccc;
136
- border-bottom: none;
137
- }
138
-
139
- #sentinel {
140
- height: 400px;
141
- margin-left: 11px;
142
- background: #8090a0;
143
- border-top: 1px solid #cccccc;
144
- border-left: 1px solid #cccccc;
145
- border-bottom: none;
146
- }
147
-
148
- input {
149
- margin-left: 10px;
150
- }
151
-
152
- .toggle {
153
- background: url(data:image/png;base64,<%= base64_image %>) no-repeat left center;
154
- float: left;
155
- width: 9px;
156
- height: 9px;
157
- margin: 2px 1px 1px 1px;
158
- }
159
-
160
- .toggle.minus {
161
- background-position: -9px 0;
162
- }
163
-
164
- .toggle.plus {
165
- background-position: -18px 0;
166
- }
167
- </style>
168
-
169
- <script type="text/javascript">
170
- function rootNode()
171
- {
172
- return currentThread
173
- }
174
-
175
- function showUL(node, show)
176
- {
177
- Array.prototype.forEach.call(node.childNodes, function(child)
178
- {
179
- if (child.nodeName == 'LI')
180
- toggle(child, show)
181
- })
182
- }
183
-
184
- function findUlChild(li)
185
- {
186
- var ul = li.childNodes[2]
187
- while (ul && ul.nodeName != "UL")
188
- {
189
- ul = ul.nextSibling
190
- }
191
- return ul
192
- }
193
-
194
- function isLeafNode(li)
195
- {
196
- var element = li.querySelector('a')
197
- return element.classList.contains('empty')
198
- }
199
-
200
- function toggle(li, show)
201
- {
202
- if (isLeafNode(li))
203
- return
204
-
205
- var img = li.firstChild
206
- img.className = 'toggle '
207
- img.className += show ? 'minus' : 'plus'
208
-
209
- var ul = findUlChild(li)
210
- if (ul)
211
- {
212
- ul.style.display = show ? 'block' : 'none'
213
- showUL(ul, true)
214
- }
215
- }
216
-
217
- function toggleLI(li)
218
- {
219
- var img = li.firstChild
220
- if (img.className.indexOf("minus") > -1)
221
- toggle(li, false)
222
- else
223
- {
224
- if (img.className.indexOf("plus") > -1)
225
- toggle(li, true)
226
- }
227
- }
228
-
229
- function aboveThreshold(text, threshold)
230
- {
231
- var match = text.match(/\d+[.,]\d+%/)
232
- if (!match)
233
- {
234
- return true
235
- }
236
- else
237
- {
238
- var value = parseFloat(match[0].replace(/,/, '.'))
239
- return value >= threshold
240
- }
241
- }
242
-
243
- function setThresholdLI(li, threshold)
244
- {
245
- var a = li.querySelector('a')
246
- var span = li.querySelector('span')
247
- var ul = li.querySelector('ul')
248
-
249
- var visible = aboveThreshold(span.textContent, threshold) ? 1 : 0
250
-
251
- var count = 0
252
- if (ul)
253
- {
254
- count = setThresholdUL(ul, threshold)
255
- }
256
-
257
- if (count > 0)
258
- {
259
- a.className = 'toggle minus'
260
- }
261
- else
262
- {
263
- a.className = 'toggle empty'
264
- }
265
-
266
- if (visible)
267
- {
268
- li.style.display = 'block'
269
- } else
270
- {
271
- li.style.display = 'none'
272
- }
273
- return visible
274
- }
275
-
276
- function setThresholdUL(node, threshold)
277
- {
278
- var count = 0
279
- Array.prototype.forEach.call(node.childNodes, function(child)
280
- {
281
- if (child.nodeName == 'LI')
282
- count = count + setThresholdLI(child, threshold)
283
- })
284
-
285
- var visible = (count > 0) ? 1 : 0
286
- if (visible)
287
- {
288
- node.style.display = 'block'
289
- } else
290
- {
291
- node.style.display = 'none'
292
- }
293
- return visible
294
- }
295
-
296
- function toggleChildren(img, event)
297
- {
298
- event.cancelBubble = true
299
- if (img.className.indexOf('empty') > -1)
300
- return
301
-
302
- var minus = (img.className.indexOf('minus') > -1)
303
-
304
- if (minus)
305
- {
306
- img.className = 'toggle plus'
307
- } else
308
- img.className = 'toggle minus'
309
-
310
- var li = img.parentNode
311
- var ul = findUlChild(li)
312
- if (ul)
313
- {
314
- if (minus)
315
- ul.style.display = 'none'
316
- else
317
- ul.style.display = 'block'
318
- }
319
- if (minus)
320
- moveSelectionIfNecessary(li)
321
- }
322
-
323
- function showChildren(li)
324
- {
325
- var img = li.firstChild
326
- if (img.className.indexOf('empty') > -1)
327
- return
328
- img.className = 'toggle minus'
329
-
330
- var ul = findUlChild(li)
331
- if (ul)
332
- {
333
- ul.style.display = 'block'
334
- }
335
- }
336
-
337
- function setThreshold()
338
- {
339
- var tv = document.getElementById("threshold").value
340
- if (tv.match(/[0-9]+([.,][0-9]+)?/))
341
- {
342
- var f = parseFloat(tv.replace(/,/, '.'))
343
- var threads = document.getElementsByName("thread")
344
- var l = threads.length
345
- for (var i = 0; i < l; i++)
346
- {
347
- setThresholdUL(threads[i], f)
348
- }
349
- var p = selectedNode
350
- while (p && p.style.display == 'none')
351
- p = p.parentNode.parentNode
352
- if (p && p.nodeName == "LI")
353
- selectNode(p)
354
- } else
355
- {
356
- alert("Please specify a decimal number as threshold value!")
357
- }
358
- }
359
-
360
- function expandAll(event)
361
- {
362
- toggleAll(event, true)
363
- }
364
-
365
- function collapseAll(event)
366
- {
367
- toggleAll(event, false)
368
- selectNode(rootNode(), null)
369
- }
370
-
371
- function toggleAll(event, show)
372
- {
373
- event.cancelBubble = true
374
- var threads = document.getElementsByName("thread")
375
- var l = threads.length
376
- for (var i = 0; i < l; i++)
377
- {
378
- showUL(threads[i], show)
379
- }
380
- }
381
-
382
- function toggleHelp(node)
383
- {
384
- var help = document.getElementById("help")
385
- if (node.value == "Show Help")
386
- {
387
- node.value = "Hide Help"
388
- help.style.display = 'block'
389
- } else
390
- {
391
- node.value = "Show Help"
392
- help.style.display = 'none'
393
- }
394
- }
395
-
396
- var selectedNode = null
397
- var selectedColor = null
398
- var selectedThread = null
399
-
400
- function descendentOf(a, b)
401
- {
402
- while (a != b && b != null)
403
- b = b.parentNode
404
- return (a == b)
405
- }
406
-
407
- function moveSelectionIfNecessary(node)
408
- {
409
- if (descendentOf(node, selectedNode))
410
- selectNode(node, null)
411
- }
412
-
413
- function selectNode(node, event)
414
- {
415
- if (event)
416
- {
417
- event.cancelBubble = true
418
- thread = findThread(node)
419
- selectThread(thread)
420
- }
421
- if (selectedNode)
422
- {
423
- selectedNode.style.background = selectedColor
424
- }
425
- selectedNode = node
426
- selectedColor = node.style.background
427
- selectedNode.style.background = "red"
428
- selectedNode.scrollIntoView()
429
- window.scrollBy(0, -400)
430
- }
431
-
432
- function moveUp()
433
- {
434
- move(selectedNode.previousSibling)
435
- }
436
-
437
- function moveDown()
438
- {
439
- move(selectedNode.nextSibling)
440
- }
441
-
442
- function move(p)
443
- {
444
- while (p && p.style.display == 'none')
445
- p = p.nextSibling
446
- if (p && p.nodeName == "LI")
447
- {
448
- selectNode(p, null)
449
- }
450
- }
451
-
452
- function moveLeft()
453
- {
454
- var p = selectedNode.parentNode.parentNode
455
- if (p && p.nodeName == "LI")
456
- {
457
- selectNode(p, null)
458
- }
459
- }
460
-
461
- function moveRight()
462
- {
463
- if (!isLeafNode(selectedNode))
464
- {
465
- showChildren(selectedNode)
466
- var ul = findUlChild(selectedNode)
467
- if (ul)
468
- {
469
- selectNode(ul.firstChild, null)
470
- }
471
- }
472
- }
473
-
474
- function moveForward()
475
- {
476
- if (isLeafNode(selectedNode))
477
- {
478
- var p = selectedNode
479
- while ((p.nextSibling == null || p.nextSibling.style.display == 'none') && p.nodeName == "LI")
480
- {
481
- p = p.parentNode.parentNode
482
- }
483
- if (p.nodeName == "LI")
484
- selectNode(p.nextSibling, null)
485
- } else
486
- {
487
- moveRight()
488
- }
489
- }
490
-
491
- function isExpandedNode(li)
492
- {
493
- var img = li.firstChild
494
- return (img.className.indexOf('minus') > -1)
495
- }
496
-
497
- function moveBackward()
498
- {
499
- var p = selectedNode
500
- var q = p.previousSibling
501
- while (q != null && q.style.display == 'none')
502
- q = q.previousSibling
503
- if (q == null)
504
- {
505
- p = p.parentNode.parentNode
506
- } else
507
- {
508
- while (!isLeafNode(q) && isExpandedNode(q))
509
- {
510
- q = findUlChild(q).lastChild
511
- while (q.style.display == 'none')
512
- q = q.previousSibling
513
- }
514
- p = q
515
- }
516
- if (p.nodeName == "LI")
517
- selectNode(p, null)
518
- }
519
-
520
- function moveHome()
521
- {
522
- selectNode(currentThread)
523
- }
524
-
525
- var currentThreadIndex = null
526
-
527
- function findThread(node)
528
- {
529
- while (node && !node.parentNode.nodeName.match(/BODY|DIV/g))
530
- {
531
- node = node.parentNode
532
- }
533
- return node.firstChild
534
- }
535
-
536
- function selectThread(node)
537
- {
538
- var threads = document.getElementsByName("thread")
539
- currentThread = node
540
- for (var i = 0; i < threads.length; i++)
541
- {
542
- if (threads[i] == currentThread.parentNode)
543
- currentThreadIndex = i
544
- }
545
- }
546
-
547
- function nextThread()
548
- {
549
- var threads = document.getElementsByName("thread")
550
- if (currentThreadIndex == threads.length - 1)
551
- currentThreadIndex = 0
552
- else
553
- currentThreadIndex += 1
554
- currentThread = threads[currentThreadIndex].firstChild
555
- selectNode(currentThread, null)
556
- }
557
-
558
- function previousThread()
559
- {
560
- var threads = document.getElementsByName("thread")
561
- if (currentThreadIndex == 0)
562
- currentThreadIndex = threads.length - 1
563
- else
564
- currentThreadIndex -= 1
565
- currentThread = threads[currentThreadIndex].firstChild
566
- selectNode(currentThread, null)
567
- }
568
-
569
- function switchThread(node, event)
570
- {
571
- event.cancelBubble = true
572
- selectThread(node.nextSibling.firstChild)
573
- selectNode(currentThread, null)
574
- }
575
-
576
- function handleKeyEvent(event)
577
- {
578
- var code = event.charCode ? event.charCode : event.keyCode
579
- var str = String.fromCharCode(code)
580
- switch (str)
581
- {
582
- case "a":
583
- moveLeft()
584
- break
585
- case "s":
586
- moveDown()
587
- break
588
- case "d":
589
- moveRight()
590
- break
591
- case "w":
592
- moveUp()
593
- break
594
- case "f":
595
- moveForward()
596
- break
597
- case "b":
598
- moveBackward()
599
- break
600
- case "x":
601
- toggleChildren(selectedNode.firstChild, event)
602
- break
603
- case "*":
604
- toggleLI(selectedNode)
605
- break
606
- case "n":
607
- nextThread()
608
- break
609
- case "h":
610
- moveHome()
611
- break
612
- case "p":
613
- previousThread()
614
- break
615
- }
616
- }
617
-
618
- document.onkeypress = function (event)
619
- {
620
- handleKeyEvent(event)
621
- }
622
-
623
- window.onload = function ()
624
- {
625
- var images = document.querySelectorAll(".toggle")
626
- for (var i = 0; i < images.length; i++)
627
- {
628
- var img = images[i]
629
- img.onclick = function (event)
630
- {
631
- toggleChildren(this, event)
632
- return false
633
- }
634
- }
635
- var divs = document.getElementsByTagName("div")
636
- for (i = 0; i < divs.length; i++)
637
- {
638
- var div = divs[i]
639
- if (div.className == "thread")
640
- div.onclick = function (event)
641
- {
642
- switchThread(this, event)
643
- }
644
- }
645
- var lis = document.getElementsByTagName("li")
646
- for (var i = 0; i < lis.length; i++)
647
- {
648
- lis[i].onclick = function (event)
649
- {
650
- selectNode(this, event)
651
- }
652
- }
653
-
654
- var threads = document.getElementsByName("thread")
655
- currentThreadIndex = 0
656
- currentThread = threads[0].querySelector('li')
657
- selectNode(currentThread, null)
658
- }
659
- </script>
660
-
661
- <% @overall_time = @result.threads.reduce(0) do |val, thread|
662
- val += thread.total_time
663
- end %>
664
- </head>
665
- <body>
666
- <div style="display: inline-block;">
667
- <div id="titlebar">
668
- Call tree for application <strong><%= application %> <%= arguments %></strong><br/> Generated on <%= Time.now %>
669
- with options <%= @options.inspect %><br/>
670
- </div>
671
- <div id="commands">
672
- <span style="font-size: 11pt; font-weight: bold;">Threshold:</span>
673
- <input value="1.0" size="3" id="threshold" type="text">
674
- <input value="Apply" onclick="setThreshold();" type="submit">
675
- <input value="Expand All" onclick="expandAll(event);" type="submit">
676
- <input value="Collapse All" onclick="collapseAll(event);" type="submit">
677
- <input value="Show Help" onclick="toggleHelp(this);" type="submit">
678
- </div>
679
- <ul style="display: none;" id="help">
680
- <li>* indicates recursively called methods</li>
681
- <li>Enter a decimal value <i>d</i> into the threshold field and click "Apply" to hide all nodes marked with time
682
- values lower than <em>d</em>.
683
- </li>
684
- <li>Click on "Expand All" for full tree expansion.</li>
685
- <li>Click on "Collapse All" to show only top level nodes.</li>
686
- <li>Use a, s, d, w as in Quake or Urban Terror to navigate the tree.</li>
687
- <li>Use f and b to navigate the tree in preorder forward and backwards.</li>
688
- <li>Use x to toggle visibility of a subtree.</li>
689
- <li>Use * to expand/collapse a whole subtree.</li>
690
- <li>Use h to navigate to thread root.</li>
691
- <li>Use n and p to navigate between threads.</li>
692
- <li>Click on background to move focus to a subtree.</li>
693
- </ul>
694
-
695
- <% @result.threads.each do |thread| %>
696
- <% thread_percent = 100 * (thread.total_time / @overall_time)
697
- thread_info = "#{"%4.2f%%" % thread_percent} ~ #{@overall_time}" %>
698
- <div class="thread">
699
- <span>Thread: <%= thread.id %>, Fiber: <%= thread.fiber_id %> (<%= thread_info %>)</span>
700
- <ul name="thread">
701
- <% visited = Set.new
702
- output = StringIO.new('')
703
- print_stack(output, visited, thread.call_tree, thread.call_tree.total_time) %>
704
- <%= output.string %>
705
- </ul>
706
- </div>
707
- <% end %>
708
- <div id="sentinel"></div>
709
- </div>
710
- </body>
711
- </html>
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title><%= title %></title>
6
+ <style>
7
+ * { box-sizing: border-box; }
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
10
+ font-size: 13px;
11
+ padding: 0;
12
+ margin: 0;
13
+ background: #f5f5f5;
14
+ color: #333;
15
+ }
16
+
17
+ ul {
18
+ margin: 0;
19
+ padding-left: 0;
20
+ list-style-type: none;
21
+ }
22
+
23
+ li {
24
+ margin-left: 20px;
25
+ padding: 2px 4px;
26
+ white-space: nowrap;
27
+ border-left: 2px solid transparent;
28
+ border-radius: 2px;
29
+ }
30
+
31
+ li:hover {
32
+ background: rgba(0,0,0,0.03);
33
+ }
34
+
35
+ .thread {
36
+ margin: 0 16px;
37
+ background: #fff;
38
+ padding: 8px 12px;
39
+ border-radius: 6px;
40
+ border: 1px solid #e0e0e0;
41
+ margin-bottom: 12px;
42
+ font-weight: 600;
43
+ color: #555;
44
+ }
45
+
46
+ .hidden {
47
+ display: none;
48
+ width: 0;
49
+ height: 0;
50
+ margin: 0;
51
+ padding: 0;
52
+ border-style: none;
53
+ }
54
+
55
+ .color01 { border-left-color: #90CAF9; background: #F5F9FF; }
56
+ .color05 { border-left-color: #64B5F6; background: #EEF5FF; }
57
+ .color0 { border-left-color: #42A5F5; background: #E8F1FF; }
58
+ .color1 { border-left-color: #26C6DA; background: #E6F9FB; }
59
+ .color2 { border-left-color: #26A69A; background: #E4F5F3; }
60
+ .color3 { border-left-color: #66BB6A; background: #E8F5E9; }
61
+ .color4 { border-left-color: #9CCC65; background: #F1F8E9; }
62
+ .color5 { border-left-color: #D4E157; background: #F9FBE7; }
63
+ .color6 { border-left-color: #FFA726; background: #FFF3E0; }
64
+ .color7 { border-left-color: #FF7043; background: #FBE9E7; }
65
+ .color8 { border-left-color: #EF5350; background: #FFEBEE; }
66
+ .color9 { border-left-color: #E53935; background: #FFEBEE; }
67
+
68
+ #header {
69
+ background: #0D2483;
70
+ color: #fff;
71
+ padding: 1rem 1.5rem;
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 16px;
75
+ flex-wrap: wrap;
76
+ }
77
+
78
+ #header .header-left {
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 2px;
82
+ }
83
+
84
+ #header h6 {
85
+ margin: 0;
86
+ font-size: 0.75rem;
87
+ text-transform: uppercase;
88
+ letter-spacing: 1.5px;
89
+ color: rgba(255, 255, 255, 0.6);
90
+ font-weight: 600;
91
+ }
92
+
93
+ #header h1 {
94
+ margin: 0;
95
+ font-size: 22px;
96
+ font-weight: 600;
97
+ white-space: nowrap;
98
+ }
99
+
100
+ .logo {
101
+ width: 140px;
102
+ height: 30px;
103
+ opacity: 0.5;
104
+ background-repeat: no-repeat;
105
+ background-image: url("data:image/svg+xml,%3Csvg id='Layer_1' data-name='Layer 1' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 190 41'%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:%23fff%7D%3C/style%3E%3C/defs%3E%3Cpath class='cls-1' d='M63.49 16.74c0-1.37-.91-2-2.26-2h-3.38v3.93h3.58c1.24.01 2.06-.56 2.06-1.93zM110.43 14.75h-3.6v5h3.6a2.32 2.32 0 0 0 2.5-2.49 2.34 2.34 0 0 0-2.5-2.51zM128.77 14.75h-3.53v4.94h3.53a2.28 2.28 0 0 0 2.47-2.45 2.3 2.3 0 0 0-2.47-2.49zM61.81 21.83h-4v4.32h3.91c1.44 0 2.45-.62 2.45-2.14s-.99-2.18-2.36-2.18zM21 14.75h-3.57v4.94H21a2.28 2.28 0 0 0 2.47-2.45A2.3 2.3 0 0 0 21 14.75z'/%3E%3Cpath class='cls-1' d='M184 .44H5.87A4.93 4.93 0 0 0 .94 5.37V35.5a4.94 4.94 0 0 0 4.93 4.94H184a4.94 4.94 0 0 0 4.94-4.94V5.37A4.94 4.94 0 0 0 184 .44zm-34.38 10.78c4.79 0 8.46 2.89 9.18 7.54h-3.86a5.48 5.48 0 0 0-10.68 0h-3.83c.71-4.65 4.36-7.54 9.19-7.54zM24 29.44L20.46 23h-3v6.46h-3.72v-18h7.68c3.41 0 5.78 2.07 5.78 5.76a5.34 5.34 0 0 1-2.88 5.14l3.82 7.06zm24.31-7.3c0 4.8-2.92 7.56-7.63 7.56S33 26.94 33 22.14V11.48h3.69v10.61c0 2.81 1.37 4.32 4 4.32s3.94-1.51 3.94-4.32V11.48h3.69zm13.92 7.3h-8v-18h7.52c3.24 0 5.59 1.61 5.59 5A4 4 0 0 1 65.51 20 4.22 4.22 0 0 1 68 24.21c0 3.43-2.4 5.23-5.81 5.23zm19.66-6.92v6.92h-3.7v-6.92l-6.81-11h4.29L80 18.85l4.34-7.37h4.32zm16-.5h-7.62v-3.26h7.59zm13 1h-4v6.39h-3.72v-18h7.77c3.41 0 5.79 2.09 5.79 5.79s-2.41 5.85-5.82 5.85zm20.86 6.39L128.26 23h-3v6.46h-3.72v-18h7.69c3.4 0 5.78 2.07 5.78 5.76a5.34 5.34 0 0 1-2.88 5.14l3.87 7.08zm17.85.26c-4.88 0-8.55-2.95-9.21-7.68h3.81A5.49 5.49 0 0 0 155 22h3.84c-.69 4.75-4.38 7.7-9.22 7.7zm18.4-.23h-3.72v-3.7H168zm6.36-7.54h-10.05v-3.26h10.08zm1.44-7.16h-11.49v-3.26h11.52z'/%3E%3C/svg%3E");
106
+ }
107
+
108
+ #commands {
109
+ background: #0D2483;
110
+ padding: 8px 1.5rem 12px 1.5rem;
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 10px;
114
+ flex-wrap: wrap;
115
+ }
116
+
117
+ #commands label {
118
+ font-size: 13px;
119
+ color: rgba(255, 255, 255, 0.6);
120
+ font-weight: 600;
121
+ }
122
+
123
+ #commands input[type="text"] {
124
+ font-size: 13px;
125
+ padding: 4px 8px;
126
+ border: 1px solid rgba(255, 255, 255, 0.3);
127
+ border-radius: 3px;
128
+ background: rgba(255, 255, 255, 0.15);
129
+ color: #fff;
130
+ width: 50px;
131
+ }
132
+
133
+ #commands button {
134
+ font-size: 13px;
135
+ padding: 4px 12px;
136
+ border: 1px solid rgba(255, 255, 255, 0.3);
137
+ border-radius: 3px;
138
+ background: rgba(255, 255, 255, 0.15);
139
+ color: #fff;
140
+ cursor: pointer;
141
+ }
142
+
143
+ #commands button:hover {
144
+ background: rgba(255, 255, 255, 0.25);
145
+ }
146
+
147
+ #help {
148
+ font-size: 13px;
149
+ padding: 12px 20px;
150
+ margin: 0;
151
+ background: #fff;
152
+ display: none;
153
+ border-bottom: 1px solid #e0e0e0;
154
+ color: #555;
155
+ }
156
+
157
+ #help li {
158
+ margin-left: 0;
159
+ padding: 2px 0;
160
+ border-left: none;
161
+ }
162
+
163
+ #content {
164
+ padding: 16px;
165
+ }
166
+
167
+ #sentinel {
168
+ height: 300px;
169
+ }
170
+
171
+ a {
172
+ color: #0984e3;
173
+ text-decoration: none;
174
+ }
175
+
176
+ a:hover {
177
+ text-decoration: underline;
178
+ }
179
+
180
+ .toggle {
181
+ display: inline-block;
182
+ width: 16px;
183
+ height: 16px;
184
+ text-align: center;
185
+ line-height: 16px;
186
+ cursor: pointer;
187
+ font-size: 10px;
188
+ color: #999;
189
+ vertical-align: middle;
190
+ margin-right: 2px;
191
+ border-radius: 2px;
192
+ user-select: none;
193
+ }
194
+
195
+ .toggle:hover {
196
+ background: rgba(0,0,0,0.08);
197
+ color: #333;
198
+ }
199
+
200
+ .toggle.minus::before { content: "\25BE"; }
201
+ .toggle.plus::before { content: "\25B8"; }
202
+ .toggle.empty { visibility: hidden; }
203
+ </style>
204
+
205
+ <script type="text/javascript">
206
+ function rootNode()
207
+ {
208
+ return currentThread
209
+ }
210
+
211
+ function showUL(node, show)
212
+ {
213
+ Array.prototype.forEach.call(node.childNodes, function(child)
214
+ {
215
+ if (child.nodeName == 'LI')
216
+ toggle(child, show)
217
+ })
218
+ }
219
+
220
+ function findUlChild(li)
221
+ {
222
+ var ul = li.childNodes[2]
223
+ while (ul && ul.nodeName != "UL")
224
+ {
225
+ ul = ul.nextSibling
226
+ }
227
+ return ul
228
+ }
229
+
230
+ function isLeafNode(li)
231
+ {
232
+ var element = li.querySelector('.toggle')
233
+ return element && element.classList.contains('empty')
234
+ }
235
+
236
+ function toggle(li, show)
237
+ {
238
+ if (isLeafNode(li))
239
+ return
240
+
241
+ var img = li.firstChild
242
+ img.className = 'toggle '
243
+ img.className += show ? 'minus' : 'plus'
244
+
245
+ var ul = findUlChild(li)
246
+ if (ul)
247
+ {
248
+ ul.style.display = show ? 'block' : 'none'
249
+ showUL(ul, true)
250
+ }
251
+ }
252
+
253
+ function toggleLI(li)
254
+ {
255
+ var img = li.firstChild
256
+ if (img.className.indexOf("minus") > -1)
257
+ toggle(li, false)
258
+ else
259
+ {
260
+ if (img.className.indexOf("plus") > -1)
261
+ toggle(li, true)
262
+ }
263
+ }
264
+
265
+ function aboveThreshold(text, threshold)
266
+ {
267
+ var match = text.match(/\d+[.,]\d+%/)
268
+ if (!match)
269
+ {
270
+ return true
271
+ }
272
+ else
273
+ {
274
+ var value = parseFloat(match[0].replace(/,/, '.'))
275
+ return value >= threshold
276
+ }
277
+ }
278
+
279
+ function setThresholdLI(li, threshold)
280
+ {
281
+ var a = li.querySelector('.toggle')
282
+ var span = li.querySelector('span')
283
+ var ul = li.querySelector('ul')
284
+
285
+ var visible = aboveThreshold(span.textContent, threshold) ? 1 : 0
286
+
287
+ var count = 0
288
+ if (ul)
289
+ {
290
+ count = setThresholdUL(ul, threshold)
291
+ }
292
+
293
+ if (count > 0)
294
+ {
295
+ a.className = 'toggle minus'
296
+ }
297
+ else
298
+ {
299
+ a.className = 'toggle empty'
300
+ }
301
+
302
+ if (visible)
303
+ {
304
+ li.style.display = 'block'
305
+ } else
306
+ {
307
+ li.style.display = 'none'
308
+ }
309
+ return visible
310
+ }
311
+
312
+ function setThresholdUL(node, threshold)
313
+ {
314
+ var count = 0
315
+ Array.prototype.forEach.call(node.childNodes, function(child)
316
+ {
317
+ if (child.nodeName == 'LI')
318
+ count = count + setThresholdLI(child, threshold)
319
+ })
320
+
321
+ var visible = (count > 0) ? 1 : 0
322
+ if (visible)
323
+ {
324
+ node.style.display = 'block'
325
+ } else
326
+ {
327
+ node.style.display = 'none'
328
+ }
329
+ return visible
330
+ }
331
+
332
+ function toggleChildren(img, event)
333
+ {
334
+ event.cancelBubble = true
335
+ if (img.className.indexOf('empty') > -1)
336
+ return
337
+
338
+ var minus = (img.className.indexOf('minus') > -1)
339
+
340
+ if (minus)
341
+ {
342
+ img.className = 'toggle plus'
343
+ } else
344
+ img.className = 'toggle minus'
345
+
346
+ var li = img.parentNode
347
+ var ul = findUlChild(li)
348
+ if (ul)
349
+ {
350
+ if (minus)
351
+ ul.style.display = 'none'
352
+ else
353
+ ul.style.display = 'block'
354
+ }
355
+ if (minus)
356
+ moveSelectionIfNecessary(li)
357
+ }
358
+
359
+ function showChildren(li)
360
+ {
361
+ var img = li.firstChild
362
+ if (img.className.indexOf('empty') > -1)
363
+ return
364
+ img.className = 'toggle minus'
365
+
366
+ var ul = findUlChild(li)
367
+ if (ul)
368
+ {
369
+ ul.style.display = 'block'
370
+ }
371
+ }
372
+
373
+ function setThreshold()
374
+ {
375
+ var tv = document.getElementById("threshold").value
376
+ if (tv.match(/[0-9]+([.,][0-9]+)?/))
377
+ {
378
+ var f = parseFloat(tv.replace(/,/, '.'))
379
+ var threads = document.getElementsByName("thread")
380
+ var l = threads.length
381
+ for (var i = 0; i < l; i++)
382
+ {
383
+ setThresholdUL(threads[i], f)
384
+ }
385
+ var p = selectedNode
386
+ while (p && p.style.display == 'none')
387
+ p = p.parentNode.parentNode
388
+ if (p && p.nodeName == "LI")
389
+ selectNode(p)
390
+ } else
391
+ {
392
+ alert("Please specify a decimal number as threshold value!")
393
+ }
394
+ }
395
+
396
+ function expandAll(event)
397
+ {
398
+ toggleAll(event, true)
399
+ }
400
+
401
+ function collapseAll(event)
402
+ {
403
+ toggleAll(event, false)
404
+ selectNode(rootNode(), null)
405
+ }
406
+
407
+ function toggleAll(event, show)
408
+ {
409
+ event.cancelBubble = true
410
+ var threads = document.getElementsByName("thread")
411
+ var l = threads.length
412
+ for (var i = 0; i < l; i++)
413
+ {
414
+ showUL(threads[i], show)
415
+ }
416
+ }
417
+
418
+ function toggleHelp(btn)
419
+ {
420
+ var help = document.getElementById("help")
421
+ if (help.style.display === 'block')
422
+ {
423
+ btn.textContent = "Help"
424
+ help.style.display = 'none'
425
+ } else
426
+ {
427
+ btn.textContent = "Hide Help"
428
+ help.style.display = 'block'
429
+ }
430
+ }
431
+
432
+ var selectedNode = null
433
+ var selectedColor = null
434
+ var selectedThread = null
435
+
436
+ function descendentOf(a, b)
437
+ {
438
+ while (a != b && b != null)
439
+ b = b.parentNode
440
+ return (a == b)
441
+ }
442
+
443
+ function moveSelectionIfNecessary(node)
444
+ {
445
+ if (descendentOf(node, selectedNode))
446
+ selectNode(node, null)
447
+ }
448
+
449
+ function selectNode(node, event)
450
+ {
451
+ if (event)
452
+ {
453
+ event.cancelBubble = true
454
+ thread = findThread(node)
455
+ selectThread(thread)
456
+ }
457
+ if (selectedNode)
458
+ {
459
+ selectedNode.style.background = selectedColor
460
+ }
461
+ selectedNode = node
462
+ selectedColor = node.style.background
463
+ selectedNode.style.background = "#BBDEFB"
464
+ selectedNode.scrollIntoView()
465
+ window.scrollBy(0, -400)
466
+ }
467
+
468
+ function moveUp()
469
+ {
470
+ move(selectedNode.previousSibling)
471
+ }
472
+
473
+ function moveDown()
474
+ {
475
+ move(selectedNode.nextSibling)
476
+ }
477
+
478
+ function move(p)
479
+ {
480
+ while (p && p.style.display == 'none')
481
+ p = p.nextSibling
482
+ if (p && p.nodeName == "LI")
483
+ {
484
+ selectNode(p, null)
485
+ }
486
+ }
487
+
488
+ function moveLeft()
489
+ {
490
+ var p = selectedNode.parentNode.parentNode
491
+ if (p && p.nodeName == "LI")
492
+ {
493
+ selectNode(p, null)
494
+ }
495
+ }
496
+
497
+ function moveRight()
498
+ {
499
+ if (!isLeafNode(selectedNode))
500
+ {
501
+ showChildren(selectedNode)
502
+ var ul = findUlChild(selectedNode)
503
+ if (ul)
504
+ {
505
+ selectNode(ul.firstChild, null)
506
+ }
507
+ }
508
+ }
509
+
510
+ function moveForward()
511
+ {
512
+ if (isLeafNode(selectedNode))
513
+ {
514
+ var p = selectedNode
515
+ while ((p.nextSibling == null || p.nextSibling.style.display == 'none') && p.nodeName == "LI")
516
+ {
517
+ p = p.parentNode.parentNode
518
+ }
519
+ if (p.nodeName == "LI")
520
+ selectNode(p.nextSibling, null)
521
+ } else
522
+ {
523
+ moveRight()
524
+ }
525
+ }
526
+
527
+ function isExpandedNode(li)
528
+ {
529
+ var img = li.firstChild
530
+ return (img.className.indexOf('minus') > -1)
531
+ }
532
+
533
+ function moveBackward()
534
+ {
535
+ var p = selectedNode
536
+ var q = p.previousSibling
537
+ while (q != null && q.style.display == 'none')
538
+ q = q.previousSibling
539
+ if (q == null)
540
+ {
541
+ p = p.parentNode.parentNode
542
+ } else
543
+ {
544
+ while (!isLeafNode(q) && isExpandedNode(q))
545
+ {
546
+ q = findUlChild(q).lastChild
547
+ while (q.style.display == 'none')
548
+ q = q.previousSibling
549
+ }
550
+ p = q
551
+ }
552
+ if (p.nodeName == "LI")
553
+ selectNode(p, null)
554
+ }
555
+
556
+ function moveHome()
557
+ {
558
+ selectNode(currentThread)
559
+ }
560
+
561
+ var currentThreadIndex = null
562
+
563
+ function findThread(node)
564
+ {
565
+ while (node && !node.parentNode.nodeName.match(/BODY|DIV/g))
566
+ {
567
+ node = node.parentNode
568
+ }
569
+ return node.firstChild
570
+ }
571
+
572
+ function selectThread(node)
573
+ {
574
+ var threads = document.getElementsByName("thread")
575
+ currentThread = node
576
+ for (var i = 0; i < threads.length; i++)
577
+ {
578
+ if (threads[i] == currentThread.parentNode)
579
+ currentThreadIndex = i
580
+ }
581
+ }
582
+
583
+ function nextThread()
584
+ {
585
+ var threads = document.getElementsByName("thread")
586
+ if (currentThreadIndex == threads.length - 1)
587
+ currentThreadIndex = 0
588
+ else
589
+ currentThreadIndex += 1
590
+ currentThread = threads[currentThreadIndex].firstChild
591
+ selectNode(currentThread, null)
592
+ }
593
+
594
+ function previousThread()
595
+ {
596
+ var threads = document.getElementsByName("thread")
597
+ if (currentThreadIndex == 0)
598
+ currentThreadIndex = threads.length - 1
599
+ else
600
+ currentThreadIndex -= 1
601
+ currentThread = threads[currentThreadIndex].firstChild
602
+ selectNode(currentThread, null)
603
+ }
604
+
605
+ function switchThread(node, event)
606
+ {
607
+ event.cancelBubble = true
608
+ selectThread(node.nextSibling.firstChild)
609
+ selectNode(currentThread, null)
610
+ }
611
+
612
+ function handleKeyEvent(event)
613
+ {
614
+ var code = event.charCode ? event.charCode : event.keyCode
615
+ var str = String.fromCharCode(code)
616
+ switch (str)
617
+ {
618
+ case "a":
619
+ moveLeft()
620
+ break
621
+ case "s":
622
+ moveDown()
623
+ break
624
+ case "d":
625
+ moveRight()
626
+ break
627
+ case "w":
628
+ moveUp()
629
+ break
630
+ case "f":
631
+ moveForward()
632
+ break
633
+ case "b":
634
+ moveBackward()
635
+ break
636
+ case "x":
637
+ toggleChildren(selectedNode.firstChild, event)
638
+ break
639
+ case "*":
640
+ toggleLI(selectedNode)
641
+ break
642
+ case "n":
643
+ nextThread()
644
+ break
645
+ case "h":
646
+ moveHome()
647
+ break
648
+ case "p":
649
+ previousThread()
650
+ break
651
+ }
652
+ }
653
+
654
+ document.onkeypress = function (event)
655
+ {
656
+ handleKeyEvent(event)
657
+ }
658
+
659
+ window.onload = function ()
660
+ {
661
+ var toggles = document.querySelectorAll(".toggle")
662
+ for (var i = 0; i < toggles.length; i++)
663
+ {
664
+ var tog = toggles[i]
665
+ tog.onclick = function (event)
666
+ {
667
+ toggleChildren(this, event)
668
+ return false
669
+ }
670
+ }
671
+ var divs = document.getElementsByTagName("div")
672
+ for (i = 0; i < divs.length; i++)
673
+ {
674
+ var div = divs[i]
675
+ if (div.className == "thread")
676
+ div.onclick = function (event)
677
+ {
678
+ switchThread(this, event)
679
+ }
680
+ }
681
+ var lis = document.getElementsByTagName("li")
682
+ for (var i = 0; i < lis.length; i++)
683
+ {
684
+ lis[i].onclick = function (event)
685
+ {
686
+ selectNode(this, event)
687
+ }
688
+ }
689
+
690
+ var threads = document.getElementsByName("thread")
691
+ currentThreadIndex = 0
692
+ currentThread = threads[0].querySelector('li')
693
+ selectNode(currentThread, null)
694
+ }
695
+ </script>
696
+
697
+ <% @overall_time = @result.threads.reduce(0) do |val, thread|
698
+ val += thread.total_time
699
+ end %>
700
+ </head>
701
+ <body>
702
+ <div id="header">
703
+ <div class="header-left">
704
+ <h6>Call Stack Report</h6>
705
+ <h1><%= @result.measure_mode_name %></h1>
706
+ </div>
707
+ <div style="margin-left: auto; display: flex; align-items: flex-end; flex-direction: column;">
708
+ <div style="font-size: 12px; margin-bottom: 0.5rem; color: rgba(255,255,255,0.6);"><%= Time.now.strftime(time_format) %></div>
709
+ <div class="logo"></div>
710
+ </div>
711
+ </div>
712
+ <div id="commands">
713
+ <label for="threshold">Threshold:</label>
714
+ <input value="1.0" size="3" id="threshold" type="text">
715
+ <button onclick="setThreshold();">Apply</button>
716
+ <button onclick="expandAll(event);">Expand All</button>
717
+ <button onclick="collapseAll(event);">Collapse All</button>
718
+ <button onclick="toggleHelp(this);">Help</button>
719
+ </div>
720
+ <ul id="help">
721
+ <li>* indicates recursively called methods</li>
722
+ <li>Enter a threshold value and click "Apply" to hide methods below that percentage.</li>
723
+ <li>Use <strong>a, s, d, w</strong> to navigate the tree (left, down, right, up).</li>
724
+ <li>Use <strong>f</strong> and <strong>b</strong> to navigate forward and backwards in preorder.</li>
725
+ <li>Use <strong>x</strong> to toggle a subtree, <strong>*</strong> to expand/collapse a whole subtree.</li>
726
+ <li>Use <strong>h</strong> to navigate to thread root, <strong>n</strong> and <strong>p</strong> to switch threads.</li>
727
+ </ul>
728
+
729
+ <div id="content">
730
+ <% @result.threads.each do |thread| %>
731
+ <% thread_percent = 100 * (thread.total_time / @overall_time)
732
+ thread_info = "#{"%4.2f%%" % thread_percent} ~ #{@overall_time}" %>
733
+ <div class="thread">
734
+ <span>Thread: <%= thread.id %>, Fiber: <%= thread.fiber_id %> (<%= thread_info %>)</span>
735
+ <ul name="thread">
736
+ <% visited = Set.new
737
+ output = StringIO.new
738
+ print_stack(output, visited, thread.call_tree, thread.call_tree.total_time) %>
739
+ <%= output.string %>
740
+ </ul>
741
+ </div>
742
+ <% end %>
743
+ <div id="sentinel"></div>
744
+ </div>
745
+ </body>
746
+ </html>