准备工作:
1.在编写mm库前必须对lua c api要熟悉,否则会无从下手,这是一个痛苦的过程,还好风云大大对这个api进行的翻译,相对来说写lua c 库就轻松很多了,api翻译地址为:https://www.codingnow.com/2000/download/lua_manual.html#lua_getstack。
2.在扩展snapshot库时会用到uthash这个第三方工具,主要是使用哈希表的方式来做快照存储以及数组来存储对象引用路径的,地址为:https://troydhanson.github.io/uthash/userguide.html
mm库具有的功能:
1.快照Registry或者_G表。
2.计算Lua对象内存大小以及引用关系。
3.储存快照。
快照Registry或者_G表:lua里面的回收对象类型如下图所示:
所以我们只需提供MarkObject,MarkTable,MarkUserData,MarkFunction,MarkThread来递归统计对象即可。核心代码如下所示:
void MarkObject(lua_State *L, const char *desc, const char *parent)
{
luaL_checkstack(L, LUA_MINSTACK, NULL);
int type = lua_type(L, -1);
switch (type) {
case LUA_TTABLE:
MarkTable(L, desc, parent);
break;
case LUA_TUSERDATA:
MarkUserData(L, desc, parent);
break;
case LUA_TFUNCTION:
MarkFunction(L, desc, parent);
break;
case LUA_TTHREAD:
MarkThread(L, desc, parent);
break;
default:
lua_pop(L,1);
break;
}
}
void MarkTable(lua_State *L, const char *desc, const char *parent)
{
const void *addr = lua_topointer(L, -1);
if (addr == NULL)
{
return;
}
struct GCObject *obj = GetGCObject(addr);
if (obj != NULL)
{
AddRefPath(obj, desc, parent);
lua_pop(L, 1);
}
else
{
obj = AddGCObject(L, addr, desc, parent);
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);
char **path = (char**)utarray_front(obj->paths);
MarkTable(L, "[metatable]", *path);
}
lua_pushnil(L);
while (lua_next(L, -2) != 0)
{
if (weakv)
{
lua_pop(L, 1);
}
else
{
char temp[128];
const char *name = KeyString(L, -2, temp);
char **path = (char**)utarray_front(obj->paths);
MarkObject(L, name, *path);
}
if (!weakk)
{
lua_pushvalue(L, -1);
char **path = (char**)utarray_front(obj->paths);
MarkObject(L, "[key]", *path);
}
}
lua_pop(L, 1);
}
}
void MarkUserData(lua_State *L, const char *desc, const char *parent)
{
const void *addr = lua_topointer(L, -1);
if (addr == NULL)
{
return;
}
struct GCObject *obj = GetGCObject(addr);
if(obj != NULL)
{
AddRefPath(obj, desc, parent);
lua_pop(L, 1);
}
else
{
obj = AddGCObject(L, addr, desc, parent);
if (lua_getmetatable(L, -1))
{
char **path = (char**)utarray_front(obj->paths);
MarkTable(L, "[metatable]", *path);
}
GetUserValue(L, -1);
if (lua_isnil(L, -1))
{
lua_pop(L, 2);
}
else
{
char **path = (char**)utarray_front(obj->paths);
MarkTable(L, "[uservalue]", *path);
lua_pop(L,1);
}
}
}
void MarkFunction(lua_State *L, const char *desc, const char *parent)
{
const void *addr = lua_topointer(L, -1);
if (addr == NULL)
{
return;
}
struct GCObject *obj = GetGCObject(addr);
if(obj != NULL)
{
AddRefPath(obj, desc, parent);
lua_pop(L, 1);
}
else
{
obj = AddGCObject(L, addr, desc, parent);
char **path = (char**)utarray_front(obj->paths);
MarkFunctionEnv(L, *path);
int i;
for (i=1;;i++)
{
const char *name = lua_getupvalue(L, -1, i);
if (name == NULL)
{
break;
}
path = (char**)utarray_front(obj->paths);
MarkObject(L, name[0] ? name : "[upvalue]", *path);
}
if (lua_iscfunction(L, -1) == 0)
{
lua_Debug ar;
lua_getinfo(L, ">S", &ar);
char temp[128];
sprintf(temp, "[%s:%d]", ar.short_src, ar.linedefined);
path = (char**)utarray_front(obj->paths);
AppendRefPath(obj, path, temp);
}
else
{
lua_pop(L, 1);
}
}
}
void MarkThread(lua_State *L, const char *desc, const char *parent)
{
const void *addr = lua_topointer(L, -1);
if (addr == NULL)
{
return;
}
struct GCObject *obj = GetGCObject(addr);
if (obj != NULL)
{
AddRefPath(obj, desc, parent);
lua_pop(L, 1);
}
else
{
obj = AddGCObject(L, addr, desc, parent);
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 temp[16];
for (i = 0; i < top; i++)
{
lua_pushvalue(cL, i + 1);
sprintf(temp, "[thread %d]", i + 1);
MarkObject(cL, temp, parent);
}
}
lua_Debug ar;
while (lua_getstack(cL, level, &ar))
{
lua_getinfo(cL, "Sl", &ar);
char temp[128];
if (ar.currentline >= 0)
{
sprintf(temp, "[%s:%d]", ar.short_src, ar.currentline);
}
else
{
sprintf(temp, "[%s]", ar.short_src);
}
char **path = (char**)utarray_front(obj->paths);
AppendRefPath(obj, path, temp);
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(temp, sizeof(temp), "%s : %s:%d", name, ar.short_src, ar.currentline);
path = (char**)utarray_front(obj->paths);
MarkObject(cL, temp, *path);
}
}
++level;
}
lua_pop(L,1);
}
}
计算Lua对象内存大小以及引用关系:由于lua并没有提供对象内存的计算方式,只能对整体内存进行统计,所以这里就做引用关系的处理。内存对象计算往后想到好的办法再补上。核心代码如下:
void AddRefPath(struct GCObject *obj, const char *desc, const char *parent)
{
UT_string *temp;
utstring_new(temp);
if (parent != NULL)
{
utstring_printf(temp, "%s.%s", parent, desc);
}
else
{
utstring_printf(temp, "%s", desc);
}
char *path = utstring_body(temp);
utarray_push_back(obj->paths, &path);
utstring_free(temp);
}
储存快照:快照存储在hash表中,并通过json串形式写入到本地文件中。核心代码如下:
void GenJsonResult(lua_State *L, const char *fileName)
{
FILE *fp = fopen(fileName, "w+");
if (fp == NULL)
{
printf("无法打开文件\n");
FreeGCHash();
return;
}
int count = lua_gc(L, LUA_GCCOUNT, 0);
UT_string *jsonStr;
utstring_new(jsonStr);
utstring_printf(jsonStr, "{count:%d, items:[", count);
unsigned int hashLen = HASH_COUNT(gcHash);
unsigned int hashIndex = 0;
// 遍历统计对象列表,填充json字符串串
struct GCObject *obj;
for (obj = gcHash; obj != NULL; obj = obj->hh.next)
{
utstring_printf(jsonStr, "{addr:\"%p\", type:\"%s\", memory:%d, refPath:\"",
obj->addr,
obj->type,
obj->memory);
unsigned int arrayLen = utarray_len(obj->paths);
unsigned int arrayIndex = 0;
char **path = NULL;
while ((path = (char**)utarray_next(obj->paths, path))) {
utstring_printf(jsonStr, "%s", *path);
if (arrayIndex != (arrayLen - 1))
{
utstring_printf(jsonStr, ",");
}
arrayIndex++;
}
utstring_printf(jsonStr, "\"}");
if (hashIndex != (hashLen - 1))
{
utstring_printf(jsonStr, ",\n");
}
hashIndex++;
}
utstring_printf(jsonStr, "]}");
fputs(utstring_body(jsonStr), fp);
fclose(fp);
utstring_free(jsonStr);
FreeGCHash();
}
mm库的测试结果:经验证mm运行正确。
测试用例如下:
测试输出如下: