mirror of
https://github.com/ComfyFactory/ComfyFactorio.git
synced 2024-12-28 23:06:38 +02:00
268 lines
8.3 KiB
Lua
268 lines
8.3 KiB
Lua
if (debug.sethook) then
|
|
local table_sort = table.sort
|
|
local string_rep = string.rep
|
|
local string_format = string.format
|
|
local debug_getinfo = debug.getinfo
|
|
local Color = require 'utils.color_presets'
|
|
local Task = require 'utils.task'
|
|
local Token = require 'utils.token'
|
|
local Event = require 'utils.event'
|
|
|
|
local Public = {
|
|
call_tree = nil,
|
|
is_running = false
|
|
}
|
|
|
|
local stop_profiler_token =
|
|
Token.register(
|
|
function()
|
|
Public.stop()
|
|
game.print('[PROFILER] Stopped!')
|
|
log('[PROFILER] Stopped!')
|
|
end
|
|
)
|
|
|
|
-- we can have this on runtime,
|
|
-- but never ever can a player run this without notifying us.
|
|
local allowed = {
|
|
['Gerkiz'] = true,
|
|
['mewmew'] = true
|
|
}
|
|
|
|
local ignored_functions = {
|
|
[debug.sethook] = true
|
|
}
|
|
|
|
local named_sources = {
|
|
['[string "local n, v = "serpent", "0.30" -- (C) 2012-17..."]'] = 'serpent'
|
|
}
|
|
|
|
local function start_command(command)
|
|
local player = game.player
|
|
if player then
|
|
if player ~= nil then
|
|
if not player.admin then
|
|
local p = player.print
|
|
p('[ERROR] Only admins are allowed to run this command!', Color.fail)
|
|
return
|
|
else
|
|
if allowed[player.name] then
|
|
Public.start(command.parameter ~= nil)
|
|
elseif _DEBUG then
|
|
Public.start(command.parameter ~= nil)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function stop_command(command)
|
|
local player = game.player
|
|
if player then
|
|
if player ~= nil then
|
|
if not player.admin then
|
|
local p = player.print
|
|
p('[ERROR] Only admins are allowed to run this command!', Color.fail)
|
|
return
|
|
else
|
|
if allowed[player.name] then
|
|
Public.stop(command.parameter ~= nil, nil)
|
|
elseif _DEBUG then
|
|
Public.stop(command.parameter ~= nil, nil)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
ignored_functions[start_command] = true
|
|
ignored_functions[stop_command] = true
|
|
|
|
commands.add_command('start_profiler', 'Starts profiling', start_command)
|
|
commands.add_command('stop_profiler', 'Stops profiling', stop_command)
|
|
|
|
--local assert_raw = assert
|
|
--function assert(expr, ...)
|
|
-- if not expr then
|
|
-- Public.stop(false, "Assertion failed")
|
|
-- end
|
|
-- assert_raw(expr, ...)
|
|
--end
|
|
local error_raw = error
|
|
--luacheck: ignore error
|
|
function error(...)
|
|
Public.stop(false, 'Error raised')
|
|
error_raw(...)
|
|
end
|
|
|
|
function Public.start(exclude_called_ms)
|
|
if Public.is_running then
|
|
return
|
|
end
|
|
|
|
local create_profiler = game.create_profiler
|
|
|
|
Public.is_running = true
|
|
|
|
Public.call_tree = {
|
|
name = 'root',
|
|
calls = 0,
|
|
profiler = create_profiler(),
|
|
next = {}
|
|
}
|
|
|
|
-- Array of Call
|
|
local stack = {[0] = Public.call_tree}
|
|
local stack_count = 0
|
|
|
|
debug.sethook(
|
|
function(event)
|
|
local info = debug_getinfo(2, 'nSf')
|
|
|
|
if ignored_functions[info.func] then
|
|
return
|
|
end
|
|
|
|
if event == 'call' or event == 'tail call' then
|
|
local prev_call = stack[stack_count]
|
|
if exclude_called_ms and prev_call then
|
|
prev_call.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
|
|
local namedSource = named_sources[source]
|
|
if namedSource ~= nil then
|
|
source = namedSource
|
|
elseif 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 prev_call_next = prev_call.next
|
|
if prev_call_next == nil then
|
|
prev_call_next = {}
|
|
prev_call.next = prev_call_next
|
|
end
|
|
|
|
local currCall = prev_call_next[name]
|
|
local profilerStartFunc
|
|
if currCall == nil then
|
|
local prof = create_profiler()
|
|
currCall = {
|
|
name = name,
|
|
calls = 1,
|
|
profiler = prof
|
|
}
|
|
prev_call_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 exclude_called_ms then
|
|
stack[stack_count].profiler.restart()
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
'cr'
|
|
)
|
|
end
|
|
ignored_functions[Public.start] = true
|
|
|
|
local function dump_tree(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 ii = 1, #sort do
|
|
local call = sort[ii]
|
|
|
|
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 Public.call_tree.next ~= nil then
|
|
recurse(Public.call_tree.next, 0)
|
|
return fullStr
|
|
end
|
|
return 'No calls'
|
|
end
|
|
|
|
function Public.stop(averageMs, message)
|
|
if not Public.is_running then
|
|
return
|
|
end
|
|
|
|
debug.sethook()
|
|
|
|
local text = {'', '\n\n----------PROFILER DUMP----------\n', dump_tree(averageMs), '\n\n----------PROFILER STOPPED----------\n'}
|
|
if message ~= nil then
|
|
text[#text + 1] = string.format('Reason: %s\n', message)
|
|
end
|
|
log(text)
|
|
Public.call_tree = nil
|
|
Public.is_running = false
|
|
end
|
|
ignored_functions[Public.stop] = true
|
|
|
|
if _PROFILE then
|
|
Event.on_init(
|
|
function()
|
|
game.print('[PROFILER] Started!')
|
|
log('[PROFILER] Started!')
|
|
Public.start()
|
|
Task.set_timeout_in_ticks(3600, stop_profiler_token)
|
|
end
|
|
)
|
|
end
|
|
|
|
return Public
|
|
end
|