git.s-ol.nu watch-cad / 064c650
add profiling support s-ol 16 days ago
2 changed file(s) with 184 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
11 import Input from require 'input'
22 import State from require 'state'
33 import Session, Script from require 'session'
4 profile = require 'profile'
45
56 export ^
67
1314 _G[k] = v
1415
1516 love.draw = ->
17 p = INPUT\key_down 'p'
18 if p
19 profile.reset!
20 profile.start!
1621 SESSION\frame!
1722 INPUT\frame!
23 if p
24 profile.stop!
25 print love.timer.getFPS!
26 print profile.report 20
1827 love.keypressed = INPUT\keypressed
1928 love.keyreleased = INPUT\keyreleased
2029 love.mousemoved = INPUT\mousemoved
1 local clock = os.clock
2
3 local profile = {}
4
5 -- function labels
6 local _labeled = {}
7 -- function definitions
8 local _defined = {}
9 -- time of last call
10 local _tcalled = {}
11 -- total execution time
12 local _telapsed = {}
13 -- number of calls
14 local _ncalls = {}
15 -- list of internal profiler functions
16 local _internal = {}
17
18 function profile.hooker(event, line, info)
19 info = info or debug.getinfo(2, 'fnS')
20 local f = info.func
21 -- ignore the profiler itself
22 if _internal[f] or info.what ~= "Lua" then
23 return
24 end
25 -- get the function name if available
26 if info.name then
27 _labeled[f] = info.name
28 end
29 -- find the line definition
30 if not _defined[f] then
31 _defined[f] = info.short_src..":"..info.linedefined
32 _ncalls[f] = 0
33 _telapsed[f] = 0
34 end
35 if _tcalled[f] then
36 local dt = clock() - _tcalled[f]
37 _telapsed[f] = _telapsed[f] + dt
38 _tcalled[f] = nil
39 end
40 if event == "tail call" then
41 local prev = debug.getinfo(3, 'fnS')
42 profile.hooker("return", line, prev)
43 profile.hooker("call", line, info)
44 elseif event == 'call' then
45 _tcalled[f] = clock()
46 else
47 _ncalls[f] = _ncalls[f] + 1
48 end
49 end
50
51 --- Sets a clock function to be used by the profiler.
52 -- @param f Clock function that returns a number
53 function profile.setclock(f)
54 assert(type(f) == "function", "clock must be a function")
55 clock = f
56 end
57
58 --- Starts collecting data.
59 function profile.start()
60 if rawget(_G, 'jit') then
61 jit.off()
62 jit.flush()
63 end
64 debug.sethook(profile.hooker, "cr")
65 end
66
67 --- Stops collecting data.
68 function profile.stop()
69 debug.sethook()
70 for f in pairs(_tcalled) do
71 local dt = clock() - _tcalled[f]
72 _telapsed[f] = _telapsed[f] + dt
73 _tcalled[f] = nil
74 end
75 -- merge closures
76 local lookup = {}
77 for f, d in pairs(_defined) do
78 local id = (_labeled[f] or '?')..d
79 local f2 = lookup[id]
80 if f2 then
81 _ncalls[f2] = _ncalls[f2] + (_ncalls[f] or 0)
82 _telapsed[f2] = _telapsed[f2] + (_telapsed[f] or 0)
83 _defined[f], _labeled[f] = nil, nil
84 _ncalls[f], _telapsed[f] = nil, nil
85 else
86 lookup[id] = f
87 end
88 end
89 collectgarbage('collect')
90 end
91
92 --- Resets all collected data.
93 function profile.reset()
94 for f in pairs(_ncalls) do
95 _ncalls[f] = 0
96 end
97 for f in pairs(_telapsed) do
98 _telapsed[f] = 0
99 end
100 for f in pairs(_tcalled) do
101 _tcalled[f] = nil
102 end
103 collectgarbage('collect')
104 end
105
106 function profile.comp(a, b)
107 local dt = _telapsed[b] - _telapsed[a]
108 if dt == 0 then
109 return _ncalls[b] < _ncalls[a]
110 end
111 return dt < 0
112 end
113
114 --- Iterates all functions that have been called since the profile was started.
115 -- @param n Number of results (optional)
116 function profile.query(limit)
117 local t = {}
118 for f, n in pairs(_ncalls) do
119 if n > 0 then
120 t[#t + 1] = f
121 end
122 end
123 table.sort(t, profile.comp)
124 if limit then
125 while #t > limit do
126 table.remove(t)
127 end
128 end
129 for i, f in ipairs(t) do
130 local dt = 0
131 if _tcalled[f] then
132 dt = clock() - _tcalled[f]
133 end
134 t[i] = { i, _labeled[f] or '?', _ncalls[f], _telapsed[f] + dt, _defined[f] }
135 end
136 return t
137 end
138
139 local cols = { 3, 29, 11, 24, 32 }
140 function profile.report(n)
141 local out = {}
142 local report = profile.query(n)
143 for i, row in ipairs(report) do
144 for j = 1, 5 do
145 local s = row[j]
146 local l2 = cols[j]
147 s = tostring(s)
148 local l1 = s:len()
149 if l1 < l2 then
150 s = s..(' '):rep(l2-l1)
151 elseif l1 > l2 then
152 s = s:sub(l1 - l2 + 1, l1)
153 end
154 row[j] = s
155 end
156 out[i] = table.concat(row, ' | ')
157 end
158
159 local row = " +-----+-------------------------------+-------------+--------------------------+----------------------------------+ \n"
160 local col = " | # | Function | Calls | Time | Code | \n"
161 local sz = row..col..row
162 if #out > 0 then
163 sz = sz..' | '..table.concat(out, ' | \n | ')..' | \n'
164 end
165 return '\n'..sz..row
166 end
167
168 -- store all internal profiler functions
169 for k, v in pairs(profile) do
170 if type(v) == "function" then
171 _internal[v] = true
172 end
173 end
174
175 return profile