jruby-async-profiler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.gitmodules +3 -0
  4. data/Gemfile +4 -0
  5. data/README.md +35 -0
  6. data/Rakefile +2 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/ext/Rakefile +6 -0
  10. data/ext/async-profiler/.gitattributes +1 -0
  11. data/ext/async-profiler/.gitignore +6 -0
  12. data/ext/async-profiler/.travis.yml +11 -0
  13. data/ext/async-profiler/CHANGELOG.md +107 -0
  14. data/ext/async-profiler/JavaHome.class +0 -0
  15. data/ext/async-profiler/LICENSE +201 -0
  16. data/ext/async-profiler/Makefile +66 -0
  17. data/ext/async-profiler/README.md +487 -0
  18. data/ext/async-profiler/demo/SwingSet2.svg +2247 -0
  19. data/ext/async-profiler/docs/cddl1.txt +358 -0
  20. data/ext/async-profiler/profiler.sh +240 -0
  21. data/ext/async-profiler/src/allocTracer.cpp +155 -0
  22. data/ext/async-profiler/src/allocTracer.h +74 -0
  23. data/ext/async-profiler/src/arch.h +69 -0
  24. data/ext/async-profiler/src/arguments.cpp +265 -0
  25. data/ext/async-profiler/src/arguments.h +152 -0
  26. data/ext/async-profiler/src/codeCache.cpp +128 -0
  27. data/ext/async-profiler/src/codeCache.h +99 -0
  28. data/ext/async-profiler/src/engine.cpp +50 -0
  29. data/ext/async-profiler/src/engine.h +38 -0
  30. data/ext/async-profiler/src/flameGraph.cpp +770 -0
  31. data/ext/async-profiler/src/flameGraph.h +118 -0
  32. data/ext/async-profiler/src/flightRecorder.cpp +727 -0
  33. data/ext/async-profiler/src/flightRecorder.h +39 -0
  34. data/ext/async-profiler/src/frameName.cpp +189 -0
  35. data/ext/async-profiler/src/frameName.h +56 -0
  36. data/ext/async-profiler/src/itimer.cpp +49 -0
  37. data/ext/async-profiler/src/itimer.h +43 -0
  38. data/ext/async-profiler/src/jattach/jattach.c +437 -0
  39. data/ext/async-profiler/src/java/one/profiler/AsyncProfiler.java +160 -0
  40. data/ext/async-profiler/src/java/one/profiler/AsyncProfilerMXBean.java +43 -0
  41. data/ext/async-profiler/src/java/one/profiler/Counter.java +25 -0
  42. data/ext/async-profiler/src/java/one/profiler/Events.java +28 -0
  43. data/ext/async-profiler/src/javaApi.cpp +124 -0
  44. data/ext/async-profiler/src/lockTracer.cpp +161 -0
  45. data/ext/async-profiler/src/lockTracer.h +55 -0
  46. data/ext/async-profiler/src/mutex.cpp +33 -0
  47. data/ext/async-profiler/src/mutex.h +49 -0
  48. data/ext/async-profiler/src/os.h +45 -0
  49. data/ext/async-profiler/src/os_linux.cpp +129 -0
  50. data/ext/async-profiler/src/os_macos.cpp +115 -0
  51. data/ext/async-profiler/src/perfEvents.h +60 -0
  52. data/ext/async-profiler/src/perfEvents_linux.cpp +550 -0
  53. data/ext/async-profiler/src/perfEvents_macos.cpp +64 -0
  54. data/ext/async-profiler/src/profiler.cpp +952 -0
  55. data/ext/async-profiler/src/profiler.h +238 -0
  56. data/ext/async-profiler/src/spinLock.h +66 -0
  57. data/ext/async-profiler/src/stackFrame.h +57 -0
  58. data/ext/async-profiler/src/stackFrame_aarch64.cpp +75 -0
  59. data/ext/async-profiler/src/stackFrame_arm.cpp +58 -0
  60. data/ext/async-profiler/src/stackFrame_i386.cpp +82 -0
  61. data/ext/async-profiler/src/stackFrame_x64.cpp +113 -0
  62. data/ext/async-profiler/src/symbols.h +37 -0
  63. data/ext/async-profiler/src/symbols_linux.cpp +354 -0
  64. data/ext/async-profiler/src/symbols_macos.cpp +156 -0
  65. data/ext/async-profiler/src/vmEntry.cpp +173 -0
  66. data/ext/async-profiler/src/vmEntry.h +105 -0
  67. data/ext/async-profiler/src/vmStructs.cpp +104 -0
  68. data/ext/async-profiler/src/vmStructs.h +112 -0
  69. data/ext/async-profiler/src/wallClock.cpp +96 -0
  70. data/ext/async-profiler/src/wallClock.h +56 -0
  71. data/ext/async-profiler/test/AllocatingTarget.java +26 -0
  72. data/ext/async-profiler/test/LoadLibraryTest.java +21 -0
  73. data/ext/async-profiler/test/Target.java +31 -0
  74. data/ext/async-profiler/test/ThreadsTarget.java +35 -0
  75. data/ext/async-profiler/test/alloc-smoke-test.sh +36 -0
  76. data/ext/async-profiler/test/load-library-test.sh +35 -0
  77. data/ext/async-profiler/test/smoke-test.sh +37 -0
  78. data/ext/async-profiler/test/thread-smoke-test.sh +32 -0
  79. data/jruby-async-profiler.gemspec +32 -0
  80. data/lib/jruby/async/profiler.rb +10 -0
  81. data/lib/jruby/async/profiler/version.rb +7 -0
  82. metadata +155 -0
