This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
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?
moonjit/src/jit/p.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
311 lines (295 sloc)
8.92 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ---------------------------------------------------------------------------- | |
| -- 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 | |
| } | |