diff options
Diffstat (limited to 'Tools/lua-snapshot')
-rw-r--r-- | Tools/lua-snapshot/.gitignore | 2 | ||||
-rw-r--r-- | Tools/lua-snapshot/LICENSE | 7 | ||||
-rw-r--r-- | Tools/lua-snapshot/Makefile | 15 | ||||
-rw-r--r-- | Tools/lua-snapshot/README.md | 19 | ||||
-rw-r--r-- | Tools/lua-snapshot/dump.lua | 51 | ||||
-rw-r--r-- | Tools/lua-snapshot/print_r.lua | 31 | ||||
-rw-r--r-- | Tools/lua-snapshot/snapshot.c | 426 | ||||
-rw-r--r-- | Tools/lua-snapshot/snapshot_utils.lua | 133 |
8 files changed, 684 insertions, 0 deletions
diff --git a/Tools/lua-snapshot/.gitignore b/Tools/lua-snapshot/.gitignore new file mode 100644 index 0000000..c181458 --- /dev/null +++ b/Tools/lua-snapshot/.gitignore @@ -0,0 +1,2 @@ +*.so +*.sw? diff --git a/Tools/lua-snapshot/LICENSE b/Tools/lua-snapshot/LICENSE new file mode 100644 index 0000000..44886d4 --- /dev/null +++ b/Tools/lua-snapshot/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2012 codingow.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Tools/lua-snapshot/Makefile b/Tools/lua-snapshot/Makefile new file mode 100644 index 0000000..4b1103e --- /dev/null +++ b/Tools/lua-snapshot/Makefile @@ -0,0 +1,15 @@ +.PHONY : all linux mingw + +all : linux + +linux : + gcc -g -Wall -fPIC --shared -o snapshot.so snapshot.c + +mingw : + gcc -g -Wall --shared -o snapshot.dll snapshot.c -I/usr/local/include -L/usr/local/bin -llua53 + +mingw51 : + gcc -g -Wall --shared -o snapshot.dll snapshot.c -I/usr/local/include -L/usr/local/bin -llua51 + +macosx : + gcc -g -Wall --shared -undefined dynamic_lookup -o snapshot.so snapshot.c diff --git a/Tools/lua-snapshot/README.md b/Tools/lua-snapshot/README.md new file mode 100644 index 0000000..52a440e --- /dev/null +++ b/Tools/lua-snapshot/README.md @@ -0,0 +1,19 @@ +lua-snapshot +============ + +Make a snapshot for lua state to detect memory leaks. + +See dump.lua for example. + +Build +===== + +make linux + +or + +make mingw (in windows) + +or + +make macosx diff --git a/Tools/lua-snapshot/dump.lua b/Tools/lua-snapshot/dump.lua new file mode 100644 index 0000000..640ea2c --- /dev/null +++ b/Tools/lua-snapshot/dump.lua @@ -0,0 +1,51 @@ +local snapshot = require "snapshot" +local snapshot_utils = require "snapshot_utils" +local construct_indentation = snapshot_utils.construct_indentation +local print_r = require "print_r" + +local S1 = snapshot() + +local tmp = { + player = { + uid = 1, + camps = { + {campid = 1}, + {campid = 2}, + }, + }, + player2 = { + roleid = 2, + }, + [3] = { + player1 = 1, + }, +} + +local a = {} +local c = {} +a.b = c +c.d = a + +local msg = "bar" +local foo = function() + print(msg) +end + +local co = coroutine.create(function () + print("hello world") +end) + +local S2 = snapshot() + +local diff = {} +for k,v in pairs(S2) do + if not S1[k] then + diff[k] = v + end +end + +print_r(diff) +print("===========================") + +local result = construct_indentation(diff) +print_r(result) diff --git a/Tools/lua-snapshot/print_r.lua b/Tools/lua-snapshot/print_r.lua new file mode 100644 index 0000000..3a4a710 --- /dev/null +++ b/Tools/lua-snapshot/print_r.lua @@ -0,0 +1,31 @@ +local print = print +local tconcat = table.concat +local tinsert = table.insert +local srep = string.rep +local type = type +local pairs = pairs +local tostring = tostring +local next = next + +local function print_r(root) + local cache = { [root] = "." } + local function _dump(t,space,name) + local temp = {} + for k,v in pairs(t) do + local key = tostring(k) + if cache[v] then + tinsert(temp,"+" .. key .. " {" .. cache[v].."}") + elseif type(v) == "table" then + local new_key = name .. "." .. key + cache[v] = new_key + tinsert(temp,"+" .. key .. _dump(v,space .. (next(t,k) and "|" or " " ).. srep(" ",#key),new_key)) + else + tinsert(temp,"+" .. key .. " [" .. tostring(v).."]") + end + end + return tconcat(temp,"\n"..space) + end + print(_dump(root, "","")) +end + +return print_r
\ No newline at end of file diff --git a/Tools/lua-snapshot/snapshot.c b/Tools/lua-snapshot/snapshot.c new file mode 100644 index 0000000..869562e --- /dev/null +++ b/Tools/lua-snapshot/snapshot.c @@ -0,0 +1,426 @@ +#include <lua.h> +#include <lauxlib.h> + +static void mark_object(lua_State *L, lua_State *dL, const void * parent, const char * desc); + +#if LUA_VERSION_NUM == 501 + +static void +luaL_checkversion(lua_State *L) { + if (lua_pushthread(L) == 0) { + luaL_error(L, "Must require in main thread"); + } + lua_setfield(L, LUA_REGISTRYINDEX, "mainthread"); +} + +static void +lua_rawsetp(lua_State *L, int idx, const void *p) { + if (idx < 0) { + idx += lua_gettop(L) + 1; + } + lua_pushlightuserdata(L, (void *)p); + lua_insert(L, -2); + lua_rawset(L, idx); +} + +static void +lua_rawgetp(lua_State *L, int idx, const void *p) { + if (idx < 0) { + idx += lua_gettop(L) + 1; + } + lua_pushlightuserdata(L, (void *)p); + lua_rawget(L, idx); +} + +static void +lua_getuservalue(lua_State *L, int idx) { + lua_getfenv(L, idx); +} + +static void +mark_function_env(lua_State *L, lua_State *dL, const void * t) { + lua_getfenv(L,-1); + if (lua_istable(L,-1)) { + mark_object(L, dL, t, "[environment]"); + } else { + lua_pop(L,1); + } +} + +// lua 5.1 has no light c function +#define is_lightcfunction(L, idx) (0) + +#else + +#define mark_function_env(L,dL,t) + +static int +is_lightcfunction(lua_State *L, int idx) { + if (lua_iscfunction(L, idx)) { + if (lua_getupvalue(L, idx, 1) == NULL) { + return 1; + } + lua_pop(L, 1); + } + return 0; +} + +#endif + +#include <stdbool.h> +#include <stdio.h> +#include <string.h> + +#define TABLE 1 +#define FUNCTION 2 +#define SOURCE 3 +#define THREAD 4 +#define USERDATA 5 +#define MARK 6 + +static bool +ismarked(lua_State *dL, const void *p) { + lua_rawgetp(dL, MARK, p); + if (lua_isnil(dL,-1)) { + lua_pop(dL,1); + lua_pushboolean(dL,1); + lua_rawsetp(dL, MARK, p); + return false; + } + lua_pop(dL,1); + return true; +} + +static const void * +readobject(lua_State *L, lua_State *dL, const void *parent, const char *desc) { + int t = lua_type(L, -1); + int tidx = 0; + switch (t) { + case LUA_TTABLE: + tidx = TABLE; + break; + case LUA_TFUNCTION: + if (is_lightcfunction(L, -1)) { + lua_pop(L, 1); + return NULL; + } + tidx = FUNCTION; + break; + case LUA_TTHREAD: + tidx = THREAD; + break; + case LUA_TUSERDATA: + tidx = USERDATA; + break; + default: + lua_pop(L, 1); + return NULL; + } + + const void * p = lua_topointer(L, -1); + if (ismarked(dL, p)) { + lua_rawgetp(dL, tidx, p); + if (!lua_isnil(dL,-1)) { + lua_pushstring(dL,desc); + lua_rawsetp(dL, -2, parent); + } + lua_pop(dL,1); + lua_pop(L,1); + return NULL; + } + + lua_newtable(dL); + lua_pushstring(dL,desc); + lua_rawsetp(dL, -2, parent); + lua_rawsetp(dL, tidx, p); + + return p; +} + +static const char * +keystring(lua_State *L, int index, char * buffer, size_t size) { + int t = lua_type(L,index); + switch (t) { + case LUA_TSTRING: + return lua_tostring(L,index); + case LUA_TNUMBER: + snprintf(buffer, size, "[%lg]",lua_tonumber(L,index)); + break; + case LUA_TBOOLEAN: + snprintf(buffer, size, "[%s]",lua_toboolean(L,index) ? "true" : "false"); + break; + case LUA_TNIL: + snprintf(buffer, size, "[nil]"); + break; + default: + snprintf(buffer, size, "[%s:%p]",lua_typename(L,t),lua_topointer(L,index)); + break; + } + return buffer; +} + +static void +mark_table(lua_State *L, lua_State *dL, const void * parent, const char * desc) { + const void * t = readobject(L, dL, parent, desc); + if (t == NULL) + return; + + bool weakk = false; + bool weakv = false; + if (lua_getmetatable(L, -1)) { + lua_pushliteral(L, "__mode"); + lua_rawget(L, -2); + if (lua_isstring(L,-1)) { + const char *mode = lua_tostring(L, -1); + if (strchr(mode, 'k')) { + weakk = true; + } + if (strchr(mode, 'v')) { + weakv = true; + } + } + lua_pop(L,1); + + luaL_checkstack(L, LUA_MINSTACK, NULL); + mark_table(L, dL, t, "[metatable]"); + } + + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (weakv) { + lua_pop(L,1); + } else { + char temp[32]; + const char * desc = keystring(L, -2, temp, sizeof(temp)); + mark_object(L, dL, t , desc); + } + if (!weakk) { + lua_pushvalue(L,-1); + mark_object(L, dL, t , "[key]"); + } + } + + lua_pop(L,1); +} + +static void +mark_userdata(lua_State *L, lua_State *dL, const void * parent, const char *desc) { + const void * t = readobject(L, dL, parent, desc); + if (t == NULL) + return; + if (lua_getmetatable(L, -1)) { + mark_table(L, dL, t, "[metatable]"); + } + + lua_getuservalue(L,-1); + if (lua_isnil(L,-1)) { + lua_pop(L,2); + } else { + mark_object(L, dL, t, "[uservalue]"); + lua_pop(L,1); + } +} + +static void +mark_function(lua_State *L, lua_State *dL, const void * parent, const char *desc) { + const void * t = readobject(L, dL, parent, desc); + if (t == NULL) + return; + + mark_function_env(L,dL,t); + int i; + for (i=1;;i++) { + const char *name = lua_getupvalue(L,-1,i); + if (name == NULL) + break; + mark_object(L, dL, t, name[0] ? name : "[upvalue]"); + } + if (lua_iscfunction(L,-1)) { + lua_pop(L,1); + } else { + lua_Debug ar; + lua_getinfo(L, ">S", &ar); + luaL_Buffer b; + luaL_buffinit(dL, &b); + luaL_addstring(&b, "func: "); + luaL_addstring(&b, ar.short_src); + char tmp[16]; + sprintf(tmp,":%d",ar.linedefined); + luaL_addstring(&b, tmp); + luaL_pushresult(&b); + lua_rawsetp(dL, SOURCE, t); + } +} + +static void +mark_thread(lua_State *L, lua_State *dL, const void * parent, const char *desc) { + const void * t = readobject(L, dL, parent, desc); + if (t == NULL) + return; + int level = 0; + lua_State *cL = lua_tothread(L,-1); + if (cL == L) { + level = 1; + } else { + // mark stack + int top = lua_gettop(cL); + luaL_checkstack(cL, 1, NULL); + int i; + char tmp[16]; + for (i=0;i<top;i++) { + lua_pushvalue(cL, i+1); + sprintf(tmp, "[%d]", i+1); + mark_object(cL, dL, cL, tmp); + } + } + lua_Debug ar; + luaL_Buffer b; + luaL_buffinit(dL, &b); + while (lua_getstack(cL, level, &ar)) { + char tmp[128]; + lua_getinfo(cL, "Sl", &ar); + luaL_addstring(&b, ar.short_src); + if (ar.currentline >=0) { + char tmp[16]; + sprintf(tmp,":%d ",ar.currentline); + luaL_addstring(&b, tmp); + } + + int i,j; + for (j=1;j>-1;j-=2) { + for (i=j;;i+=j) { + const char * name = lua_getlocal(cL, &ar, i); + if (name == NULL) + break; + snprintf(tmp, sizeof(tmp), "%s : %s:%d",name,ar.short_src,ar.currentline); + mark_object(cL, dL, t, tmp); + } + } + + ++level; + } + luaL_addstring(&b, "thread: "); + luaL_pushresult(&b); + lua_rawsetp(dL, SOURCE, t); + lua_pop(L,1); +} + +static void +mark_object(lua_State *L, lua_State *dL, const void * parent, const char *desc) { + luaL_checkstack(L, LUA_MINSTACK, NULL); + int t = lua_type(L, -1); + switch (t) { + case LUA_TTABLE: + mark_table(L, dL, parent, desc); + break; + case LUA_TUSERDATA: + mark_userdata(L, dL, parent, desc); + break; + case LUA_TFUNCTION: + mark_function(L, dL, parent, desc); + break; + case LUA_TTHREAD: + mark_thread(L, dL, parent, desc); + break; + default: + lua_pop(L,1); + break; + } +} + +static int +count_table(lua_State *L, int idx) { + int n = 0; + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + ++n; + lua_pop(L,1); + } + return n; +} + +static void +gen_table_desc(lua_State *dL, luaL_Buffer *b, const void * parent, const char *desc) { + char tmp[32]; + size_t l = snprintf(tmp, sizeof(tmp), "%p : ",parent); + luaL_addlstring(b, tmp, l); + luaL_addstring(b, desc); + luaL_addchar(b, '\n'); +} + +static void +pdesc(lua_State *L, lua_State *dL, int idx, const char * typename) { + lua_pushnil(dL); + while (lua_next(dL, idx) != 0) { + luaL_Buffer b; + luaL_buffinit(L, &b); + const void * key = lua_touserdata(dL, -2); + if (idx == FUNCTION) { + lua_rawgetp(dL, SOURCE, key); + if (lua_isnil(dL, -1)) { + luaL_addstring(&b,"cfunction\n"); + } else { + size_t l = 0; + const char * s = lua_tolstring(dL, -1, &l); + luaL_addlstring(&b,s,l); + luaL_addchar(&b,'\n'); + } + lua_pop(dL, 1); + } else if (idx == THREAD) { + lua_rawgetp(dL, SOURCE, key); + size_t l = 0; + const char * s = lua_tolstring(dL, -1, &l); + luaL_addlstring(&b,s,l); + luaL_addchar(&b,'\n'); + lua_pop(dL, 1); + } else { + luaL_addstring(&b, typename); + luaL_addchar(&b,'\n'); + } + lua_pushnil(dL); + while (lua_next(dL, -2) != 0) { + const void * parent = lua_touserdata(dL,-2); + const char * desc = luaL_checkstring(dL,-1); + gen_table_desc(dL, &b, parent, desc); + lua_pop(dL,1); + } + luaL_pushresult(&b); + lua_rawsetp(L, -2, key); + lua_pop(dL,1); + } +} + +static void +gen_result(lua_State *L, lua_State *dL) { + int count = 0; + count += count_table(dL, TABLE); + count += count_table(dL, FUNCTION); + count += count_table(dL, USERDATA); + count += count_table(dL, THREAD); + lua_createtable(L, 0, count); + pdesc(L, dL, TABLE, "table"); + pdesc(L, dL, USERDATA, "userdata"); + pdesc(L, dL, FUNCTION, "function"); + pdesc(L, dL, THREAD, "thread"); +} + +static int +snapshot(lua_State *L) { + int i; + lua_State *dL = luaL_newstate(); + for (i=0;i<MARK;i++) { + lua_newtable(dL); + } + lua_pushvalue(L, LUA_REGISTRYINDEX); + mark_table(L, dL, NULL, "[registry]"); + gen_result(L, dL); + lua_close(dL); + return 1; +} + +int +luaopen_snapshot(lua_State *L) { + luaL_checkversion(L); + lua_pushcfunction(L, snapshot); + return 1; +} diff --git a/Tools/lua-snapshot/snapshot_utils.lua b/Tools/lua-snapshot/snapshot_utils.lua new file mode 100644 index 0000000..a041ba3 --- /dev/null +++ b/Tools/lua-snapshot/snapshot_utils.lua @@ -0,0 +1,133 @@ +local function trim(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +local function cleanup_key_value(input) + local ret = {} + for k, v in pairs(input) do + local key = tostring(k) + local clean_key = key:gmatch("userdata: 0x(%w+)")() + local val_type + if v:find("^table") then + val_type = "table" + elseif v:find("^func:") then + val_type = "func" + elseif v:find("^thread:") then + val_type = "thread" + else + val_type = "userdata" + end + local parent = v:match("0x(%w+) :") + local _, finish = v:find("0x(%w+) : ") + local extra = v:sub(finish + 1, #v) + local val_key = extra:match("(%w+) :") + local trim_extra = trim(extra) + if not val_key then + val_key = trim_extra + end + ret[clean_key] = { + val_type = val_type, + parent = parent, + extra = trim_extra, + key = val_key, + } + end + return ret +end + +local function reduce(input_diff) + local a_set = {} + local b_set = {} + local step = 0 + -- 先收入叶节点 + for self_addr, info in pairs(input_diff) do + local flag = true + for _, node in pairs(input_diff) do + if node.parent == self_addr then + flag = false + break + end + end + if flag then + a_set[self_addr] = info + end + end + step = step + 1 + local MAX_DEPTH = 32 + local dirty + while step < MAX_DEPTH do + dirty = false + -- 遍历叶节点,将parent拉进来 + for self_addr, info in pairs(a_set) do + local key = info.key + local parent = info.parent + local parent_node = input_diff[parent] + if parent_node then + if not b_set[parent] then + b_set[parent] = parent_node + end + parent_node[key] = info + step = step + 1 + dirty = true + else + b_set[self_addr] = info + end + a_set[self_addr] = nil + end + -- 遍历节点,将祖父节点拉进来 + for self_addr, info in pairs(b_set) do + local key = info.key + local parent = info.parent + local parent_node = input_diff[parent] + if parent_node then + if not a_set[parent] then + a_set[parent] = parent_node + end + parent_node[key] = info + step = step + 1 + dirty = true + else + a_set[self_addr] = info + end + b_set[self_addr] = nil + end + if not dirty then + break + end + end + return a_set +end + +local unwanted_key = { + --extra = 1, + --key = 1, + parent = 1, +} +local function cleanup_forest(input) + local cache = {[input] = "."} + local function _clean(forest) + if cache[forest] then + return + end + for k, v in pairs(forest) do + if unwanted_key[k] then + forest[k] = nil + else + if type(v) == "table" then + cleanup_forest(v) + end + end + end + end + return _clean(input) +end + +local M = {} +function M.construct_indentation(input_diff) + local clean_diff = cleanup_key_value(input_diff) + local forest = reduce(clean_diff) + cleanup_forest(forest) + return forest +end + +return M |