forked from 26F-Studio/Zenitha
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprofile.lua
165 lines (150 loc) · 4.21 KB
/
profile.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
local clock=os.clock
local profile={}
local _labeled={} -- function labels
local _defined={} -- function definitions
local _tcalled={} -- time of last call
local _telapsed={} -- total execution time
local _ncalls={} -- number of calls
local _internal={} -- list of internal profiler functions
local getInfo=debug.getinfo
local function _hooker(event,line,info)
info=info or getInfo(2,'fnS')
local f=info.func
if _internal[f] then return end -- ignore the profiler itself
if info.name then _labeled[f]=info.name end -- get the function name if available
-- 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=getInfo(3,'fnS')
_hooker('return',line,prev)
_hooker('call',line,info)
elseif event=='call' then
_tcalled[f]=clock()
else
_ncalls[f]=_ncalls[f]+1
end
end
local function _comp(a,b)
local dt=_telapsed[b]-_telapsed[a]
return dt==0 and _ncalls[b]<_ncalls[a] or dt<0
end
---Starts collecting data.
function profile.start()
if jit then
jit.off()
jit.flush()
end
debug.sethook(_hooker,'cr')
end
---Stops collecting data.
function profile.stop()
debug.sethook()
for f in next,_tcalled do
local dt=clock()-_tcalled[f]
_telapsed[f]=_telapsed[f]+dt
_tcalled[f]=nil
end
-- merge closures
local lookup={}
for f,d in next,_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()
end
---Resets all collected data.
function profile.reset()
for f in next,_ncalls do
_ncalls[f]=0
_telapsed[f]=0
_tcalled[f]=nil
end
collectgarbage()
end
---Iterates all functions that have been called since the profile was started.
---@param limit? number limit the number of functions to return
function profile.query(limit)
local t={}
for f,n in next,_ncalls do
if n>0 then
t[#t+1]=f
end
end
table.sort(t,_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 "?",math.floor((_telapsed[f]+dt)*1e6)/1e6,_ncalls[f],_defined[f]}
end
return t
end
local cols={3,20,8,6,32}
---Generate the datasheet
---@param limit? number limit the number of functions to return
---@return string
function profile.report(limit)
local out={}
local report=profile.query(limit)
for i,row in ipairs(report) do
for j=1,5 do
local s=tostring(row[j])
local l1,l2=#s,cols[j]
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 | Time | Calls | Code | \n"
local sz=row..col..row
if #out>0 then
sz=sz.." | "..table.concat(out," | \n | ").." | \n"
end
return "\n"..sz..row
end
local switch=false
---Turn profile mode on/off
---
---Automatically copy the report to clipboard when turned off
---@return boolean #current state
function profile.switch()
switch=not switch
if not switch then
profile.stop()
love.system.setClipboardText(profile.report())
profile.reset()
return false
else
profile.start()
return true
end
end
-- store all internal profiler functions
for _,v in next,profile do
_internal[v]=type(v)=='function'
end
return profile