forked from Boodals/Factorio-Profiler
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathprofiler.lua
213 lines (175 loc) · 4.38 KB
/
profiler.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
local table_sort = table.sort
local string_rep = string.rep
local string_format = string.format
local string_sub = string.sub
local debug_getinfo = debug.getinfo
-- Call
-- name (string)
-- calls (int)
-- profiler (LuaProfiler)
-- next (Array of Call)
local Profiler =
{
-- Call
CallTree = nil,
IsRunning = false,
}
local ignoredFunctions =
{
[debug.sethook] = true
}
local function startCommand(command)
Profiler.Start(command.parameter ~= nil)
end
local function stopCommand(command)
Profiler.Stop(command.parameter ~= nil, nil)
end
ignoredFunctions[startCommand] = true
ignoredFunctions[stopCommand] = true
if not No_Profiler_Commands then
commands.add_command("startProfiler", "Starts profiling", startCommand)
commands.add_command("stopProfiler", "Stops profiling", stopCommand)
end
local assert_raw = assert
function assert(expr, ...)
if not expr then
Profiler.Stop(false, "Assertion failed")
end
assert_raw(expr, ...)
end
local error_raw = error
function error(...)
Profiler.Stop(false, "Error raised")
error_raw(...)
end
function Profiler.Start(excludeCalledMs)
if Profiler.IsRunning then
return
end
local create_profiler = game.create_profiler
Profiler.IsRunning = true
Profiler.CallTree =
{
name = "root",
calls = 0,
profiler = create_profiler(),
next = { },
}
-- Array of Call
local stack = { [0] = Profiler.CallTree }
local stack_count = 0
debug.sethook(function(event)
local info = debug_getinfo(2, "nSf")
if ignoredFunctions[info.func] then
return
end
if event == "call" or event == "tail call" then
local prevCall = stack[stack_count]
if excludeCalledMs then
prevCall.profiler.stop()
end
local what = info.what
local name
if what == "C" then
name = string_format("C function %q", info.name or "anonymous")
else
local source = info.short_src
if string_sub(source, 1, 1) == "@" then
source = string_sub(source, 1)
end
name = string_format("%q in %q, line %d", info.name or "anonymous", source, info.linedefined)
end
local prevCall_next = prevCall.next
if prevCall_next == nil then
prevCall_next = { }
prevCall.next = prevCall_next
end
local currCall = prevCall_next[name]
local profilerStartFunc
if currCall == nil then
local prof = create_profiler()
currCall =
{
name = name,
calls = 1,
profiler = prof,
}
prevCall_next[name] = currCall
profilerStartFunc = prof.reset
else
currCall.calls = currCall.calls + 1
profilerStartFunc = currCall.profiler.restart
end
stack_count = stack_count + 1
stack[stack_count] = currCall
profilerStartFunc()
end
if event == "return" or event == "tail call" then
if stack_count > 0 then
stack[stack_count].profiler.stop()
stack[stack_count] = nil
stack_count = stack_count - 1
if excludeCalledMs then
stack[stack_count].profiler.restart()
end
end
end
end, "cr")
end
ignoredFunctions[Profiler.Start] = true
local function DumpTree(averageMs)
local function sort_Call(a, b)
return a.calls > b.calls
end
local fullStr = { "" }
local str = fullStr
local line = 1
local function recurse(curr, depth)
local sort = { }
local i = 1
for k, v in pairs(curr) do
sort[i] = v
i = i + 1
end
table_sort(sort, sort_Call)
for i = 1, #sort do
local call = sort[i]
if line >= 19 then --Localised string can only have up to 20 parameters
local newStr = { "" } --So nest them!
str[line + 1] = newStr
str = newStr
line = 1
end
if averageMs then
call.profiler.divide(call.calls)
end
str[line + 1] = string_format("\n%s%dx %s. %s ", string_rep("\t", depth), call.calls, call.name, averageMs and "Average" or "Total")
str[line + 2] = call.profiler
line = line + 2
local next = call.next
if next ~= nil then
recurse(next, depth + 1)
end
end
end
if Profiler.CallTree.next ~= nil then
recurse(Profiler.CallTree.next, 0)
return fullStr
end
return "No calls"
end
function Profiler.Stop(averageMs, message)
if not Profiler.IsRunning then
return
end
debug.sethook()
local text = { "", "\n\n----------PROFILER DUMP----------\n", DumpTree(averageMs), "\n\n----------PROFILER STOPPED----------\n" }
if message ~= nil then
text[#text + 1] = string.format("Reason: %s\n", message)
end
log(text)
Profiler.CallTree = nil
Profiler.IsRunning = false
end
ignoredFunctions[Profiler.Stop] = true
return Profiler