neyuki 0.6.3

A small Lua-like scripting language implemented in Rust
-- Counts calls and measures time per function through a call/return hook.

local os = require("@neyuki/os")
local table = require("@neyuki/table")

local profile = {}

local stats = {}
local stack = {}
local running = false

local function tracer(event: string)
    local info = debug.getinfo(2)
    if info == nil then
        return
    end
    if info.func == profile.start or info.func == profile.stop or info.func == profile.isRunning then
        return
    end
    if event == "call" or event == "tail call" then
        local name = info.name ?? "main chunk"
        local entry = stats[name]
        if entry == nil then
            entry = { calls = 0, time = 0 }
            stats[name] = entry
        end
        entry.calls = entry.calls + 1
        stack[#stack + 1] = { name = name, started = os.clock() }
    elseif event == "return" and #stack > 0 then
        local top = table.remove(stack)
        local entry = stats[top.name]
        entry.time = entry.time + (os.clock() - top.started)
    end
end

-- Starts profiling, discarding earlier results.
function profile.start()
    stats = {}
    stack = {}
    running = true
    debug.sethook(tracer, "cr")
end

-- Stops profiling and returns one `name: calls=N, time=Ts` line per
-- function, slowest first.
function profile.stop(): string
    debug.sethook()
    running = false
    stack = {}
    local names = {}
    for name, _ in stats do
        names[#names + 1] = name
    end
    table.sort(names, function(a, b) return stats[a].time > stats[b].time end)
    local lines = {}
    for _, name in names do
        local entry = stats[name]
        lines[#lines + 1] = name .. ": calls=" .. tostring(entry.calls) .. ", time=" .. tostring(entry.time) .. "s"
    end
    return table.concat(lines, "\n")
end

function profile.isRunning(): boolean
    return running
end

return table.freeze(profile)