Call math.randomseed() without arguments to seed from system entropy.
[luajit-2.0.git] / src / jit / p.lua
blob36f836c51da632200a2e903a4c063fb3775dd3e5
1 ----------------------------------------------------------------------------
2 -- LuaJIT profiler.
3 --
4 -- Copyright (C) 2005-2023 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.
11 -- The lower-level API of the profiler is accessible via the "jit.profile"
12 -- module or the luaJIT_profile_* C API.
14 -- Example usage:
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
22 -- The following dump features are available:
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.
40 ----------------------------------------------------------------------------
42 -- Cache some library functions and objects.
43 local jit = require("jit")
44 local profile = require("jit.profile")
45 local vmdef = require("jit.vmdef")
46 local math = math
47 local pairs, ipairs, tonumber, floor = pairs, ipairs, tonumber, math.floor
48 local sort, format = table.sort, string.format
49 local stdout = io.stdout
50 local zone -- Load jit.zone module on demand.
52 -- Output file handle.
53 local out
55 ------------------------------------------------------------------------------
57 local prof_ud
58 local prof_states, prof_split, prof_min, prof_raw, prof_fmt, prof_depth
59 local prof_ann, prof_count1, prof_count2, prof_samples
61 local map_vmmode = {
62 N = "Compiled",
63 I = "Interpreted",
64 C = "C code",
65 G = "Garbage Collector",
66 J = "JIT Compiler",
69 -- Profiler callback.
70 local function prof_cb(th, samples, vmmode)
71 prof_samples = prof_samples + samples
72 local key_stack, key_stack2, key_state
73 -- Collect keys for sample.
74 if prof_states then
75 if prof_states == "v" then
76 key_state = map_vmmode[vmmode] or vmmode
77 else
78 key_state = zone:get() or "(none)"
79 end
80 end
81 if prof_fmt then
82 key_stack = profile.dumpstack(th, prof_fmt, prof_depth)
83 key_stack = key_stack:gsub("%[builtin#(%d+)%]", function(x)
84 return vmdef.ffnames[tonumber(x)]
85 end)
86 if prof_split == 2 then
87 local k1, k2 = key_stack:match("(.-) [<>] (.*)")
88 if k2 then key_stack, key_stack2 = k1, k2 end
89 elseif prof_split == 3 then
90 key_stack2 = profile.dumpstack(th, "l", 1)
91 end
92 end
93 -- Order keys.
94 local k1, k2
95 if prof_split == 1 then
96 if key_state then
97 k1 = key_state
98 if key_stack then k2 = key_stack end
99 end
100 elseif key_stack then
101 k1 = key_stack
102 if key_stack2 then k2 = key_stack2 elseif key_state then k2 = key_state end
104 -- Coalesce samples in one or two levels.
105 if k1 then
106 local t1 = prof_count1
107 t1[k1] = (t1[k1] or 0) + samples
108 if k2 then
109 local t2 = prof_count2
110 local t3 = t2[k1]
111 if not t3 then t3 = {}; t2[k1] = t3 end
112 t3[k2] = (t3[k2] or 0) + samples
117 ------------------------------------------------------------------------------
119 -- Show top N list.
120 local function prof_top(count1, count2, samples, indent)
121 local t, n = {}, 0
122 for k in pairs(count1) do
123 n = n + 1
124 t[n] = k
126 sort(t, function(a, b) return count1[a] > count1[b] end)
127 for i=1,n do
128 local k = t[i]
129 local v = count1[k]
130 local pct = floor(v*100/samples + 0.5)
131 if pct < prof_min then break end
132 if not prof_raw then
133 out:write(format("%s%2d%% %s\n", indent, pct, k))
134 elseif prof_raw == "r" then
135 out:write(format("%s%5d %s\n", indent, v, k))
136 else
137 out:write(format("%s %d\n", k, v))
139 if count2 then
140 local r = count2[k]
141 if r then
142 prof_top(r, nil, v, (prof_split == 3 or prof_split == 1) and " -- " or
143 (prof_depth < 0 and " -> " or " <- "))
149 -- Annotate source code
150 local function prof_annotate(count1, samples)
151 local files = {}
152 local ms = 0
153 for k, v in pairs(count1) do
154 local pct = floor(v*100/samples + 0.5)
155 ms = math.max(ms, v)
156 if pct >= prof_min then
157 local file, line = k:match("^(.*):(%d+)$")
158 if not file then file = k; line = 0 end
159 local fl = files[file]
160 if not fl then fl = {}; files[file] = fl; files[#files+1] = file end
161 line = tonumber(line)
162 fl[line] = prof_raw and v or pct
165 sort(files)
166 local fmtv, fmtn = " %3d%% | %s\n", " | %s\n"
167 if prof_raw then
168 local n = math.max(5, math.ceil(math.log10(ms)))
169 fmtv = "%"..n.."d | %s\n"
170 fmtn = (" "):rep(n).." | %s\n"
172 local ann = prof_ann
173 for _, file in ipairs(files) do
174 local f0 = file:byte()
175 if f0 == 40 or f0 == 91 then
176 out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file))
177 break
179 local fp, err = io.open(file)
180 if not fp then
181 out:write(format("====== ERROR: %s: %s\n", file, err))
182 break
184 out:write(format("\n====== %s ======\n", file))
185 local fl = files[file]
186 local n, show = 1, false
187 if ann ~= 0 then
188 for i=1,ann do
189 if fl[i] then show = true; out:write("@@ 1 @@\n"); break end
192 for line in fp:lines() do
193 if line:byte() == 27 then
194 out:write("[Cannot annotate bytecode file]\n")
195 break
197 local v = fl[n]
198 if ann ~= 0 then
199 local v2 = fl[n+ann]
200 if show then
201 if v2 then show = n+ann elseif v then show = n
202 elseif show+ann < n then show = false end
203 elseif v2 then
204 show = n+ann
205 out:write(format("@@ %d @@\n", n))
207 if not show then goto next end
209 if v then
210 out:write(format(fmtv, v, line))
211 else
212 out:write(format(fmtn, line))
214 ::next::
215 n = n + 1
217 fp:close()
221 ------------------------------------------------------------------------------
223 -- Finish profiling and dump result.
224 local function prof_finish()
225 if prof_ud then
226 profile.stop()
227 local samples = prof_samples
228 if samples == 0 then
229 if prof_raw ~= true then out:write("[No samples collected]\n") end
230 return
232 if prof_ann then
233 prof_annotate(prof_count1, samples)
234 else
235 prof_top(prof_count1, prof_count2, samples, "")
237 prof_count1 = nil
238 prof_count2 = nil
239 prof_ud = nil
240 if out ~= stdout then out:close() end
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
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 > ")
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
294 ------------------------------------------------------------------------------
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
303 prof_start(mode or "f")
306 -- Public module functions.
307 return {
308 start = start, -- For -j command line option.
309 stop = prof_finish