aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfluxionary <25628292+fluxionary@users.noreply.github.com>2022-02-13 06:54:41 -0800
committerGitHub <noreply@github.com>2022-02-13 15:54:41 +0100
commit2651262fa3134415f349f63840c89486fabd9063 (patch)
tree3fa7f2ac228e662b4e9bc8071acc2f0650c44e65
parent1a41379e1d7ae69347f0e6ee6a997234f7590793 (diff)
downloadmesecons_debug-2651262fa3134415f349f63840c89486fabd9063.tar.gz
mesecons_debug-2651262fa3134415f349f63840c89486fabd9063.zip
rework mesecons debug to be more flexible (#7)
* 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
-rw-r--r--.luacheckrc2
-rw-r--r--README.md96
-rw-r--r--chatcommands.lua131
-rw-r--r--cleanup.lua29
-rw-r--r--clear_penalty.lua29
-rw-r--r--commands/admin_commands.lua122
-rw-r--r--commands/clear_penalty.lua33
-rw-r--r--commands/create_lag.lua28
-rw-r--r--commands/flush.lua8
-rw-r--r--commands/user_commands.lua60
-rw-r--r--compat/convert_old_whitelist.lua12
-rw-r--r--context.lua45
-rw-r--r--flush.lua10
-rw-r--r--functions.lua9
-rw-r--r--hud.lua151
-rw-r--r--init.lua85
-rw-r--r--luacontroller.lua45
-rw-r--r--mod.conf4
-rw-r--r--nodes/mesecons_lagger.lua59
-rw-r--r--nodes/penalty_controller.lua (renamed from penalty_controller.lua)24
-rw-r--r--overrides.lua36
-rw-r--r--overrides/mesecons_queue.lua49
-rw-r--r--overrides/node_timers.lua69
-rw-r--r--penalty.lua213
-rw-r--r--privs.lua3
-rw-r--r--settings.lua67
-rw-r--r--settingtypes.txt51
-rw-r--r--util.lua21
-rw-r--r--whitelist.lua21
29 files changed, 1023 insertions, 489 deletions
diff --git a/.luacheckrc b/.luacheckrc
index 50f0eeb..61bc753 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -1,4 +1,6 @@
+ignore = {"212/_.*"}
+
globals = {
"mesecons_debug",
"mesecon",
diff --git a/README.md b/README.md
index b289345..8e066f5 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,61 @@
# Mesecons Debug Collection
-Allows to throttle mesecons activity per mapblock
+Throttles mesecons if the server is lagging, in particular when mesecons is causing the lag.
# Overview
-There is a cpu quota for every mapblock, if that quota is used up
-the mesecons contraptions will be slowed down for that mapblock
+This mod can penalizes mesecons activity on a per-mapblock basis, which can help laggy or machine-heavy servers
+to be less laggy overall. Penalties are implemented as delays on various events.
-The current mapblock-stats can be viewed with `/mesecons_hud on`
+Lag and per-mapblock mesecons usage are tracked over time, in order to calculate how much to penalize each
+mapblock, or whether to reduce the penalty.
+
+The mod defines 3 regimes of lag, with different scales of penalties.
+
+* If the server steps are not taking much longer than the time allotted for them, the "low penalty" regime will apply.
+* If the server steps are taking too long on average, and mesecons usage isn't particularly high, the "medium penalty"
+ regime will apply.
+* If the server steps are taking too long on average and mesecons usage is high, or the server steps are taking much too
+ long to execute, the "high penalty" regime will apply.
+
+Each of these regimes has an associated "scale" and "offset". Every time the penalties are re-evaluated,
+they are changed according to this formula:
+
+```lua
+ new_penalty = old_penalty + (relative_load * penalty_scale) + penalty_offset
+```
+
+Here, relative_load is the ratio of how much time the current mapblock spends doing mesecons, to the mean time
+(spent doing mesecons) across all mapblocks currently running mesecons. This value is currently clamped between 0.1
+and 10, to prevent certain edge cases from being penalized too rapidly. A value of 10 would mean that the mapblock
+under consideration is using 10x as much mesecons as the average mapblock.
+
+Note that, depending on the values of `penalty_scale` and `penalty_offset`, the new penalty may be *less* than the old
+penalty. This is to allow penalties to reach equilibrium under a constant load, and to taper off over time if the
+usage in the mapblock declines, or the regime changes.
## Settings
-* `mesecons_debug.max_usage_micros` default: 15000
+* `penalty_clear_cooldown = 120` Seconds that a player has to wait between using the `mesecons_clear_penalty` command
+* `max_penalty = 120` Upper limit of the per-mapblock penalty
+* `penalty_mapblock_disabled = 110`
+ Completely disable mesecons in a mapblock, if the penalty exceeds this value.
+ Set above `max_penalty` to disable this feature.
+* `penalty_check_steps = 50` # of server steps between updating the penalties
+* `gc_interval = 61` Seconds after which data about unloaded mapblocks is removed from memory.
+* `hud_refresh_interval = 1` Seconds between updating the client's HUD
+* `moderate_lag_ratio = 3`
+ Ratio between actual and expected length of a server step at which lag is considered "moderate"
+* `high_lag_ratio = 9` Ratio between actual and expected length of a server step at which lag is considered "high"
+* `high_load_threshold = 0.33`
+ % of processing a server spends on mesecons at which the mescons load is considered "high".
+* `low_penalty_offset = -1` Offset of the penalty in the low-lag regime.
+* `low_penalty_scale = 0.1`
+ Scale of the penalty in the low-lag regime. The default values ensure that nothing is penalized in the low-lag regime.
+* `medium_penalty_offset = -0.8` Offset of the penalty in the moderate-lag regime.
+* `medium_penalty_scale = 0.2` Scale of the penalty in the moderate-lag regime.
+* `high_penalty_offset = -0.5` Offset of the penalty in the high-lag regime.
+* `high_penalty_scale = 0.5` Scale of the penalty in the high-lag regime.
## Privs
@@ -19,21 +63,39 @@ The current mapblock-stats can be viewed with `/mesecons_hud on`
## Commands
+* `/mesecons_clear_penalty`
+ Clears the penalty for the current mapblock. Users can only execute this every `penalty_clear_cooldown` seconds
+* `/mesecons_global_stats` shows the mapblock with the most prominent usage of mesecons activity
+* `/mesecons_hud` toggles the hud
+* `/mesecons_stats` shows some mesecons stats for the current position
+
+
+### Admin Commands
+
All of these commands require the `mesecons_debug` privilege.
-* `/mesecons_hud [on|off]` enables or disables the hud
-* `/mesecons_flush` Flushes the action queue
-* `/mesecons_enable` Enable the mesecons queue
-* `/mesecons_disable` Disables the mesecons queue
-* `/mesecons_stats` shows some mesecons stats for the current position
-* `/mesecons_global_stats` shows the mapblock with the most prominent usage of mesecons activity
-* `/mesecons_whitelist_get` shows the list of whitelisted mapblocks
+* `/create_lag <microseconds> <chance>`
+ Artificially slow down the server by `microseconds` every `chance` server steps. Useful for debugging this mod.
+* `/mesecons_debug_get <setting>` Inspect the current value of a setting.
+* `/mesecons_debug_set <setting> <value>` Change a setting value. This does *not* save the value between reboots!
+* `/mesecons_disable` Disables mesecons entirely
+* `/mesecons_enable` Undoes the above command
+* `/mesecons_flush` Flushes the mesecons action queue
* `/mesecons_whitelist_add` adds the current mapblock to the whitelist
+* `/mesecons_whitelist_get` shows the list of whitelisted mapblocks
* `/mesecons_whitelist_remove` removes the current mapblock from the whitelist
-## Penalty controller
+## Nodes
+
+### Mesecons Lagger
+
+A node which can create `n` microseconds of lag once every `chance` server steps. Useful for debugging this mod.
+
+### Penalty Controller
+
+Requires the `digiline` mod.
-Can query the penalty and usage values of the placed-in mapblock (requires the `digiline` mod)
+Can query the penalty and usage values of the mapblock it is placed in.
Example code to query it with the luacontroller:
@@ -45,9 +107,9 @@ end
if event.type == "digiline" and event.channel == "penalty_ctrl" then
--[[
event.msg = {
- micros = 0,
- avg_micros = 0,
- penalty = 0,
+ micros = 0, -- micros_per_second
+ avg_micros = 0, -- avg_micros_per_second
+ penalty = 0, -- in seconds
whitelisted = false
}
--]]
diff --git a/chatcommands.lua b/chatcommands.lua
deleted file mode 100644
index 229de91..0000000
--- a/chatcommands.lua
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
-minetest.register_chatcommand("mesecons_hud", {
- description = "mesecons_hud on/off",
- func = function(name, params)
- local enable = params == "on"
- mesecons_debug.hud[name] = enable
- if enable then
- return true, "mesecons hud enabled"
- else
- return true, "mesecons hud disabled"
- end
- end
-})
-
-minetest.register_chatcommand("mesecons_global_stats", {
- description = "shows the global mesecons stats",
- func = function()
- local top_ctx, top_hash
-
- for hash, ctx in pairs(mesecons_debug.context_store) do
- if not top_ctx or top_ctx.avg_micros < ctx.avg_micros then
- -- store context with the most average time
- top_ctx = ctx
- top_hash = hash
- end
- end
-
- local txt
- if top_ctx then
- local pos = minetest.get_position_from_hash(top_hash)
-
- txt = "Most prominent mesecons usage at mapblock " .. minetest.pos_to_string(pos) ..
- " with " .. top_ctx.penalty .. " seconds penalty and " .. top_ctx.avg_micros .. " us average use"
- else
- txt = "no context available"
- end
-
- return true, txt
- end
-})
-
-minetest.register_chatcommand("mesecons_stats", {
- description = "shows some mesecons stats for the current position",
- func = function(name)
- local player = minetest.get_player_by_name(name)
- if not player then
- return
- end
-
- local ctx = mesecons_debug.get_context(player:get_pos())
- return true, "Mapblock usage: " .. ctx.avg_micros .. " us/s " ..
- "(across " .. mesecons_debug.context_store_size .." mapblocks)"
- end
-})
-
-minetest.register_chatcommand("mesecons_enable", {
- description = "enables the mesecons globlastep",
- privs = {mesecons_debug=true},
- func = function()
- -- flush actions, while we are on it
- mesecon.queue.actions = {}
- mesecons_debug.enabled = true
- return true, "mesecons enabled"
- end
-})
-
-minetest.register_chatcommand("mesecons_disable", {
- description = "disables the mesecons globlastep",
- privs = {mesecons_debug=true},
- func = function()
- mesecons_debug.enabled = false
- return true, "mesecons disabled"
- end
-})
-
-minetest.register_chatcommand("mesecons_whitelist_get", {
- description = "shows the current mapblock whitelist",
- privs = {mesecons_debug=true},
- func = function()
- local whitelist = "mesecons whitelist:\n"
- local count = 0
- for hash, _ in pairs(mesecons_debug.whitelist) do
- whitelist = whitelist .. minetest.pos_to_string(minetest.get_position_from_hash(hash)) .. "\n"
- count = count + 1
- end
- whitelist = whitelist .. string.format("%d mapblocks whitelisted", count)
-
- return true, whitelist
- end
-})
-
-minetest.register_chatcommand("mesecons_whitelist_add", {
- description = "adds the current mapblock to the whitelist",
- privs = {mesecons_debug=true},
- func = function(name)
- local player = minetest.get_player_by_name(name)
- if not player then
- return
- end
-
- local ppos = player:get_pos()
- local blockpos = mesecons_debug.get_blockpos(ppos)
- local hash = minetest.hash_node_position(blockpos)
-
- mesecons_debug.whitelist[hash] = true
- mesecons_debug.save_whitelist()
-
- return true, "mapblock whitlisted"
- end
-})
-
-minetest.register_chatcommand("mesecons_whitelist_remove", {
- description = "removes the current mapblock from the whitelist",
- privs = {mesecons_debug=true},
- func = function(name)
- local player = minetest.get_player_by_name(name)
- if not player then
- return
- end
-
- local ppos = player:get_pos()
- local blockpos = mesecons_debug.get_blockpos(ppos)
- local hash = minetest.hash_node_position(blockpos)
-
- mesecons_debug.whitelist[hash] = nil
- mesecons_debug.save_whitelist()
-
- return true, "mapblock removed from whitelist"
- end
-})
diff --git a/cleanup.lua b/cleanup.lua
new file mode 100644
index 0000000..69bd20c
--- /dev/null
+++ b/cleanup.lua
@@ -0,0 +1,29 @@
+local subscribe_for_modification = mesecons_debug.settings._subscribe_for_modification
+local gc_interval = mesecons_debug.settings.gc_interval
+local cleanup_time_micros = gc_interval * 1000000
+
+subscribe_for_modification("gc_interval", function(value)
+ gc_interval = value
+ cleanup_time_micros = value * 1000000
+end)
+
+local context_store = mesecons_debug.context_store
+
+local cleanup_timer = 0
+minetest.register_globalstep(function(dtime)
+ cleanup_timer = cleanup_timer + dtime
+ if cleanup_timer < gc_interval then
+ return
+ end
+ cleanup_timer = 0
+
+ local now = minetest.get_us_time()
+ for hash, ctx in pairs(context_store) do
+ local time_diff = now - ctx.mtime
+ if time_diff > cleanup_time_micros then
+ -- remove item
+ context_store[hash] = nil
+ mesecons_debug.context_store_size = mesecons_debug.context_store_size - 1
+ end
+ end
+end)
diff --git a/clear_penalty.lua b/clear_penalty.lua
deleted file mode 100644
index 0fe84fe..0000000
--- a/clear_penalty.lua
+++ /dev/null
@@ -1,29 +0,0 @@
-
--- playername => time-of-last-cooldown
-local cooldown = {}
-
-minetest.register_chatcommand("mesecons_clear_penalty", {
- description = "clears the penalty in the current mapblock " ..
- "(cooldown: " .. mesecons_debug.penalty_clear_cooldown .. ")",
- func = function(name)
- local player = minetest.get_player_by_name(name)
- if not player then
- return
- end
-
- local last_cooldown_time = cooldown[name] or 0
- local remaining_time = mesecons_debug.penalty_clear_cooldown - (os.time() - last_cooldown_time)
- if remaining_time > 0 then
- -- cooldown still in progress
- return true, "cooldown still in progress, remaining time: " .. remaining_time .. " seconds"
- end
-
- -- set timer
- cooldown[name] = os.time()
-
- local ctx = mesecons_debug.get_context(player:get_pos())
- ctx.penalty = 0
-
- return true, "penalty reset"
- end
-})
diff --git a/commands/admin_commands.lua b/commands/admin_commands.lua
new file mode 100644
index 0000000..19a7cb6
--- /dev/null
+++ b/commands/admin_commands.lua
@@ -0,0 +1,122 @@
+minetest.register_chatcommand("mesecons_enable", {
+ description = "enables the mesecons globlastep",
+ privs = { mesecons_debug = true },
+ func = function()
+ -- flush actions, while we are on it
+ mesecon.queue.actions = {}
+ mesecons_debug.mesecons_enabled = true
+ return true, "mesecons enabled"
+ end
+})
+
+minetest.register_chatcommand("mesecons_disable", {
+ description = "disables the mesecons globlastep",
+ privs = { mesecons_debug = true },
+ func = function()
+ mesecons_debug.mesecons_enabled = false
+ return true, "mesecons disabled"
+ end
+})
+
+minetest.register_chatcommand("mesecons_whitelist_get", {
+ description = "shows the current mapblock whitelist",
+ privs = { mesecons_debug = true },
+ func = function()
+ local count = 0
+ local list = {}
+ for hash, _ in pairs(mesecons_debug.storage:to_table().fields) do
+ table.insert(list, minetest.pos_to_string(minetest.get_position_from_hash(hash)))
+ count = count + 1
+ end
+
+ return true, (
+ "mesecons whitelist:\n" ..
+ "%s\n" ..
+ "%i mapblocks whitelisted"
+ ):format(
+ table.concat(list, "\n"),
+ count
+ )
+ end
+})
+
+minetest.register_chatcommand("mesecons_whitelist_add", {
+ description = "adds the current mapblock to the whitelist",
+ privs = { mesecons_debug = true },
+ func = function(name)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return
+ end
+
+ local hash = mesecons_debug.hashpos(player:get_pos())
+ local ctx = mesecons_debug.get_context(hash)
+ ctx.whitelisted = true
+ mesecons_debug.storage:set_string(hash, "1")
+
+ return true, "mapblock whitlisted"
+ end
+})
+
+minetest.register_chatcommand("mesecons_whitelist_remove", {
+ description = "removes the current mapblock from the whitelist",
+ privs = { mesecons_debug = true },
+ func = function(name)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return
+ end
+
+ local hash = mesecons_debug.hashpos(player:get_pos())
+ local ctx = mesecons_debug.get_context(hash)
+ ctx.whitelisted = false
+ mesecons_debug.storage:set_string(hash, "")
+
+ return true, "mapblock removed from whitelist"
+ end
+})
+
+minetest.register_chatcommand("mesecons_debug_set", {
+ description = "modify mesecons_debug settings",
+ params = "<setting> <value>",
+ privs = { mesecons_debug = true },
+ func = function(name, params)
+ local player = minetest.get_player_by_name(name)
+ if not player or not params then
+ return false
+ end
+
+ local setting, value = params:match('^([a-zA-Z0-9_-]+)%s+(.*)$')
+ value = tonumber(value)
+ if not setting or not value then
+ return false
+ end
+
+ if not mesecons_debug.settings[setting] then
+ return false, "unknown setting"
+ end
+
+ mesecons_debug.settings.modify_setting(setting, value)
+
+ return true, "setting updated"
+ end
+})
+
+minetest.register_chatcommand("mesecons_debug_get", {
+ description = "get mesecons_debug settings",
+ params = "<setting>",
+ privs = { mesecons_debug = true },
+ func = function(name, setting)
+ local player = minetest.get_player_by_name(name)
+ if not player or not setting then
+ return false
+ end
+
+ local value = mesecons_debug.settings[setting]
+ if value then
+ return true, tostring(value)
+ else
+ return false, "unknown setting"
+ end
+ end
+})
diff --git a/commands/clear_penalty.lua b/commands/clear_penalty.lua
new file mode 100644
index 0000000..cf64103
--- /dev/null
+++ b/commands/clear_penalty.lua
@@ -0,0 +1,33 @@
+local penalty_clear_cooldown = mesecons_debug.settings.penalty_clear_cooldown
+
+-- playername => time-of-last-cooldown
+local cooldown_expiry_by_name = {}
+
+
+minetest.register_chatcommand("mesecons_clear_penalty", {
+ description = "clears the penalty in the current mapblock " ..
+ "(cooldown: " .. penalty_clear_cooldown .. ")",
+ func = function(name)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return
+ end
+
+ local is_admin = minetest.check_player_privs(player, "mesecons_debug")
+ if not is_admin then
+ local now = os.time()
+ local expires = cooldown_expiry_by_name[name] or 0
+ local remaining_time = math.floor(expires - now)
+ if remaining_time > 0 then
+ -- cooldown still in progress
+ return true, "cooldown still in progress, remaining time: " .. remaining_time .. " seconds"
+ end
+ cooldown_expiry_by_name[name] = now + penalty_clear_cooldown
+ end
+
+ local ctx = mesecons_debug.get_context(player:get_pos())
+ ctx.penalty = 0
+
+ return true, "penalty reset"
+ end
+})
diff --git a/commands/create_lag.lua b/commands/create_lag.lua
new file mode 100644
index 0000000..6598910
--- /dev/null
+++ b/commands/create_lag.lua
@@ -0,0 +1,28 @@
+local current_lag = 0
+local lag_chance = 0
+
+local wait = mesecons_debug.wait
+
+
+minetest.register_chatcommand("create_lag", {
+ description = "foce a wait of <duration> us for 1 / <chance> server steps",
+ params = "<duration> <chance>",
+ privs = { mesecons_debug = true },
+ func = function(_name, setting)
+ local lag, chance = setting:match('^(%S+)%s+(%S+)$')
+ lag = tonumber(lag)
+ chance = tonumber(chance)
+ if not (lag and chance) then
+ return false, "can't grok lag duration and chance"
+ end
+ current_lag = lag
+ lag_chance = chance
+ return true
+ end,
+})
+
+minetest.register_globalstep(function(_dtime)
+ if lag_chance > 0 and current_lag > 0 and math.random() < 1 / lag_chance then
+ wait(current_lag)
+ end
+end)
diff --git a/commands/flush.lua b/commands/flush.lua
new file mode 100644
index 0000000..ba3f3cd
--- /dev/null
+++ b/commands/flush.lua
@@ -0,0 +1,8 @@
+minetest.register_chatcommand("mesecons_flush", {
+ description = "flushes the mesecon actionqueue",
+ privs = { mesecons_debug = true },
+ func = function(name)
+ minetest.log("warning", "Player " .. name .. " flushes mesecon actionqueue")
+ mesecon.queue.actions = {}
+ end
+})
diff --git a/commands/user_commands.lua b/commands/user_commands.lua
new file mode 100644
index 0000000..5836373
--- /dev/null
+++ b/commands/user_commands.lua
@@ -0,0 +1,60 @@
+minetest.register_chatcommand("mesecons_hud", {
+ description = "mesecons_hud toggle",
+ func = function(name)
+ local enabled = (not mesecons_debug.hud_enabled_by_playername[name]) or nil
+ mesecons_debug.hud_enabled_by_playername[name] = enabled
+ if enabled then
+ return true, "mesecons hud enabled"
+ else
+ return true, "mesecons hud disabled"
+ end
+ end
+})
+
+minetest.register_chatcommand("mesecons_global_stats", {
+ description = "shows the global mesecons stats",
+ func = function()
+ local top_ctx, top_hash
+
+ for hash, ctx in pairs(mesecons_debug.context_store) do
+ if not top_ctx or top_ctx.avg_micros_per_second < ctx.avg_micros_per_second then
+ -- store context with the most average time
+ top_ctx = ctx
+ top_hash = hash
+ end
+ end
+
+ local txt
+ if top_ctx then
+ txt = (
+ "Most prominent mesecons usage at mapblock %s" ..
+ " with %f seconds penalty and %i us average use"
+ ):format(
+ minetest.pos_to_string(minetest.get_position_from_hash(top_hash)),
+ top_ctx.penalty,
+ top_ctx.avg_micros_per_second
+ )
+ else
+ txt = "no context available"
+ end
+
+ return true, txt
+ end
+})
+
+minetest.register_chatcommand("mesecons_stats", {
+ description = "shows some mesecons stats for the current position",
+ func = function(name)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return
+ end
+
+ local ctx = mesecons_debug.get_context(player:get_pos())
+ return true, ("Mapblock usage: %i us/s (across %i mapblocks)"):format(
+ ctx.avg_micros_per_second,
+ mesecons_debug.context_store_size
+ )
+ end
+})
+
diff --git a/compat/convert_old_whitelist.lua b/compat/convert_old_whitelist.lua
new file mode 100644
index 0000000..c51b667
--- /dev/null
+++ b/compat/convert_old_whitelist.lua
@@ -0,0 +1,12 @@
+local filename = minetest.get_worldpath() .. "/mesecons_debug_whiltelist"
+local file = io.open(filename, "r")
+
+if file then
+ local data = file:read("*a")
+ local whitelist = minetest.deserialize(data) or {}
+ for hash, _ in pairs(whitelist) do
+ mesecons_debug.storage:set_string(hash, "1")
+ end
+ file:close()
+ os.remove(filename)
+end
diff --git a/context.lua b/context.lua
index cf9b343..5f22100 100644
--- a/context.lua
+++ b/context.lua
@@ -1,30 +1,27 @@
+local storage = mesecons_debug.storage
-- returns the context data for the node-position
mesecons_debug.get_context = function(pos)
- local blockpos = mesecons_debug.get_blockpos(pos)
- local hash = minetest.hash_node_position(blockpos)
+ local hash = mesecons_debug.hashpos(pos)
+ local ctx = mesecons_debug.context_store[hash]
- local ctx = mesecons_debug.context_store[hash]
- if not ctx then
- -- create a new context
- ctx = {
- -- usage in us
- micros = 0,
- -- average micros per second
- avg_micros = 0,
- -- time penalty
- penalty = 0,
+ if not ctx then
+ -- create a new context
+ ctx = {
+ -- usage in us
+ micros = 0,
+ -- "running average" micros per second
+ avg_micros_per_second = 0,
+ -- time penalty
+ penalty = 0,
+ -- modification time
+ mtime = minetest.get_us_time(),
+ -- whitelist status
+ whitelisted = storage:contains(hash)
+ }
+ mesecons_debug.context_store[hash] = ctx
+ mesecons_debug.context_store_size = mesecons_debug.context_store_size + 1
+ end
- -- mtime
- mtime = minetest.get_us_time(),
- }
- mesecons_debug.context_store[hash] = ctx
- end
-
- -- update context
-
- -- whitelist flag
- ctx.whitelisted = mesecons_debug.whitelist[hash]
-
- return ctx
+ return ctx
end
diff --git a/flush.lua b/flush.lua
deleted file mode 100644
index 1f74100..0000000
--- a/flush.lua
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-minetest.register_chatcommand("mesecons_flush", {
- description = "flushes the mesecon actionqueue",
- privs = {mesecons_debug=true},
- func = function(name)
- minetest.log("warning", "Player " .. name .. " flushes mesecon actionqueue")
- mesecon.queue.actions = {}
- end
-})
diff --git a/functions.lua b/functions.lua
deleted file mode 100644
index d68396d..0000000
--- a/functions.lua
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-function mesecons_debug.get_blockpos(pos)
- return {
- x = math.floor(pos.x / 16),
- y = math.floor(pos.y / 16),
- z = math.floor(pos.z / 16)
- }
-end
diff --git a/hud.lua b/hud.lua
index c769d37..d8a4e25 100644
--- a/hud.lua
+++ b/hud.lua
@@ -1,84 +1,103 @@
-local HUD_POSITION = {x = 0.1, y = 0.8}
-local HUD_ALIGNMENT = {x = 1, y = 0}
+local hud_refresh_interval = mesecons_debug.settings.hud_refresh_interval
+mesecons_debug.settings._subscribe_for_modification("hud_refresh_interval",
+ function(value) hud_refresh_interval = value end)
-local hud = {}
-
-
-minetest.register_on_joinplayer(function(player)
- local hud_data = {}
- hud[player:get_player_name()] = hud_data
-
- hud_data.txt = player:hud_add({
- hud_elem_type = "text",
- position = HUD_POSITION,
- offset = {x = 0, y = 0},
- text = "",
- alignment = HUD_ALIGNMENT,
- scale = {x = 100, y = 100},
- number = 0xFF0000
- })
-
-end)
+local HUD_POSITION = { x = 0.1, y = 0.8 }
+local HUD_ALIGNMENT = { x = 1, y = 0 }
+local hudid_by_playername = {}
minetest.register_on_leaveplayer(function(player)
- hud[player:get_player_name()] = nil
+ hudid_by_playername[player:get_player_name()] = nil
end)
-
-
local function get_info(player)
- local pos = player:get_pos()
- local blockpos = mesecons_debug.get_blockpos(pos)
- local ctx = mesecons_debug.get_context(pos)
+ local pos = player:get_pos()
+ local blockpos = mesecons_debug.get_blockpos(pos)
+ local ctx = mesecons_debug.get_context(pos)
- local percent = math.floor(ctx.avg_micros / mesecons_debug.max_usage_micros * 100)
+ local total = mesecons_debug.avg_total_micros_per_second
+ if total == 0 then total = 1 end
+ local percent = ctx.avg_micros_per_second * 100 / total
- local txt = "Mesecons @ (" .. blockpos.x .. "/" .. blockpos.y .. "/" .. blockpos.z .. ") "
+ local txt = ("mesecons @ %s\n"):format(
+ minetest.pos_to_string(blockpos)
+ )
- if ctx.whitelisted then
- txt = txt .. "whitelisted, no limits"
- return txt, 0x00FF00
- end
-
- txt = txt ..
- " usage: " .. ctx.avg_micros .. " us/s .. (" .. percent .. "%) " ..
- "penalty: " .. math.floor(ctx.penalty*10)/10 .. " s"
+ if ctx.whitelisted then
+ txt = txt .. "whitelisted, no limits"
+ return txt, 0x00FFFF
+ end
- if ctx.penalty <= 0.1 then
- return txt, 0x00FF00
- elseif ctx.penalty < 0.5 then
- return txt, 0xFFFF00
- else
- return txt, 0xFF0000
- end
+ txt = txt .. ("usage: %.0f us/s .. (%.1f%%) penalty: %.2fs"):format(
+ ctx.avg_micros_per_second,
+ percent,
+ ctx.penalty
+ )
+ txt = txt .. ("\nlag: %.2f (%s); mesecons load = %s"):format(
+ mesecons_debug.avg_lag,
+ mesecons_debug.lag_level,
+ mesecons_debug.load_level
+ )
+ if minetest.get_server_max_lag then
+ txt = txt .. ("; max_lag: %.2f"):format(
+ minetest.get_server_max_lag()
+ )
+ end
+ txt = txt .. ("; #players = %i"):format(
+ #minetest.get_connected_players()
+ )
+ txt = txt .. ("\npenalties enabled = %s; mesecons enabled = %s"):format(
+ mesecons_debug.enabled,
+ mesecons_debug.mesecons_enabled
+ )
+
+ if ctx.penalty <= 1 then
+ return txt, 0x00FF00
+ elseif ctx.penalty <= 10 then
+ return txt, 0xFFFF00
+ else
+ return txt, 0xFF0000
+ end
end
local timer = 0
minetest.register_globalstep(function(dtime)
- timer = timer + dtime
- if timer < 1 then
- return
- end
- timer = 0
-
- for _, player in ipairs(minetest.get_connected_players()) do
- local playername = player:get_player_name()
- local hud_data = hud[playername]
- local hud_enable = mesecons_debug.hud[playername]
- if hud_enable then
- local txt, color = get_info(player)
- player:hud_change(hud_data.txt, "text", txt)
- player:hud_change(hud_data.txt, "color", color)
-
- elseif hud_enable == false then
- mesecons_debug.hud[playername] = nil
- player:hud_change(hud_data.txt, "text", "")
-
+ timer = timer + dtime
+ if timer < hud_refresh_interval then
+ return
+ end
+ timer = 0
+
+ for _, player in ipairs(minetest.get_connected_players()) do
+ local playername = player:get_player_name()
+ local hudid = hudid_by_playername[playername]
+ local hud_enabled = mesecons_debug.hud_enabled_by_playername[playername]
+
+ if hud_enabled then
+ local text, color = get_info(player)
+ if hudid then
+ player:hud_change(hudid, "text", text)
+ player:hud_change(hudid, "number", color)
+
+ else
+ hudid_by_playername[playername] = player:hud_add({
+ hud_elem_type = "text",
+ position = HUD_POSITION,
+ offset = { x = 0, y = 0 },
+ text = text,
+ number = color,
+ alignment = HUD_ALIGNMENT,
+ scale = { x = 100, y = 100 },
+ })
+ end
+
+ else
+ if hudid then
+ player:hud_remove(hudid)
+ hudid_by_playername[playername] = nil
+ end
+ end
end
-
- end
-
-
end)
diff --git a/init.lua b/init.lua
index 84bc862..b9b5c1f 100644
--- a/init.lua
+++ b/init.lua
@@ -1,46 +1,63 @@
-local MP = minetest.get_modpath("mesecons_debug")
+local modname = minetest.get_current_modname()
+local modpath = minetest.get_modpath(modname)
mesecons_debug = {
- enabled = true,
- -- blockpos-hash => context
- context_store = {},
- context_store_size = 0,
+ -- is mesecons_debug enabled?
+ enabled = true,
- -- max penalty in seconds
- max_penalty = 300,
+ -- is mescons enabled?
+ mesecons_enabled = true,
- -- everything above this threshold will disable the mesecons in that mapblock
- penalty_mapblock_disabled = 60,
+ -- blockpos-hash => context
+ context_store = {},
+ context_store_size = 0,
- -- time between /mesecons_clear_penalty commands, in seconds
- penalty_clear_cooldown = 120,
+ -- persistent storage for whitelist
+ storage = minetest.get_mod_storage(),
- -- mapblock-hash -> true
- whitelist = {},
+ -- total amount of time used by mesecons in the last period
+ total_micros = 0,
- -- playername => true
- hud = {},
+ -- running average of how much mesecons is doing
+ avg_total_micros_per_second = 0,
- -- cpu usage in microseconds that triggers the penalty mechanism
- max_usage_micros = tonumber(minetest.settings:get("mesecons_debug.max_usage_micros")) or 15000
+ -- average lag
+ avg_lag = 1,
+ lag_level = 'none',
+ load_level = 'none',
+
+ -- playername => true
+ hud_enabled_by_playername = {},
+
+ -- which optional dependencies are installed?
+ has = {
+ monitoring = minetest.get_modpath("monitoring"),
+ digilines = minetest.get_modpath("digilines"),
+ },
+
+ log = function(level, message_fmt, ...)
+ minetest.log(level, ("[%s] "):format(modname) .. message_fmt:format(...))
+ end
}
-dofile(MP.."/functions.lua")
-dofile(MP.."/whitelist.lua")
-dofile(MP.."/privs.lua")
-dofile(MP.."/flush.lua")
-dofile(MP.."/context.lua")
-dofile(MP.."/penalty.lua")
-dofile(MP.."/clear_penalty.lua")
-dofile(MP.."/overrides.lua")
-dofile(MP.."/luacontroller.lua")
-dofile(MP.."/chatcommands.lua")
-dofile(MP.."/hud.lua")
-
-if minetest.get_modpath("digilines") then
- dofile(MP.."/penalty_controller.lua")
+dofile(modpath .. "/settings.lua")
+dofile(modpath .. "/util.lua")
+dofile(modpath .. "/privs.lua")
+dofile(modpath .. "/context.lua")
+dofile(modpath .. "/penalty.lua")
+dofile(modpath .. "/cleanup.lua")
+dofile(modpath .. "/hud.lua")
+dofile(modpath .. "/overrides/mesecons_queue.lua")
+dofile(modpath .. "/overrides/node_timers.lua")
+dofile(modpath .. "/commands/user_commands.lua")
+dofile(modpath .. "/commands/admin_commands.lua")
+dofile(modpath .. "/commands/create_lag.lua")
+dofile(modpath .. "/commands/clear_penalty.lua")
+dofile(modpath .. "/commands/flush.lua")
+
+dofile(modpath .. "/nodes/mesecons_lagger.lua")
+if mesecons_debug.has.digilines then
+ dofile(modpath .. "/nodes/penalty_controller.lua")
end
-mesecons_debug.load_whitelist()
-
-print("[OK] mesecons_debug loaded")
+dofile(modpath .. "/compat/convert_old_whitelist.lua")
diff --git a/luacontroller.lua b/luacontroller.lua
deleted file mode 100644
index c143bdc..0000000
--- a/luacontroller.lua
+++ /dev/null
@@ -1,45 +0,0 @@
-
-local function override_node_timer(node_name)
- local def = minetest.registered_nodes[node_name]
- local old_node_timer = def.on_timer
- def.on_timer = function(pos)
- local ctx = mesecons_debug.get_context(pos)
- if ctx.penalty > 0 then
- -- defer
- local timer = minetest.get_node_timer(pos)
- local meta = minetest.get_meta(pos)
- local is_defered = meta:get_int("_defered") == 1
-
- if is_defered then
- -- already delayed
- meta:set_int("_defered", 0)
- return old_node_timer(pos)
- else
- -- start timer
- meta:set_int("_defered", 1)
- timer:start(ctx.penalty)
- end
- else
- -- immediate
- return old_node_timer(pos)
- end
- end
-end
-
--- luaC
-local BASENAME = "mesecons_luacontroller:luacontroller"
-for a = 0, 1 do -- 0 = off 1 = on
- for b = 0, 1 do
- for c = 0, 1 do
- for d = 0, 1 do
- local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a)
- local node_name = BASENAME..cid
- override_node_timer(node_name)
- end
- end
- end
-end
-
--- blinky
-override_node_timer("mesecons_blinkyplant:blinky_plant_off")
-override_node_timer("mesecons_blinkyplant:blinky_plant_on")
diff --git a/mod.conf b/mod.conf
index 4693667..b3bbbc0 100644
--- a/mod.conf
+++ b/mod.conf
@@ -1,3 +1,3 @@
name = mesecons_debug
-depends = mesecons, mesecons_blinkyplant, mesecons_luacontroller
-optional_depends = monitoring, digilines
+depends = mesecons
+optional_depends = digilines, digistuff, mesecons_blinkyplant, mesecons_luacontroller, moremesecons_adjustable_blinkyplant, moremesecons_injector_controller, monitoring, pipeworks
diff --git a/nodes/mesecons_lagger.lua b/nodes/mesecons_lagger.lua
new file mode 100644
index 0000000..e4d966e
--- /dev/null
+++ b/nodes/mesecons_lagger.lua
@@ -0,0 +1,59 @@
+
+local wait = mesecons_debug.wait
+
+mesecon.queue:add_function("create_lag", function(_pos, duration)
+ wait(duration)
+end)
+
+
+minetest.register_node("mesecons_debug:mesecons_lagger", {
+ description = "machine for adding artificial mesecons lag",
+ group = {
+ not_in_creative_inventory = 1,
+ unbreakable = 1,
+ },
+ tiles = {"default_mese_block.png^[colorize:#F00:128"},
+ on_blast = function() end,
+ drop = "",
+
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_float("lag", 0.0)
+ meta:set_float("chance", 0.0)
+ meta:set_string("formspec",
+ ("field[lag;Lag (in us);%s]field[chance;Chance;%s]"):format(0.0, 0.0))
+
+ local timer = minetest.get_node_timer(pos)
+ timer:start(0)
+ end,
+
+ on_receive_fields = function(pos, _formname, fields, sender)
+ if not minetest.check_player_privs(sender, "mesecons_debug") then
+ return
+ end
+ local meta = minetest.get_meta(pos)
+ if fields.lag then
+ meta:set_float("lag", fields.lag)
+ end
+ if fields.chance then
+ meta:set_float("chance", fields.chance)
+ end
+ meta:set_string("formspec",
+ ("field[lag;Lag (in us);%s]field[chance;Chance;%s]"):format(
+ meta:get_float("lag"), meta:get_float("chance")))
+
+ end,
+
+ on_timer = function(pos, _elapsed)
+ local meta = minetest.get_meta(pos)
+ local lag = meta:get_float("lag")
+ local chance = meta:get_float("chance")
+ if lag > 0 and chance > 0 then
+ if math.random() < 1 / chance then
+ mesecon.queue:add_action(pos, "create_lag", { lag })
+ end
+ end
+
+ return true
+ end,
+})
diff --git a/penalty_controller.lua b/nodes/penalty_controller.lua
index 33093ca..6e160c1 100644
--- a/penalty_controller.lua
+++ b/nodes/penalty_controller.lua
@@ -1,13 +1,13 @@
-
minetest.register_node("mesecons_debug:penalty_controller", {
description = "Mesecons penalty controller",
groups = {
- cracky=3
+ cracky = 3,
+ not_in_creative_inventory = 1,
},
on_construct = function(pos)
local meta = minetest.get_meta(pos)
- meta:set_string("formspec","field[channel;Channel;${channel}")
+ meta:set_string("formspec", "field[channel;Channel;${channel}")
end,
tiles = {
@@ -24,15 +24,15 @@ minetest.register_node("mesecons_debug:penalty_controller", {
selection_box = {
--From luacontroller
type = "fixed",
- fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
+ fixed = { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -5 / 16, 8 / 16 },
},
node_box = {
--From Luacontroller
type = "fixed",
fixed = {
- {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, -- Bottom slab
- {-5/16, -7/16, -5/16, 5/16, -6/16, 5/16}, -- Circuit board
- {-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC
+ { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 }, -- Bottom slab
+ { -5 / 16, -7 / 16, -5 / 16, 5 / 16, -6 / 16, 5 / 16 }, -- Circuit board
+ { -3 / 16, -6 / 16, -3 / 16, 3 / 16, -5 / 16, 3 / 16 }, -- IC
}
},
@@ -41,12 +41,14 @@ minetest.register_node("mesecons_debug:penalty_controller", {
on_receive_fields = function(pos, _, fields, sender)
local name = sender:get_player_name()
- if minetest.is_protected(pos,name) and not minetest.check_player_privs(name,{protection_bypass=true}) then
- minetest.record_protection_violation(pos,name)
+ if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, { protection_bypass = true }) then
+ minetest.record_protection_violation(pos, name)
return
end
local meta = minetest.get_meta(pos)
- if fields.channel then meta:set_string("channel",fields.channel) end
+ if fields.channel then
+ meta:set_string("channel", fields.channel)
+ end
end,
digiline = {
@@ -62,7 +64,7 @@ minetest.register_node("mesecons_debug:penalty_controller", {
-- copy and send values
digiline:receptor_send(pos, digiline.rules.default, channel, {
micros = ctx.micros,
- avg_micros = ctx.avg_micros,
+ avg_micros = ctx.avg_micros_per_second,
penalty = ctx.penalty,
whitelisted = ctx.whitelisted
})
diff --git a/overrides.lua b/overrides.lua
deleted file mode 100644
index b50b6e5..0000000
--- a/overrides.lua
+++ /dev/null
@@ -1,36 +0,0 @@
-
--- execute()
-local old_execute = mesecon.queue.execute
-mesecon.queue.execute = function(self, action)
- if mesecons_debug.enabled then
- local t0 = minetest.get_us_time()
- old_execute(self, action)
- local t1 = minetest.get_us_time()
- local micros = t1 - t0
-
- local ctx = mesecons_debug.get_context(action.pos)
- ctx.micros = ctx.micros + micros
- ctx.mtime = t0
-
- --print("execute() func=" .. action.func .. " pos=" .. minetest.pos_to_string(action.pos) .. " micros=" .. micros)
- end
-end
-
-
--- add_action()
-local old_add_action = mesecon.queue.add_action
-mesecon.queue.add_action = function(self, pos, func, params, time, overwritecheck, priority)
- if mesecons_debug.enabled then
- local ctx = mesecons_debug.get_context(pos)
-
- time = time or 0
- time = time + ctx.penalty
- if time > mesecons_debug.penalty_mapblock_disabled then
- -- penalty exceeded disable-threshold, don't even add the action
- return
- end
-
- old_add_action(self, pos, func, params, time, overwritecheck, priority)
- --print("add_action() pos=" .. minetest.pos_to_string(pos))
- end
-end
diff --git a/overrides/mesecons_queue.lua b/overrides/mesecons_queue.lua
new file mode 100644
index 0000000..4375d7d
--- /dev/null
+++ b/overrides/mesecons_queue.lua
@@ -0,0 +1,49 @@
+local penalty_mapblock_disabled = mesecons_debug.settings.penalty_mapblock_disabled
+mesecons_debug.settings._subscribe_for_modification("penalty_mapblock_disabled",
+ function(value) penalty_mapblock_disabled = value end)
+
+-- execute()
+local old_execute = mesecon.queue.execute
+mesecon.queue.execute = function(self, action)
+ if not mesecons_debug.enabled then
+ return old_execute(self, action)
+ elseif not mesecons_debug.mesecons_enabled then
+ return
+ end
+
+ local ctx = mesecons_debug.get_context(action.pos)
+ if ctx.whitelisted then
+ return old_execute(self, action)
+ end
+
+ local t0 = minetest.get_us_time()
+ local rv = old_execute(self, action)
+ local micros = minetest.get_us_time() - t0
+
+ mesecons_debug.total_micros = mesecons_debug.total_micros + micros
+ ctx.micros = ctx.micros + micros
+ ctx.mtime = t0 -- modification time
+
+ return rv
+end
+
+
+-- add_action()
+local old_add_action = mesecon.queue.add_action
+mesecon.queue.add_action = function(self, pos, func, params, time, overwritecheck, priority)
+ if not mesecons_debug.enabled then
+ return old_add_action(self, pos, func, params, time, overwritecheck, priority)
+
+ elseif not mesecons_debug.mesecons_enabled then
+ return
+ end
+
+ local ctx = mesecons_debug.get_context(pos)
+
+ if not ctx.whitelisted and ctx.penalty > penalty_mapblock_disabled then
+ -- penalty exceeded disable-threshold, don't even add the action
+ return
+ end
+
+ return old_add_action(self, pos, func, params, (time or 0) + ctx.penalty, overwritecheck, priority)
+end
diff --git a/overrides/node_timers.lua b/overrides/node_timers.lua
new file mode 100644
index 0000000..ea37d6a
--- /dev/null
+++ b/overrides/node_timers.lua
@@ -0,0 +1,69 @@
+local function override_node_timer(node_name)
+ local old_node_timer = minetest.registered_nodes[node_name].on_timer
+ minetest.override_item(node_name, {
+ on_timer = function(pos, elapsed)
+ if not mesecons_debug.enabled then
+ return old_node_timer(pos, elapsed)
+
+ elseif not mesecons_debug.mesecons_enabled then
+ return true
+ end
+
+ local ctx = mesecons_debug.get_context(pos)
+
+ if ctx.whitelisted or elapsed > ctx.penalty then
+ return old_node_timer(pos, elapsed)
+ else
+ -- defer
+ return true
+ end
+ end,
+ })
+end
+
+if minetest.get_modpath("digistuff") then
+ override_node_timer("digistuff:timer")
+end
+
+if minetest.get_modpath("mesecons_luacontroller") then
+ for a = 0, 1 do
+ for b = 0, 1 do
+ for c = 0, 1 do
+ for d = 0, 1 do
+ override_node_timer(("mesecons_luacontroller:luacontroller%i%i%i%i"):format(a, b, c, d))
+ end
+ end
+ end
+ end
+end
+
+if minetest.get_modpath("mesecons_blinkyplant") then
+ override_node_timer("mesecons_blinkyplant:blinky_plant_off")
+ override_node_timer("mesecons_blinkyplant:blinky_plant_on")
+end
+
+if minetest.get_modpath("moremesecons_adjustable_blinkyplant") then
+ override_node_timer("moremesecons_adjustable_blinkyplant:adjustable_blinky_plant_off")
+ override_node_timer("moremesecons_adjustable_blinkyplant:adjustable_blinky_plant_on")
+end
+
+if minetest.get_modpath("moremesecons_injector_controller") then
+ override_node_timer("moremesecons_injector_controller:injector_controller_on")
+ override_node_timer("moremesecons_injector_controller:injector_controller_off")
+end
+
+if minetest.get_modpath("pipeworks") then
+ for a = 0, 1 do
+ for b = 0, 1 do
+ for c = 0, 1 do
+ for d = 0, 1 do
+ for e = 0, 1 do
+ for f = 0, 1 do
+ override_node_timer(("pipeworks:lua_tube%i%i%i%i%i%i"):format(a, b, c, d, e, f))
+ end
+ end
+ end
+ end
+ end
+ end
+end
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)
diff --git a/privs.lua b/privs.lua
index eededf9..c7d1075 100644
--- a/privs.lua
+++ b/privs.lua
@@ -1,4 +1,3 @@
-
minetest.register_privilege("mesecons_debug", {
- description = "Allows execution of mesecon debug chatcommands"
+ description = "Allows execution of mesecon debug chatcommands"
})
diff --git a/settings.lua b/settings.lua
new file mode 100644
index 0000000..fa1254a
--- /dev/null
+++ b/settings.lua
@@ -0,0 +1,67 @@
+mesecons_debug.settings = {
+ -- in seconds
+ hud_refresh_interval = tonumber(minetest.settings:get("mesecons_debug.hud_refresh_interval")) or 1,
+
+ -- max penalty in seconds
+ max_penalty = tonumber(minetest.settings:get("mesecons_debug.max_penalty")) or 120,
+
+ -- everything above this threshold will disable the mesecons in that mapblock entirely
+ penalty_mapblock_disabled = tonumber(minetest.settings:get("mesecons_debug.penalty_mapblock_disabled")) or 110,
+
+ -- time between /mesecons_clear_penalty commands, in seconds
+ penalty_clear_cooldown = tonumber(minetest.settings:get("mesecons_debug.penalty_clear_cooldown")) or 120,
+
+ -- remove unmodified penalty data for a mapblock from memory after this many seconds
+ gc_interval = tonumber(minetest.settings:get("mesecons_debug.gc_interval")) or 61,
+
+ -- steps between updating penalties
+ penalty_check_steps = tonumber(minetest.settings:get("mesecons_debug.penalty_check_steps")) or 50,
+
+ -- ratio of actual to expected duration of server steps
+ moderate_lag_ratio = tonumber(minetest.settings:get("mesecons_debug.moderate_lag_ratio")) or 3,
+
+ -- ratio of actual to expected duration of server steps
+ high_lag_ratio = tonumber(minetest.settings:get("mesecons_debug.high_lag_ratio")) or 10,
+
+ -- percentage of total server load due to mesecons
+ high_load_threshold = tonumber(minetest.settings:get("mesecons_debug.high_load_threshold")) or 0.33,
+
+ -- scale of penalty during high load
+ high_penalty_scale = tonumber(minetest.settings:get("mesecons_debug.high_penalty_scale")) or 0.5,
+
+ -- offset of penalty during high load
+ high_penalty_offset = tonumber(minetest.settings:get("mesecons_debug.high_penalty_offset")) or -0.5,
+
+ -- scale of penalty during medium load
+ medium_penalty_scale = tonumber(minetest.settings:get("mesecons_debug.medium_penalty_scale")) or 0.2,
+
+ -- offset of penalty during medium load
+ medium_penalty_offset = tonumber(minetest.settings:get("mesecons_debug.medium_penalty_offset")) or -0.67,
+
+ -- scale of penalty during low load
+ low_penalty_scale = tonumber(minetest.settings:get("mesecons_debug.low_penalty_scale")) or 0.1,
+
+ -- offset of penalty during low load
+ low_penalty_offset = tonumber(minetest.settings:get("mesecons_debug.low_penalty_offset")) or -1,
+
+ -- forces (1 / clamp) <= relative load <= clamp
+ relative_load_clamp = tonumber(minetest.settings:get("mesecons_debug.relative_load_clamp")) or 10,
+
+ -- coefficient used in calculating an exponential moving average of values across penalty checks
+ averaging_coefficient = tonumber(minetest.settings:get("mesecons_debug.averaging_coefficient")) or 0.2,
+
+ _listeners = {},
+ _subscribe_for_modification = function(name, func)
+ local listeners = mesecons_debug.settings._listeners[name] or {}
+ table.insert(listeners, func)
+ mesecons_debug.settings._listeners[name] = listeners
+ end,
+
+ modify_setting = function(name, value)
+ value = tonumber(value)
+ mesecons_debug.settings[name] = value
+ for _, func in ipairs(mesecons_debug.settings._listeners[name] or {}) do
+ func(value)
+ end
+ end,
+}
diff --git a/settingtypes.txt b/settingtypes.txt
new file mode 100644
index 0000000..23cbbf5
--- /dev/null
+++ b/settingtypes.txt
@@ -0,0 +1,51 @@
+# in seconds
+mesecons_debug.hud_refresh_interval (hud refresh interval) int 1 1 60
+
+# max penalty in seconds
+mesecons_debug.max_penalty (maximum penalty) int 120 1 3600
+
+# everything above this threshold will disable the mesecons in that mapblock
+mesecons_debug.penalty_mapblock_disabled (threshold to disable mapblock) int 110 1 3600
+
+# time between /mesecons_clear_penalty commands, in seconds
+mesecons_debug.penalty_clear_cooldown (penalty clear command cooldown interval) int 120 1 3600
+
+# remove unused mapblock penalty data from memory after this many seconds
+mesecons_debug.gc_interval (garbage collection interval) float 61 1 3600
+
+# ratio between actual and expected duration of server steps, above which is considered laggy
+mesecons_debug.moderate_lag_ratio (low lag ratio) float 3 1 1000
+
+# ratio between actual and expected duration of server steps, above which is considered very laggy
+mesecons_debug.high_lag_ratio (high lag ratio) float 9 1 1000
+
+# percent of server step that is due solely to mesecons, above which is considered excessive
+mesecons_debug.high_load_threshold (high load threshold) float 0.33 0 1
+
+# number of server steps between penalty updates
+mesecons_debug.penalty_check_steps (steps between penalty updates) float 50 1 10000
+
+# scale of penalty during high load
+mesecons_debug.high_penalty_scale (high penalty scale) float 0.5 0.01 1
+
+# offset of penalty during high load
+mesecons_debug.high_penalty_offset (high penalty offset) float -0.5 -1 0
+
+# scale of penalty during medium load
+mesecons_debug.medium_penalty_scale (medium penalty scale) float 0.2 0.01 1
+
+# offset of penalty during medium load
+mesecons_debug.medium_penalty_offset (medium penalty offset) float -0.8 -1 0
+
+# scale of penalty during low load
+mesecons_debug.low_penalty_scale (low penalty scale) float 0.1 0.01 1
+
+# offset of penalty during low load
+mesecons_debug.low_penalty_offset (low penalty offset) float -1 -1 0
+
+# forces (1 / clamp) <= relative load <= clamp
+mesecons_debug.relative_load_clamp (clamp of relative load value) float 10 1 100
+
+# coefficient used in calculating an exponential moving average of values across penalty checks.
+# smaller values give more weight to history, larger values give more weight to the present.
+mesecons_debug.averaging_coefficient (averaging coefficient) float 0.2 0.01 1
diff --git a/util.lua b/util.lua
new file mode 100644
index 0000000..1e45914
--- /dev/null
+++ b/util.lua
@@ -0,0 +1,21 @@
+function mesecons_debug.get_blockpos(pos)
+ return {
+ x = math.floor(pos.x / 16),
+ y = math.floor(pos.y / 16),
+ z = math.floor(pos.z / 16)
+ }
+end
+
+function mesecons_debug.hashpos(pos)
+ return minetest.hash_node_position({
+ x = math.floor(pos.x / 16),
+ y = math.floor(pos.y / 16),
+ z = math.floor(pos.z / 16)
+ })
+end
+
+
+function mesecons_debug.wait(n)
+ local wait_until = minetest.get_us_time() + n
+ while minetest.get_us_time() < wait_until do end
+end
diff --git a/whitelist.lua b/whitelist.lua
deleted file mode 100644
index 3b713f0..0000000
--- a/whitelist.lua
+++ /dev/null
@@ -1,21 +0,0 @@
-local filename = minetest.get_worldpath() .. "/mesecons_debug_whiltelist"
-
-function mesecons_debug.save_whitelist()
- local file = io.open(filename,"w")
- local data = minetest.serialize(mesecons_debug.whitelist)
- if file and file:write(data) and file:close() then
- return
- else
- minetest.log("error","mesecons_debug: save failed")
- return
- end
-end
-
-function mesecons_debug.load_whitelist()
- local file = io.open(filename, "r")
-
- if file then
- local data = file:read("*a")
- mesecons_debug.whitelist = minetest.deserialize(data) or {}
- end
-end