ljx

FORK: LuaJIT with native 5.2 and 5.3 support
git clone https://git.neptards.moe/neptards/ljx.git
Log | Files | Refs | README

p.lua (9092B)


      1 ----------------------------------------------------------------------------
      2 -- LuaJIT profiler.
      3 --
      4 -- Copyright (C) 2005-2016 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, 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
    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       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
    219 end
    220 
    221 ------------------------------------------------------------------------------
    222 
    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
    241 end
    242 
    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
    291 end
    292 
    293 ------------------------------------------------------------------------------
    294 
    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")
    303 end
    304 
    305 -- Public module functions.
    306 return {
    307   start = start, -- For -j command line option.
    308   stop = prof_finish
    309 }
    310