diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2019-11-03 15:59:43 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2019-11-03 16:01:13 +0000 |
| commit | 064c650c3977c7a9d3aebac6bb91f73c0c79d584 (patch) | |
| tree | 241f5323237dd5e1c56737a15ebfb476cba812df | |
| parent | update/fix API (diff) | |
| download | watch-cad-064c650c3977c7a9d3aebac6bb91f73c0c79d584.tar.gz watch-cad-064c650c3977c7a9d3aebac6bb91f73c0c79d584.zip | |
add profiling support
| -rw-r--r-- | init.moon | 9 | ||||
| -rw-r--r-- | profile.lua | 175 |
2 files changed, 184 insertions, 0 deletions
@@ -1,6 +1,7 @@ import Input from require 'input' import State from require 'state' import Session, Script from require 'session' +profile = require 'profile' export ^ @@ -13,8 +14,16 @@ for k, v in pairs require 'api' _G[k] = v love.draw = -> + p = INPUT\key_down 'p' + if p + profile.reset! + profile.start! SESSION\frame! INPUT\frame! + if p + profile.stop! + print love.timer.getFPS! + print profile.report 20 love.keypressed = INPUT\keypressed love.keyreleased = INPUT\keyreleased love.mousemoved = INPUT\mousemoved diff --git a/profile.lua b/profile.lua new file mode 100644 index 0000000..4609d3f --- /dev/null +++ b/profile.lua @@ -0,0 +1,175 @@ +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 = {} + +function profile.hooker(event, line, info) + info = info or debug.getinfo(2, 'fnS') + local f = info.func + -- ignore the profiler itself + if _internal[f] or info.what ~= "Lua" 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 = debug.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 + +--- Sets a clock function to be used by the profiler. +-- @param f Clock function that returns a number +function profile.setclock(f) + assert(type(f) == "function", "clock must be a function") + clock = f +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 pairs(_tcalled) do + local dt = clock() - _tcalled[f] + _telapsed[f] = _telapsed[f] + dt + _tcalled[f] = nil + end + -- merge closures + local lookup = {} + for f, d in pairs(_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 pairs(_ncalls) do + _ncalls[f] = 0 + end + for f in pairs(_telapsed) do + _telapsed[f] = 0 + end + for f in pairs(_tcalled) do + _tcalled[f] = nil + end + collectgarbage('collect') +end + +function profile.comp(a, b) + local dt = _telapsed[b] - _telapsed[a] + if dt == 0 then + return _ncalls[b] < _ncalls[a] + end + return 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 pairs(_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 '?', _ncalls[f], _telapsed[f] + dt, _defined[f] } + end + return t +end + +local cols = { 3, 29, 11, 24, 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 = row[j] + local l2 = cols[j] + s = tostring(s) + local l1 = s:len() + 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 | Calls | Time | Code | \n" + local sz = row..col..row + if #out > 0 then + sz = sz..' | '..table.concat(out, ' | \n | ')..' | \n' + end + return '\n'..sz..row +end + +-- store all internal profiler functions +for k, v in pairs(profile) do + if type(v) == "function" then + _internal[v] = true + end +end + +return profile |
