1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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
})
|