158 lines
3.9 KiB
Lua
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
|