Skip to content

Instantly share code, notes, and snippets.

@waldnercharles
Created September 30, 2024 20:44
Show Gist options
  • Save waldnercharles/3b62d57db9342f184b91f60505b3cd4d to your computer and use it in GitHub Desktop.
Save waldnercharles/3b62d57db9342f184b91f60505b3cd4d to your computer and use it in GitHub Desktop.
---@class Profiler
local profiler = {
internal_fns = {},
call_stack = {},
call_tree = {},
clock = function()
return get_ticks() / get_tick_frequency()
end
}
local function get_filename_without_extension(path)
local filename = string.match(path, "^.+[\\/](.+)$") or path or "?"
return string.match(filename, "(.+)%..+$") or filename
end
local function find_child(parent, id)
for _, child in ipairs(parent.children) do if child.id == id then return child end end
end
local function on_function_call(info)
local parent = (#profiler.call_stack > 0 and profiler.call_stack[#profiler.call_stack]) or { children = profiler.call_tree }
local node_id = (parent.id or "[ROOT]")..string.format(":%s:%d", info.name or "?", info.currentline)
local node = find_child(parent, node_id)
if not node then
node = {
id = node_id,
name = info.name or "?",
src = get_filename_without_extension(info.short_src or "?"),
defined_line = info.linedefined,
line = info.currentline,
calls = 0,
duration = 0,
parent = parent,
children = {}
}
if parent then
table.insert(parent.children, node)
end
end
node.called_at = profiler.clock()
node.calls = node.calls + 1
table.insert(profiler.call_stack, node)
end
local function on_function_return()
if (#profiler.call_stack == 0) then return end
local node = table.remove(profiler.call_stack)
local dt = profiler.clock() - node.called_at
node.duration = node.duration + dt
end
local function hook(event, info)
info = info or debug.getinfo(2, "flnS")
local f = info.func
-- ignore functions from the profiler
if profiler.internal_fns[f] then
return
end
if event == "call" then
on_function_call(info)
elseif event == "tail call" then
local prev = debug.getinfo(3, "fnS")
hook("return", prev)
hook("call", info)
elseif event == "return" then
on_function_return()
end
end
function profiler.reset()
profiler.call_stack = {}
profiler.call_tree = {}
end
function profiler.start()
debug.sethook(hook, "cr")
end
function profiler.stop()
debug.sethook()
end
local function imgui_call_tree_node(tree)
if (not tree) then return end
for _, node in ipairs(tree) do
local line_number = (node.parent.line or node.line_defined or -1)
local label = string.format("%s.%s%s - %.6f seconds (called %d times)", node.src, node.name, (line_number > -1 and ":"..line_number) or "", node.duration, node.calls)
if imgui_tree_node(node.id, #node.children == 0 and ImGuiTreeNodeFlags_Leaf or ImGuiTreeNodeFlags_None, label) then
if #node.children > 0 then
imgui_call_tree_node(node.children)
end
imgui_tree_pop()
end
end
end
function profiler.imgui_call_tree()
imgui_text("Call Tree")
imgui_call_tree_node(profiler.call_tree)
end
for _, v in pairs(profiler) do
if type(v) == "function" then
profiler.internal_fns[v] = true
end
end
return profiler
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment