2 -- Collect memory reference info.
3 -- https://github.com/yaukeywang/LuaMemorySnapshotDump
5 -- @filename MemoryReferenceInfo.lua
9 -- The global config of the mri.
12 m_bAllMemoryRefFileAddTime = true,
13 m_bSingleMemoryRefFileAddTime = true,
14 m_bComparedMemoryRefFileAddTime = true
17 -- Get the format string of date time.
18 local function FormatDateTimeNow()
19 local cDateTime = os.date("*t")
20 local strDateTime = string.format("%04d%02d%02d-%02d%02d%02d", tostring(cDateTime.year), tostring(cDateTime.month), tostring(cDateTime.day),
21 tostring(cDateTime.hour), tostring(cDateTime.min), tostring(cDateTime.sec))
25 -- Get the string result without overrided __tostring.
26 local function GetOriginalToStringResult(cObject)
31 local cMt = getmetatable(cObject)
33 return tostring(cObject)
36 -- Check tostring override.
38 local cToString = rawget(cMt, "__tostring")
40 rawset(cMt, "__tostring", nil)
41 strName = tostring(cObject)
42 rawset(cMt, "__tostring", cToString)
44 strName = tostring(cObject)
50 -- Create a container to collect the mem ref info results.
51 local function CreateObjectReferenceInfoContainer()
52 -- Create new container.
55 -- Contain [table/function] - [reference count] info.
56 local cObjectReferenceCount = {}
57 setmetatable(cObjectReferenceCount, {__mode = "k"})
59 -- Contain [table/function] - [name] info.
60 local cObjectAddressToName = {}
61 setmetatable(cObjectAddressToName, {__mode = "k"})
64 cContainer.m_cObjectReferenceCount = cObjectReferenceCount
65 cContainer.m_cObjectAddressToName = cObjectAddressToName
68 cContainer.m_nStackLevel = -1
69 cContainer.m_strShortSrc = "None"
70 cContainer.m_nCurrentLine = -1
75 -- Create a container to collect the mem ref info results from a dumped file.
76 -- strFilePath - The file path.
77 local function CreateObjectReferenceInfoContainerFromFile(strFilePath)
78 -- Create a empty container.
79 local cContainer = CreateObjectReferenceInfoContainer()
80 cContainer.m_strShortSrc = strFilePath
83 local cRefInfo = cContainer.m_cObjectReferenceCount
84 local cNameInfo = cContainer.m_cObjectAddressToName
86 -- Read each line from file.
87 local cFile = assert(io.open(strFilePath, "rb"))
88 for strLine in cFile:lines() do
89 local strHeader = string.sub(strLine, 1, 2)
90 if "--" ~= strHeader then
91 local _, _, strAddr, strName, strRefCount= string.find(strLine, "(.+)\t(.*)\t(%d+)")
93 cRefInfo[strAddr] = strRefCount
94 cNameInfo[strAddr] = strName
99 -- Close and clear file handler.
106 -- Create a container to collect the mem ref info results from a dumped file.
107 -- strObjectName - The object name you need to collect info.
108 -- cObject - The object you need to collect info.
109 local function CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)
110 -- Create new container.
111 local cContainer = {}
113 -- Contain [address] - [true] info.
114 local cObjectExistTag = {}
115 setmetatable(cObjectExistTag, {__mode = "k"})
117 -- Contain [name] - [true] info.
118 local cObjectAliasName = {}
120 -- Contain [access] - [true] info.
121 local cObjectAccessTag = {}
122 setmetatable(cObjectAccessTag, {__mode = "k"})
125 cContainer.m_cObjectExistTag = cObjectExistTag
126 cContainer.m_cObjectAliasName = cObjectAliasName
127 cContainer.m_cObjectAccessTag = cObjectAccessTag
130 cContainer.m_nStackLevel = -1
131 cContainer.m_strShortSrc = "None"
132 cContainer.m_nCurrentLine = -1
134 -- Init with object values.
135 cContainer.m_strObjectName = strObjectName
136 cContainer.m_strAddressName = (("string" == type(cObject)) and ("\"" .. tostring(cObject) .. "\"")) or GetOriginalToStringResult(cObject)
137 cContainer.m_cObjectExistTag[cObject] = true
142 -- Collect memory reference info from a root table or function.
143 -- strName - The root object name that start to search, default is "_G" if leave this to nil.
144 -- cObject - The root object that start to search, default is _G if leave this to nil.
145 -- cDumpInfoContainer - The container of the dump result info.
146 local function CollectObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)
156 if (not cDumpInfoContainer) then
157 cDumpInfoContainer = CreateObjectReferenceInfoContainer()
161 if cDumpInfoContainer.m_nStackLevel > 0 then
162 local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
164 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
165 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
168 cDumpInfoContainer.m_nStackLevel = -1
171 -- Get ref and name info.
172 local cRefInfoContainer = cDumpInfoContainer.m_cObjectReferenceCount
173 local cNameInfoContainer = cDumpInfoContainer.m_cObjectAddressToName
175 local strType = type(cObject)
176 if "table" == strType then
177 -- Check table with class name.
178 if rawget(cObject, "__cname") then
179 if "string" == type(cObject.__cname) then
180 strName = strName .. "[class:" .. cObject.__cname .. "]"
182 elseif rawget(cObject, "class") then
183 if "string" == type(cObject.class) then
184 strName = strName .. "[class:" .. cObject.class .. "]"
186 elseif rawget(cObject, "_className") then
187 if "string" == type(cObject._className) then
188 strName = strName .. "[class:" .. cObject._className .. "]"
192 -- Check if table is _G.
193 if cObject == _G then
194 strName = strName .. "[_G]"
200 local cMt = getmetatable(cObject)
203 local strMode = rawget(cMt, "__mode")
205 if "k" == strMode then
207 elseif "v" == strMode then
209 elseif "kv" == strMode then
216 -- Add reference and name.
217 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
218 if cNameInfoContainer[cObject] then
223 cNameInfoContainer[cObject] = strName
225 -- Dump table key and value.
226 for k, v in pairs(cObject) do
228 local strKeyType = type(k)
229 if "table" == strKeyType then
231 CollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
235 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
237 elseif "function" == strKeyType then
239 CollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
243 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
245 elseif "thread" == strKeyType then
247 CollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
251 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
253 elseif "userdata" == strKeyType then
255 CollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
259 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
262 CollectObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
268 CollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
270 elseif "function" == strType then
271 -- Get function info.
272 local cDInfo = debug.getinfo(cObject, "Su")
275 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
276 if cNameInfoContainer[cObject] then
281 cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
284 local nUpsNum = cDInfo.nups
285 for i = 1, nUpsNum do
286 local strUpName, cUpValue = debug.getupvalue(cObject, i)
287 local strUpValueType = type(cUpValue)
288 --print(strUpName, cUpValue)
289 if "table" == strUpValueType then
290 CollectObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
291 elseif "function" == strUpValueType then
292 CollectObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
293 elseif "thread" == strUpValueType then
294 CollectObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
295 elseif "userdata" == strUpValueType then
296 CollectObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
300 -- Dump environment table.
301 local getfenv = debug.getfenv
303 local cEnv = getfenv(cObject)
305 CollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
308 elseif "thread" == strType then
309 -- Add reference and name.
310 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
311 if cNameInfoContainer[cObject] then
316 cNameInfoContainer[cObject] = strName
318 -- Dump environment table.
319 local getfenv = debug.getfenv
321 local cEnv = getfenv(cObject)
323 CollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
328 local cMt = getmetatable(cObject)
330 CollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
332 elseif "userdata" == strType then
333 -- Add reference and name.
334 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
335 if cNameInfoContainer[cObject] then
340 cNameInfoContainer[cObject] = strName
342 -- Dump environment table.
343 local getfenv = debug.getfenv
345 local cEnv = getfenv(cObject)
347 CollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
352 local cMt = getmetatable(cObject)
354 CollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
356 elseif "string" == strType then
357 -- Add reference and name.
358 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
359 if cNameInfoContainer[cObject] then
364 cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]"
366 -- For "number" and "boolean". (If you want to dump them, uncomment the followed lines.)
368 -- -- Add reference and name.
369 -- cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
370 -- if cNameInfoContainer[cObject] then
375 -- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]"
379 -- Collect memory reference info of a single object from a root table or function.
380 -- strName - The root object name that start to search, can not be nil.
381 -- cObject - The root object that start to search, can not be nil.
382 -- cDumpInfoContainer - The container of the dump result info.
383 local function CollectSingleObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)
393 if (not cDumpInfoContainer) then
394 cDumpInfoContainer = CreateObjectReferenceInfoContainer()
398 if cDumpInfoContainer.m_nStackLevel > 0 then
399 local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
401 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
402 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
405 cDumpInfoContainer.m_nStackLevel = -1
408 local cExistTag = cDumpInfoContainer.m_cObjectExistTag
409 local cNameAllAlias = cDumpInfoContainer.m_cObjectAliasName
410 local cAccessTag = cDumpInfoContainer.m_cObjectAccessTag
412 local strType = type(cObject)
413 if "table" == strType then
414 -- Check table with class name.
415 if rawget(cObject, "__cname") then
416 if "string" == type(cObject.__cname) then
417 strName = strName .. "[class:" .. cObject.__cname .. "]"
419 elseif rawget(cObject, "class") then
420 if "string" == type(cObject.class) then
421 strName = strName .. "[class:" .. cObject.class .. "]"
423 elseif rawget(cObject, "_className") then
424 if "string" == type(cObject._className) then
425 strName = strName .. "[class:" .. cObject._className .. "]"
429 -- Check if table is _G.
430 if cObject == _G then
431 strName = strName .. "[_G]"
437 local cMt = getmetatable(cObject)
440 local strMode = rawget(cMt, "__mode")
442 if "k" == strMode then
444 elseif "v" == strMode then
446 elseif "kv" == strMode then
453 -- Check if the specified object.
454 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
455 cNameAllAlias[strName] = true
458 -- Add reference and name.
459 if cAccessTag[cObject] then
464 cAccessTag[cObject] = true
466 -- Dump table key and value.
467 for k, v in pairs(cObject) do
469 local strKeyType = type(k)
470 if "table" == strKeyType then
472 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
476 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
478 elseif "function" == strKeyType then
480 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
484 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
486 elseif "thread" == strKeyType then
488 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
492 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
494 elseif "userdata" == strKeyType then
496 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
500 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
503 CollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
509 CollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
511 elseif "function" == strType then
512 -- Get function info.
513 local cDInfo = debug.getinfo(cObject, "Su")
514 local cCombinedName = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
516 -- Check if the specified object.
517 if cExistTag[cObject] and (not cNameAllAlias[cCombinedName]) then
518 cNameAllAlias[cCombinedName] = true
522 if cAccessTag[cObject] then
527 cAccessTag[cObject] = true
530 local nUpsNum = cDInfo.nups
531 for i = 1, nUpsNum do
532 local strUpName, cUpValue = debug.getupvalue(cObject, i)
533 local strUpValueType = type(cUpValue)
534 --print(strUpName, cUpValue)
535 if "table" == strUpValueType then
536 CollectSingleObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
537 elseif "function" == strUpValueType then
538 CollectSingleObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
539 elseif "thread" == strUpValueType then
540 CollectSingleObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
541 elseif "userdata" == strUpValueType then
542 CollectSingleObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
546 -- Dump environment table.
547 local getfenv = debug.getfenv
549 local cEnv = getfenv(cObject)
551 CollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
554 elseif "thread" == strType then
555 -- Check if the specified object.
556 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
557 cNameAllAlias[strName] = true
560 -- Add reference and name.
561 if cAccessTag[cObject] then
566 cAccessTag[cObject] = true
568 -- Dump environment table.
569 local getfenv = debug.getfenv
571 local cEnv = getfenv(cObject)
573 CollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
578 local cMt = getmetatable(cObject)
580 CollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
582 elseif "userdata" == strType then
583 -- Check if the specified object.
584 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
585 cNameAllAlias[strName] = true
588 -- Add reference and name.
589 if cAccessTag[cObject] then
594 cAccessTag[cObject] = true
596 -- Dump environment table.
597 local getfenv = debug.getfenv
599 local cEnv = getfenv(cObject)
601 CollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
606 local cMt = getmetatable(cObject)
608 CollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
610 elseif "string" == strType then
611 -- Check if the specified object.
612 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
613 cNameAllAlias[strName] = true
616 -- Add reference and name.
617 if cAccessTag[cObject] then
622 cAccessTag[cObject] = true
624 -- For "number" and "boolean" type, they are not object type, skip.
628 -- The base method to dump a mem ref info result into a file.
629 -- 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.
630 -- 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 "".
631 -- 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.
632 -- strRootObjectName - The header info to show the root object name, can be nil.
633 -- cRootObject - The header info to show the root object address, can be nil.
634 -- cDumpInfoResultsBase - The base dumped mem info result, nil means no compare and only output cDumpInfoResults, otherwise to compare with cDumpInfoResults.
635 -- cDumpInfoResults - The compared dumped mem info result, dump itself only if cDumpInfoResultsBase is nil, otherwise dump compared results with cDumpInfoResultsBase.
636 local function OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, cDumpInfoResultsBase, cDumpInfoResults)
638 if not cDumpInfoResults then
642 -- Get time format string.
643 local strDateTime = FormatDateTimeNow()
645 -- Collect memory info.
646 local cRefInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectReferenceCount) or nil
647 local cNameInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectAddressToName) or nil
648 local cRefInfo = cDumpInfoResults.m_cObjectReferenceCount
649 local cNameInfo = cDumpInfoResults.m_cObjectAddressToName
651 -- Create a cache result to sort by ref count.
654 for k in pairs(cRefInfo) do
660 table.sort(cRes, function (l, r)
661 return cRefInfo[l] > cRefInfo[r]
664 -- Save result to file.
665 local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
666 local cOutputHandle = nil
667 local cOutputEntry = print
670 -- Check save path affix.
671 local strAffix = string.sub(strSavePath, -1)
672 if ("/" ~= strAffix) and ("\\" ~= strAffix) then
673 strSavePath = strSavePath .. "/"
676 -- Combine file name.
677 local strFileName = strSavePath .. "LuaMemRefInfo-All"
678 if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then
679 if cDumpInfoResultsBase then
680 if cConfig.m_bComparedMemoryRefFileAddTime then
681 strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
683 strFileName = strFileName .. ".txt"
686 if cConfig.m_bAllMemoryRefFileAddTime then
687 strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
689 strFileName = strFileName .. ".txt"
693 if cDumpInfoResultsBase then
694 if cConfig.m_bComparedMemoryRefFileAddTime then
695 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
697 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
700 if cConfig.m_bAllMemoryRefFileAddTime then
701 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
703 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
708 local cFile = assert(io.open(strFileName, "w"))
709 cOutputHandle = cFile
710 cOutputEntry = cFile.write
713 local cOutputer = function (strContent)
714 if cOutputHandle then
715 cOutputEntry(cOutputHandle, strContent)
717 cOutputEntry(strContent)
721 -- Write table header.
722 if cDumpInfoResultsBase then
723 cOutputer("--------------------------------------------------------\n")
724 cOutputer("-- This is compared memory information.\n")
726 cOutputer("--------------------------------------------------------\n")
727 cOutputer("-- Collect base memory reference at line:" .. tostring(cDumpInfoResultsBase.m_nCurrentLine) .. "@file:" .. cDumpInfoResultsBase.m_strShortSrc .. "\n")
728 cOutputer("-- Collect compared memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
730 cOutputer("--------------------------------------------------------\n")
731 cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
734 cOutputer("--------------------------------------------------------\n")
735 cOutputer("-- [Table/Function/String Address/Name]\t[Reference Path]\t[Reference Count]\n")
736 cOutputer("--------------------------------------------------------\n")
738 if strRootObjectName and cRootObject then
739 if "string" == type(cRootObject) then
740 cOutputer("-- From Root Object: \"" .. tostring(cRootObject) .. "\" (" .. strRootObjectName .. ")\n")
742 cOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n")
747 for i, v in ipairs(cRes) do
748 if (not cDumpInfoResultsBase) or (not cRefInfoBase[v]) then
749 if (nMaxRescords > 0) then
750 if (i <= nMaxRescords) then
751 if "string" == type(v) then
752 local strOrgString = tostring(v)
753 local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")
754 if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then
755 local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")
756 cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
758 cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
761 cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
765 if "string" == type(v) then
766 local strOrgString = tostring(v)
767 local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")
768 if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then
769 local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")
770 cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
772 cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
775 cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
782 io.close(cOutputHandle)
787 -- The base method to dump a mem ref info result of a single object into a file.
788 -- 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.
789 -- 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 "".
790 -- 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.
791 -- cDumpInfoResults - The dumped results.
792 local function OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoResults)
794 if not cDumpInfoResults then
798 -- Get time format string.
799 local strDateTime = FormatDateTimeNow()
801 -- Collect memory info.
802 local cObjectAliasName = cDumpInfoResults.m_cObjectAliasName
804 -- Save result to file.
805 local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
806 local cOutputHandle = nil
807 local cOutputEntry = print
810 -- Check save path affix.
811 local strAffix = string.sub(strSavePath, -1)
812 if ("/" ~= strAffix) and ("\\" ~= strAffix) then
813 strSavePath = strSavePath .. "/"
816 -- Combine file name.
817 local strFileName = strSavePath .. "LuaMemRefInfo-Single"
818 if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then
819 if cConfig.m_bSingleMemoryRefFileAddTime then
820 strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
822 strFileName = strFileName .. ".txt"
825 if cConfig.m_bSingleMemoryRefFileAddTime then
826 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
828 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
832 local cFile = assert(io.open(strFileName, "w"))
833 cOutputHandle = cFile
834 cOutputEntry = cFile.write
837 local cOutputer = function (strContent)
838 if cOutputHandle then
839 cOutputEntry(cOutputHandle, strContent)
841 cOutputEntry(strContent)
845 -- Write table header.
846 cOutputer("--------------------------------------------------------\n")
847 cOutputer("-- Collect single object memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
848 cOutputer("--------------------------------------------------------\n")
850 -- Calculate reference count.
852 for k in pairs(cObjectAliasName) do
856 -- Output reference count.
857 cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n")
858 cOutputer("--------------------------------------------------------\n")
861 for k in pairs(cObjectAliasName) do
862 if (nMaxRescords > 0) then
863 if (i <= nMaxRescords) then
872 io.close(cOutputHandle)
877 -- Fileter an existing result file and output it.
878 -- strFilePath - The existing result file.
879 -- strFilter - The filter string.
880 -- bIncludeFilter - Include(true) or exclude(false) the filter.
881 -- bOutputFile - Output to file(true) or console(false).
882 local function OutputFilteredResult(strFilePath, strFilter, bIncludeFilter, bOutputFile)
883 if (not strFilePath) or (0 == string.len(strFilePath)) then
884 print("You need to specify a file path.")
888 if (not strFilter) or (0 == string.len(strFilter)) then
889 print("You need to specify a filter string.")
894 local cFilteredResult = {}
895 local cReadFile = assert(io.open(strFilePath, "rb"))
896 for strLine in cReadFile:lines() do
897 local nBegin, nEnd = string.find(strLine, strFilter)
898 if nBegin and nEnd then
899 if bIncludeFilter then
900 nBegin, nEnd = string.find(strLine, "[\r\n]")
901 if nBegin and nEnd and (string.len(strLine) == nEnd) then
902 table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))
904 table.insert(cFilteredResult, strLine)
908 if not bIncludeFilter then
909 nBegin, nEnd = string.find(strLine, "[\r\n]")
910 if nBegin and nEnd and (string.len(strLine) == nEnd) then
911 table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))
913 table.insert(cFilteredResult, strLine)
919 -- Close and clear read file handle.
923 -- Write filtered result.
924 local cOutputHandle = nil
925 local cOutputEntry = print
928 -- Combine file name.
929 local _, _, strResFileName = string.find(strFilePath, "(.*)%.txt")
930 strResFileName = strResFileName .. "-Filter-" .. ((bIncludeFilter and "I") or "E") .. "-[" .. strFilter .. "].txt"
932 local cFile = assert(io.open(strResFileName, "w"))
933 cOutputHandle = cFile
934 cOutputEntry = cFile.write
937 local cOutputer = function (strContent)
938 if cOutputHandle then
939 cOutputEntry(cOutputHandle, strContent)
941 cOutputEntry(strContent)
946 for i, v in ipairs(cFilteredResult) do
951 io.close(cOutputHandle)
956 -- Dump memory reference at current time.
957 -- 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.
958 -- 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 "".
959 -- 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.
960 -- strRootObjectName - The root object name that start to search, default is "_G" if leave this to nil.
961 -- cRootObject - The root object that start to search, default is _G if leave this to nil.
962 local function DumpMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject)
963 -- Get time format string.
964 local strDateTime = FormatDateTimeNow()
966 -- Check root object.
968 if (not strRootObjectName) or (0 == string.len(strRootObjectName)) then
969 strRootObjectName = tostring(cRootObject)
972 cRootObject = debug.getregistry()
973 strRootObjectName = "registry"
977 local cDumpInfoContainer = CreateObjectReferenceInfoContainer()
978 local cStackInfo = debug.getinfo(2, "Sl")
980 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
981 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
984 -- Collect memory info.
985 CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer)
988 OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, nil, cDumpInfoContainer)
991 -- Dump compared memory reference results generated by DumpMemorySnapshot.
992 -- 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.
993 -- 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 "".
994 -- 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.
995 -- cResultBefore - The base dumped results.
996 -- cResultAfter - The compared dumped results.
997 local function DumpMemorySnapshotCompared(strSavePath, strExtraFileName, nMaxRescords, cResultBefore, cResultAfter)
999 OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)
1002 -- Dump compared memory reference file results generated by DumpMemorySnapshot.
1003 -- 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.
1004 -- 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 "".
1005 -- 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.
1006 -- strResultFilePathBefore - The base dumped results file.
1007 -- strResultFilePathAfter - The compared dumped results file.
1008 local function DumpMemorySnapshotComparedFile(strSavePath, strExtraFileName, nMaxRescords, strResultFilePathBefore, strResultFilePathAfter)
1009 -- Read results from file.
1010 local cResultBefore = CreateObjectReferenceInfoContainerFromFile(strResultFilePathBefore)
1011 local cResultAfter = CreateObjectReferenceInfoContainerFromFile(strResultFilePathAfter)
1014 OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)
1017 -- Dump memory reference of a single object at current time.
1018 -- 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.
1019 -- 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 "".
1020 -- 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.
1021 -- strObjectName - The object name reference you want to dump.
1022 -- cObject - The object reference you want to dump.
1023 local function DumpMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, strObjectName, cObject)
1029 if (not strObjectName) or (0 == string.len(strObjectName)) then
1030 strObjectName = GetOriginalToStringResult(cObject)
1033 -- Get time format string.
1034 local strDateTime = FormatDateTimeNow()
1036 -- Create container.
1037 local cDumpInfoContainer = CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)
1038 local cStackInfo = debug.getinfo(2, "Sl")
1040 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
1041 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
1044 -- Collect memory info.
1045 CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer)
1048 OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer)
1052 local cPublications = {m_cConfig = nil, m_cMethods = {}, m_cHelpers = {}, m_cBases = {}}
1054 cPublications.m_cConfig = cConfig
1056 cPublications.m_cMethods.DumpMemorySnapshot = DumpMemorySnapshot
1057 cPublications.m_cMethods.DumpMemorySnapshotCompared = DumpMemorySnapshotCompared
1058 cPublications.m_cMethods.DumpMemorySnapshotComparedFile = DumpMemorySnapshotComparedFile
1059 cPublications.m_cMethods.DumpMemorySnapshotSingleObject = DumpMemorySnapshotSingleObject
1061 cPublications.m_cHelpers.FormatDateTimeNow = FormatDateTimeNow
1062 cPublications.m_cHelpers.GetOriginalToStringResult = GetOriginalToStringResult
1064 cPublications.m_cBases.CreateObjectReferenceInfoContainer = CreateObjectReferenceInfoContainer
1065 cPublications.m_cBases.CreateObjectReferenceInfoContainerFromFile = CreateObjectReferenceInfoContainerFromFile
1066 cPublications.m_cBases.CreateSingleObjectReferenceInfoContainer = CreateSingleObjectReferenceInfoContainer
1067 cPublications.m_cBases.CollectObjectReferenceInMemory = CollectObjectReferenceInMemory
1068 cPublications.m_cBases.CollectSingleObjectReferenceInMemory = CollectSingleObjectReferenceInMemory
1069 cPublications.m_cBases.OutputMemorySnapshot = OutputMemorySnapshot
1070 cPublications.m_cBases.OutputMemorySnapshotSingleObject = OutputMemorySnapshotSingleObject
1071 cPublications.m_cBases.OutputFilteredResult = OutputFilteredResult
1073 return cPublications