src/jit/p.lua - luajit-2.0-src

Functions defined

Source code

  1. ----------------------------------------------------------------------------
  2. -- LuaJIT profiler.
  3. --
  4. -- Copyright (C) 2005-2015 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. -- Cache some library functions and objects.
  42. local jit = require("jit")
  43. assert(jit.version_num == 20100, "LuaJIT core/library version mismatch")
  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.

  51. -- Output file handle.
  52. local out

  53. ------------------------------------------------------------------------------

  54. local prof_ud
  55. local prof_states, prof_split, prof_min, prof_raw, prof_fmt, prof_depth
  56. local prof_ann, prof_count1, prof_count2, prof_samples

  57. local map_vmmode = {
  58.   N = "Compiled",
  59.   I = "Interpreted",
  60.   C = "C code",
  61.   G = "Garbage Collector",
  62.   J = "JIT Compiler",
  63. }

  64. -- Profiler callback.
  65. local function prof_cb(th, samples, vmmode)
  66.   prof_samples = prof_samples + samples
  67.   local key_stack, key_stack2, key_state
  68.   -- Collect keys for sample.
  69.   if prof_states then
  70.     if prof_states == "v" then
  71.       key_state = map_vmmode[vmmode] or vmmode
  72.     else
  73.       key_state = zone:get() or "(none)"
  74.     end
  75.   end
  76.   if prof_fmt then
  77.     key_stack = profile.dumpstack(th, prof_fmt, prof_depth)
  78.     key_stack = key_stack:gsub("%[builtin#(%d+)%]", function(x)
  79.       return vmdef.ffnames[tonumber(x)]
  80.     end)
  81.     if prof_split == 2 then
  82.       local k1, k2 = key_stack:match("(.-) [<>] (.*)")
  83.       if k2 then key_stack, key_stack2 = k1, k2 end
  84.     elseif prof_split == 3 then
  85.       key_stack2 = profile.dumpstack(th, "l", 1)
  86.     end
  87.   end
  88.   -- Order keys.
  89.   local k1, k2
  90.   if prof_split == 1 then
  91.     if key_state then
  92.       k1 = key_state
  93.       if key_stack then k2 = key_stack end
  94.     end
  95.   elseif key_stack then
  96.     k1 = key_stack
  97.     if key_stack2 then k2 = key_stack2 elseif key_state then k2 = key_state end
  98.   end
  99.   -- Coalesce samples in one or two levels.
  100.   if k1 then
  101.     local t1 = prof_count1
  102.     t1[k1] = (t1[k1] or 0) + samples
  103.     if k2 then
  104.       local t2 = prof_count2
  105.       local t3 = t2[k1]
  106.       if not t3 then t3 = {}; t2[k1] = t3 end
  107.       t3[k2] = (t3[k2] or 0) + samples
  108.     end
  109.   end
  110. end

  111. ------------------------------------------------------------------------------

  112. -- Show top N list.
  113. local function prof_top(count1, count2, samples, indent)
  114.   local t, n = {}, 0
  115.   for k, v in pairs(count1) do
  116.     n = n + 1
  117.     t[n] = k
  118.   end
  119.   sort(t, function(a, b) return count1[a] > count1[b] end)
  120.   for i=1,n do
  121.     local k = t[i]
  122.     local v = count1[k]
  123.     local pct = floor(v*100/samples + 0.5)
  124.     if pct < prof_min then break end
  125.     if not prof_raw then
  126.       out:write(format("%s%2d%%  %s\n", indent, pct, k))
  127.     elseif prof_raw == "r" then
  128.       out:write(format("%s%5d  %s\n", indent, v, k))
  129.     else
  130.       out:write(format("%s %d\n", k, v))
  131.     end
  132.     if count2 then
  133.       local r = count2[k]
  134.       if r then
  135.         prof_top(r, nil, v, (prof_split == 3 or prof_split == 1) and "  -- " or
  136.                             (prof_depth < 0 and "  -> " or "  <- "))
  137.       end
  138.     end
  139.   end
  140. end

  141. -- Annotate source code
  142. local function prof_annotate(count1, samples)
  143.   local files = {}
  144.   local ms = 0
  145.   for k, v in pairs(count1) do
  146.     local pct = floor(v*100/samples + 0.5)
  147.     ms = math.max(ms, v)
  148.     if pct >= prof_min then
  149.       local file, line = k:match("^(.*):(%d+)$")
  150.       local fl = files[file]
  151.       if not fl then fl = {}; files[file] = fl; files[#files+1] = file end
  152.       line = tonumber(line)
  153.       fl[line] = prof_raw and v or pct
  154.     end
  155.   end
  156.   sort(files)
  157.   local fmtv, fmtn = " %3d%% | %s\n", "      | %s\n"
  158.   if prof_raw then
  159.     local n = math.max(5, math.ceil(math.log10(ms)))
  160.     fmtv = "%"..n.."d | %s\n"
  161.     fmtn = (" "):rep(n).." | %s\n"
  162.   end
  163.   local ann = prof_ann
  164.   for _, file in ipairs(files) do
  165.     local f0 = file:byte()
  166.     if f0 == 40 or f0 == 91 then
  167.       out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file))
  168.       break
  169.     end
  170.     local fp, err = io.open(file)
  171.     if not fp then
  172.       out:write(format("====== ERROR: %s: %s\n", file, err))
  173.       break
  174.     end
  175.     out:write(format("\n====== %s ======\n", file))
  176.     local fl = files[file]
  177.     local n, show = 1, false
  178.     if ann ~= 0 then
  179.       for i=1,ann do
  180.         if fl[i] then show = true; out:write("@@ 1 @@\n"); break end
  181.       end
  182.     end
  183.     for line in fp:lines() do
  184.       if line:byte() == 27 then
  185.         out:write("[Cannot annotate bytecode file]\n")
  186.         break
  187.       end
  188.       local v = fl[n]
  189.       if ann ~= 0 then
  190.         local v2 = fl[n+ann]
  191.         if show then
  192.           if v2 then show = n+ann elseif v then show = n
  193.           elseif show+ann < n then show = false end
  194.         elseif v2 then
  195.           show = n+ann
  196.           out:write(format("@@ %d @@\n", n))
  197.         end
  198.         if not show then goto next end
  199.       end
  200.       if v then
  201.         out:write(format(fmtv, v, line))
  202.       else
  203.         out:write(format(fmtn, line))
  204.       end
  205.     ::next::
  206.       n = n + 1
  207.     end
  208.     fp:close()
  209.   end
  210. end

  211. ------------------------------------------------------------------------------

  212. -- Finish profiling and dump result.
  213. local function prof_finish()
  214.   if prof_ud then
  215.     profile.stop()
  216.     local samples = prof_samples
  217.     if samples == 0 then
  218.       if prof_raw ~= true then out:write("[No samples collected]\n") end
  219.       return
  220.     end
  221.     if prof_ann then
  222.       prof_annotate(prof_count1, samples)
  223.     else
  224.       prof_top(prof_count1, prof_count2, samples, "")
  225.     end
  226.     prof_count1 = nil
  227.     prof_count2 = nil
  228.     prof_ud = nil
  229.   end
  230. end

  231. -- Start profiling.
  232. local function prof_start(mode)
  233.   local interval = ""
  234.   mode = mode:gsub("i%d*", function(s) interval = s; return "" end)
  235.   prof_min = 3
  236.   mode = mode:gsub("m(%d+)", function(s) prof_min = tonumber(s); return "" end)
  237.   prof_depth = 1
  238.   mode = mode:gsub("%-?%d+", function(s) prof_depth = tonumber(s); return "" end)
  239.   local m = {}
  240.   for c in mode:gmatch(".") do m[c] = c end
  241.   prof_states = m.z or m.v
  242.   if prof_states == "z" then zone = require("jit.zone") end
  243.   local scope = m.l or m.f or m.F or (prof_states and "" or "f")
  244.   local flags = (m.p or "")
  245.   prof_raw = m.r
  246.   if m.s then
  247.     prof_split = 2
  248.     if prof_depth == -1 or m["-"] then prof_depth = -2
  249.     elseif prof_depth == 1 then prof_depth = 2 end
  250.   elseif mode:find("[fF].*l") then
  251.     scope = "l"
  252.     prof_split = 3
  253.   else
  254.     prof_split = (scope == "" or mode:find("[zv].*[lfF]")) and 1 or 0
  255.   end
  256.   prof_ann = m.A and 0 or (m.a and 3)
  257.   if prof_ann then
  258.     scope = "l"
  259.     prof_fmt = "pl"
  260.     prof_split = 0
  261.     prof_depth = 1
  262.   elseif m.G and scope ~= "" then
  263.     prof_fmt = flags..scope.."Z;"
  264.     prof_depth = -100
  265.     prof_raw = true
  266.     prof_min = 0
  267.   elseif scope == "" then
  268.     prof_fmt = false
  269.   else
  270.     local sc = prof_split == 3 and m.f or m.F or scope
  271.     prof_fmt = flags..sc..(prof_depth >= 0 and "Z < " or "Z > ")
  272.   end
  273.   prof_count1 = {}
  274.   prof_count2 = {}
  275.   prof_samples = 0
  276.   profile.start(scope:lower()..interval, prof_cb)
  277.   prof_ud = newproxy(true)
  278.   getmetatable(prof_ud).__gc = prof_finish
  279. end

  280. ------------------------------------------------------------------------------

  281. local function start(mode, outfile)
  282.   if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end
  283.   if outfile then
  284.     out = outfile == "-" and stdout or assert(io.open(outfile, "w"))
  285.   else
  286.     out = stdout
  287.   end
  288.   prof_start(mode or "f")
  289. end

  290. -- Public module functions.
  291. return {
  292.   start = start, -- For -j command line option.
  293.   stop = prof_finish
  294. }