aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2019-11-03 15:59:43 +0000
committers-ol <s-ol@users.noreply.github.com>2019-11-03 16:01:13 +0000
commit064c650c3977c7a9d3aebac6bb91f73c0c79d584 (patch)
tree241f5323237dd5e1c56737a15ebfb476cba812df
parentupdate/fix API (diff)
downloadwatch-cad-064c650c3977c7a9d3aebac6bb91f73c0c79d584.tar.gz
watch-cad-064c650c3977c7a9d3aebac6bb91f73c0c79d584.zip
add profiling support
-rw-r--r--init.moon9
-rw-r--r--profile.lua175
2 files changed, 184 insertions, 0 deletions
diff --git a/init.moon b/init.moon
index 638ef97..1c0f5ef 100644
--- a/init.moon
+++ b/init.moon
@@ -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