aboutsummaryrefslogtreecommitdiff
path: root/circuit_breaker.lua
blob: ce1452a513084f9531079e1768b93fa7d746427d (plain) (blame)
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
})