diff options
-rw-r--r-- | actionqueue.lua | 74 | ||||
-rw-r--r-- | chatcommands.lua | 22 | ||||
-rw-r--r-- | circuit_breaker.lua | 138 | ||||
-rw-r--r-- | globalstep.lua | 62 | ||||
-rw-r--r-- | init.lua | 7 |
5 files changed, 188 insertions, 115 deletions
diff --git a/actionqueue.lua b/actionqueue.lua deleted file mode 100644 index 3f1862f..0000000 --- a/actionqueue.lua +++ /dev/null @@ -1,74 +0,0 @@ --- smarter mesecons actionqueue --- TODO: create PR if ot works properly - - --- execute the stored functions on a globalstep --- if however, the pos of a function is not loaded (get_node_or_nil == nil), do NOT execute the function --- this makes sure that resuming mesecons circuits when restarting minetest works fine --- However, even that does not work in some cases, that's why we delay the time the globalsteps --- start to be execute by 5 seconds -local get_highest_priority = function (actions) - local highestp = -1 - local highesti - for i, ac in ipairs(actions) do - if ac.priority > highestp then - highestp = ac.priority - highesti = i - end - end - - return highesti -end - -local m_time = 0 -local resumetime = mesecon.setting("resumetime", 4) -minetest.register_globalstep(function (dtime) - m_time = m_time + dtime - -- don't even try if server has not been running for XY seconds; resumetime = time to wait - -- after starting the server before processing the ActionQueue, don't set this too low - if (m_time < resumetime) then return end - - if not mesecons_debug.enabled then - return - end - - local actions = mesecon.tablecopy(mesecon.queue.actions) - local actions_now={} - - mesecon.queue.actions = {} - - -- sort actions into two categories: - -- those toexecute now (actions_now) and those to execute later (mesecon.queue.actions) - for _, ac in ipairs(actions) do - if ac.time > 0 then - ac.time = ac.time - dtime -- executed later - table.insert(mesecon.queue.actions, ac) - else - table.insert(actions_now, ac) - end - end - - if #actions_now > 30000 then - -- too much actions, purge them - return - end - - local t0 = minetest.get_us_time() - - while(#actions_now > 0) do -- execute highest priorities first, until all are executed - local hp = get_highest_priority(actions_now) - local action = actions_now[hp] - - local t1 = minetest.get_us_time() - local diff = t1 - t0 - if diff > 75000 then - -- execute remaining actions in next globalstep - table.insert(mesecon.queue.actions, 1, action) - else - mesecon.queue:execute(action) - table.remove(actions_now, hp) - end - - end -end) - diff --git a/chatcommands.lua b/chatcommands.lua deleted file mode 100644 index f382b98..0000000 --- a/chatcommands.lua +++ /dev/null @@ -1,22 +0,0 @@ - --- mesecons commands - -minetest.register_chatcommand("mesecons_enable", { - description = "enables the mesecons globlastep", - privs = {mesecons_debug=true}, - func = function() - 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 - -- flush actions, while we are on it - mesecon.queue.actions = {} - return true, "mesecons disabled" - end -}) diff --git a/circuit_breaker.lua b/circuit_breaker.lua new file mode 100644 index 0000000..ce1452a --- /dev/null +++ b/circuit_breaker.lua @@ -0,0 +1,138 @@ + +-- "circuit break" mapblocks in which mesecons took too long to execute +-- TODO: toggleable hud with current cpu usage + +-- sample/reset interval +local sample_interval = 10 + +-- util +-- minetest.hash_node_position(get_blockpos(pos)) +local function get_blockpos(pos) + return {x = math.floor(pos.x / 16), + y = math.floor(pos.y / 16), + z = math.floor(pos.z / 16)} +end + + +-- per block cpu time usage in micros +local per_block_time_usage = {} + +-- max per block cpu time usage in micros +local max_per_block_time_usage = {} + +-- disabled/dark mapblocks +local dark_mapblocks = {} + +-- switch off setting +local max_time_setting +local dark_time + +function update_settings() + max_time_setting = tonumber( minetest.settings:get("mesecons_debug.circuit_breaker") or "75000" ) + dark_time = tonumber( minetest.settings:get("mesecons_debug.dark_time") or "30000000" ) +end + +update_settings() + +-- periodic timer +local timer = 0 +minetest.register_globalstep(function(dtime) + timer = timer + dtime + if timer < sample_interval then return end + timer=0 + + -- reset time usage + per_block_time_usage = {} + + -- update settings, if changed + update_settings() + +end) + +-- mesecon mod overrides +local old_execute = mesecon.queue.execute +mesecon.queue.execute = function(self, action) + local blockpos = get_blockpos(action.pos) + local hash = minetest.hash_node_position(blockpos) + local t0 = minetest.get_us_time() + + local dark_timer = dark_mapblocks[hash] + if dark_timer and dark_timer < t0 then + -- timeout expired, disable mapblock throttling + dark_mapblocks[hash] = nil + dark_timer = nil + end + + local time_usage = per_block_time_usage[hash] or 0 + + old_execute(self, action) + local t1 = minetest.get_us_time() + local diff = t1 -t0 + time_usage = time_usage + diff + + -- update max stats + if (max_per_block_time_usage[hash] or 0) < time_usage then + max_per_block_time_usage[hash] = time_usage + end + + if time_usage > max_time_setting and not dark_timer then + -- time usage exceeded, throttle mapblock + dark_mapblocks[hash] = t1 + dark_time + minetest.log("warning", "[mesecons_debug] throttled mapblock at " .. + minetest.pos_to_string(action.pos)) + end + + -- update time usage + per_block_time_usage[hash] = time_usage +end + +local old_add_action = mesecon.queue.add_action +mesecon.queue.add_action = function(self, pos, func, params, time, overwritecheck, priority) + time = time or 0 + local blockpos = get_blockpos(pos) + local hash = minetest.hash_node_position(blockpos) + + local dark_timer = dark_mapblocks[hash] + if dark_timer then + -- throttle add actions + time = time + 1 + end + + old_add_action(self, pos, func, params, time, overwritecheck, priority) +end + + +-- chat commands + +minetest.register_chatcommand("mesecons_debug_circuit_breaker_stats", { + description = "shows the stats for the current mapblock", + func = function(name) + local player = minetest.get_player_by_name(name) + local pos = player:get_pos() + local blockpos = get_blockpos(pos) + local hash = minetest.hash_node_position(blockpos) + local time_usage = max_per_block_time_usage[hash] or 0 + + local t0 = minetest.get_us_time() + local dark_timer = dark_mapblocks[hash] + + local msg = "Max-time usage: " .. time_usage .. " micro-seconds " .. + "(sampled over " .. sample_interval .. " seconds)" + + if dark_timer and dark_timer > t0 then + msg = msg .. " [Mapblock throttled!]" + end + + return true, msg + end +}) + +minetest.register_chatcommand("mesecons_debug_circuit_breaker_stats_reset", { + description = "resets the max stats", + privs = {mesecons_debug=true}, + func = function() + max_per_block_time_usage = {} + dark_mapblocks = {} + return true, "circuit breaker stats cleared!" + end +}) diff --git a/globalstep.lua b/globalstep.lua index cd0d95d..c00e132 100644 --- a/globalstep.lua +++ b/globalstep.lua @@ -1,7 +1,10 @@ +-- enable/disable mesecons entirely + +local enabled = true + -- globalstep on/off -local i = 0 -for _, globalstep in ipairs(minetest.registered_globalsteps) do +for i, globalstep in ipairs(minetest.registered_globalsteps) do local info = minetest.callback_origins[globalstep] if not info then break @@ -10,24 +13,34 @@ for _, globalstep in ipairs(minetest.registered_globalsteps) do local modname = info.mod if modname == "mesecons" then - i = i + 1 - -- 1 = execute globalstep - -- 2 = cooldown globalstep - if i == 1 then - local fn = function(dtime) - globalstep(dtime) - end - - minetest.callback_origins[fn] = info - minetest.registered_globalsteps[i] = fn + local cooldown = 0 + local fn = function(dtime) + if cooldown > 0 then + cooldown = cooldown - 1 + return + end + + if enabled then + local t0 = minetest.get_us_time() + globalstep(dtime) + local t1 = minetest.get_us_time() + local diff = t1 - t0 + if diff > 75000 then + cooldown = 7 + minetest.log("warning", "[mesecons_debug] cooldown triggered") + end + end end + + minetest.callback_origins[fn] = info + minetest.registered_globalsteps[i] = fn end end -- execute() local old_execute = mesecon.queue.execute mesecon.queue.execute = function(...) - if mesecons_debug.enabled then + if enabled then old_execute(...) end end @@ -35,9 +48,30 @@ end -- add_action() local old_add_action = mesecon.queue.add_action mesecon.queue.add_action = function(...) - if mesecons_debug.enabled then + if enabled then old_add_action(...) end end +-- mesecons commands + +minetest.register_chatcommand("mesecons_enable", { + description = "enables the mesecons globlastep", + privs = {mesecons_debug=true}, + func = function() + enabled = true + return true, "mesecons enabled" + end +}) + +minetest.register_chatcommand("mesecons_disable", { + description = "disables the mesecons globlastep", + privs = {mesecons_debug=true}, + func = function() + enabled = false + -- flush actions, while we are on it + mesecon.queue.actions = {} + return true, "mesecons disabled" + end +}) @@ -1,16 +1,13 @@ local MP = minetest.get_modpath("mesecons_debug") -mesecons_debug = { - enabled = true -} +mesecons_debug = {} dofile(MP.."/privs.lua") -dofile(MP.."/chatcommands.lua") dofile(MP.."/api_action_on.lua") dofile(MP.."/api_nodetimer.lua") dofile(MP.."/register.lua") dofile(MP.."/flush.lua") dofile(MP.."/globalstep.lua") -dofile(MP.."/actionqueue.lua") +-- dofile(MP.."/circuit_breaker.lua") print("[OK] mesecons_debug loaded") |