MemoryReferenceInfo.lua.unused 42 KB

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