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