143 lines
3.9 KiB
Lua
143 lines
3.9 KiB
Lua
-- Modified from https://gist.github.com/cigumo/88d7f84ca364015eaf577590db7e6577
|
|
|
|
local logger = require "debugplus.logger"
|
|
|
|
local profSucc, profile = pcall(require, "jit.profile")
|
|
if not profSucc then
|
|
logger.debug("jit.profile unavalible. Falling back to vanilla profiler.\n", profile)
|
|
return require "engine/profile"
|
|
end
|
|
local vmdefSucc, vmdef = pcall(require, "jit.vmdef")
|
|
if not vmdefSucc then
|
|
logger.debug("jit.vmdef unavailable. Profiler will not be able to resolve builtins.\n", vmdef)
|
|
vmdef = nil
|
|
end
|
|
|
|
local format = string.format
|
|
local sort = table.sort
|
|
local math = math
|
|
local floor = math.floor
|
|
local vmstates = {
|
|
N = "Native",
|
|
I = "Interpreted",
|
|
C = "C Code",
|
|
G = "Garbage Collector",
|
|
J = "JIT Compiler",
|
|
}
|
|
|
|
local prof = {}
|
|
|
|
prof.running = false
|
|
prof.flag_l2_shown = true
|
|
prof.flag_l2_levels = 3
|
|
prof.profiler_fmt = "Fi10"
|
|
prof.min_percent = 1
|
|
prof.l1_stack_fmt = "F"
|
|
prof.l2_stack_fmt = "l <"
|
|
prof.counts = {} -- double index
|
|
prof.top_str = nil
|
|
|
|
local total_samples = 0
|
|
|
|
------------------------------------------------------------
|
|
local function prof_cb(thread,samples,vmmode)
|
|
local c = prof.counts
|
|
total_samples = total_samples + samples
|
|
local l1_stack = profile.dumpstack(thread, prof.l1_stack_fmt, 1)
|
|
local l2_stack = profile.dumpstack(thread, prof.l2_stack_fmt, 5)
|
|
|
|
if vmdef then
|
|
l1_stack = l1_stack:gsub("%[builtin#(%d+)%]", function(x) return vmdef.ffnames[tonumber(x)] end)
|
|
l2_stack = l2_stack:gsub("%[builtin#(%d+)%]", function(x) return vmdef.ffnames[tonumber(x)] end)
|
|
end
|
|
|
|
if not c[l1_stack] then
|
|
local vl1 = {key=l1_stack, count=0, callers={}, vmmodes = {}} -- double index
|
|
c[l1_stack] = vl1
|
|
c[#c+1] = vl1
|
|
end
|
|
c[l1_stack].count = c[l1_stack].count + samples
|
|
c[l1_stack].vmmodes[vmmode] = (c[l1_stack].vmmodes[vmmode] or 0) + 1
|
|
|
|
if not c[l1_stack].callers[l2_stack] then
|
|
local vl2 = {key=l2_stack, count=0, vmmodes = {}}
|
|
local c2 = c[l1_stack].callers
|
|
c2[l2_stack] = vl2
|
|
c2[#c2+1] = vl2
|
|
end
|
|
c[l1_stack].callers[l2_stack].count = c[l1_stack].callers[l2_stack].count + samples
|
|
c[l1_stack].callers[l2_stack].vmmodes[vmmode] = (c[l1_stack].callers[l2_stack].vmmodes[vmmode] or 0) + 1
|
|
end
|
|
|
|
local function format_vmmodes(vmmodes)
|
|
local ret = ""
|
|
for k,v in pairs(vmmodes) do
|
|
if ret ~= "" then
|
|
ret = ret .. ", "
|
|
end
|
|
ret = ret .. (vmstates[k] or k) .. ": " .. tostring(v)
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function prof.format_result()
|
|
local c = prof.counts
|
|
local out = {}
|
|
|
|
-- sort l1
|
|
sort(c, function(a,b) return a.count > b.count end)
|
|
|
|
-- sort l2
|
|
for i,v in ipairs(c) do
|
|
sort(v.callers, function(a,b) return a.count > b.count end)
|
|
end
|
|
|
|
-- format
|
|
for i=1,#c do
|
|
local vl1 = c[i]
|
|
local pct = floor(vl1.count * 100 / total_samples + 0.5)
|
|
if pct < prof.min_percent then break end
|
|
table.insert(out, format("%2d%% %s (%s)", pct, vl1.key, format_vmmodes(vl1.vmmodes)))
|
|
local c2 = vl1.callers
|
|
|
|
if prof.flag_l2_shown then
|
|
for j=1,#c2 do
|
|
if j > prof.flag_l2_levels then break end
|
|
local vl2 = c2[j]
|
|
table.insert(out, format(" %4d %s (%s)", vl2.count, vl2.key, format_vmmodes(vl2.vmmodes)))
|
|
end
|
|
end
|
|
end
|
|
|
|
return table.concat(out,'\n')
|
|
end
|
|
|
|
prof.report = prof.format_result
|
|
|
|
function prof.start()
|
|
if prof.running then
|
|
logger.error("Profiler already running?")
|
|
return
|
|
end
|
|
|
|
total_samples = 0
|
|
prof.counts = {}
|
|
profile.start(prof.profiler_fmt, prof_cb)
|
|
prof.running = true
|
|
end
|
|
|
|
function prof.stop()
|
|
if not prof.running then
|
|
logger.error("Profiler not running?")
|
|
return
|
|
end
|
|
profile.stop()
|
|
prof.running = false
|
|
prof.flag_dirty = true
|
|
end
|
|
|
|
|
|
------------------------------------------------------------
|
|
return prof
|
|
|