balatro-mods/DebugPlus/debugplus/prof.lua
2025-03-25 03:09:00 +08:00

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