Files
Techmino/Zframework/profile.lua
2024-11-01 08:00:01 +08:00

158 lines
3.9 KiB
Lua

local clock=os.clock
local profile={}
local _labeled={} -- function labels
local _defined={} -- function definitions
local _tcalled={} -- time of last call
local _telapsed={}-- total execution time
local _ncalls={} -- number of calls
local _internal={}-- list of internal profiler functions
local getInfo=debug.getinfo
function profile.hooker(event,line,info)
info=info or getInfo(2,'fnS')
local f=info.func
if _internal[f] then return end-- ignore the profiler itself
if info.name then _labeled[f]=info.name end-- get the function name if available
-- find the line definition
if not _defined[f] then
_defined[f]=info.short_src..":"..info.linedefined
_ncalls[f]=0
_telapsed[f]=0
end
if _tcalled[f] then
local dt=clock()-_tcalled[f]
_telapsed[f]=_telapsed[f]+dt
_tcalled[f]=nil
end
if event=='tail call' then
local prev=getInfo(3,'fnS')
profile.hooker('return',line,prev)
profile.hooker('call',line,info)
elseif event=='call' then
_tcalled[f]=clock()
else
_ncalls[f]=_ncalls[f]+1
end
end
--- Starts collecting data.
function profile.start()
if jit then
jit.off()
jit.flush()
end
debug.sethook(profile.hooker,'cr')
end
--- Stops collecting data.
function profile.stop()
debug.sethook()
for f in next,_tcalled do
local dt=clock()-_tcalled[f]
_telapsed[f]=_telapsed[f]+dt
_tcalled[f]=nil
end
-- merge closures
local lookup={}
for f,d in next,_defined do
local id=(_labeled[f] or "?")..d
local f2=lookup[id]
if f2 then
_ncalls[f2]=_ncalls[f2]+(_ncalls[f] or 0)
_telapsed[f2]=_telapsed[f2]+(_telapsed[f] or 0)
_defined[f],_labeled[f]=nil,nil
_ncalls[f],_telapsed[f]=nil,nil
else
lookup[id]=f
end
end
collectgarbage()
end
--- Resets all collected data.
function profile.reset()
for f in next,_ncalls do
_ncalls[f]=0
_telapsed[f]=0
_tcalled[f]=nil
end
collectgarbage()
end
local function _comp(a,b)
local dt=_telapsed[b]-_telapsed[a]
return dt==0 and _ncalls[b]<_ncalls[a] or dt<0
end
--- Iterates all functions that have been called since the profile was started.
function profile.query(limit)
local t={}
for f,n in next,_ncalls do
if n>0 then
t[#t+1]=f
end
end
table.sort(t,_comp)
if limit then while #t>limit do table.remove(t) end end
for i,f in ipairs(t) do
local dt=0
if _tcalled[f] then
dt=clock()-_tcalled[f]
end
t[i]={i,_labeled[f] or "?",math.floor((_telapsed[f]+dt)*1e6)/1e6,_ncalls[f],_defined[f]}
end
return t
end
local cols={3,20,8,6,32}
function profile.report(n)
local out={}
local report=profile.query(n)
for i,row in ipairs(report) do
for j=1,5 do
local s=tostring(row[j])
local l1,l2=#s,cols[j]
if l1<l2 then
s=s..(" "):rep(l2-l1)
elseif l1>l2 then
s=s:sub(l1-l2+1,l1)
end
row[j]=s
end
out[i]=table.concat(row," | ")
end
local row=" +-----+----------------------+----------+--------+----------------------------------+ \n"
local col=" | # | Function | Time | Calls | Code | \n"
local sz=row..col..row
if #out>0 then
sz=sz.." | "..table.concat(out," | \n | ").." | \n"
end
return "\n"..sz..row
end
local switch=false
function profile.switch()
switch=not switch
if not switch then
profile.stop()
CLIPBOARD.set(profile.report())
profile.reset()
return false
else
profile.start()
return true
end
end
-- store all internal profiler functions
for _,v in next,profile do
_internal[v]=type(v)=='function'
end
return profile