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
@@ -0,0 +1,150 @@
1
+ events: wall_time
2
+
3
+ fl=
4
+ fn=Array::take
5
+ 0 2
6
+
7
+ fl=C:/Source/ruby-prof/<internal:numeric>
8
+ fn=Integer::-@
9
+ 104 6
10
+
11
+ fl=
12
+ fn=Hash::each
13
+ 0 17
14
+ cfl=C:/Source/ruby-prof/<internal:numeric>
15
+ cfn=Integer::-@
16
+ calls=25 19
17
+ 19 6
18
+
19
+ fl=
20
+ fn=Enumerable::sort_by
21
+ 0 7
22
+ cfl=
23
+ cfn=Hash::each
24
+ calls=1 0
25
+ 0 23
26
+
27
+ fl=C:/Source/ruby-prof/docs/public/examples/example.rb
28
+ fn=Object::top_words
29
+ 18 6
30
+ cfl=
31
+ cfn=Enumerable::sort_by
32
+ calls=1 19
33
+ 19 30
34
+ cfl=
35
+ cfn=Array::take
36
+ calls=1 19
37
+ 19 2
38
+
39
+ fl=
40
+ fn=Hash::[]=
41
+ 0 1000
42
+
43
+ fl=
44
+ fn=Integer::+
45
+ 0 675
46
+
47
+ fl=
48
+ fn=Hash::[]
49
+ 0 878
50
+
51
+ fl=
52
+ fn=Array::each
53
+ 0 3021
54
+ cfl=
55
+ cfn=Hash::[]
56
+ calls=5800 14
57
+ 14 878
58
+ cfl=
59
+ cfn=Integer::+
60
+ calls=5800 14
61
+ 14 675
62
+ cfl=
63
+ cfn=Hash::[]=
64
+ calls=5800 14
65
+ 14 1000
66
+
67
+ fl=C:/Source/ruby-prof/<internal:hash>
68
+ fn=Hash::initialize
69
+ 37 3
70
+
71
+ fl=C:/Source/ruby-prof/docs/public/examples/example.rb
72
+ fn=Object::count_words
73
+ 12 7
74
+ cfl=C:/Source/ruby-prof/<internal:hash>
75
+ cfn=Hash::initialize
76
+ calls=1 13
77
+ 13 3
78
+ cfl=
79
+ cfn=Array::each
80
+ calls=1 14
81
+ 14 5574
82
+
83
+ fl=
84
+ fn=String::split
85
+ 0 1508
86
+
87
+ fl=C:/Source/ruby-prof/docs/public/examples/example.rb
88
+ fn=Object::tokenize
89
+ 8 2
90
+ cfl=
91
+ cfn=String::split
92
+ calls=1 9
93
+ 9 1508
94
+
95
+ fl=
96
+ fn=String::gsub
97
+ 0 591
98
+
99
+ fl=
100
+ fn=String::downcase
101
+ 0 16
102
+
103
+ fl=C:/Source/ruby-prof/docs/public/examples/example.rb
104
+ fn=Object::normalize
105
+ 4 4
106
+ cfl=
107
+ cfn=String::downcase
108
+ calls=1 5
109
+ 5 16
110
+ cfl=
111
+ cfn=String::gsub
112
+ calls=1 5
113
+ 5 591
114
+
115
+ fl=
116
+ fn=String::*
117
+ 0 6
118
+
119
+ fl=C:/Source/ruby-prof/docs/public/examples/example.rb
120
+ fn=Object::run_example
121
+ 22 12
122
+ cfl=
123
+ cfn=String::*
124
+ calls=1 23
125
+ 23 6
126
+ cfl=C:/Source/ruby-prof/docs/public/examples/example.rb
127
+ cfn=Object::normalize
128
+ calls=1 29
129
+ 29 611
130
+ cfl=C:/Source/ruby-prof/docs/public/examples/example.rb
131
+ cfn=Object::tokenize
132
+ calls=1 30
133
+ 30 1510
134
+ cfl=C:/Source/ruby-prof/docs/public/examples/example.rb
135
+ cfn=Object::count_words
136
+ calls=1 31
137
+ 31 5583
138
+ cfl=C:/Source/ruby-prof/docs/public/examples/example.rb
139
+ cfn=Object::top_words
140
+ calls=1 32
141
+ 32 38
142
+
143
+ fl=C:/Source/ruby-prof/docs/public/examples/generate_reports.rb
144
+ fn=[global]::
145
+ 24 14
146
+ cfl=C:/Source/ruby-prof/docs/public/examples/example.rb
147
+ cfn=Object::run_example
148
+ calls=1 24
149
+ 24 7761
150
+
@@ -0,0 +1,408 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>ruby-prof flame graph</title>
6
+ <style>
7
+ * { box-sizing: border-box; }
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
10
+ margin: 0;
11
+ padding: 0;
12
+ background: #f5f5f5;
13
+ color: #333;
14
+ }
15
+ #header {
16
+ background: #0D2483;
17
+ color: #fff;
18
+ padding: 1rem 1.5rem;
19
+ display: flex;
20
+ align-items: center;
21
+ gap: 16px;
22
+ flex-wrap: wrap;
23
+ }
24
+ #header .header-left {
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 2px;
28
+ }
29
+ #header h6 {
30
+ margin: 0;
31
+ font-size: 0.75rem;
32
+ text-transform: uppercase;
33
+ letter-spacing: 1.5px;
34
+ color: rgba(255, 255, 255, 0.6);
35
+ font-weight: 600;
36
+ }
37
+ #header h1 {
38
+ margin: 0;
39
+ font-size: 22px;
40
+ font-weight: 600;
41
+ white-space: nowrap;
42
+ }
43
+ .logo {
44
+ width: 140px;
45
+ height: 30px;
46
+ opacity: 0.5;
47
+ background-repeat: no-repeat;
48
+ 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");
49
+ }
50
+ #controls {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 10px;
54
+ flex-wrap: wrap;
55
+ }
56
+ #controls label {
57
+ font-size: 13px;
58
+ color: rgba(255, 255, 255, 0.6);
59
+ }
60
+ #controls select, #controls input[type="text"] {
61
+ font-size: 13px;
62
+ padding: 4px 8px;
63
+ border: 1px solid rgba(255, 255, 255, 0.3);
64
+ border-radius: 3px;
65
+ background: rgba(255, 255, 255, 0.15);
66
+ color: #fff;
67
+ }
68
+ #controls button {
69
+ font-size: 13px;
70
+ padding: 4px 12px;
71
+ border: 1px solid rgba(255, 255, 255, 0.3);
72
+ border-radius: 3px;
73
+ background: rgba(255, 255, 255, 0.15);
74
+ color: #fff;
75
+ cursor: pointer;
76
+ }
77
+ #controls button:hover {
78
+ background: rgba(255, 255, 255, 0.25);
79
+ }
80
+ #chart-container {
81
+ padding: 10px 20px 0 20px;
82
+ overflow-x: auto;
83
+ }
84
+ #tooltip {
85
+ position: fixed;
86
+ display: none;
87
+ background: rgba(0,0,0,0.88);
88
+ color: #fff;
89
+ padding: 8px 12px;
90
+ border-radius: 4px;
91
+ font-size: 12px;
92
+ line-height: 1.5;
93
+ pointer-events: none;
94
+ z-index: 1000;
95
+ max-width: 500px;
96
+ white-space: nowrap;
97
+ }
98
+ #tooltip .tt-name {
99
+ font-weight: 600;
100
+ margin-bottom: 2px;
101
+ white-space: normal;
102
+ word-break: break-all;
103
+ }
104
+ #tooltip .tt-detail {
105
+ color: #ccc;
106
+ }
107
+ svg {
108
+ display: block;
109
+ }
110
+ svg text {
111
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
112
+ font-size: 11px;
113
+ fill: white;
114
+ pointer-events: none;
115
+ }
116
+ svg rect.frame {
117
+ stroke: #fff;
118
+ stroke-width: 0.5;
119
+ cursor: pointer;
120
+ }
121
+ svg rect.frame:hover {
122
+ stroke: #333;
123
+ stroke-width: 1;
124
+ }
125
+ svg rect.frame.highlight {
126
+ stroke: #0984e3;
127
+ stroke-width: 2;
128
+ }
129
+ #zoom-info {
130
+ padding: 6px 20px;
131
+ font-size: 12px;
132
+ color: #636e72;
133
+ display: none;
134
+ }
135
+ #zoom-info a {
136
+ color: #0984e3;
137
+ cursor: pointer;
138
+ text-decoration: underline;
139
+ }
140
+ </style>
141
+ </head>
142
+ <body>
143
+ <div id="header">
144
+ <div class="header-left">
145
+ <h6>Flame Graph</h6>
146
+ <h1>Wall Time</h1>
147
+ </div>
148
+ <div id="controls">
149
+ <label for="thread-select">Thread:</label>
150
+ <select id="thread-select"></select>
151
+ <label for="search-input">Search:</label>
152
+ <input type="text" id="search-input" placeholder="Method name...">
153
+ <button id="reset-zoom-btn">Reset Zoom</button>
154
+ <button id="toggle-icicle-btn">Icicle</button>
155
+ </div>
156
+ <div style="margin-left: auto; display: flex; align-items: flex-end; flex-direction: column;">
157
+ <div style="font-size: 12px; margin-bottom: 0.5rem; color: rgba(255,255,255,0.6);">Sunday, February 8 at 12:46:07 AM (Pacific Standard Time)</div>
158
+ <div class="logo"></div>
159
+ </div>
160
+ </div>
161
+ <div id="zoom-info">Zoomed into: <span id="zoom-target"></span> &mdash; <a id="zoom-reset-link">reset</a></div>
162
+ <div id="thread-info" style="padding: 6px 20px; font-size: 12px; color: #636e72;"></div>
163
+ <div id="chart-container">
164
+ <svg id="flame-svg"></svg>
165
+ </div>
166
+ <div id="tooltip"></div>
167
+
168
+ <script>
169
+ (function() {
170
+ var threads = [{"id":464,"fiber_id":456,"total_time":0.007774999990942888,"data":{"name":"[global]#","value":0.007774999990942888,"self_value":0.000014399993233382702,"called":1,"children":[{"name":"Object#run_example","value":0.007760599997709505,"self_value":0.000012100004823878407,"called":1,"children":[{"name":"Object#count_words","value":0.005583399994065985,"self_value":0.0000071999820647761226,"called":1,"children":[{"name":"Array#each","value":0.005573500005993992,"self_value":0.0030208003445295617,"called":1,"children":[{"name":"Hash#[]=","value":0.000999900177703239,"self_value":0.000999900177703239,"called":5800,"children":[]},{"name":"Hash#[]","value":0.0008777998446021229,"self_value":0.0008777998446021229,"called":5800,"children":[]},{"name":"Integer#+","value":0.0006749996391590685,"self_value":0.0006749996391590685,"called":5800,"children":[]}]},{"name":"Hash#initialize","value":0.0000027000060072168708,"self_value":0.0000027000060072168708,"called":1,"children":[]}]},{"name":"Object#tokenize","value":0.0015103999903658405,"self_value":0.0000019999861251562834,"called":1,"children":[{"name":"String#split","value":0.0015084000042406842,"self_value":0.0015084000042406842,"called":1,"children":[]}]},{"name":"Object#normalize","value":0.000611299998126924,"self_value":0.000003799985279329121,"called":1,"children":[{"name":"String#gsub","value":0.0005913000059081241,"self_value":0.0005913000059081241,"called":1,"children":[]},{"name":"String#downcase","value":0.000016200006939470768,"self_value":0.000016200006939470768,"called":1,"children":[]}]},{"name":"Object#top_words","value":0.00003780001134146005,"self_value":0.000006200018106028438,"called":1,"children":[{"name":"Enumerable#sort_by","value":0.000029799994081258774,"self_value":0.0000072999828262254596,"called":1,"children":[{"name":"Hash#each","value":0.000022500011255033314,"self_value":0.0000168999977177009,"called":1,"children":[{"name":"Integer#-@","value":0.000005600013537332416,"self_value":0.000005600013537332416,"called":25,"children":[]}]}]},{"name":"Array#take","value":0.0000017999991541728377,"self_value":0.0000017999991541728377,"called":1,"children":[]}]},{"name":"String#*","value":0.000005599998985417187,"self_value":0.000005599998985417187,"called":1,"children":[]}]}]}}];
171
+ var currentThreadIdx = 0;
172
+ var zoomNode = null;
173
+ var searchPattern = null;
174
+ var icicleMode = false;
175
+
176
+ var FRAME_HEIGHT = 18;
177
+ var MIN_WIDTH_PX = 1;
178
+ var PADDING_BOTTOM = 4;
179
+ var CHAR_WIDTH = 6.5;
180
+ var TEXT_PADDING = 4;
181
+
182
+ // --- Color ---
183
+ function hashCode(s) {
184
+ var hash = 0;
185
+ for (var i = 0; i < s.length; i++) {
186
+ hash = ((hash << 5) - hash) + s.charCodeAt(i);
187
+ hash = hash & hash;
188
+ }
189
+ return Math.abs(hash);
190
+ }
191
+
192
+ function frameColor(name) {
193
+ var h = hashCode(name);
194
+ var hue = 190 + (h % 50);
195
+ var sat = 50 + (h % 30);
196
+ var lit = 55 + (h % 20);
197
+ return 'hsl(' + hue + ',' + sat + '%,' + lit + '%)';
198
+ }
199
+
200
+ // --- Layout ---
201
+ function maxDepth(node) {
202
+ var max = 0;
203
+ if (node.children) {
204
+ for (var i = 0; i < node.children.length; i++) {
205
+ var d = maxDepth(node.children[i]);
206
+ if (d > max) max = d;
207
+ }
208
+ }
209
+ return max + 1;
210
+ }
211
+
212
+ function render() {
213
+ var thread = threads[currentThreadIdx];
214
+ var root = zoomNode || thread.data;
215
+ var totalTime = root.value;
216
+ var depth = maxDepth(root);
217
+
218
+ // Update thread info text above the SVG
219
+ var threadInfo = 'Thread ' + thread.id + ' | Total: ' + root.value.toFixed(4) + 's';
220
+ if (zoomNode) threadInfo += ' (zoomed)';
221
+ document.getElementById('thread-info').textContent = threadInfo;
222
+
223
+ var container = document.getElementById('chart-container');
224
+ var svgWidth = Math.max(container.clientWidth - 40, 800);
225
+ var svgHeight = depth * FRAME_HEIGHT + PADDING_BOTTOM;
226
+
227
+ var svg = document.getElementById('flame-svg');
228
+ svg.setAttribute('width', svgWidth);
229
+ svg.setAttribute('height', svgHeight);
230
+ svg.innerHTML = '';
231
+
232
+ renderNode(svg, root, 0, svgWidth, 0, totalTime, svgHeight);
233
+ updateZoomInfo();
234
+ }
235
+
236
+ function renderNode(svg, node, x, width, depth, totalTime, svgHeight) {
237
+ if (width < MIN_WIDTH_PX) return;
238
+
239
+ var y = icicleMode
240
+ ? depth * FRAME_HEIGHT
241
+ : svgHeight - PADDING_BOTTOM - (depth + 1) * FRAME_HEIGHT;
242
+ var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
243
+ rect.setAttribute('class', 'frame');
244
+ rect.setAttribute('x', x);
245
+ rect.setAttribute('y', y);
246
+ rect.setAttribute('width', width);
247
+ rect.setAttribute('height', FRAME_HEIGHT - 1);
248
+ rect.setAttribute('fill', frameColor(node.name));
249
+ rect.setAttribute('rx', 1);
250
+
251
+ if (searchPattern && searchPattern.test(node.name)) {
252
+ rect.classList.add('highlight');
253
+ }
254
+
255
+ rect.addEventListener('click', function(e) {
256
+ e.stopPropagation();
257
+ zoomNode = node;
258
+ render();
259
+ });
260
+
261
+ rect.addEventListener('mouseenter', function(e) {
262
+ showTooltip(e, node, totalTime);
263
+ });
264
+ rect.addEventListener('mousemove', function(e) {
265
+ positionTooltip(e);
266
+ });
267
+ rect.addEventListener('mouseleave', hideTooltip);
268
+
269
+ svg.appendChild(rect);
270
+
271
+ // Text label
272
+ var maxChars = Math.floor((width - TEXT_PADDING * 2) / CHAR_WIDTH);
273
+ if (maxChars > 2) {
274
+ var label = node.name;
275
+ if (label.length > maxChars) {
276
+ label = label.substring(0, maxChars - 2) + '..';
277
+ }
278
+ var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
279
+ text.setAttribute('x', x + TEXT_PADDING);
280
+ text.setAttribute('y', y + FRAME_HEIGHT - 5);
281
+ text.textContent = label;
282
+ svg.appendChild(text);
283
+ }
284
+
285
+ // Children
286
+ if (node.children) {
287
+ var childX = x;
288
+ for (var i = 0; i < node.children.length; i++) {
289
+ var child = node.children[i];
290
+ var childWidth = (child.value / (node.value || 1)) * width;
291
+ renderNode(svg, child, childX, childWidth, depth + 1, totalTime, svgHeight);
292
+ childX += childWidth;
293
+ }
294
+ }
295
+ }
296
+
297
+ // --- Tooltip ---
298
+ var tooltipEl = document.getElementById('tooltip');
299
+
300
+ function showTooltip(e, node, totalTime) {
301
+ var pct = totalTime > 0 ? (node.value / totalTime * 100).toFixed(2) : '0.00';
302
+ var selfPct = totalTime > 0 ? (node.self_value / totalTime * 100).toFixed(2) : '0.00';
303
+ tooltipEl.innerHTML =
304
+ '<div class="tt-name">' + escapeHtml(node.name) + '</div>' +
305
+ '<div class="tt-detail">Total: ' + node.value.toFixed(4) + 's (' + pct + '%)</div>' +
306
+ '<div class="tt-detail">Self: ' + node.self_value.toFixed(4) + 's (' + selfPct + '%)</div>' +
307
+ '<div class="tt-detail">Calls: ' + node.called + '</div>';
308
+ tooltipEl.style.display = 'block';
309
+ positionTooltip(e);
310
+ }
311
+
312
+ function positionTooltip(e) {
313
+ var x = e.clientX + 12;
314
+ var y = e.clientY + 12;
315
+ if (x + tooltipEl.offsetWidth > window.innerWidth - 10) {
316
+ x = e.clientX - tooltipEl.offsetWidth - 12;
317
+ }
318
+ if (y + tooltipEl.offsetHeight > window.innerHeight - 10) {
319
+ y = e.clientY - tooltipEl.offsetHeight - 12;
320
+ }
321
+ tooltipEl.style.left = x + 'px';
322
+ tooltipEl.style.top = y + 'px';
323
+ }
324
+
325
+ function hideTooltip() {
326
+ tooltipEl.style.display = 'none';
327
+ }
328
+
329
+ function escapeHtml(s) {
330
+ var div = document.createElement('div');
331
+ div.appendChild(document.createTextNode(s));
332
+ return div.innerHTML;
333
+ }
334
+
335
+ // --- Zoom ---
336
+ function updateZoomInfo() {
337
+ var info = document.getElementById('zoom-info');
338
+ if (zoomNode && zoomNode !== threads[currentThreadIdx].data) {
339
+ info.style.display = 'block';
340
+ document.getElementById('zoom-target').textContent = zoomNode.name;
341
+ } else {
342
+ info.style.display = 'none';
343
+ }
344
+ }
345
+
346
+ function resetZoom() {
347
+ zoomNode = null;
348
+ render();
349
+ }
350
+
351
+ document.getElementById('reset-zoom-btn').addEventListener('click', resetZoom);
352
+ document.getElementById('zoom-reset-link').addEventListener('click', resetZoom);
353
+
354
+ // --- Icicle toggle ---
355
+ var icicleBtn = document.getElementById('toggle-icicle-btn');
356
+ icicleBtn.addEventListener('click', function() {
357
+ icicleMode = !icicleMode;
358
+ icicleBtn.textContent = icicleMode ? 'Flame' : 'Icicle';
359
+ render();
360
+ });
361
+
362
+ // --- Thread selector ---
363
+ var threadSelect = document.getElementById('thread-select');
364
+ for (var i = 0; i < threads.length; i++) {
365
+ var opt = document.createElement('option');
366
+ opt.value = i;
367
+ opt.textContent = 'Thread ' + threads[i].id + ' (Fiber ' + threads[i].fiber_id + ')';
368
+ threadSelect.appendChild(opt);
369
+ }
370
+ threadSelect.addEventListener('change', function() {
371
+ currentThreadIdx = parseInt(this.value, 10);
372
+ zoomNode = null;
373
+ render();
374
+ });
375
+
376
+ // --- Search ---
377
+ var searchInput = document.getElementById('search-input');
378
+ var searchTimeout = null;
379
+ searchInput.addEventListener('input', function() {
380
+ clearTimeout(searchTimeout);
381
+ searchTimeout = setTimeout(function() {
382
+ var val = searchInput.value.trim();
383
+ if (val.length > 0) {
384
+ try {
385
+ searchPattern = new RegExp(val, 'i');
386
+ } catch(e) {
387
+ searchPattern = new RegExp(val.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
388
+ }
389
+ } else {
390
+ searchPattern = null;
391
+ }
392
+ render();
393
+ }, 200);
394
+ });
395
+
396
+ // --- Resize ---
397
+ var resizeTimeout = null;
398
+ window.addEventListener('resize', function() {
399
+ clearTimeout(resizeTimeout);
400
+ resizeTimeout = setTimeout(render, 150);
401
+ });
402
+
403
+ // --- Init ---
404
+ render();
405
+ })();
406
+ </script>
407
+ </body>
408
+ </html>
@@ -0,0 +1,45 @@
1
+ Measure Mode: wall_time
2
+ Thread ID: 464
3
+ Fiber ID: 456
4
+ Total: 0.007775
5
+ Sort by: self_time
6
+
7
+ %self total self wait child calls name location
8
+ 38.85 0.006 0.003 0.000 0.003 1 Array#each
9
+ 19.40 0.002 0.002 0.000 0.000 1 String#split
10
+ 12.86 0.001 0.001 0.000 0.000 5800 Hash#[]=
11
+ 11.29 0.001 0.001 0.000 0.000 5800 Hash#[]
12
+ 8.68 0.001 0.001 0.000 0.000 5800 Integer#+
13
+ 7.61 0.001 0.001 0.000 0.000 1 String#gsub
14
+ 0.22 0.000 0.000 0.000 0.000 1 Hash#each
15
+ 0.21 0.000 0.000 0.000 0.000 1 String#downcase
16
+ 0.19 0.008 0.000 0.000 0.008 1 [global]# C:/Source/ruby-prof/docs/public/examples/generate_reports.rb:24
17
+ 0.16 0.008 0.000 0.000 0.008 1 Object#run_example C:/Source/ruby-prof/docs/public/examples/example.rb:22
18
+ 0.09 0.000 0.000 0.000 0.000 1 Enumerable#sort_by
19
+ 0.09 0.006 0.000 0.000 0.006 1 Object#count_words C:/Source/ruby-prof/docs/public/examples/example.rb:12
20
+ 0.08 0.000 0.000 0.000 0.000 1 Object#top_words C:/Source/ruby-prof/docs/public/examples/example.rb:18
21
+ 0.07 0.000 0.000 0.000 0.000 25 Integer#-@ <internal:numeric>:104
22
+ 0.07 0.000 0.000 0.000 0.000 1 String#*
23
+ 0.05 0.001 0.000 0.000 0.001 1 Object#normalize C:/Source/ruby-prof/docs/public/examples/example.rb:4
24
+ 0.03 0.000 0.000 0.000 0.000 1 Hash#initialize <internal:hash>:37
25
+ 0.03 0.002 0.000 0.000 0.002 1 Object#tokenize C:/Source/ruby-prof/docs/public/examples/example.rb:8
26
+ 0.02 0.000 0.000 0.000 0.000 1 Array#take
27
+
28
+ * recursively called methods
29
+
30
+ Columns are:
31
+
32
+ %self - The percentage of time spent by this method relative to the total time in the entire program.
33
+ total - The total time spent by this method and its children.
34
+ self - The time spent by this method.
35
+ wait - The time this method spent waiting for other threads.
36
+ child - The time spent by this method's children.
37
+ calls - The number of times this method was called.
38
+ name - The name of the method.
39
+ location - The location of the method.
40
+
41
+ The interpretation of method names is:
42
+
43
+ * MyObject#test - An instance method "test" of the class "MyObject"
44
+ * <Object:MyObject>#test - The <> characters indicate a method on a singleton class.
45
+