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 l1l2 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