675e1e1943
* add proper settings (untested) * more constants -> settings * normalize whitespace between code files * refactor globalsteps in order to simplify logic * minor refactoring * rename file * use mod_storage for persistent data; optimize context initialization * refactoring (moving files around) * rewrite penalty * add settings; document; allow changing while game is running * add command to update settings * update init after splitting commands into files * fix bugs; add debugging tools; too much for one commit... * fix whitelist conversion * add adjustable blinky plant to timer overrides * add some more mesecons nodes with repeating timers * resolve luacheck warnings * tweak hud * Update documentation; parameterize more things; refactor some logic for readability * update lag even when mesecons isn't active
169 lines
6.9 KiB
Lua
169 lines
6.9 KiB
Lua
local expected_dtime = tonumber(minetest.settings:get("dedicated_server_step")) or 0.09
|
|
|
|
local subscribe_for_modification = mesecons_debug.settings._subscribe_for_modification
|
|
local max_penalty = mesecons_debug.settings.max_penalty
|
|
subscribe_for_modification("max_penalty", function(value) max_penalty = value end)
|
|
local moderate_lag_ratio = mesecons_debug.settings.moderate_lag_ratio
|
|
subscribe_for_modification("moderate_lag_ratio", function(value) moderate_lag_ratio = value end)
|
|
local high_lag_ratio = mesecons_debug.settings.high_lag_ratio
|
|
local high_lag_dtime = expected_dtime * high_lag_ratio
|
|
subscribe_for_modification("high_lag_ratio", function(value)
|
|
high_lag_ratio = value
|
|
high_lag_dtime = expected_dtime * value
|
|
end)
|
|
local high_load_threshold = mesecons_debug.settings.high_load_threshold
|
|
subscribe_for_modification("high_load_threshold", function(value) high_load_threshold = value end)
|
|
local penalty_check_steps = mesecons_debug.settings.penalty_check_steps
|
|
subscribe_for_modification("penalty_check_steps", function(value) penalty_check_steps = value end)
|
|
local high_penalty_scale = mesecons_debug.settings.high_penalty_scale
|
|
subscribe_for_modification("high_penalty_scale", function(value) high_penalty_scale = value end)
|
|
local high_penalty_offset = mesecons_debug.settings.high_penalty_offset
|
|
subscribe_for_modification("high_penalty_offset", function(value) high_penalty_offset = value end)
|
|
local medium_penalty_scale = mesecons_debug.settings.medium_penalty_scale
|
|
subscribe_for_modification("medium_penalty_scale", function(value) medium_penalty_scale = value end)
|
|
local medium_penalty_offset = mesecons_debug.settings.medium_penalty_offset
|
|
subscribe_for_modification("medium_penalty_offset", function(value) medium_penalty_offset = value end)
|
|
local low_penalty_scale = mesecons_debug.settings.low_penalty_scale
|
|
subscribe_for_modification("low_penalty_scale", function(value) low_penalty_scale = value end)
|
|
local low_penalty_offset = mesecons_debug.settings.low_penalty_offset
|
|
subscribe_for_modification("low_penalty_offset", function(value) low_penalty_offset = value end)
|
|
local relative_load_max = mesecons_debug.settings.relative_load_clamp
|
|
local relative_load_min = 1 / mesecons_debug.settings.relative_load_clamp
|
|
subscribe_for_modification("relative_load_clamp", function(value)
|
|
relative_load_max = value
|
|
relative_load_min = 1 / value
|
|
end)
|
|
-- see https://en.wikipedia.org/w/index.php?title=Moving_average&oldid=1069105690#Exponential_moving_average
|
|
local averaging_coefficient = mesecons_debug.settings.averaging_coefficient
|
|
subscribe_for_modification("averaging_coefficient", function(value) averaging_coefficient = value end)
|
|
|
|
local max = math.max
|
|
local min = math.min
|
|
|
|
local function clamp(low, value, high)
|
|
return max(low, min(value, high))
|
|
end
|
|
|
|
local function clamp_load(load)
|
|
return max(relative_load_min, min(load, relative_load_max))
|
|
end
|
|
|
|
local function update_average(current, history)
|
|
return (current * averaging_coefficient) + (history * (1 - averaging_coefficient))
|
|
end
|
|
|
|
local has_monitoring = mesecons_debug.has.monitoring
|
|
local mapblock_count, penalized_mapblock_count
|
|
if has_monitoring then
|
|
mapblock_count = monitoring.gauge("mesecons_debug_mapblock_count", "count of tracked mapblocks")
|
|
penalized_mapblock_count = monitoring.gauge("mesecons_debug_penalized_mapblock_count",
|
|
"count of penalized mapblocks")
|
|
end
|
|
|
|
local elapsed_steps = 0
|
|
local elapsed = 0
|
|
|
|
minetest.register_globalstep(function(dtime)
|
|
elapsed = elapsed + dtime
|
|
elapsed_steps = elapsed_steps + 1
|
|
|
|
--[[
|
|
we check every N steps instead of every T seconds because we are more interested in the length of the steps
|
|
than in the number of them.
|
|
we also force a check if a particular step takes quite a long time, to keep things responsive.
|
|
]]
|
|
if dtime < high_lag_dtime and elapsed_steps < penalty_check_steps then
|
|
return
|
|
end
|
|
|
|
local context_store_size = mesecons_debug.context_store_size -- # of blocks w/ active mesecons
|
|
local total_micros = mesecons_debug.total_micros
|
|
local total_micros_per_second = total_micros / elapsed
|
|
local avg_total_micros_per_second = update_average(total_micros_per_second,
|
|
mesecons_debug.avg_total_micros_per_second)
|
|
mesecons_debug.avg_total_micros_per_second = avg_total_micros_per_second
|
|
|
|
-- how much lag is there?
|
|
local lag = elapsed / (elapsed_steps * expected_dtime)
|
|
local avg_lag = update_average(lag, mesecons_debug.avg_lag)
|
|
mesecons_debug.avg_lag = avg_lag
|
|
|
|
local is_high_lag = avg_lag > high_lag_ratio
|
|
local is_moderate_lag = avg_lag > moderate_lag_ratio
|
|
-- for use by HUD
|
|
if is_high_lag then
|
|
mesecons_debug.lag_level = "high"
|
|
elseif is_moderate_lag then
|
|
mesecons_debug.lag_level = "moderate"
|
|
else
|
|
mesecons_debug.lag_level = "low"
|
|
end
|
|
|
|
if context_store_size == 0 or avg_total_micros_per_second == 0 then
|
|
-- nothing to do, but reset counters
|
|
elapsed = 0
|
|
elapsed_steps = 0
|
|
mesecons_debug.total_micros = 0
|
|
mesecons_debug.load_level = "none"
|
|
return
|
|
end
|
|
|
|
-- how much of the lag was mesecons?
|
|
local mesecons_load = avg_total_micros_per_second / 1000000
|
|
local is_high_load = mesecons_load > high_load_threshold
|
|
|
|
-- for use by HUD
|
|
if is_high_load then
|
|
mesecons_debug.load_level = "high"
|
|
else
|
|
mesecons_debug.load_level = "low"
|
|
end
|
|
|
|
local penalty_scale, penalty_offset
|
|
if is_high_lag or (is_moderate_lag and is_high_load) then
|
|
penalty_scale = high_penalty_scale
|
|
penalty_offset = high_penalty_offset
|
|
elseif is_moderate_lag then
|
|
penalty_scale = medium_penalty_scale
|
|
penalty_offset = medium_penalty_offset
|
|
else
|
|
penalty_scale = low_penalty_scale
|
|
penalty_offset = low_penalty_offset
|
|
end
|
|
|
|
-- avg load per active context
|
|
local avg_avg_micros_per_second = avg_total_micros_per_second / context_store_size
|
|
|
|
local penalized_count = 0 -- for monitoring
|
|
for _, ctx in pairs(mesecons_debug.context_store) do
|
|
if not ctx.whitelisted then
|
|
-- moving avg
|
|
local micros_per_second = ctx.micros / elapsed
|
|
local avg_micros_per_second = update_average(micros_per_second, ctx.avg_micros_per_second)
|
|
ctx.avg_micros_per_second = avg_micros_per_second
|
|
|
|
local relative_load = clamp_load(avg_micros_per_second / avg_avg_micros_per_second)
|
|
|
|
local new_penalty = ctx.penalty + (relative_load * penalty_scale) + penalty_offset
|
|
ctx.penalty = clamp(0, new_penalty, max_penalty)
|
|
|
|
if has_monitoring and new_penalty > 0 then
|
|
penalized_count = penalized_count + 1
|
|
end
|
|
|
|
-- reset cpu usage counter
|
|
ctx.micros = 0
|
|
end
|
|
end
|
|
|
|
if has_monitoring then
|
|
mapblock_count.set(mesecons_debug.context_store_size)
|
|
penalized_mapblock_count.set(penalized_count)
|
|
end
|
|
|
|
-- reset counters
|
|
elapsed = 0
|
|
elapsed_steps = 0
|
|
mesecons_debug.total_micros = 0
|
|
end)
|