diff options
Diffstat (limited to 'src/jit/p.lua')
-rw-r--r-- | src/jit/p.lua | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/src/jit/p.lua b/src/jit/p.lua new file mode 100644 index 00000000..ac3ec40a --- /dev/null +++ b/src/jit/p.lua | |||
@@ -0,0 +1,311 @@ | |||
1 | ---------------------------------------------------------------------------- | ||
2 | -- LuaJIT profiler. | ||
3 | -- | ||
4 | -- Copyright (C) 2005-2020 Mike Pall. All rights reserved. | ||
5 | -- Released under the MIT license. See Copyright Notice in luajit.h | ||
6 | ---------------------------------------------------------------------------- | ||
7 | -- | ||
8 | -- This module is a simple command line interface to the built-in | ||
9 | -- low-overhead profiler of LuaJIT. | ||
10 | -- | ||
11 | -- The lower-level API of the profiler is accessible via the "jit.profile" | ||
12 | -- module or the luaJIT_profile_* C API. | ||
13 | -- | ||
14 | -- Example usage: | ||
15 | -- | ||
16 | -- luajit -jp myapp.lua | ||
17 | -- luajit -jp=s myapp.lua | ||
18 | -- luajit -jp=-s myapp.lua | ||
19 | -- luajit -jp=vl myapp.lua | ||
20 | -- luajit -jp=G,profile.txt myapp.lua | ||
21 | -- | ||
22 | -- The following dump features are available: | ||
23 | -- | ||
24 | -- f Stack dump: function name, otherwise module:line. Default mode. | ||
25 | -- F Stack dump: ditto, but always prepend module. | ||
26 | -- l Stack dump: module:line. | ||
27 | -- <number> stack dump depth (callee < caller). Default: 1. | ||
28 | -- -<number> Inverse stack dump depth (caller > callee). | ||
29 | -- s Split stack dump after first stack level. Implies abs(depth) >= 2. | ||
30 | -- p Show full path for module names. | ||
31 | -- v Show VM states. Can be combined with stack dumps, e.g. vf or fv. | ||
32 | -- z Show zones. Can be combined with stack dumps, e.g. zf or fz. | ||
33 | -- r Show raw sample counts. Default: show percentages. | ||
34 | -- a Annotate excerpts from source code files. | ||
35 | -- A Annotate complete source code files. | ||
36 | -- G Produce raw output suitable for graphical tools (e.g. flame graphs). | ||
37 | -- m<number> Minimum sample percentage to be shown. Default: 3. | ||
38 | -- i<number> Sampling interval in milliseconds. Default: 10. | ||
39 | -- | ||
40 | ---------------------------------------------------------------------------- | ||
41 | |||
42 | -- Cache some library functions and objects. | ||
43 | local jit = require("jit") | ||
44 | assert(jit.version_num == 20100, "LuaJIT core/library version mismatch") | ||
45 | local profile = require("jit.profile") | ||
46 | local vmdef = require("jit.vmdef") | ||
47 | local math = math | ||
48 | local pairs, ipairs, tonumber, floor = pairs, ipairs, tonumber, math.floor | ||
49 | local sort, format = table.sort, string.format | ||
50 | local stdout = io.stdout | ||
51 | local zone -- Load jit.zone module on demand. | ||
52 | |||
53 | -- Output file handle. | ||
54 | local out | ||
55 | |||
56 | ------------------------------------------------------------------------------ | ||
57 | |||
58 | local prof_ud | ||
59 | local prof_states, prof_split, prof_min, prof_raw, prof_fmt, prof_depth | ||
60 | local prof_ann, prof_count1, prof_count2, prof_samples | ||
61 | |||
62 | local map_vmmode = { | ||
63 | N = "Compiled", | ||
64 | I = "Interpreted", | ||
65 | C = "C code", | ||
66 | G = "Garbage Collector", | ||
67 | J = "JIT Compiler", | ||
68 | } | ||
69 | |||
70 | -- Profiler callback. | ||
71 | local function prof_cb(th, samples, vmmode) | ||
72 | prof_samples = prof_samples + samples | ||
73 | local key_stack, key_stack2, key_state | ||
74 | -- Collect keys for sample. | ||
75 | if prof_states then | ||
76 | if prof_states == "v" then | ||
77 | key_state = map_vmmode[vmmode] or vmmode | ||
78 | else | ||
79 | key_state = zone:get() or "(none)" | ||
80 | end | ||
81 | end | ||
82 | if prof_fmt then | ||
83 | key_stack = profile.dumpstack(th, prof_fmt, prof_depth) | ||
84 | key_stack = key_stack:gsub("%[builtin#(%d+)%]", function(x) | ||
85 | return vmdef.ffnames[tonumber(x)] | ||
86 | end) | ||
87 | if prof_split == 2 then | ||
88 | local k1, k2 = key_stack:match("(.-) [<>] (.*)") | ||
89 | if k2 then key_stack, key_stack2 = k1, k2 end | ||
90 | elseif prof_split == 3 then | ||
91 | key_stack2 = profile.dumpstack(th, "l", 1) | ||
92 | end | ||
93 | end | ||
94 | -- Order keys. | ||
95 | local k1, k2 | ||
96 | if prof_split == 1 then | ||
97 | if key_state then | ||
98 | k1 = key_state | ||
99 | if key_stack then k2 = key_stack end | ||
100 | end | ||
101 | elseif key_stack then | ||
102 | k1 = key_stack | ||
103 | if key_stack2 then k2 = key_stack2 elseif key_state then k2 = key_state end | ||
104 | end | ||
105 | -- Coalesce samples in one or two levels. | ||
106 | if k1 then | ||
107 | local t1 = prof_count1 | ||
108 | t1[k1] = (t1[k1] or 0) + samples | ||
109 | if k2 then | ||
110 | local t2 = prof_count2 | ||
111 | local t3 = t2[k1] | ||
112 | if not t3 then t3 = {}; t2[k1] = t3 end | ||
113 | t3[k2] = (t3[k2] or 0) + samples | ||
114 | end | ||
115 | end | ||
116 | end | ||
117 | |||
118 | ------------------------------------------------------------------------------ | ||
119 | |||
120 | -- Show top N list. | ||
121 | local function prof_top(count1, count2, samples, indent) | ||
122 | local t, n = {}, 0 | ||
123 | for k in pairs(count1) do | ||
124 | n = n + 1 | ||
125 | t[n] = k | ||
126 | end | ||
127 | sort(t, function(a, b) return count1[a] > count1[b] end) | ||
128 | for i=1,n do | ||
129 | local k = t[i] | ||
130 | local v = count1[k] | ||
131 | local pct = floor(v*100/samples + 0.5) | ||
132 | if pct < prof_min then break end | ||
133 | if not prof_raw then | ||
134 | out:write(format("%s%2d%% %s\n", indent, pct, k)) | ||
135 | elseif prof_raw == "r" then | ||
136 | out:write(format("%s%5d %s\n", indent, v, k)) | ||
137 | else | ||
138 | out:write(format("%s %d\n", k, v)) | ||
139 | end | ||
140 | if count2 then | ||
141 | local r = count2[k] | ||
142 | if r then | ||
143 | prof_top(r, nil, v, (prof_split == 3 or prof_split == 1) and " -- " or | ||
144 | (prof_depth < 0 and " -> " or " <- ")) | ||
145 | end | ||
146 | end | ||
147 | end | ||
148 | end | ||
149 | |||
150 | -- Annotate source code | ||
151 | local function prof_annotate(count1, samples) | ||
152 | local files = {} | ||
153 | local ms = 0 | ||
154 | for k, v in pairs(count1) do | ||
155 | local pct = floor(v*100/samples + 0.5) | ||
156 | ms = math.max(ms, v) | ||
157 | if pct >= prof_min then | ||
158 | local file, line = k:match("^(.*):(%d+)$") | ||
159 | if not file then file = k; line = 0 end | ||
160 | local fl = files[file] | ||
161 | if not fl then fl = {}; files[file] = fl; files[#files+1] = file end | ||
162 | line = tonumber(line) | ||
163 | fl[line] = prof_raw and v or pct | ||
164 | end | ||
165 | end | ||
166 | sort(files) | ||
167 | local fmtv, fmtn = " %3d%% | %s\n", " | %s\n" | ||
168 | if prof_raw then | ||
169 | local n = math.max(5, math.ceil(math.log10(ms))) | ||
170 | fmtv = "%"..n.."d | %s\n" | ||
171 | fmtn = (" "):rep(n).." | %s\n" | ||
172 | end | ||
173 | local ann = prof_ann | ||
174 | for _, file in ipairs(files) do | ||
175 | local f0 = file:byte() | ||
176 | if f0 == 40 or f0 == 91 then | ||
177 | out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file)) | ||
178 | break | ||
179 | end | ||
180 | local fp, err = io.open(file) | ||
181 | if not fp then | ||
182 | out:write(format("====== ERROR: %s: %s\n", file, err)) | ||
183 | break | ||
184 | end | ||
185 | out:write(format("\n====== %s ======\n", file)) | ||
186 | local fl = files[file] | ||
187 | local n, show = 1, false | ||
188 | if ann ~= 0 then | ||
189 | for i=1,ann do | ||
190 | if fl[i] then show = true; out:write("@@ 1 @@\n"); break end | ||
191 | end | ||
192 | end | ||
193 | for line in fp:lines() do | ||
194 | if line:byte() == 27 then | ||
195 | out:write("[Cannot annotate bytecode file]\n") | ||
196 | break | ||
197 | end | ||
198 | local v = fl[n] | ||
199 | if ann ~= 0 then | ||
200 | local v2 = fl[n+ann] | ||
201 | if show then | ||
202 | if v2 then show = n+ann elseif v then show = n | ||
203 | elseif show+ann < n then show = false end | ||
204 | elseif v2 then | ||
205 | show = n+ann | ||
206 | out:write(format("@@ %d @@\n", n)) | ||
207 | end | ||
208 | if not show then goto next end | ||
209 | end | ||
210 | if v then | ||
211 | out:write(format(fmtv, v, line)) | ||
212 | else | ||
213 | out:write(format(fmtn, line)) | ||
214 | end | ||
215 | ::next:: | ||
216 | n = n + 1 | ||
217 | end | ||
218 | fp:close() | ||
219 | end | ||
220 | end | ||
221 | |||
222 | ------------------------------------------------------------------------------ | ||
223 | |||
224 | -- Finish profiling and dump result. | ||
225 | local function prof_finish() | ||
226 | if prof_ud then | ||
227 | profile.stop() | ||
228 | local samples = prof_samples | ||
229 | if samples == 0 then | ||
230 | if prof_raw ~= true then out:write("[No samples collected]\n") end | ||
231 | return | ||
232 | end | ||
233 | if prof_ann then | ||
234 | prof_annotate(prof_count1, samples) | ||
235 | else | ||
236 | prof_top(prof_count1, prof_count2, samples, "") | ||
237 | end | ||
238 | prof_count1 = nil | ||
239 | prof_count2 = nil | ||
240 | prof_ud = nil | ||
241 | end | ||
242 | end | ||
243 | |||
244 | -- Start profiling. | ||
245 | local function prof_start(mode) | ||
246 | local interval = "" | ||
247 | mode = mode:gsub("i%d*", function(s) interval = s; return "" end) | ||
248 | prof_min = 3 | ||
249 | mode = mode:gsub("m(%d+)", function(s) prof_min = tonumber(s); return "" end) | ||
250 | prof_depth = 1 | ||
251 | mode = mode:gsub("%-?%d+", function(s) prof_depth = tonumber(s); return "" end) | ||
252 | local m = {} | ||
253 | for c in mode:gmatch(".") do m[c] = c end | ||
254 | prof_states = m.z or m.v | ||
255 | if prof_states == "z" then zone = require("jit.zone") end | ||
256 | local scope = m.l or m.f or m.F or (prof_states and "" or "f") | ||
257 | local flags = (m.p or "") | ||
258 | prof_raw = m.r | ||
259 | if m.s then | ||
260 | prof_split = 2 | ||
261 | if prof_depth == -1 or m["-"] then prof_depth = -2 | ||
262 | elseif prof_depth == 1 then prof_depth = 2 end | ||
263 | elseif mode:find("[fF].*l") then | ||
264 | scope = "l" | ||
265 | prof_split = 3 | ||
266 | else | ||
267 | prof_split = (scope == "" or mode:find("[zv].*[lfF]")) and 1 or 0 | ||
268 | end | ||
269 | prof_ann = m.A and 0 or (m.a and 3) | ||
270 | if prof_ann then | ||
271 | scope = "l" | ||
272 | prof_fmt = "pl" | ||
273 | prof_split = 0 | ||
274 | prof_depth = 1 | ||
275 | elseif m.G and scope ~= "" then | ||
276 | prof_fmt = flags..scope.."Z;" | ||
277 | prof_depth = -100 | ||
278 | prof_raw = true | ||
279 | prof_min = 0 | ||
280 | elseif scope == "" then | ||
281 | prof_fmt = false | ||
282 | else | ||
283 | local sc = prof_split == 3 and m.f or m.F or scope | ||
284 | prof_fmt = flags..sc..(prof_depth >= 0 and "Z < " or "Z > ") | ||
285 | end | ||
286 | prof_count1 = {} | ||
287 | prof_count2 = {} | ||
288 | prof_samples = 0 | ||
289 | profile.start(scope:lower()..interval, prof_cb) | ||
290 | prof_ud = newproxy(true) | ||
291 | getmetatable(prof_ud).__gc = prof_finish | ||
292 | end | ||
293 | |||
294 | ------------------------------------------------------------------------------ | ||
295 | |||
296 | local function start(mode, outfile) | ||
297 | if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end | ||
298 | if outfile then | ||
299 | out = outfile == "-" and stdout or assert(io.open(outfile, "w")) | ||
300 | else | ||
301 | out = stdout | ||
302 | end | ||
303 | prof_start(mode or "f") | ||
304 | end | ||
305 | |||
306 | -- Public module functions. | ||
307 | return { | ||
308 | start = start, -- For -j command line option. | ||
309 | stop = prof_finish | ||
310 | } | ||
311 | |||