Merge branch 'master' into v2.1
[luajit-2.0.git] / src / jit / p.lua
blobcd6a06167f8f25176df8817c3e76dd8351de292f
1 ----------------------------------------------------------------------------
2 -- LuaJIT profiler.
3 --
4 -- Copyright (C) 2005-2013 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 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.
53 -- Output file handle.
54 local out
56 ------------------------------------------------------------------------------
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
62 local map_vmmode = {
63   N = "Compiled",
64   I = "Interpreted",
65   C = "C code",
66   G = "Garbage Collector",
67   J = "JIT Compiler",
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
118 ------------------------------------------------------------------------------
120 -- Show top N list.
121 local function prof_top(count1, count2, samples, indent)
122   local t, n = {}, 0
123   for k, v 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
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       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
163     end
164   end
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"
171   end
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
178     end
179     local fp, err = io.open(file)
180     if not fp then
181       out:write(format("====== ERROR: %s: %s\n", file, err))
182       break
183     end
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
190       end
191     end
192     for line in fp:lines() do
193       if line:byte() == 27 then
194         out:write("[Cannot annotate bytecode file]\n")
195         break
196       end
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))
206         end
207         if not show then goto next end
208       end
209       if v then
210         out:write(format(fmtv, v, line))
211       else
212         out:write(format(fmtn, line))
213       end
214     ::next::
215       n = n + 1
216     end
217     fp:close()
218   end
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
231     end
232     if prof_ann then
233       prof_annotate(prof_count1, samples)
234     else
235       prof_top(prof_count1, prof_count2, samples, "")
236     end
237     prof_count1 = nil
238     prof_count2 = nil
239     prof_ud = nil
240   end
243 -- Start profiling.
244 local function prof_start(mode)
245   local interval = ""
246   mode = mode:gsub("i%d*", function(s) interval = s; return "" end)
247   prof_min = 3
248   mode = mode:gsub("m(%d+)", function(s) prof_min = tonumber(s); return "" end)
249   prof_depth = 1
250   mode = mode:gsub("%-?%d+", function(s) prof_depth = tonumber(s); return "" end)
251   local m = {}
252   for c in mode:gmatch(".") do m[c] = c end
253   prof_states = m.z or m.v
254   if prof_states == "z" then zone = require("jit.zone") end
255   local scope = m.l or m.f or m.F or (prof_states and "" or "f")
256   local flags = (m.p or "")
257   prof_raw = m.r
258   if m.s then
259     prof_split = 2
260     if prof_depth == -1 or m["-"] then prof_depth = -2
261     elseif prof_depth == 1 then prof_depth = 2 end
262   elseif mode:find("[fF].*l") then
263     scope = "l"
264     prof_split = 3
265   else
266     prof_split = (scope == "" or mode:find("[zv].*[lfF]")) and 1 or 0
267   end
268   prof_ann = m.A and 0 or (m.a and 3)
269   if prof_ann then
270     scope = "l"
271     prof_fmt = "pl"
272     prof_split = 0
273     prof_depth = 1
274   elseif m.G and scope ~= "" then
275     prof_fmt = flags..scope.."Z;"
276     prof_depth = -100
277     prof_raw = true
278     prof_min = 0
279   elseif scope == "" then
280     prof_fmt = false
281   else
282     local sc = prof_split == 3 and m.f or m.F or scope
283     prof_fmt = flags..sc..(prof_depth >= 0 and "Z < " or "Z > ")
284   end
285   prof_count1 = {}
286   prof_count2 = {}
287   prof_samples = 0
288   profile.start(scope:lower()..interval, prof_cb)
289   prof_ud = newproxy(true)
290   getmetatable(prof_ud).__gc = prof_finish
293 ------------------------------------------------------------------------------
295 local function start(mode, outfile)
296   if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end
297   if outfile then
298     out = outfile == "-" and stdout or assert(io.open(outfile, "w"))
299   else
300     out = stdout
301   end
302   prof_start(mode or "f")
305 -- Public module functions.
306 return {
307   start = start, -- For -j command line option.
308   stop = prof_finish