random_snippets/whosit_instrument_mod.lua

206 lines
6.4 KiB
Lua

-- luacheck: globals debuggery whosit minetest futil
-- based on debuggery by flux, but outputs result in chat and calculates max/avg
local f = string.format
local S = debuggery.S
local get_us_time = minetest.get_us_time
local log = debuggery.log
local log_level = minetest.settings:get("debug_log_level") or "action"
local pairs_by_key = futil.table.pairs_by_key
local s = debuggery.settings
whosit = _G.whosit or {}
_G.whosit = whosit
local old_values = whosit.instrument_old_values or {}
whosit.instrument_old_values = old_values
local total_steps = whosit.instrument_total_steps or {}
whosit.instrument_total_steps = total_steps
local total_elapsed = whosit.instrument_total_elapsed or {}
whosit.instrument_total_elapsed = total_elapsed
local total_calls = whosit.instrument_total_calls or {}
whosit.instrument_total_calls = total_calls
local max_time = whosit.instrument_max_time or {}
whosit.instrument_max_time = max_time
local num_instrumented = 0
local function instrument(name, value, _cache)
if type(value) == "function" then
log("action", "instrumenting %q", name)
return function(...)
if s.instrument_log_every_call then
log("action", "%s(%s)", name, dump({ ... }))
end
local begin = get_us_time()
local rvs = { value(...) }
local elapsed = get_us_time() - begin
total_elapsed[name] = (total_elapsed[name] or 0) + elapsed
total_calls[name] = (total_calls[name] or 0) + 1
max_time[name] = math.max(elapsed, max_time[name] or 0)
-- if s.instrument_log_every_call then
-- log("action", "%s(...) -> %s", name, dump(rvs))
-- end
return unpack(rvs)
end
elseif type(value) == "table" then
_cache = _cache or {}
local cached = _cache[value]
if cached then
return cached
end
local t = {}
_cache[value] = t
for k, v in pairs(value) do
if type(k) == "string" then
t[k] = instrument(f("%s.%s", name, k), v, _cache)
else
t[k] = instrument(f("%s[%s]", name, k), v, _cache)
end
end
setmetatable(t, instrument(f("getmetatable(%s)", name), getmetatable(value), _cache))
return t
else
return value
end
end
local function instrument_mod(mod)
log("action", "instrumenting %s", mod)
old_values[mod] = _G[mod]
_G[mod] = instrument(mod, _G[mod])
num_instrumented = num_instrumented + 1
end
local function uninstrument_mod(mod)
log("action", "uninstrumenting %s", mod)
_G[mod] = old_values[mod]
old_values[mod] = nil
num_instrumented = num_instrumented - 1
end
minetest.register_chatcommand(
"whosit_instrument_mod",
{
params = S("<global_name>"),
description = S("toggles recording timing data for all functions declared in a particular global"),
privs = { [s.admin_priv] = true },
func = function(name, param)
if param == "" then
local mods = {}
for mod in pairs(old_values) do
table.insert(mods, mod)
end
if #mods == 0 then
return true, S("no mods currently instrumented")
else
return true, S("mods currently instrumented: @1", table.concat(mods, ", "))
end
end
if not (minetest.global_exists(param) and _G[param]) then
return false, S("unknown global @1", param)
end
if old_values[param] then
uninstrument_mod(param)
return true, S("instrumentation disabled for @1", param)
else
instrument_mod(param)
return true, S("instrumentation enabled for @1", param)
end
end,
})
local function get_modname(fname)
local dot = fname:find('.',1,true)
if dot then
return fname:sub(1, dot - 1)
else
return fname
end
end
function whosit.instrument_summary_lines(perstep)
if futil.table.is_empty(total_calls) then
return false, "nothing to report"
end
local out = {}
for name, num_calls in pairs_by_key(total_calls, function(a,b) return total_elapsed[a] > total_elapsed[b] end) do
local steps = perstep and total_steps[get_modname(name)] or 1
local te = math.round(total_elapsed[name])
local avg = total_elapsed[name]/num_calls
local tm = max_time[name]
table.insert(out, f("%15.1f | n:%11d | avg:%9.1f | max:%9.1f -- %s()", te/steps, num_calls/steps, avg, tm, name))
end
return true, out
end
minetest.register_chatcommand(
"whosit_instrument_summary",
{
-- params = S("<global_name>"),
-- description = S("toggles recording timing data for all functions declared in a particular global"),
privs = { [s.admin_priv] = true },
func = function(name, param)
if futil.table.is_empty(total_calls) then
return false, "nothing to report"
end
local ok, res = whosit.instrument_summary_lines()
if ok then
core.chat_send_player(name, table.concat(res, '\n'))
end
return true, "done."
end,
})
minetest.register_chatcommand(
"whosit_instrument_reset",
{
-- params = S("<global_name>"),
-- description = S("toggles recording timing data for all functions declared in a particular global"),
privs = { [s.admin_priv] = true },
func = function(name, param)
for name, _ in pairs(total_calls) do
total_calls[name] = nil
total_elapsed[name] = nil
max_time[name] = nil
end
for name, _ in pairs(total_steps) do
total_steps[name] = nil
end
return true, "Stats reset."
end,
})
function whosit.instrument_summary_text(perstep)
local _,out = whosit.instrument_summary_lines(perstep)
return table.concat(out, '\n')
end
function whosit.instrument_update_steps()
for name,_ in pairs(old_values) do
total_steps[name] = (total_steps[name] or 0) + 1
end
end
if not whosit.instrument_globalstep_registered then
minetest.register_globalstep(
function(_dtime)
whosit.instrument_update_steps()
end
)
whosit.instrument_globalstep_registered = true
end