|
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
|