MemoryReferenceInfo.lua.0 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  1. --
  2. -- Collect memory reference info.
  3. -- https://github.com/yaukeywang/LuaMemorySnapshotDump
  4. --
  5. -- @filename MemoryReferenceInfo.lua
  6. -- @author WangYaoqi
  7. -- @date 2016-02-03
  8. -- The global config of the mri.
  9. local cConfig =
  10. {
  11. m_bAllMemoryRefFileAddTime = true,
  12. m_bSingleMemoryRefFileAddTime = true,
  13. m_bComparedMemoryRefFileAddTime = true
  14. }
  15. -- Get the format string of date time.
  16. local function FormatDateTimeNow()
  17. local cDateTime = os.date("*t")
  18. local strDateTime = string.format("%04d%02d%02d-%02d%02d%02d", tostring(cDateTime.year), tostring(cDateTime.month), tostring(cDateTime.day),
  19. tostring(cDateTime.hour), tostring(cDateTime.min), tostring(cDateTime.sec))
  20. return strDateTime
  21. end
  22. -- Get the string result without overrided __tostring.
  23. local function GetOriginalToStringResult(cObject)
  24. if not cObject then
  25. return ""
  26. end
  27. local cMt = getmetatable(cObject)
  28. if not cMt then
  29. return tostring(cObject)
  30. end
  31. -- Check tostring override.
  32. local strName = ""
  33. local cToString = rawget(cMt, "__tostring")
  34. if cToString then
  35. rawset(cMt, "__tostring", nil)
  36. strName = tostring(cObject)
  37. rawset(cMt, "__tostring", cToString)
  38. else
  39. strName = tostring(cObject)
  40. end
  41. return strName
  42. end
  43. -- Create a container to collect the mem ref info results.
  44. local function CreateObjectReferenceInfoContainer()
  45. -- Create new container.
  46. local cContainer = {}
  47. -- Contain [table/function] - [reference count] info.
  48. local cObjectReferenceCount = {}
  49. setmetatable(cObjectReferenceCount, {__mode = "k"})
  50. -- Contain [table/function] - [name] info.
  51. local cObjectAddressToName = {}
  52. setmetatable(cObjectAddressToName, {__mode = "k"})
  53. -- Set members.
  54. cContainer.m_cObjectReferenceCount = cObjectReferenceCount
  55. cContainer.m_cObjectAddressToName = cObjectAddressToName
  56. -- For stack info.
  57. cContainer.m_nStackLevel = -1
  58. cContainer.m_strShortSrc = "None"
  59. cContainer.m_nCurrentLine = -1
  60. return cContainer
  61. end
  62. -- Create a container to collect the mem ref info results from a dumped file.
  63. -- strFilePath - The file path.
  64. local function CreateObjectReferenceInfoContainerFromFile(strFilePath)
  65. -- Create a empty container.
  66. local cContainer = CreateObjectReferenceInfoContainer()
  67. cContainer.m_strShortSrc = strFilePath
  68. -- Cache ref info.
  69. local cRefInfo = cContainer.m_cObjectReferenceCount
  70. local cNameInfo = cContainer.m_cObjectAddressToName
  71. -- Read each line from file.
  72. local cFile = assert(io.open(strFilePath, "rb"))
  73. for strLine in cFile:lines() do
  74. local strHeader = string.sub(strLine, 1, 2)
  75. if "--" ~= strHeader then
  76. local _, _, strAddr, strName, strRefCount= string.find(strLine, "(.+)\t(.*)\t(%d+)")
  77. if strAddr then
  78. cRefInfo[strAddr] = strRefCount
  79. cNameInfo[strAddr] = strName
  80. end
  81. end
  82. end
  83. -- Close and clear file handler.
  84. io.close(cFile)
  85. cFile = nil
  86. return cContainer
  87. end
  88. -- Create a container to collect the mem ref info results from a dumped file.
  89. -- strObjectName - The object name you need to collect info.
  90. -- cObject - The object you need to collect info.
  91. local function CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)
  92. -- Create new container.
  93. local cContainer = {}
  94. -- Contain [address] - [true] info.
  95. local cObjectExistTag = {}
  96. setmetatable(cObjectExistTag, {__mode = "k"})
  97. -- Contain [name] - [true] info.
  98. local cObjectAliasName = {}
  99. -- Contain [access] - [true] info.
  100. local cObjectAccessTag = {}
  101. setmetatable(cObjectAccessTag, {__mode = "k"})
  102. -- Set members.
  103. cContainer.m_cObjectExistTag = cObjectExistTag
  104. cContainer.m_cObjectAliasName = cObjectAliasName
  105. cContainer.m_cObjectAccessTag = cObjectAccessTag
  106. -- For stack info.
  107. cContainer.m_nStackLevel = -1
  108. cContainer.m_strShortSrc = "None"
  109. cContainer.m_nCurrentLine = -1
  110. -- Init with object values.
  111. cContainer.m_strObjectName = strObjectName
  112. cContainer.m_strAddressName = (("string" == type(cObject)) and ("\"" .. tostring(cObject) .. "\"")) or GetOriginalToStringResult(cObject)
  113. cContainer.m_cObjectExistTag[cObject] = true
  114. return cContainer
  115. end
  116. -- Collect memory reference info from a root table or function.
  117. -- strName - The root object name that start to search, default is "_G" if leave this to nil.
  118. -- cObject - The root object that start to search, default is _G if leave this to nil.
  119. -- cDumpInfoContainer - The container of the dump result info.
  120. local function CollectObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)
  121. if not cObject then
  122. return
  123. end
  124. if not strName then
  125. strName = ""
  126. end
  127. -- Check container.
  128. if (not cDumpInfoContainer) then
  129. cDumpInfoContainer = CreateObjectReferenceInfoContainer()
  130. end
  131. -- Check stack.
  132. if cDumpInfoContainer.m_nStackLevel > 0 then
  133. local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
  134. if cStackInfo then
  135. cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
  136. cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
  137. end
  138. cDumpInfoContainer.m_nStackLevel = -1
  139. end
  140. -- Get ref and name info.
  141. local cRefInfoContainer = cDumpInfoContainer.m_cObjectReferenceCount
  142. local cNameInfoContainer = cDumpInfoContainer.m_cObjectAddressToName
  143. local strType = type(cObject)
  144. if "table" == strType then
  145. -- Check table with class name.
  146. if rawget(cObject, "__cname") then
  147. if "string" == type(cObject.__cname) then
  148. strName = strName .. "[class:" .. cObject.__cname .. "]"
  149. end
  150. elseif rawget(cObject, "class") then
  151. if "string" == type(cObject.class) then
  152. strName = strName .. "[class:" .. cObject.class .. "]"
  153. end
  154. elseif rawget(cObject, "_className") then
  155. if "string" == type(cObject._className) then
  156. strName = strName .. "[class:" .. cObject._className .. "]"
  157. end
  158. end
  159. -- Check if table is _G.
  160. if cObject == _G then
  161. strName = strName .. "[_G]"
  162. end
  163. -- Get metatable.
  164. local bWeakK = false
  165. local bWeakV = false
  166. local cMt = getmetatable(cObject)
  167. if cMt then
  168. -- Check mode.
  169. local strMode = rawget(cMt, "__mode")
  170. if strMode then
  171. if "k" == strMode then
  172. bWeakK = true
  173. elseif "v" == strMode then
  174. bWeakV = true
  175. elseif "kv" == strMode then
  176. bWeakK = true
  177. bWeakV = true
  178. end
  179. end
  180. end
  181. -- Add reference and name.
  182. cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
  183. if cNameInfoContainer[cObject] then
  184. return
  185. end
  186. -- Set name.
  187. cNameInfoContainer[cObject] = strName
  188. -- Dump table key and value.
  189. for k, v in pairs(cObject) do
  190. -- Check key type.
  191. local strKeyType = type(k)
  192. if "table" == strKeyType then
  193. if not bWeakK then
  194. CollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
  195. end
  196. if not bWeakV then
  197. CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  198. end
  199. elseif "function" == strKeyType then
  200. if not bWeakK then
  201. CollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
  202. end
  203. if not bWeakV then
  204. CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  205. end
  206. elseif "thread" == strKeyType then
  207. if not bWeakK then
  208. CollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
  209. end
  210. if not bWeakV then
  211. CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  212. end
  213. elseif "userdata" == strKeyType then
  214. if not bWeakK then
  215. CollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
  216. end
  217. if not bWeakV then
  218. CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  219. end
  220. else
  221. CollectObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
  222. end
  223. end
  224. -- Dump metatable.
  225. if cMt then
  226. CollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
  227. end
  228. elseif "function" == strType then
  229. -- Get function info.
  230. local cDInfo = debug.getinfo(cObject, "Su")
  231. -- Write this info.
  232. cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
  233. if cNameInfoContainer[cObject] then
  234. return
  235. end
  236. -- Set name.
  237. cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
  238. -- Get upvalues.
  239. local nUpsNum = cDInfo.nups
  240. for i = 1, nUpsNum do
  241. local strUpName, cUpValue = debug.getupvalue(cObject, i)
  242. local strUpValueType = type(cUpValue)
  243. --print(strUpName, cUpValue)
  244. if "table" == strUpValueType then
  245. CollectObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  246. elseif "function" == strUpValueType then
  247. CollectObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  248. elseif "thread" == strUpValueType then
  249. CollectObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  250. elseif "userdata" == strUpValueType then
  251. CollectObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  252. end
  253. end
  254. -- Dump environment table.
  255. local getfenv = debug.getfenv
  256. if getfenv then
  257. local cEnv = getfenv(cObject)
  258. if cEnv then
  259. CollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
  260. end
  261. end
  262. elseif "thread" == strType then
  263. -- Add reference and name.
  264. cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
  265. if cNameInfoContainer[cObject] then
  266. return
  267. end
  268. -- Set name.
  269. cNameInfoContainer[cObject] = strName
  270. -- Dump environment table.
  271. local getfenv = debug.getfenv
  272. if getfenv then
  273. local cEnv = getfenv(cObject)
  274. if cEnv then
  275. CollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
  276. end
  277. end
  278. -- Dump metatable.
  279. local cMt = getmetatable(cObject)
  280. if cMt then
  281. CollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
  282. end
  283. elseif "userdata" == strType then
  284. -- Add reference and name.
  285. cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
  286. if cNameInfoContainer[cObject] then
  287. return
  288. end
  289. -- Set name.
  290. cNameInfoContainer[cObject] = strName
  291. -- Dump environment table.
  292. local getfenv = debug.getfenv
  293. if getfenv then
  294. local cEnv = getfenv(cObject)
  295. if cEnv then
  296. CollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
  297. end
  298. end
  299. -- Dump metatable.
  300. local cMt = getmetatable(cObject)
  301. if cMt then
  302. CollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
  303. end
  304. elseif "string" == strType then
  305. -- Add reference and name.
  306. cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
  307. if cNameInfoContainer[cObject] then
  308. return
  309. end
  310. -- Set name.
  311. cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]"
  312. else
  313. -- For "number" and "boolean". (If you want to dump them, uncomment the followed lines.)
  314. -- -- Add reference and name.
  315. -- cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
  316. -- if cNameInfoContainer[cObject] then
  317. -- return
  318. -- end
  319. -- -- Set name.
  320. -- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]"
  321. end
  322. end
  323. -- Collect memory reference info of a single object from a root table or function.
  324. -- strName - The root object name that start to search, can not be nil.
  325. -- cObject - The root object that start to search, can not be nil.
  326. -- cDumpInfoContainer - The container of the dump result info.
  327. local function CollectSingleObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)
  328. if not cObject then
  329. return
  330. end
  331. if not strName then
  332. strName = ""
  333. end
  334. -- Check container.
  335. if (not cDumpInfoContainer) then
  336. cDumpInfoContainer = CreateObjectReferenceInfoContainer()
  337. end
  338. -- Check stack.
  339. if cDumpInfoContainer.m_nStackLevel > 0 then
  340. local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
  341. if cStackInfo then
  342. cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
  343. cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
  344. end
  345. cDumpInfoContainer.m_nStackLevel = -1
  346. end
  347. local cExistTag = cDumpInfoContainer.m_cObjectExistTag
  348. local cNameAllAlias = cDumpInfoContainer.m_cObjectAliasName
  349. local cAccessTag = cDumpInfoContainer.m_cObjectAccessTag
  350. local strType = type(cObject)
  351. if "table" == strType then
  352. -- Check table with class name.
  353. if rawget(cObject, "__cname") then
  354. if "string" == type(cObject.__cname) then
  355. strName = strName .. "[class:" .. cObject.__cname .. "]"
  356. end
  357. elseif rawget(cObject, "class") then
  358. if "string" == type(cObject.class) then
  359. strName = strName .. "[class:" .. cObject.class .. "]"
  360. end
  361. elseif rawget(cObject, "_className") then
  362. if "string" == type(cObject._className) then
  363. strName = strName .. "[class:" .. cObject._className .. "]"
  364. end
  365. end
  366. -- Check if table is _G.
  367. if cObject == _G then
  368. strName = strName .. "[_G]"
  369. end
  370. -- Get metatable.
  371. local bWeakK = false
  372. local bWeakV = false
  373. local cMt = getmetatable(cObject)
  374. if cMt then
  375. -- Check mode.
  376. local strMode = rawget(cMt, "__mode")
  377. if strMode then
  378. if "k" == strMode then
  379. bWeakK = true
  380. elseif "v" == strMode then
  381. bWeakV = true
  382. elseif "kv" == strMode then
  383. bWeakK = true
  384. bWeakV = true
  385. end
  386. end
  387. end
  388. -- Check if the specified object.
  389. if cExistTag[cObject] and (not cNameAllAlias[strName]) then
  390. cNameAllAlias[strName] = true
  391. end
  392. -- Add reference and name.
  393. if cAccessTag[cObject] then
  394. return
  395. end
  396. -- Get this name.
  397. cAccessTag[cObject] = true
  398. -- Dump table key and value.
  399. for k, v in pairs(cObject) do
  400. -- Check key type.
  401. local strKeyType = type(k)
  402. if "table" == strKeyType then
  403. if not bWeakK then
  404. CollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
  405. end
  406. if not bWeakV then
  407. CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  408. end
  409. elseif "function" == strKeyType then
  410. if not bWeakK then
  411. CollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
  412. end
  413. if not bWeakV then
  414. CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  415. end
  416. elseif "thread" == strKeyType then
  417. if not bWeakK then
  418. CollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
  419. end
  420. if not bWeakV then
  421. CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  422. end
  423. elseif "userdata" == strKeyType then
  424. if not bWeakK then
  425. CollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
  426. end
  427. if not bWeakV then
  428. CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
  429. end
  430. else
  431. CollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
  432. end
  433. end
  434. -- Dump metatable.
  435. if cMt then
  436. CollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
  437. end
  438. elseif "function" == strType then
  439. -- Get function info.
  440. local cDInfo = debug.getinfo(cObject, "Su")
  441. local cCombinedName = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
  442. -- Check if the specified object.
  443. if cExistTag[cObject] and (not cNameAllAlias[cCombinedName]) then
  444. cNameAllAlias[cCombinedName] = true
  445. end
  446. -- Write this info.
  447. if cAccessTag[cObject] then
  448. return
  449. end
  450. -- Set name.
  451. cAccessTag[cObject] = true
  452. -- Get upvalues.
  453. local nUpsNum = cDInfo.nups
  454. for i = 1, nUpsNum do
  455. local strUpName, cUpValue = debug.getupvalue(cObject, i)
  456. local strUpValueType = type(cUpValue)
  457. --print(strUpName, cUpValue)
  458. if "table" == strUpValueType then
  459. CollectSingleObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  460. elseif "function" == strUpValueType then
  461. CollectSingleObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  462. elseif "thread" == strUpValueType then
  463. CollectSingleObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  464. elseif "userdata" == strUpValueType then
  465. CollectSingleObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
  466. end
  467. end
  468. -- Dump environment table.
  469. local getfenv = debug.getfenv
  470. if getfenv then
  471. local cEnv = getfenv(cObject)
  472. if cEnv then
  473. CollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
  474. end
  475. end
  476. elseif "thread" == strType then
  477. -- Check if the specified object.
  478. if cExistTag[cObject] and (not cNameAllAlias[strName]) then
  479. cNameAllAlias[strName] = true
  480. end
  481. -- Add reference and name.
  482. if cAccessTag[cObject] then
  483. return
  484. end
  485. -- Get this name.
  486. cAccessTag[cObject] = true
  487. -- Dump environment table.
  488. local getfenv = debug.getfenv
  489. if getfenv then
  490. local cEnv = getfenv(cObject)
  491. if cEnv then
  492. CollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
  493. end
  494. end
  495. -- Dump metatable.
  496. local cMt = getmetatable(cObject)
  497. if cMt then
  498. CollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
  499. end
  500. elseif "userdata" == strType then
  501. -- Check if the specified object.
  502. if cExistTag[cObject] and (not cNameAllAlias[strName]) then
  503. cNameAllAlias[strName] = true
  504. end
  505. -- Add reference and name.
  506. if cAccessTag[cObject] then
  507. return
  508. end
  509. -- Get this name.
  510. cAccessTag[cObject] = true
  511. -- Dump environment table.
  512. local getfenv = debug.getfenv
  513. if getfenv then
  514. local cEnv = getfenv(cObject)
  515. if cEnv then
  516. CollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
  517. end
  518. end
  519. -- Dump metatable.
  520. local cMt = getmetatable(cObject)
  521. if cMt then
  522. CollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
  523. end
  524. elseif "string" == strType then
  525. -- Check if the specified object.
  526. if cExistTag[cObject] and (not cNameAllAlias[strName]) then
  527. cNameAllAlias[strName] = true
  528. end
  529. -- Add reference and name.
  530. if cAccessTag[cObject] then
  531. return
  532. end
  533. -- Get this name.
  534. cAccessTag[cObject] = true
  535. else
  536. -- For "number" and "boolean" type, they are not object type, skip.
  537. end
  538. end
  539. -- The base method to dump a mem ref info result into a file.
  540. -- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.
  541. -- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".
  542. -- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.
  543. -- strRootObjectName - The header info to show the root object name, can be nil.
  544. -- cRootObject - The header info to show the root object address, can be nil.
  545. -- cDumpInfoResultsBase - The base dumped mem info result, nil means no compare and only output cDumpInfoResults, otherwise to compare with cDumpInfoResults.
  546. -- cDumpInfoResults - The compared dumped mem info result, dump itself only if cDumpInfoResultsBase is nil, otherwise dump compared results with cDumpInfoResultsBase.
  547. local function OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, cDumpInfoResultsBase, cDumpInfoResults)
  548. -- Check results.
  549. if not cDumpInfoResults then
  550. return
  551. end
  552. -- Get time format string.
  553. local strDateTime = FormatDateTimeNow()
  554. -- Collect memory info.
  555. local cRefInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectReferenceCount) or nil
  556. local cNameInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectAddressToName) or nil
  557. local cRefInfo = cDumpInfoResults.m_cObjectReferenceCount
  558. local cNameInfo = cDumpInfoResults.m_cObjectAddressToName
  559. -- Create a cache result to sort by ref count.
  560. local cRes = {}
  561. local nIdx = 0
  562. for k in pairs(cRefInfo) do
  563. nIdx = nIdx + 1
  564. cRes[nIdx] = k
  565. end
  566. -- Sort result.
  567. table.sort(cRes, function (l, r)
  568. return cRefInfo[l] > cRefInfo[r]
  569. end)
  570. -- Save result to file.
  571. local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
  572. local cOutputHandle = nil
  573. local cOutputEntry = print
  574. if bOutputFile then
  575. -- Check save path affix.
  576. local strAffix = string.sub(strSavePath, -1)
  577. if ("/" ~= strAffix) and ("\\" ~= strAffix) then
  578. strSavePath = strSavePath .. "/"
  579. end
  580. -- Combine file name.
  581. local strFileName = strSavePath .. "LuaMemRefInfo-All"
  582. if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then
  583. if cDumpInfoResultsBase then
  584. if cConfig.m_bComparedMemoryRefFileAddTime then
  585. strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
  586. else
  587. strFileName = strFileName .. ".txt"
  588. end
  589. else
  590. if cConfig.m_bAllMemoryRefFileAddTime then
  591. strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
  592. else
  593. strFileName = strFileName .. ".txt"
  594. end
  595. end
  596. else
  597. if cDumpInfoResultsBase then
  598. if cConfig.m_bComparedMemoryRefFileAddTime then
  599. strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
  600. else
  601. strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
  602. end
  603. else
  604. if cConfig.m_bAllMemoryRefFileAddTime then
  605. strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
  606. else
  607. strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
  608. end
  609. end
  610. end
  611. local cFile = assert(io.open(strFileName, "w"))
  612. cOutputHandle = cFile
  613. cOutputEntry = cFile.write
  614. end
  615. local cOutputer = function (strContent)
  616. if cOutputHandle then
  617. cOutputEntry(cOutputHandle, strContent)
  618. else
  619. cOutputEntry(strContent)
  620. end
  621. end
  622. -- Write table header.
  623. if cDumpInfoResultsBase then
  624. cOutputer("--------------------------------------------------------\n")
  625. cOutputer("-- This is compared memory information.\n")
  626. cOutputer("--------------------------------------------------------\n")
  627. cOutputer("-- Collect base memory reference at line:" .. tostring(cDumpInfoResultsBase.m_nCurrentLine) .. "@file:" .. cDumpInfoResultsBase.m_strShortSrc .. "\n")
  628. cOutputer("-- Collect compared memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
  629. else
  630. cOutputer("--------------------------------------------------------\n")
  631. cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
  632. end
  633. cOutputer("--------------------------------------------------------\n")
  634. cOutputer("-- [Table/Function/String Address/Name]\t[Reference Path]\t[Reference Count]\n")
  635. cOutputer("--------------------------------------------------------\n")
  636. if strRootObjectName and cRootObject then
  637. if "string" == type(cRootObject) then
  638. cOutputer("-- From Root Object: \"" .. tostring(cRootObject) .. "\" (" .. strRootObjectName .. ")\n")
  639. else
  640. cOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n")
  641. end
  642. end
  643. -- Save each info.
  644. for i, v in ipairs(cRes) do
  645. if (not cDumpInfoResultsBase) or (not cRefInfoBase[v]) then
  646. if (nMaxRescords > 0) then
  647. if (i <= nMaxRescords) then
  648. if "string" == type(v) then
  649. local strOrgString = tostring(v)
  650. local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")
  651. if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then
  652. local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")
  653. cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
  654. else
  655. cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
  656. end
  657. else
  658. cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
  659. end
  660. end
  661. else
  662. if "string" == type(v) then
  663. local strOrgString = tostring(v)
  664. local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")
  665. if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then
  666. local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")
  667. cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
  668. else
  669. cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
  670. end
  671. else
  672. cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
  673. end
  674. end
  675. end
  676. end
  677. if bOutputFile then
  678. io.close(cOutputHandle)
  679. cOutputHandle = nil
  680. end
  681. end
  682. -- The base method to dump a mem ref info result of a single object into a file.
  683. -- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.
  684. -- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".
  685. -- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.
  686. -- cDumpInfoResults - The dumped results.
  687. local function OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoResults)
  688. -- Check results.
  689. if not cDumpInfoResults then
  690. return
  691. end
  692. -- Get time format string.
  693. local strDateTime = FormatDateTimeNow()
  694. -- Collect memory info.
  695. local cObjectAliasName = cDumpInfoResults.m_cObjectAliasName
  696. -- Save result to file.
  697. local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
  698. local cOutputHandle = nil
  699. local cOutputEntry = print
  700. if bOutputFile then
  701. -- Check save path affix.
  702. local strAffix = string.sub(strSavePath, -1)
  703. if ("/" ~= strAffix) and ("\\" ~= strAffix) then
  704. strSavePath = strSavePath .. "/"
  705. end
  706. -- Combine file name.
  707. local strFileName = strSavePath .. "LuaMemRefInfo-Single"
  708. if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then
  709. if cConfig.m_bSingleMemoryRefFileAddTime then
  710. strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
  711. else
  712. strFileName = strFileName .. ".txt"
  713. end
  714. else
  715. if cConfig.m_bSingleMemoryRefFileAddTime then
  716. strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
  717. else
  718. strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
  719. end
  720. end
  721. local cFile = assert(io.open(strFileName, "w"))
  722. cOutputHandle = cFile
  723. cOutputEntry = cFile.write
  724. end
  725. local cOutputer = function (strContent)
  726. if cOutputHandle then
  727. cOutputEntry(cOutputHandle, strContent)
  728. else
  729. cOutputEntry(strContent)
  730. end
  731. end
  732. -- Write table header.
  733. cOutputer("--------------------------------------------------------\n")
  734. cOutputer("-- Collect single object memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
  735. cOutputer("--------------------------------------------------------\n")
  736. -- Calculate reference count.
  737. local nCount = 0
  738. for k in pairs(cObjectAliasName) do
  739. nCount = nCount + 1
  740. end
  741. -- Output reference count.
  742. cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n")
  743. cOutputer("--------------------------------------------------------\n")
  744. -- Save each info.
  745. for k in pairs(cObjectAliasName) do
  746. if (nMaxRescords > 0) then
  747. if (i <= nMaxRescords) then
  748. cOutputer(k .. "\n")
  749. end
  750. else
  751. cOutputer(k .. "\n")
  752. end
  753. end
  754. if bOutputFile then
  755. io.close(cOutputHandle)
  756. cOutputHandle = nil
  757. end
  758. end
  759. -- Fileter an existing result file and output it.
  760. -- strFilePath - The existing result file.
  761. -- strFilter - The filter string.
  762. -- bIncludeFilter - Include(true) or exclude(false) the filter.
  763. -- bOutputFile - Output to file(true) or console(false).
  764. local function OutputFilteredResult(strFilePath, strFilter, bIncludeFilter, bOutputFile)
  765. if (not strFilePath) or (0 == string.len(strFilePath)) then
  766. print("You need to specify a file path.")
  767. return
  768. end
  769. if (not strFilter) or (0 == string.len(strFilter)) then
  770. print("You need to specify a filter string.")
  771. return
  772. end
  773. -- Read file.
  774. local cFilteredResult = {}
  775. local cReadFile = assert(io.open(strFilePath, "rb"))
  776. for strLine in cReadFile:lines() do
  777. local nBegin, nEnd = string.find(strLine, strFilter)
  778. if nBegin and nEnd then
  779. if bIncludeFilter then
  780. nBegin, nEnd = string.find(strLine, "[\r\n]")
  781. if nBegin and nEnd and (string.len(strLine) == nEnd) then
  782. table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))
  783. else
  784. table.insert(cFilteredResult, strLine)
  785. end
  786. end
  787. else
  788. if not bIncludeFilter then
  789. nBegin, nEnd = string.find(strLine, "[\r\n]")
  790. if nBegin and nEnd and (string.len(strLine) == nEnd) then
  791. table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))
  792. else
  793. table.insert(cFilteredResult, strLine)
  794. end
  795. end
  796. end
  797. end
  798. -- Close and clear read file handle.
  799. io.close(cReadFile)
  800. cReadFile = nil
  801. -- Write filtered result.
  802. local cOutputHandle = nil
  803. local cOutputEntry = print
  804. if bOutputFile then
  805. -- Combine file name.
  806. local _, _, strResFileName = string.find(strFilePath, "(.*)%.txt")
  807. strResFileName = strResFileName .. "-Filter-" .. ((bIncludeFilter and "I") or "E") .. "-[" .. strFilter .. "].txt"
  808. local cFile = assert(io.open(strResFileName, "w"))
  809. cOutputHandle = cFile
  810. cOutputEntry = cFile.write
  811. end
  812. local cOutputer = function (strContent)
  813. if cOutputHandle then
  814. cOutputEntry(cOutputHandle, strContent)
  815. else
  816. cOutputEntry(strContent)
  817. end
  818. end
  819. -- Output result.
  820. for i, v in ipairs(cFilteredResult) do
  821. cOutputer(v .. "\n")
  822. end
  823. if bOutputFile then
  824. io.close(cOutputHandle)
  825. cOutputHandle = nil
  826. end
  827. end
  828. -- Dump memory reference at current time.
  829. -- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.
  830. -- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".
  831. -- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.
  832. -- strRootObjectName - The root object name that start to search, default is "_G" if leave this to nil.
  833. -- cRootObject - The root object that start to search, default is _G if leave this to nil.
  834. local function DumpMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject)
  835. -- Get time format string.
  836. local strDateTime = FormatDateTimeNow()
  837. -- Check root object.
  838. if cRootObject then
  839. if (not strRootObjectName) or (0 == string.len(strRootObjectName)) then
  840. strRootObjectName = tostring(cRootObject)
  841. end
  842. else
  843. cRootObject = debug.getregistry()
  844. strRootObjectName = "registry"
  845. end
  846. -- Create container.
  847. local cDumpInfoContainer = CreateObjectReferenceInfoContainer()
  848. local cStackInfo = debug.getinfo(2, "Sl")
  849. if cStackInfo then
  850. cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
  851. cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
  852. end
  853. -- Collect memory info.
  854. CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer)
  855. -- Dump the result.
  856. OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, nil, cDumpInfoContainer)
  857. end
  858. -- Dump compared memory reference results generated by DumpMemorySnapshot.
  859. -- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.
  860. -- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".
  861. -- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.
  862. -- cResultBefore - The base dumped results.
  863. -- cResultAfter - The compared dumped results.
  864. local function DumpMemorySnapshotCompared(strSavePath, strExtraFileName, nMaxRescords, cResultBefore, cResultAfter)
  865. -- Dump the result.
  866. OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)
  867. end
  868. -- Dump compared memory reference file results generated by DumpMemorySnapshot.
  869. -- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.
  870. -- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".
  871. -- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.
  872. -- strResultFilePathBefore - The base dumped results file.
  873. -- strResultFilePathAfter - The compared dumped results file.
  874. local function DumpMemorySnapshotComparedFile(strSavePath, strExtraFileName, nMaxRescords, strResultFilePathBefore, strResultFilePathAfter)
  875. -- Read results from file.
  876. local cResultBefore = CreateObjectReferenceInfoContainerFromFile(strResultFilePathBefore)
  877. local cResultAfter = CreateObjectReferenceInfoContainerFromFile(strResultFilePathAfter)
  878. -- Dump the result.
  879. OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)
  880. end
  881. -- Dump memory reference of a single object at current time.
  882. -- strSavePath - The save path of the file to store the result, must be a directory path, If nil or "" then the result will output to console as print does.
  883. -- strExtraFileName - If you want to add extra info append to the end of the result file, give a string, nothing will do if set to nil or "".
  884. -- nMaxRescords - How many rescords of the results in limit to save in the file or output to the console, -1 will give all the result.
  885. -- strObjectName - The object name reference you want to dump.
  886. -- cObject - The object reference you want to dump.
  887. local function DumpMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, strObjectName, cObject)
  888. -- Check object.
  889. if not cObject then
  890. return
  891. end
  892. if (not strObjectName) or (0 == string.len(strObjectName)) then
  893. strObjectName = GetOriginalToStringResult(cObject)
  894. end
  895. -- Get time format string.
  896. local strDateTime = FormatDateTimeNow()
  897. -- Create container.
  898. local cDumpInfoContainer = CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)
  899. local cStackInfo = debug.getinfo(2, "Sl")
  900. if cStackInfo then
  901. cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
  902. cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
  903. end
  904. -- Collect memory info.
  905. CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer)
  906. -- Dump the result.
  907. OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer)
  908. end
  909. -- Return methods.
  910. local cPublications = {m_cConfig = nil, m_cMethods = {}, m_cHelpers = {}, m_cBases = {}}
  911. cPublications.m_cConfig = cConfig
  912. cPublications.m_cMethods.DumpMemorySnapshot = DumpMemorySnapshot
  913. cPublications.m_cMethods.DumpMemorySnapshotCompared = DumpMemorySnapshotCompared
  914. cPublications.m_cMethods.DumpMemorySnapshotComparedFile = DumpMemorySnapshotComparedFile
  915. cPublications.m_cMethods.DumpMemorySnapshotSingleObject = DumpMemorySnapshotSingleObject
  916. cPublications.m_cHelpers.FormatDateTimeNow = FormatDateTimeNow
  917. cPublications.m_cHelpers.GetOriginalToStringResult = GetOriginalToStringResult
  918. cPublications.m_cBases.CreateObjectReferenceInfoContainer = CreateObjectReferenceInfoContainer
  919. cPublications.m_cBases.CreateObjectReferenceInfoContainerFromFile = CreateObjectReferenceInfoContainerFromFile
  920. cPublications.m_cBases.CreateSingleObjectReferenceInfoContainer = CreateSingleObjectReferenceInfoContainer
  921. cPublications.m_cBases.CollectObjectReferenceInMemory = CollectObjectReferenceInMemory
  922. cPublications.m_cBases.CollectSingleObjectReferenceInMemory = CollectSingleObjectReferenceInMemory
  923. cPublications.m_cBases.OutputMemorySnapshot = OutputMemorySnapshot
  924. cPublications.m_cBases.OutputMemorySnapshotSingleObject = OutputMemorySnapshotSingleObject
  925. cPublications.m_cBases.OutputFilteredResult = OutputFilteredResult
  926. return cPublications