Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
v2.1
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
----------------------------------------------------------------------------
-- LuaJIT profiler.
--
-- Copyright (C) 2005-2017 Mike Pall. All rights reserved.
-- Released under the MIT license. See Copyright Notice in luajit.h
----------------------------------------------------------------------------
--
-- This module is a simple command line interface to the built-in
-- low-overhead profiler of LuaJIT.
--
-- The lower-level API of the profiler is accessible via the "jit.profile"
-- module or the luaJIT_profile_* C API.
--
-- Example usage:
--
-- luajit -jp myapp.lua
-- luajit -jp=s myapp.lua
-- luajit -jp=-s myapp.lua
-- luajit -jp=vl myapp.lua
-- luajit -jp=G,profile.txt myapp.lua
--
-- The following dump features are available:
--
-- f Stack dump: function name, otherwise module:line. Default mode.
-- F Stack dump: ditto, but always prepend module.
-- l Stack dump: module:line.
-- <number> stack dump depth (callee < caller). Default: 1.
-- -<number> Inverse stack dump depth (caller > callee).
-- s Split stack dump after first stack level. Implies abs(depth) >= 2.
-- p Show full path for module names.
-- v Show VM states. Can be combined with stack dumps, e.g. vf or fv.
-- z Show zones. Can be combined with stack dumps, e.g. zf or fz.
-- r Show raw sample counts. Default: show percentages.
-- a Annotate excerpts from source code files.
-- A Annotate complete source code files.
-- G Produce raw output suitable for graphical tools (e.g. flame graphs).
-- m<number> Minimum sample percentage to be shown. Default: 3.
-- i<number> Sampling interval in milliseconds. Default: 10.
--
----------------------------------------------------------------------------
-- Cache some library functions and objects.
local jit = require("jit")
assert(jit.version_num == 20102, "LuaJIT core/library version mismatch")
local profile = require("jit.profile")
local vmdef = require("jit.vmdef")
local math = math
local pairs, ipairs, tonumber, floor = pairs, ipairs, tonumber, math.floor
local sort, format = table.sort, string.format
local stdout = io.stdout
local zone -- Load jit.zone module on demand.
-- Output file handle.
local out
------------------------------------------------------------------------------
local prof_ud
local prof_states, prof_split, prof_min, prof_raw, prof_fmt, prof_depth
local prof_ann, prof_count1, prof_count2, prof_samples
local map_vmmode = {
N = "Compiled",
I = "Interpreted",
C = "C code",
G = "Garbage Collector",
J = "JIT Compiler",
}
-- Profiler callback.
local function prof_cb(th, samples, vmmode)
prof_samples = prof_samples + samples
local key_stack, key_stack2, key_state
-- Collect keys for sample.
if prof_states then
if prof_states == "v" then
key_state = map_vmmode[vmmode] or vmmode
else
key_state = zone:get() or "(none)"
end
end
if prof_fmt then
key_stack = profile.dumpstack(th, prof_fmt, prof_depth)
key_stack = key_stack:gsub("%[builtin#(%d+)%]", function(x)
return vmdef.ffnames[tonumber(x)]
end)
if prof_split == 2 then
local k1, k2 = key_stack:match("(.-) [<>] (.*)")
if k2 then key_stack, key_stack2 = k1, k2 end
elseif prof_split == 3 then
key_stack2 = profile.dumpstack(th, "l", 1)
end
end
-- Order keys.
local k1, k2
if prof_split == 1 then
if key_state then
k1 = key_state
if key_stack then k2 = key_stack end
end
elseif key_stack then
k1 = key_stack
if key_stack2 then k2 = key_stack2 elseif key_state then k2 = key_state end
end
-- Coalesce samples in one or two levels.
if k1 then
local t1 = prof_count1
t1[k1] = (t1[k1] or 0) + samples
if k2 then
local t2 = prof_count2
local t3 = t2[k1]
if not t3 then t3 = {}; t2[k1] = t3 end
t3[k2] = (t3[k2] or 0) + samples
end
end
end
------------------------------------------------------------------------------
-- Show top N list.
local function prof_top(count1, count2, samples, indent)
local t, n = {}, 0
for k in pairs(count1) do
n = n + 1
t[n] = k
end
sort(t, function(a, b) return count1[a] > count1[b] end)
for i=1,n do
local k = t[i]
local v = count1[k]
local pct = floor(v*100/samples + 0.5)
if pct < prof_min then break end
if not prof_raw then
out:write(format("%s%2d%% %s\n", indent, pct, k))
elseif prof_raw == "r" then
out:write(format("%s%5d %s\n", indent, v, k))
else
out:write(format("%s %d\n", k, v))
end
if count2 then
local r = count2[k]
if r then
prof_top(r, nil, v, (prof_split == 3 or prof_split == 1) and " -- " or
(prof_depth < 0 and " -> " or " <- "))
end
end
end
end
-- Annotate source code
local function prof_annotate(count1, samples)
local files = {}
local ms = 0
for k, v in pairs(count1) do
local pct = floor(v*100/samples + 0.5)
ms = math.max(ms, v)
if pct >= prof_min then
local file, line = k:match("^(.*):(%d+)$")
if not file then file = k; line = 0 end
local fl = files[file]
if not fl then fl = {}; files[file] = fl; files[#files+1] = file end
line = tonumber(line)
fl[line] = prof_raw and v or pct
end
end
sort(files)
local fmtv, fmtn = " %3d%% | %s\n", " | %s\n"
if prof_raw then
local n = math.max(5, math.ceil(math.log10(ms)))
fmtv = "%"..n.."d | %s\n"
fmtn = (" "):rep(n).." | %s\n"
end
local ann = prof_ann
for _, file in ipairs(files) do
local f0 = file:byte()
if f0 == 40 or f0 == 91 then
out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file))
break
end
local fp, err = io.open(file)
if not fp then
out:write(format("====== ERROR: %s: %s\n", file, err))
break
end
out:write(format("\n====== %s ======\n", file))
local fl = files[file]
local n, show = 1, false
if ann ~= 0 then
for i=1,ann do
if fl[i] then show = true; out:write("@@ 1 @@\n"); break end
end
end
for line in fp:lines() do
if line:byte() == 27 then
out:write("[Cannot annotate bytecode file]\n")
break
end
local v = fl[n]
if ann ~= 0 then
local v2 = fl[n+ann]
if show then
if v2 then show = n+ann elseif v then show = n
elseif show+ann < n then show = false end
elseif v2 then
show = n+ann
out:write(format("@@ %d @@\n", n))
end
if not show then goto next end
end
if v then
out:write(format(fmtv, v, line))
else
out:write(format(fmtn, line))
end
::next::
n = n + 1
end
fp:close()
end
end
------------------------------------------------------------------------------
-- Finish profiling and dump result.
local function prof_finish()
if prof_ud then
profile.stop()
local samples = prof_samples
if samples == 0 then
if prof_raw ~= true then out:write("[No samples collected]\n") end
return
end
if prof_ann then
prof_annotate(prof_count1, samples)
else
prof_top(prof_count1, prof_count2, samples, "")
end
prof_count1 = nil
prof_count2 = nil
prof_ud = nil
end
end
-- Start profiling.
local function prof_start(mode)
local interval = ""
mode = mode:gsub("i%d*", function(s) interval = s; return "" end)
prof_min = 3
mode = mode:gsub("m(%d+)", function(s) prof_min = tonumber(s); return "" end)
prof_depth = 1
mode = mode:gsub("%-?%d+", function(s) prof_depth = tonumber(s); return "" end)
local m = {}
for c in mode:gmatch(".") do m[c] = c end
prof_states = m.z or m.v
if prof_states == "z" then zone = require("jit.zone") end
local scope = m.l or m.f or m.F or (prof_states and "" or "f")
local flags = (m.p or "")
prof_raw = m.r
if m.s then
prof_split = 2
if prof_depth == -1 or m["-"] then prof_depth = -2
elseif prof_depth == 1 then prof_depth = 2 end
elseif mode:find("[fF].*l") then
scope = "l"
prof_split = 3
else
prof_split = (scope == "" or mode:find("[zv].*[lfF]")) and 1 or 0
end
prof_ann = m.A and 0 or (m.a and 3)
if prof_ann then
scope = "l"
prof_fmt = "pl"
prof_split = 0
prof_depth = 1
elseif m.G and scope ~= "" then
prof_fmt = flags..scope.."Z;"
prof_depth = -100
prof_raw = true
prof_min = 0
elseif scope == "" then
prof_fmt = false
else
local sc = prof_split == 3 and m.f or m.F or scope
prof_fmt = flags..sc..(prof_depth >= 0 and "Z < " or "Z > ")
end
prof_count1 = {}
prof_count2 = {}
prof_samples = 0
profile.start(scope:lower()..interval, prof_cb)
prof_ud = newproxy(true)
getmetatable(prof_ud).__gc = prof_finish
end
------------------------------------------------------------------------------
local function start(mode, outfile)
if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end
if outfile then
out = outfile == "-" and stdout or assert(io.open(outfile, "w"))
else
out = stdout
end
prof_start(mode or "f")
end
-- Public module functions.
return {
start = start, -- For -j command line option.
stop = prof_finish
}