@@ -0,0 +1,770 @@
1
+ /*
2
+ * Copyright 2018 Andrei Pangin
3
+ *
4
+ * This is a specialized C++ port of the FlameGraph script available at
5
+ * https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl
6
+ *
7
+ * Copyright 2016 Netflix, Inc.
8
+ * Copyright 2011 Joyent, Inc. All rights reserved.
9
+ * Copyright 2011 Brendan Gregg. All rights reserved.
10
+ *
11
+ * CDDL HEADER START
12
+ *
13
+ * The contents of this file are subject to the terms of the
14
+ * Common Development and Distribution License (the "License").
15
+ * You may not use this file except in compliance with the License.
16
+ *
17
+ * You can obtain a copy of the license at docs/cddl1.txt or
18
+ * http://opensource.org/licenses/CDDL-1.0.
19
+ * See the License for the specific language governing permissions
20
+ * and limitations under the License.
21
+ *
22
+ * When distributing Covered Code, include this CDDL HEADER in each
23
+ * file and include the License file at docs/cddl1.txt.
24
+ * If applicable, add the following below this CDDL HEADER, with the
25
+ * fields enclosed by brackets "[]" replaced with your own identifying
26
+ * information: Portions Copyright [yyyy] [name of copyright owner]
27
+ *
28
+ * CDDL HEADER END
29
+ */
30
+
31
+ #include <iomanip>
32
+ #include <vector>
33
+ #include <algorithm>
34
+ #include <math.h>
35
+ #include <stdio.h>
36
+ #include <stdlib.h>
37
+ #include "flameGraph.h"
38
+
39
+
40
+ static const char SVG_HEADER[] =
41
+ "<?xml version=\"1.0\" standalone=\"no\"?>\n"
42
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
43
+ "<svg version=\"1.1\" width=\"%d\" height=\"%d\" onload=\"init(evt)\" viewBox=\"0 0 %d %d\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
44
+ "<style type=\"text/css\">\n"
45
+ "\ttext { font-family:Verdana; font-size:12px; fill:rgb(0,0,0); }\n"
46
+ "\t#search { opacity:0.1; cursor:pointer; }\n"
47
+ "\t#search:hover, #search.show { opacity:1; }\n"
48
+ "\t#subtitle { text-anchor:middle; font-color:rgb(160,160,160); }\n"
49
+ "\t#title { text-anchor:middle; font-size:17px}\n"
50
+ "\t#unzoom { cursor:pointer; }\n"
51
+ "\t#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }\n"
52
+ "\t.hide { display:none; }\n"
53
+ "\t.parent { opacity:0.5; }\n"
54
+ "</style>\n"
55
+ "<script type=\"text/ecmascript\">\n"
56
+ "<![CDATA[\n"
57
+ "\t\"use strict\";\n"
58
+ "\tvar details, searchbtn, unzoombtn, matchedtxt, svg, searching;\n"
59
+ "\tfunction init(evt) {\n"
60
+ "\t\tdetails = document.getElementById(\"details\").firstChild;\n"
61
+ "\t\tsearchbtn = document.getElementById(\"search\");\n"
62
+ "\t\tunzoombtn = document.getElementById(\"unzoom\");\n"
63
+ "\t\tmatchedtxt = document.getElementById(\"matched\");\n"
64
+ "\t\tsvg = document.getElementsByTagName(\"svg\")[0];\n"
65
+ "\t\tsearching = 0;\n"
66
+ "\t}\n"
67
+ "\n"
68
+ "\twindow.addEventListener(\"click\", function(e) {\n"
69
+ "\t\tvar target = find_group(e.target);\n"
70
+ "\t\tif (target) {\n"
71
+ "\t\t\tif (target.nodeName == \"a\") {\n"
72
+ "\t\t\t\tif (e.ctrlKey === false) return;\n"
73
+ "\t\t\t\te.preventDefault();\n"
74
+ "\t\t\t}\n"
75
+ "\t\t\tif (target.classList.contains(\"parent\")) unzoom();\n"
76
+ "\t\t\tzoom(target);\n"
77
+ "\t\t}\n"
78
+ "\t\telse if (e.target.id == \"unzoom\") unzoom();\n"
79
+ "\t\telse if (e.target.id == \"search\") search_prompt();\n"
80
+ "\t}, false)\n"
81
+ "\n"
82
+ "\t// mouse-over for info\n"
83
+ "\t// show\n"
84
+ "\twindow.addEventListener(\"mouseover\", function(e) {\n"
85
+ "\t\tvar target = find_group(e.target);\n"
86
+ "\t\tif (target) details.nodeValue = \"Function: \" + g_to_text(target);\n"
87
+ "\t}, false)\n"
88
+ "\n"
89
+ "\t// clear\n"
90
+ "\twindow.addEventListener(\"mouseout\", function(e) {\n"
91
+ "\t\tvar target = find_group(e.target);\n"
92
+ "\t\tif (target) details.nodeValue = ' ';\n"
93
+ "\t}, false)\n"
94
+ "\n"
95
+ "\t// ctrl-F for search\n"
96
+ "\twindow.addEventListener(\"keydown\",function (e) {\n"
97
+ "\t\tif (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {\n"
98
+ "\t\t\te.preventDefault();\n"
99
+ "\t\t\tsearch_prompt();\n"
100
+ "\t\t}\n"
101
+ "\t}, false)\n"
102
+ "\n"
103
+ "\t// functions\n"
104
+ "\tfunction find_child(node, selector) {\n"
105
+ "\t\tvar children = node.querySelectorAll(selector);\n"
106
+ "\t\tif (children.length) return children[0];\n"
107
+ "\t\treturn;\n"
108
+ "\t}\n"
109
+ "\tfunction find_group(node) {\n"
110
+ "\t\tvar parent = node.parentElement;\n"
111
+ "\t\tif (!parent) return;\n"
112
+ "\t\tif (parent.id == \"frames\") return node;\n"
113
+ "\t\treturn find_group(parent);\n"
114
+ "\t}\n"
115
+ "\tfunction orig_save(e, attr, val) {\n"
116
+ "\t\tif (e.attributes[\"_orig_\" + attr] != undefined) return;\n"
117
+ "\t\tif (e.attributes[attr] == undefined) return;\n"
118
+ "\t\tif (val == undefined) val = e.attributes[attr].value;\n"
119
+ "\t\te.setAttribute(\"_orig_\" + attr, val);\n"
120
+ "\t}\n"
121
+ "\tfunction orig_load(e, attr) {\n"
122
+ "\t\tif (e.attributes[\"_orig_\"+attr] == undefined) return;\n"
123
+ "\t\te.attributes[attr].value = e.attributes[\"_orig_\" + attr].value;\n"
124
+ "\t\te.removeAttribute(\"_orig_\"+attr);\n"
125
+ "\t}\n"
126
+ "\tfunction g_to_text(e) {\n"
127
+ "\t\tvar text = find_child(e, \"title\").firstChild.nodeValue;\n"
128
+ "\t\treturn (text)\n"
129
+ "\t}\n"
130
+ "\tfunction g_to_func(e) {\n"
131
+ "\t\tvar func = g_to_text(e);\n"
132
+ "\t\t// if there's any manipulation we want to do to the function\n"
133
+ "\t\t// name before it's searched, do it here before returning.\n"
134
+ "\t\treturn (func);\n"
135
+ "\t}\n"
136
+ "\tfunction update_text(e) {\n"
137
+ "\t\tvar r = find_child(e, \"rect\");\n"
138
+ "\t\tvar t = find_child(e, \"text\");\n"
139
+ "\t\tvar w = parseFloat(r.attributes.width.value) -3;\n"
140
+ "\t\tvar txt = find_child(e, \"title\").textContent.replace(/\\([^(]*\\)$/,\"\");\n"
141
+ "\t\tt.attributes.x.value = parseFloat(r.attributes.x.value) + 3;\n"
142
+ "\n"
143
+ "\t\t// Smaller than this size won't fit anything\n"
144
+ "\t\tif (w < 2 * 12 * 0.59) {\n"
145
+ "\t\t\tt.textContent = \"\";\n"
146
+ "\t\t\treturn;\n"
147
+ "\t\t}\n"
148
+ "\n"
149
+ "\t\tt.textContent = txt;\n"
150
+ "\t\t// Fit in full text width\n"
151
+ "\t\tif (/^ *$/.test(txt) || t.getSubStringLength(0, txt.length) < w)\n"
152
+ "\t\t\treturn;\n"
153
+ "\n"
154
+ "\t\tfor (var x = txt.length - 2; x > 0; x--) {\n"
155
+ "\t\t\tif (t.getSubStringLength(0, x + 2) <= w) {\n"
156
+ "\t\t\t\tt.textContent = txt.substring(0, x) + \"..\";\n"
157
+ "\t\t\t\treturn;\n"
158
+ "\t\t\t}\n"
159
+ "\t\t}\n"
160
+ "\t\tt.textContent = \"\";\n"
161
+ "\t}\n"
162
+ "\n"
163
+ "\t// zoom\n"
164
+ "\tfunction zoom_reset(e) {\n"
165
+ "\t\tif (e.attributes != undefined) {\n"
166
+ "\t\t\torig_load(e, \"x\");\n"
167
+ "\t\t\torig_load(e, \"width\");\n"
168
+ "\t\t}\n"
169
+ "\t\tif (e.childNodes == undefined) return;\n"
170
+ "\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n"
171
+ "\t\t\tzoom_reset(c[i]);\n"
172
+ "\t\t}\n"
173
+ "\t}\n"
174
+ "\tfunction zoom_child(e, x, ratio) {\n"
175
+ "\t\tif (e.attributes != undefined) {\n"
176
+ "\t\t\tif (e.attributes.x != undefined) {\n"
177
+ "\t\t\t\torig_save(e, \"x\");\n"
178
+ "\t\t\t\te.attributes.x.value = (parseFloat(e.attributes.x.value) - x - 10) * ratio + 10;\n"
179
+ "\t\t\t\tif (e.tagName == \"text\")\n"
180
+ "\t\t\t\t\te.attributes.x.value = find_child(e.parentNode, \"rect[x]\").attributes.x.value + 3;\n"
181
+ "\t\t\t}\n"
182
+ "\t\t\tif (e.attributes.width != undefined) {\n"
183
+ "\t\t\t\torig_save(e, \"width\");\n"
184
+ "\t\t\t\te.attributes.width.value = parseFloat(e.attributes.width.value) * ratio;\n"
185
+ "\t\t\t}\n"
186
+ "\t\t}\n"
187
+ "\n"
188
+ "\t\tif (e.childNodes == undefined) return;\n"
189
+ "\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n"
190
+ "\t\t\tzoom_child(c[i], x - 10, ratio);\n"
191
+ "\t\t}\n"
192
+ "\t}\n"
193
+ "\tfunction zoom_parent(e) {\n"
194
+ "\t\tif (e.attributes) {\n"
195
+ "\t\t\tif (e.attributes.x != undefined) {\n"
196
+ "\t\t\t\torig_save(e, \"x\");\n"
197
+ "\t\t\t\te.attributes.x.value = 10;\n"
198
+ "\t\t\t}\n"
199
+ "\t\t\tif (e.attributes.width != undefined) {\n"
200
+ "\t\t\t\torig_save(e, \"width\");\n"
201
+ "\t\t\t\te.attributes.width.value = parseInt(svg.width.baseVal.value) - (10 * 2);\n"
202
+ "\t\t\t}\n"
203
+ "\t\t}\n"
204
+ "\t\tif (e.childNodes == undefined) return;\n"
205
+ "\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n"
206
+ "\t\t\tzoom_parent(c[i]);\n"
207
+ "\t\t}\n"
208
+ "\t}\n"
209
+ "\tfunction zoom(node) {\n"
210
+ "\t\tvar attr = find_child(node, \"rect\").attributes;\n"
211
+ "\t\tvar width = parseFloat(attr.width.value);\n"
212
+ "\t\tvar xmin = parseFloat(attr.x.value);\n"
213
+ "\t\tvar xmax = parseFloat(xmin + width);\n"
214
+ "\t\tvar ymin = parseFloat(attr.y.value);\n"
215
+ "\t\tvar ratio = (svg.width.baseVal.value - 2 * 10) / width;\n"
216
+ "\n"
217
+ "\t\t// XXX: Workaround for JavaScript float issues (fix me)\n"
218
+ "\t\tvar fudge = 0.0001;\n"
219
+ "\n"
220
+ "\t\tunzoombtn.classList.remove(\"hide\");\n"
221
+ "\n"
222
+ "\t\tvar el = document.getElementById(\"frames\").children;\n"
223
+ "\t\tfor (var i = 0; i < el.length; i++) {\n"
224
+ "\t\t\tvar e = el[i];\n"
225
+ "\t\t\tvar a = find_child(e, \"rect\").attributes;\n"
226
+ "\t\t\tvar ex = parseFloat(a.x.value);\n"
227
+ "\t\t\tvar ew = parseFloat(a.width.value);\n"
228
+ "\t\t\tvar upstack;\n"
229
+ "\t\t\t// Is it an ancestor\n"
230
+ "\t\t\tif (%d == 0) {\n"
231
+ "\t\t\t\tupstack = parseFloat(a.y.value) > ymin;\n"
232
+ "\t\t\t} else {\n"
233
+ "\t\t\t\tupstack = parseFloat(a.y.value) < ymin;\n"
234
+ "\t\t\t}\n"
235
+ "\t\t\tif (upstack) {\n"
236
+ "\t\t\t\t// Direct ancestor\n"
237
+ "\t\t\t\tif (ex <= xmin && (ex+ew+fudge) >= xmax) {\n"
238
+ "\t\t\t\t\te.classList.add(\"parent\");\n"
239
+ "\t\t\t\t\tzoom_parent(e);\n"
240
+ "\t\t\t\t\tupdate_text(e);\n"
241
+ "\t\t\t\t}\n"
242
+ "\t\t\t\t// not in current path\n"
243
+ "\t\t\t\telse\n"
244
+ "\t\t\t\t\te.classList.add(\"hide\");\n"
245
+ "\t\t\t}\n"
246
+ "\t\t\t// Children maybe\n"
247
+ "\t\t\telse {\n"
248
+ "\t\t\t\t// no common path\n"
249
+ "\t\t\t\tif (ex < xmin || ex + fudge >= xmax) {\n"
250
+ "\t\t\t\t\te.classList.add(\"hide\");\n"
251
+ "\t\t\t\t}\n"
252
+ "\t\t\t\telse {\n"
253
+ "\t\t\t\t\tzoom_child(e, xmin, ratio);\n"
254
+ "\t\t\t\t\tupdate_text(e);\n"
255
+ "\t\t\t\t}\n"
256
+ "\t\t\t}\n"
257
+ "\t\t}\n"
258
+ "\t}\n"
259
+ "\tfunction unzoom() {\n"
260
+ "\t\tunzoombtn.classList.add(\"hide\");\n"
261
+ "\t\tvar el = document.getElementById(\"frames\").children;\n"
262
+ "\t\tfor(var i = 0; i < el.length; i++) {\n"
263
+ "\t\t\tel[i].classList.remove(\"parent\");\n"
264
+ "\t\t\tel[i].classList.remove(\"hide\");\n"
265
+ "\t\t\tzoom_reset(el[i]);\n"
266
+ "\t\t\tupdate_text(el[i]);\n"
267
+ "\t\t}\n"
268
+ "\t}\n"
269
+ "\n"
270
+ "\t// search\n"
271
+ "\tfunction reset_search() {\n"
272
+ "\t\tvar el = document.querySelectorAll(\"#frames rect\");\n"
273
+ "\t\tfor (var i = 0; i < el.length; i++) {\n"
274
+ "\t\t\torig_load(el[i], \"fill\")\n"
275
+ "\t\t}\n"
276
+ "\t}\n"
277
+ "\tfunction search_prompt() {\n"
278
+ "\t\tif (!searching) {\n"
279
+ "\t\t\tvar term = prompt(\"Enter a search term (regexp \" +\n"
280
+ "\t\t\t \"allowed, eg: ^ext4_)\", \"\");\n"
281
+ "\t\t\tif (term != null) {\n"
282
+ "\t\t\t\tsearch(term)\n"
283
+ "\t\t\t}\n"
284
+ "\t\t} else {\n"
285
+ "\t\t\treset_search();\n"
286
+ "\t\t\tsearching = 0;\n"
287
+ "\t\t\tsearchbtn.classList.remove(\"show\");\n"
288
+ "\t\t\tsearchbtn.firstChild.nodeValue = \"Search\"\n"
289
+ "\t\t\tmatchedtxt.classList.add(\"hide\");\n"
290
+ "\t\t\tmatchedtxt.firstChild.nodeValue = \"\"\n"
291
+ "\t\t}\n"
292
+ "\t}\n"
293
+ "\tfunction search(term) {\n"
294
+ "\t\tvar re = new RegExp(term);\n"
295
+ "\t\tvar el = document.getElementById(\"frames\").children;\n"
296
+ "\t\tvar matches = new Object();\n"
297
+ "\t\tvar maxwidth = 0;\n"
298
+ "\t\tfor (var i = 0; i < el.length; i++) {\n"
299
+ "\t\t\tvar e = el[i];\n"
300
+ "\t\t\tvar func = g_to_func(e);\n"
301
+ "\t\t\tvar rect = find_child(e, \"rect\");\n"
302
+ "\t\t\tif (func == null || rect == null)\n"
303
+ "\t\t\t\tcontinue;\n"
304
+ "\n"
305
+ "\t\t\t// Save max width. Only works as we have a root frame\n"
306
+ "\t\t\tvar w = parseFloat(rect.attributes.width.value);\n"
307
+ "\t\t\tif (w > maxwidth)\n"
308
+ "\t\t\t\tmaxwidth = w;\n"
309
+ "\n"
310
+ "\t\t\tif (func.match(re)) {\n"
311
+ "\t\t\t\t// highlight\n"
312
+ "\t\t\t\tvar x = parseFloat(rect.attributes.x.value);\n"
313
+ "\t\t\t\torig_save(rect, \"fill\");\n"
314
+ "\t\t\t\trect.attributes.fill.value = \"rgb(230,0,230)\";\n"
315
+ "\n"
316
+ "\t\t\t\t// remember matches\n"
317
+ "\t\t\t\tif (matches[x] == undefined) {\n"
318
+ "\t\t\t\t\tmatches[x] = w;\n"
319
+ "\t\t\t\t} else {\n"
320
+ "\t\t\t\t\tif (w > matches[x]) {\n"
321
+ "\t\t\t\t\t\t// overwrite with parent\n"
322
+ "\t\t\t\t\t\tmatches[x] = w;\n"
323
+ "\t\t\t\t\t}\n"
324
+ "\t\t\t\t}\n"
325
+ "\t\t\t\tsearching = 1;\n"
326
+ "\t\t\t}\n"
327
+ "\t\t}\n"
328
+ "\t\tif (!searching)\n"
329
+ "\t\t\treturn;\n"
330
+ "\n"
331
+ "\t\tsearchbtn.classList.add(\"show\");\n"
332
+ "\t\tsearchbtn.firstChild.nodeValue = \"Reset Search\";\n"
333
+ "\n"
334
+ "\t\t// calculate percent matched, excluding vertical overlap\n"
335
+ "\t\tvar count = 0;\n"
336
+ "\t\tvar lastx = -1;\n"
337
+ "\t\tvar lastw = 0;\n"
338
+ "\t\tvar keys = Array();\n"
339
+ "\t\tfor (k in matches) {\n"
340
+ "\t\t\tif (matches.hasOwnProperty(k))\n"
341
+ "\t\t\t\tkeys.push(k);\n"
342
+ "\t\t}\n"
343
+ "\t\t// sort the matched frames by their x location\n"
344
+ "\t\t// ascending, then width descending\n"
345
+ "\t\tkeys.sort(function(a, b){\n"
346
+ "\t\t\treturn a - b;\n"
347
+ "\t\t});\n"
348
+ "\t\t// Step through frames saving only the biggest bottom-up frames\n"
349
+ "\t\t// thanks to the sort order. This relies on the tree property\n"
350
+ "\t\t// where children are always smaller than their parents.\n"
351
+ "\t\tvar fudge = 0.0001;\t// JavaScript floating point\n"
352
+ "\t\tfor (var k in keys) {\n"
353
+ "\t\t\tvar x = parseFloat(keys[k]);\n"
354
+ "\t\t\tvar w = matches[keys[k]];\n"
355
+ "\t\t\tif (x >= lastx + lastw - fudge) {\n"
356
+ "\t\t\t\tcount += w;\n"
357
+ "\t\t\t\tlastx = x;\n"
358
+ "\t\t\t\tlastw = w;\n"
359
+ "\t\t\t}\n"
360
+ "\t\t}\n"
361
+ "\t\t// display matched percent\n"
362
+ "\t\tmatchedtxt.classList.remove(\"hide\");\n"
363
+ "\t\tvar pct = 100 * count / maxwidth;\n"
364
+ "\t\tif (pct != 100) pct = pct.toFixed(1)\n"
365
+ "\t\tmatchedtxt.firstChild.nodeValue = \"Matched: \" + pct + \"%%\";\n"
366
+ "\t}\n"
367
+ "]]>\n"
368
+ "</script>\n"
369
+ "<rect x=\"0\" y=\"0\" width=\"100%%\" height=\"100%%\" fill=\"rgb(240,240,220)\"/>\n"
370
+ "<text id=\"title\" x=\"%d\" y=\"%d\">%s</text>\n"
371
+ "<text id=\"details\" x=\"%d\" y=\"%d\"> </text>\n"
372
+ "<text id=\"unzoom\" x=\"%d\" y=\"%d\" class=\"hide\">Reset Zoom</text>\n"
373
+ "<text id=\"search\" x=\"%d\" y=\"%d\">Search</text>\n"
374
+ "<text id=\"matched\" x=\"%d\" y=\"%d\"> </text>\n"
375
+ "<g id=\"frames\">\n";
376
+
377
+ static const char TREE_HEADER[] =
378
+ "<!DOCTYPE html>\n"
379
+ "<html lang=\"en\">\n"
380
+ "<head>\n"
381
+ "<title>Tree view</title>\n"
382
+ "<meta charset=\"utf-8\"/>\n"
383
+ "<style>\n"
384
+ "body {\n"
385
+ " font-family: Arial;\n"
386
+ "}\n"
387
+ "ul.tree li {\n"
388
+ " list-style-type: none;\n"
389
+ " position: relative;\n"
390
+ "}\n"
391
+ "ul.tree ul {\n"
392
+ " margin-left: 20px; padding-left: 0;\n"
393
+ "}\n"
394
+ "ul.tree li ul {\n"
395
+ " display: none;\n"
396
+ "}\n"
397
+ "ul.tree li.open > ul {\n"
398
+ " display: block;\n"
399
+ "}\n"
400
+ "ul.tree li div:before {\n"
401
+ " height: 1em;\n"
402
+ " padding:0 .1em;\n"
403
+ " font-size: .8em;\n"
404
+ " display: block;\n"
405
+ " position: absolute;\n"
406
+ " left: -1.3em;\n"
407
+ " top: .2em;\n"
408
+ "}\n"
409
+ "ul.tree li > div:not(:nth-last-child(2)):before {\n"
410
+ " content: '+';\n"
411
+ "}\n"
412
+ "ul.tree li.open > div:not(:nth-last-child(2)):before {\n"
413
+ " content: '-';\n"
414
+ "}\n"
415
+ ".sc {\n"
416
+ " text-decoration: underline;\n"
417
+ " text-decoration-color: black;\n"
418
+ " font-weight: bold;\n"
419
+ " background-color: #D9D9D9;\n"
420
+ "}\n"
421
+ ".green {\n"
422
+ " color: #32c832;\n"
423
+ "}\n"
424
+ ".aqua {\n"
425
+ " color: #32a5a5;\n"
426
+ "}\n"
427
+ ".brown {\n"
428
+ " color: #be5a00;\n"
429
+ "}\n"
430
+ ".yellow {\n"
431
+ " color: #afaf32;\n"
432
+ "}\n"
433
+ ".red {\n"
434
+ " color: #c83232;\n"
435
+ "}\n"
436
+ "ul.tree li > div {\n"
437
+ " display: inline;\n"
438
+ " cursor: pointer;\n"
439
+ " color: black;\n"
440
+ " text-decoration: none;\n"
441
+ "}\n"
442
+ "</style>\n"
443
+ "<script>\n"
444
+ "function treeView(opt) {\n"
445
+ " var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
446
+ " for(var i = 0; i < tree.length; i++){\n"
447
+ " var parent = tree[i].parentElement;\n"
448
+ " var classList = parent.classList;\n"
449
+ " if(opt == 0) {\n"
450
+ " classList.add('open');\n"
451
+ " } else {\n"
452
+ " classList.remove('open');\n"
453
+ " }\n"
454
+ " }\n"
455
+ "}\n"
456
+ "function openParent(p,t) {\n"
457
+ " if(p.parentElement.classList.contains(\"tree\")) {\n"
458
+ " return;\n"
459
+ " }\n"
460
+ " p.parentElement.classList.add('open');\n"
461
+ " openParent(p.parentElement,t);\n"
462
+ "}\n"
463
+ "function search() {\n"
464
+ " var tree = document.querySelectorAll('ul.tree span');\n"
465
+ " var check = document.getElementById('check');\n"
466
+ " for(var i = 0; i < tree.length; i++){\n"
467
+ " tree[i].classList.remove('sc');\n"
468
+ " if(tree[i].innerHTML.includes(document.getElementById(\"search\").value)) {\n"
469
+ " tree[i].classList.add('sc');\n"
470
+ " openParent(tree[i].parentElement,tree);\n"
471
+ " }\n"
472
+ " }\n"
473
+ "}\n"
474
+ "function openUL(n) {\n"
475
+ " var children = n.children;\n"
476
+ " if(children.length == 1) {\n"
477
+ " openNode(children[0]);\n"
478
+ " }\n"
479
+ "}\n"
480
+ "function openNode(n) {\n"
481
+ " var children = n.children;\n"
482
+ " for(var i = 0; i < children.length; i++){\n"
483
+ " if(children[i].nodeName == 'UL') {\n"
484
+ " n.classList.add('open');\n"
485
+ " openUL(children[i]);\n"
486
+ " }\n"
487
+ " }\n"
488
+ "}\n"
489
+ "function addClickActions() {\n"
490
+ "var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
491
+ "for(var i = 0; i < tree.length; i++){\n"
492
+ " tree[i].addEventListener('click', function(e) {\n"
493
+ " var parent = e.target.parentElement;\n"
494
+ " var classList = parent.classList;\n"
495
+ " if(classList.contains(\"open\")) {\n"
496
+ " classList.remove('open');\n"
497
+ " var opensubs = parent.querySelectorAll(':scope .open');\n"
498
+ " for(var i = 0; i < opensubs.length; i++){\n"
499
+ " opensubs[i].classList.remove('open');\n"
500
+ " }\n"
501
+ " } else {\n"
502
+ " if(e.altKey) {\n"
503
+ " classList.add('open');\n"
504
+ " var opensubs = parent.querySelectorAll('li');\n"
505
+ " for(var i = 0; i < opensubs.length; i++){\n"
506
+ " opensubs[i].classList.add('open');\n"
507
+ " }\n"
508
+ " } else {\n"
509
+ " openNode(parent);\n"
510
+ " }\n"
511
+ " }\n"
512
+ " });\n"
513
+ "}\n"
514
+ "}\n"
515
+ "</script>\n"
516
+ "</head>\n"
517
+ "<body>\n"
518
+ "<div style=\"padding-left: 25px;\">%s view, total %s: %s </div>\n"
519
+ "<div style=\"padding-left: 25px;\"><button type='button' onclick='treeView(0)'>++</button><button type='button' onclick='treeView(1)'>--</button>\n"
520
+ "<input type='text' id='search' value='' size='35' onkeypress=\"if(event.keyCode == 13) document.getElementById('searchBtn').click()\">\n"
521
+ "<button type='button' id='searchBtn' onclick='search()'>search</button></div>\n"
522
+ "<ul class=\"tree\">\n";
523
+
524
+ static const char TREE_FOOTER[] =
525
+ "<script>\n"
526
+ "addClickActions();\n"
527
+ "</script>\n"
528
+ "</ul>\n"
529
+ "</body>\n"
530
+ "</html>\n";
531
+
532
+
533
+ class StringUtils {
534
+ public:
535
+ static bool endsWith(const std::string& s, const char* suffix, size_t suffixlen) {
536
+ size_t len = s.length();
537
+ return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
538
+ }
539
+
540
+ static std::string trim(const std::string& s, size_t maxchars) {
541
+ if (maxchars < 3) {
542
+ return "";
543
+ } else if (s.length() > maxchars) {
544
+ return s.substr(0, maxchars - 2) + "..";
545
+ } else {
546
+ return s;
547
+ }
548
+ }
549
+
550
+ static void replace(std::string& s, char c, const char* replacement) {
551
+ for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i++) {
552
+ s.replace(i, 1, replacement);
553
+ }
554
+ }
555
+
556
+ static void escape(std::string& s) {
557
+ replace(s, '&', "&amp;");
558
+ replace(s, '<', "&lt;");
559
+ replace(s, '>', "&gt;");
560
+ }
561
+ };
562
+
563
+
564
+ class Format {
565
+ private:
566
+ char _buf[32];
567
+
568
+ public:
569
+ const char* thousands(u64 value) {
570
+ char* p = _buf + sizeof(_buf) - 1;
571
+ *p = 0;
572
+
573
+ while (value >= 1000) {
574
+ p -= 4;
575
+ p[0] = ',';
576
+ p[1] = '0' + char(value % 1000 / 100);
577
+ p[2] = '0' + char(value % 100 / 10);
578
+ p[3] = '0' + char(value % 10);
579
+ value /= 1000;
580
+ }
581
+
582
+ do {
583
+ *--p = '0' + char(value % 10);
584
+ } while ((value /= 10) > 0);
585
+
586
+ return p;
587
+ }
588
+ };
589
+
590
+
591
+ class Palette {
592
+ private:
593
+ const char* _name;
594
+ int _base;
595
+ int _r, _g, _b;
596
+
597
+ public:
598
+ Palette(const char* name, int base, int r, int g, int b) : _name(name), _base(base), _r(r), _g(g), _b(b) {
599
+ }
600
+
601
+ const char* name() const {
602
+ return _name;
603
+ }
604
+
605
+ int pickColor() const {
606
+ double value = double(rand()) / RAND_MAX;
607
+ return _base + (int(_r * value) << 16 | int(_g * value) << 8 | int(_b * value));
608
+ }
609
+ };
610
+
611
+
612
+ void FlameGraph::dump(std::ostream& out, bool tree) {
613
+ _scale = (_imagewidth - 20) / (double)_root._total;
614
+ _pct = 100 / (double)_root._total;
615
+
616
+ u64 cutoff = (u64)ceil(_minwidth / _scale);
617
+ _imageheight = _frameheight * _root.depth(cutoff) + 70;
618
+
619
+ if (tree) {
620
+ printTreeHeader(out);
621
+ printTreeFrame(out, _root, 0);
622
+ printTreeFooter(out);
623
+ } else {
624
+ printHeader(out);
625
+ printFrame(out, "all", _root, 10, _reverse ? 35 : (_imageheight - _frameheight - 35));
626
+ printFooter(out);
627
+ }
628
+ }
629
+
630
+ void FlameGraph::printHeader(std::ostream& out) {
631
+ char buf[sizeof(SVG_HEADER) + 256];
632
+ int x0 = _imagewidth / 2;
633
+ int x1 = 10;
634
+ int x2 = _imagewidth - 110;
635
+ int y0 = 24;
636
+ int y1 = _imageheight - 17;
637
+
638
+ sprintf(buf, SVG_HEADER,
639
+ _imagewidth, _imageheight, _imagewidth, _imageheight, _reverse,
640
+ x0, y0, _title, x1, y1, x1, y0, x2, y0, x2, y1);
641
+ out << buf;
642
+ }
643
+
644
+ void FlameGraph::printFooter(std::ostream& out) {
645
+ out << "</g>\n</svg>\n";
646
+ }
647
+
648
+ double FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, double x, double y) {
649
+ double framewidth = f._total * _scale;
650
+
651
+ // Skip too narrow frames, they are not important
652
+ if (framewidth >= _minwidth) {
653
+ std::string full_title = name;
654
+ int color = selectFramePalette(full_title).pickColor();
655
+ std::string short_title = StringUtils::trim(full_title, size_t(framewidth / 7));
656
+ StringUtils::escape(full_title);
657
+ StringUtils::escape(short_title);
658
+
659
+ // Compensate rounding error in frame width
660
+ double w = (round((x + framewidth) * 10) - round(x * 10)) / 10.0;
661
+
662
+ snprintf(_buf, sizeof(_buf),
663
+ "<g>\n"
664
+ "<title>%s (%s samples, %.2f%%)</title><rect x=\"%.1f\" y=\"%.1f\" width=\"%.1f\" height=\"%d\" fill=\"#%06x\" rx=\"2\" ry=\"2\"/>\n"
665
+ "<text x=\"%.1f\" y=\"%.1f\">%s</text>\n"
666
+ "</g>\n",
667
+ full_title.c_str(), Format().thousands(f._total), f._total * _pct, x, y, w, _frameheight - 1, color,
668
+ x + 3, y + 3 + _frameheight * 0.5, short_title.c_str());
669
+ out << _buf;
670
+
671
+ x += f._self * _scale;
672
+ y += _reverse ? _frameheight : -_frameheight;
673
+
674
+ for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
675
+ x += printFrame(out, it->first, it->second, x, y);
676
+ }
677
+ }
678
+
679
+ return framewidth;
680
+ }
681
+
682
+ void FlameGraph::printTreeHeader(std::ostream& out) {
683
+ char buf[sizeof(TREE_HEADER) + 256];
684
+ const char* title = _reverse ? "Backtrace" : "Call tree";
685
+ const char* counter = _counter == COUNTER_SAMPLES ? "samples" : "counter";
686
+ sprintf(buf, TREE_HEADER, title, counter, Format().thousands(_root._total));
687
+ out << buf;
688
+ }
689
+
690
+ void FlameGraph::printTreeFooter(std::ostream& out) {
691
+ out << TREE_FOOTER;
692
+ }
693
+
694
+ bool FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int depth) {
695
+ double framewidth = f._total * _scale;
696
+ if (framewidth < _minwidth) {
697
+ return false;
698
+ }
699
+
700
+ std::vector<Node> subnodes;
701
+ for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
702
+ subnodes.push_back(Node(it->first, it->second));
703
+ }
704
+ std::sort(subnodes.begin(), subnodes.end());
705
+
706
+ for (size_t i = 0; i < subnodes.size(); i++) {
707
+ std::string full_title = subnodes[i]._name;
708
+ const Trie* trie = subnodes[i]._trie;
709
+ const char* color = selectFramePalette(full_title).name();
710
+ StringUtils::escape(full_title);
711
+
712
+ if (_reverse) {
713
+ snprintf(_buf, sizeof(_buf),
714
+ "<li><div>[%d] %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
715
+ depth,
716
+ trie->_total * _pct, Format().thousands(trie->_total),
717
+ color, full_title.c_str());
718
+ } else {
719
+ snprintf(_buf, sizeof(_buf),
720
+ "<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
721
+ depth,
722
+ trie->_total * _pct, Format().thousands(trie->_total),
723
+ trie->_self * _pct, Format().thousands(trie->_self),
724
+ color, full_title.c_str());
725
+ }
726
+ out << _buf;
727
+
728
+ if (trie->_children.size() > 0) {
729
+ out << "<ul>\n";
730
+ if (!printTreeFrame(out, *trie, depth + 1)) {
731
+ out << "<li>...\n";
732
+ }
733
+ out << "</ul>\n";
734
+ }
735
+ }
736
+
737
+ return true;
738
+ }
739
+
740
+ const Palette& FlameGraph::selectFramePalette(std::string& name) {
741
+ static const Palette
742
+ green ("green", 0x50e150, 30, 30, 30),
743
+ aqua ("aqua", 0x50bebe, 30, 30, 30),
744
+ brown ("brown", 0xe17d00, 30, 30, 0),
745
+ yellow("yellow", 0xc8c83c, 30, 30, 10),
746
+ red ("red", 0xe15a5a, 30, 40, 40);
747
+
748
+ if (StringUtils::endsWith(name, "_[j]", 4)) {
749
+ // Java compiled frame
750
+ name = name.substr(0, name.length() - 4);
751
+ return green;
752
+ } else if (StringUtils::endsWith(name, "_[i]", 4)) {
753
+ // Java inlined frame
754
+ name = name.substr(0, name.length() - 4);
755
+ return aqua;
756
+ } else if (StringUtils::endsWith(name, "_[k]", 4)) {
757
+ // Kernel function
758
+ name = name.substr(0, name.length() - 4);
759
+ return brown;
760
+ } else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
761
+ // C++ function or Objective C method
762
+ return yellow;
763
+ } else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
764
+ // Java regular method
765
+ return green;
766
+ } else {
767
+ // Other native code
768
+ return red;
769
+ }
770
+ }