local clock = os.clock local profile = {} -- function labels local _labeled = {} -- function definitions local _defined = {} -- time of last call local _tcalled = {} -- total execution time local _telapsed = {} -- number of calls local _ncalls = {} -- list of internal profiler functions local _internal = {} local getInfo = debug.getinfo function profile.hooker(event, line, info) info = info or getInfo(2, 'fnS') local f = info.func -- ignore the profiler itself if _internal[f] then return end -- get the function name if available if info.name then _labeled[f] = info.name end -- 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 rawget(_G, '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('collect') 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('collect') end function profile.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. -- @param n Number of results (optional) 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, profile.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() if switch then profile.stop() love.system.setClipboardText(PROFILE.report()) PROFILE.reset() LOG.print("profile report copied!") else PROFILE.start() LOG.print("profile start!") end switch = not switch end -- store all internal profiler functions for _, v in next, profile do _internal[v] = type(v) == "function" end return profile