diff options
-rw-r--r-- | server-beta/src/brave/brave.lua | 10 | ||||
-rw-r--r-- | server-beta/src/doctor.lua | 336 | ||||
-rw-r--r-- | server-beta/src/pub/init.lua | 4 | ||||
-rw-r--r-- | server-beta/src/pub/pub.lua | 11 | ||||
-rw-r--r-- | server-beta/src/service/service.lua | 38 |
5 files changed, 387 insertions, 12 deletions
diff --git a/server-beta/src/brave/brave.lua b/server-beta/src/brave/brave.lua index bf6cb559..93a93142 100644 --- a/server-beta/src/brave/brave.lua +++ b/server-beta/src/brave/brave.lua @@ -25,6 +25,7 @@ end --- 开始找工作 function m.start() + m.push('mem', collectgarbage 'count') while true do local suc, name, id, params = m.taskpad:pop() if not suc then @@ -39,12 +40,13 @@ function m.start() log.error('Brave can not handle this work: ' .. name) goto CONTINUE end - local suc, res = xpcall(ability, log.error, params) - if not suc then + local ok, res = xpcall(ability, log.error, params) + if ok then + m.waiter:push(id, res) + else m.waiter:push(id) - goto CONTINUE end - m.waiter:push(id, res) + m.push('mem', collectgarbage 'count') ::CONTINUE:: end end diff --git a/server-beta/src/doctor.lua b/server-beta/src/doctor.lua new file mode 100644 index 00000000..48c3ebbe --- /dev/null +++ b/server-beta/src/doctor.lua @@ -0,0 +1,336 @@ +local ac = ac +local type = type +local next = next +local ipairs = ipairs +local rawget = rawget +local pcall = pcall +local collectgarbage = collectgarbage +local getregistry = debug.getregistry +local getmetatable = debug.getmetatable +local getupvalue = debug.getupvalue +local getuservalue = debug.getuservalue +local getlocal = debug.getlocal +local getinfo = debug.getinfo +local maxinterger = math.maxinteger +local mathType = math.type +local tableConcat = table.concat +local _G = _G +local registry = getregistry() + +_ENV = nil + +local m = {} + +--- 内存快照 +---@return table +function m.snapshot() + local mark = {} + local find + + local function getTostring(obj) + local mt = getmetatable(obj) + if not mt then + return nil + end + local toString = rawget(mt, '__tostring') + if not toString then + return nil + end + local suc, str = pcall(toString, obj) + if not suc then + return nil + end + if type(str) ~= 'string' then + return nil + end + return str + end + + local function formatName(obj) + local tp = type(obj) + if tp == 'nil' then + return 'nil:nil' + elseif tp == 'boolean' then + if obj == true then + return 'boolean:true' + else + return 'boolean:false' + end + elseif tp == 'number' then + if mathType(obj) == 'integer' then + return ('number:%d'):format(obj) + else + -- 如果浮点数可以完全表示为整数,那么就转换为整数 + local str = ('%.10f'):format(obj):gsub('%.?[0]+$', '') + if str:find('.', 1, true) then + -- 如果浮点数不能表示为整数,那么再加上它的精确表示法 + str = ('%s(%q)'):format(str, obj) + end + return 'number:' .. str + end + elseif tp == 'string' then + local str = ('%q'):format(obj) + if #str > 100 then + local new = ('%s...(len=%d)'):format(str:sub(1, 100), #str) + if #new < #str then + str = new + end + end + return 'string:' .. str + elseif tp == 'function' then + local info = getinfo(obj, 'S') + if info.what == 'c' then + return ('function:%p(C)'):format(obj) + elseif info.what == 'main' then + return ('function:%p(main)'):format(obj) + else + return ('function:%p(%s:%d-%d)'):format(obj, info.source, info.linedefined, info.lastlinedefined) + end + elseif tp == 'table' then + local id = getTostring(obj) + if not id then + if obj == _G then + id = '_G' + elseif obj == registry then + id = 'registry' + end + end + if id then + return ('table:%p(%s)'):format(obj, id) + else + return ('table:%p'):format(obj) + end + elseif tp == 'userdata' then + local id = getTostring(obj) + if id then + return ('userdata:%p(%s)'):format(obj, id) + else + return ('userdata:%p'):format(obj) + end + else + return ('%s:%p'):format(tp, obj) + end + end + + local function findTable(t, result) + result = result or {} + local mt = getmetatable(t) + local wk, wv + if mt then + local mode = rawget(mt, '__mode') + if type(mode) == 'string' then + if mode:find('k', 1, true) then + wk = true + end + if mode:find('v', 1, true) then + wv = true + end + end + end + for k, v in next, t do + if not wk then + local keyInfo = find(k) + if keyInfo then + result[#result+1] = { + type = 'key', + name = formatName(k), + info = keyInfo, + } + end + end + if not wv then + local valueInfo = find(v) + if valueInfo then + result[#result+1] = { + type = 'field', + name = formatName(k) .. '|' .. formatName(v), + info = valueInfo, + } + end + end + end + local MTInfo = find(getmetatable(t)) + if MTInfo then + result[#result+1] = { + type = 'metatable', + name = '', + info = MTInfo, + } + end + if #result == 0 then + return nil + end + return result + end + + local function findFunction(f, result, trd, stack) + result = result or {} + for i = 1, maxinterger do + local n, v = getupvalue(f, i) + if not n then + break + end + local valueInfo = find(v) + if valueInfo then + result[#result+1] = { + type = 'upvalue', + name = n, + info = valueInfo, + } + end + end + if trd then + for i = 1, maxinterger do + local n, l = getlocal(trd, stack, i) + if not n then + break + end + local valueInfo = find(l) + if valueInfo then + result[#result+1] = { + type = 'local', + name = n, + info = valueInfo, + } + end + end + end + if #result == 0 then + return nil + end + return result + end + + local function findUserData(u, result) + result = result or {} + for i = 1, maxinterger do + local v, b = getuservalue(u, i) + if not b then + break + end + local valueInfo = find(v) + if valueInfo then + result[#result+1] = { + type = 'uservalue', + name = formatName(i), + info = valueInfo, + } + end + end + local MTInfo = find(getmetatable(u)) + if MTInfo then + result[#result+1] = { + type = 'metatable', + name = '', + info = MTInfo, + } + end + if #result == 0 then + return nil + end + return result + end + + local function findThread(trd, result) + -- 不查找主线程,主线程一定是临时的(视为弱引用) + if trd == registry[1] then + return nil + end + result = result or {} + + for i = 1, maxinterger do + local info = getinfo(trd, i, 'Sf') + if not info then + break + end + local funcInfo = find(info.func, trd, i) + if funcInfo then + result[#result+1] = { + type = 'stack', + name = i .. '@' .. formatName(info.func), + info = funcInfo, + } + end + end + + if #result == 0 then + return nil + end + return result + end + + function find(obj, trd, stack) + if mark[obj] then + return mark[obj] + end + local tp = type(obj) + if tp == 'table' then + mark[obj] = {} + mark[obj] = findTable(obj, mark[obj]) + elseif tp == 'function' then + mark[obj] = {} + mark[obj] = findFunction(obj, mark[obj], trd, stack) + elseif tp == 'userdata' then + mark[obj] = {} + mark[obj] = findUserData(obj, mark[obj]) + elseif tp == 'thread' then + mark[obj] = {} + mark[obj] = findThread(obj, mark[obj]) + else + return nil + end + if mark[obj] then + mark[obj].object = obj + end + return mark[obj] + end + + return { + name = formatName(registry), + type = 'root', + info = find(registry), + } +end + +--- 寻找对象的引用 +---@return string +function m.catch(...) + local targets = {} + for _, target in ipairs {...} do + targets[target] = true + end + local report = m.snapshot() + local path = {} + local result = {} + local mark = {} + + local function push() + result[#result+1] = tableConcat(path, ' => ') + end + + local function search(t) + path[#path+1] = ('(%s)%s'):format(t.type, t.name) + local addTarget + if targets[t.info.object] then + targets[t.info.object] = nil + addTarget = t.info.object + push(t) + end + if not mark[t.info] then + mark[t.info] = true + for _, obj in ipairs(t.info) do + search(obj) + end + end + path[#path] = nil + if addTarget then + targets[addTarget] = true + end + end + + search(report) + + return result +end + +return m diff --git a/server-beta/src/pub/init.lua b/server-beta/src/pub/init.lua index f86a42b6..bf188e8d 100644 --- a/server-beta/src/pub/init.lua +++ b/server-beta/src/pub/init.lua @@ -4,4 +4,8 @@ pub.on('log', function (params, brave) log.raw(brave.id, params.level, params.msg, params.src, params.line) end) +pub.on('mem', function (count, brave) + brave.memory = count +end) + return pub diff --git a/server-beta/src/pub/pub.lua b/server-beta/src/pub/pub.lua index 69331357..ac4c779c 100644 --- a/server-beta/src/pub/pub.lua +++ b/server-beta/src/pub/pub.lua @@ -46,6 +46,7 @@ function m.recruitBraves(num) taskList = {}, counter = utility.counter(), currentTask = nil, + memory = 0, } end end @@ -145,4 +146,14 @@ function m.checkDead() end end +function m.listen() + task.create(function () + while true do + m.checkDead() + m.recieve() + task.sleep(0) + end + end) +end + return m diff --git a/server-beta/src/service/service.lua b/server-beta/src/service/service.lua index 3712b7b8..fb7200b8 100644 --- a/server-beta/src/service/service.lua +++ b/server-beta/src/service/service.lua @@ -7,14 +7,35 @@ local proto = require 'proto' local m = {} m.type = 'service' -function m.listenPub() - task.create(function () - while true do - pub.checkDead() - pub.recieve() - task.sleep(0) - end +function m.reportMemory() + local mems = {} + local totalMem = 0 + mems[0] = collectgarbage 'count' + totalMem = totalMem + collectgarbage 'count' + for id, brave in ipairs(pub.braves) do + mems[id] = brave.memory + totalMem = totalMem + brave.memory + end + + local lines = {} + lines[#lines+1] = ' --------------- Memory ---------------' + lines[#lines+1] = (' Total: %.3f MB'):format(totalMem / 1000.0) + for i = 0, #mems do + lines[#lines+1] = (' # %02d : %.3f MB'):format(i, mems[i] / 1000.0) + end + return table.concat(lines, '\n') +end + +function m.report() + local t = timer.loop(60.0, function () + local lines = {} + lines[#lines+1] = '========= Medical Examination Report =========' + lines[#lines+1] = m.reportMemory() + lines[#lines+1] = '==============================================' + + log.debug(table.concat(lines, '\n')) end) + t:onTimer() end function m.startTimer() @@ -32,7 +53,8 @@ function m.start() pub.recruitBraves(4) task.setErrorHandle(log.error) proto.listen() - m.listenPub() + pub.listen() + m.report() m.startTimer() end |