diff options
Diffstat (limited to 'penalty.lua')
-rw-r--r-- | penalty.lua | 213 |
1 files changed, 161 insertions, 52 deletions
diff --git a/penalty.lua b/penalty.lua index b7173b7..1e20706 100644 --- a/penalty.lua +++ b/penalty.lua @@ -1,60 +1,169 @@ -local has_monitoring = minetest.get_modpath("monitoring") +local expected_dtime = tonumber(minetest.settings:get("dedicated_server_step")) or 0.09 -local mapblock_count, penalized_mapblock_count +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") + 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 timer = 0 +local elapsed_steps = 0 +local elapsed = 0 + minetest.register_globalstep(function(dtime) - timer = timer + dtime - if timer < 1 then return end - timer=0 - - local penalized_count = 0 - local now = minetest.get_us_time() - local cleanup_time_micros = 300 * 1000 * 1000 - - mesecons_debug.context_store_size = 0 - for hash, ctx in pairs(mesecons_debug.context_store) do - local time_diff = now - ctx.mtime - if time_diff > cleanup_time_micros then - -- remove item - mesecons_debug.context_store[hash] = nil - - else - -- calculate moving average - ctx.avg_micros = math.floor((ctx.avg_micros * 0.8) + (ctx.micros * 0.2)) - -- reset cpu usage counter - ctx.micros = 0 - - -- apply penalty values - if ctx.avg_micros > (mesecons_debug.max_usage_micros * 10) then - -- 10 times the limit used, potential abuse, add a greater penalty value - ctx.penalty = math.min(ctx.penalty + 5, mesecons_debug.max_penalty) - - elseif ctx.avg_micros > mesecons_debug.max_usage_micros then - -- add penalty value - ctx.penalty = math.min(ctx.penalty + 0.2, mesecons_debug.max_penalty) - - elseif ctx.penalty > 0 then - -- remove penalty (very slowly) - ctx.penalty = math.max(ctx.penalty - 0.001, 0) - end - - mesecons_debug.context_store_size = mesecons_debug.context_store_size + 1 - if ctx.penalty > 0 then - penalized_count = penalized_count + 1 - end - - end - end - - if has_monitoring then - mapblock_count.set(mesecons_debug.context_store_size) - penalized_mapblock_count.set(penalized_count) - end + 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 + + 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 + return + end + + + -- 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 + + -- 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_lag then + mesecons_debug.lag_level = 'high' + elseif is_moderate_lag then + mesecons_debug.lag_level = 'moderate' + else + mesecons_debug.lag_level = 'low' + end + + -- 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) |