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 print('tostring overridden:', tostring(cObject))
41 --? rawset(cMt, "__tostring", nil)
42 --? strName = tostring(cObject)
43 --? rawset(cMt, "__tostring", cToString)
45 --? strName = tostring(cObject)
47 strName = tostring(cObject)
52 -- Create a container to collect the mem ref info results.
53 local function CreateObjectReferenceInfoContainer()
54 -- Create new container.
57 -- Contain [table/function] - [reference count] info.
58 local cObjectReferenceCount = {}
59 setmetatable(cObjectReferenceCount, {__mode = "k"})
61 -- Contain [table/function] - [name] info.
62 local cObjectAddressToName = {}
63 setmetatable(cObjectAddressToName, {__mode = "k"})
66 cContainer.m_cObjectReferenceCount = cObjectReferenceCount
67 cContainer.m_cObjectAddressToName = cObjectAddressToName
70 cContainer.m_nStackLevel = -1
71 cContainer.m_strShortSrc = "None"
72 cContainer.m_nCurrentLine = -1
77 -- Create a container to collect the mem ref info results from a dumped file.
78 -- strFilePath - The file path.
79 local function CreateObjectReferenceInfoContainerFromFile(strFilePath)
80 -- Create a empty container.
81 local cContainer = CreateObjectReferenceInfoContainer()
82 cContainer.m_strShortSrc = strFilePath
85 local cRefInfo = cContainer.m_cObjectReferenceCount
86 local cNameInfo = cContainer.m_cObjectAddressToName
88 -- Read each line from file.
89 local cFile = assert(io.open(strFilePath, "rb"))
90 for strLine in cFile:lines() do
91 local strHeader = string.sub(strLine, 1, 2)
92 if "--" ~= strHeader then
93 local _, _, strAddr, strName, strRefCount= string.find(strLine, "(.+)\t(.*)\t(%d+)")
95 cRefInfo[strAddr] = strRefCount
96 cNameInfo[strAddr] = strName
101 -- Close and clear file handler.
108 -- Create a container to collect the mem ref info results from a dumped file.
109 -- strObjectName - The object name you need to collect info.
110 -- cObject - The object you need to collect info.
111 local function CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)
112 -- Create new container.
113 local cContainer = {}
115 -- Contain [address] - [true] info.
116 local cObjectExistTag = {}
117 setmetatable(cObjectExistTag, {__mode = "k"})
119 -- Contain [name] - [true] info.
120 local cObjectAliasName = {}
122 -- Contain [access] - [true] info.
123 local cObjectAccessTag = {}
124 setmetatable(cObjectAccessTag, {__mode = "k"})
127 cContainer.m_cObjectExistTag = cObjectExistTag
128 cContainer.m_cObjectAliasName = cObjectAliasName
129 cContainer.m_cObjectAccessTag = cObjectAccessTag
132 cContainer.m_nStackLevel = -1
133 cContainer.m_strShortSrc = "None"
134 cContainer.m_nCurrentLine = -1
136 -- Init with object values.
137 cContainer.m_strObjectName = strObjectName
138 cContainer.m_strAddressName = (("string" == type(cObject)) and ("\"" .. tostring(cObject) .. "\"")) or GetOriginalToStringResult(cObject)
139 cContainer.m_cObjectExistTag[cObject] = true
144 -- Collect memory reference info from a root table or function.
145 -- strName - The root object name that start to search, default is "_G" if leave this to nil.
146 -- cObject - The root object that start to search, default is _G if leave this to nil.
147 -- cDumpInfoContainer - The container of the dump result info.
148 local function CollectObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)
158 if (not cDumpInfoContainer) then
159 cDumpInfoContainer = CreateObjectReferenceInfoContainer()
163 if cDumpInfoContainer.m_nStackLevel > 0 then
164 local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
166 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
167 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
170 cDumpInfoContainer.m_nStackLevel = -1
173 -- Get ref and name info.
174 local cRefInfoContainer = cDumpInfoContainer.m_cObjectReferenceCount
175 local cNameInfoContainer = cDumpInfoContainer.m_cObjectAddressToName
177 local strType = type(cObject)
178 if "table" == strType then
179 -- Check table with class name.
180 if rawget(cObject, "__cname") then
181 if "string" == type(cObject.__cname) then
182 strName = strName .. "[class:" .. cObject.__cname .. "]"
184 elseif rawget(cObject, "class") then
185 if "string" == type(cObject.class) then
186 strName = strName .. "[class:" .. cObject.class .. "]"
188 elseif rawget(cObject, "_className") then
189 if "string" == type(cObject._className) then
190 strName = strName .. "[class:" .. cObject._className .. "]"
194 -- Check if table is _G.
195 if cObject == _G then
196 strName = strName .. "[_G]"
202 local cMt = getmetatable(cObject)
205 local strMode = rawget(cMt, "__mode")
207 if "k" == strMode then
209 elseif "v" == strMode then
211 elseif "kv" == strMode then
218 -- Add reference and name.
219 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
220 if cNameInfoContainer[cObject] then
225 cNameInfoContainer[cObject] = strName
227 -- Dump table key and value.
228 for k, v in pairs(cObject) do
230 local strKeyType = type(k)
231 if "table" == strKeyType then
233 CollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
237 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
239 elseif "function" == strKeyType then
241 CollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
245 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
247 elseif "thread" == strKeyType then
249 CollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
253 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
255 elseif "userdata" == strKeyType then
257 CollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
261 CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
264 CollectObjectReferenceInMemory(strName .. "." .. tostring(k), v, cDumpInfoContainer)
270 CollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
272 elseif "function" == strType then
273 -- Get function info.
274 local cDInfo = debug.getinfo(cObject, "Su")
277 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
278 if cNameInfoContainer[cObject] then
283 cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
286 local nUpsNum = cDInfo.nups
287 for i = 1, nUpsNum do
288 local strUpName, cUpValue = debug.getupvalue(cObject, i)
289 local strUpValueType = type(cUpValue)
290 --print(strUpName, cUpValue)
291 if "table" == strUpValueType then
292 CollectObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
293 elseif "function" == strUpValueType then
294 CollectObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
295 elseif "thread" == strUpValueType then
296 CollectObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
297 elseif "userdata" == strUpValueType then
298 CollectObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
302 -- Dump environment table.
303 local getfenv = debug.getfenv
305 local cEnv = getfenv(cObject)
307 CollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
310 elseif "thread" == strType then
311 -- Add reference and name.
312 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
313 if cNameInfoContainer[cObject] then
318 cNameInfoContainer[cObject] = strName
320 -- Dump environment table.
321 local getfenv = debug.getfenv
323 local cEnv = getfenv(cObject)
325 CollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
330 local cMt = getmetatable(cObject)
332 CollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
334 elseif "userdata" == strType then
335 -- Add reference and name.
336 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
337 if cNameInfoContainer[cObject] then
342 cNameInfoContainer[cObject] = strName
344 -- Dump environment table.
345 local getfenv = debug.getfenv
347 local cEnv = getfenv(cObject)
349 CollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
354 local cMt = getmetatable(cObject)
356 CollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
358 elseif "string" == strType then
359 -- Add reference and name.
360 cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
361 if cNameInfoContainer[cObject] then
366 cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]"
368 -- For "number" and "boolean". (If you want to dump them, uncomment the followed lines.)
370 -- -- Add reference and name.
371 -- cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
372 -- if cNameInfoContainer[cObject] then
377 -- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]"
381 -- Collect memory reference info of a single object from a root table or function.
382 -- strName - The root object name that start to search, can not be nil.
383 -- cObject - The root object that start to search, can not be nil.
384 -- cDumpInfoContainer - The container of the dump result info.
385 local function CollectSingleObjectReferenceInMemory(strName, cObject, cDumpInfoContainer)
395 if (not cDumpInfoContainer) then
396 cDumpInfoContainer = CreateObjectReferenceInfoContainer()
400 if cDumpInfoContainer.m_nStackLevel > 0 then
401 local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
403 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
404 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
407 cDumpInfoContainer.m_nStackLevel = -1
410 local cExistTag = cDumpInfoContainer.m_cObjectExistTag
411 local cNameAllAlias = cDumpInfoContainer.m_cObjectAliasName
412 local cAccessTag = cDumpInfoContainer.m_cObjectAccessTag
414 local strType = type(cObject)
415 if "table" == strType then
416 -- Check table with class name.
417 if rawget(cObject, "__cname") then
418 if "string" == type(cObject.__cname) then
419 strName = strName .. "[class:" .. cObject.__cname .. "]"
421 elseif rawget(cObject, "class") then
422 if "string" == type(cObject.class) then
423 strName = strName .. "[class:" .. cObject.class .. "]"
425 elseif rawget(cObject, "_className") then
426 if "string" == type(cObject._className) then
427 strName = strName .. "[class:" .. cObject._className .. "]"
431 -- Check if table is _G.
432 if cObject == _G then
433 strName = strName .. "[_G]"
439 local cMt = getmetatable(cObject)
442 local strMode = rawget(cMt, "__mode")
444 if "k" == strMode then
446 elseif "v" == strMode then
448 elseif "kv" == strMode then
455 -- Check if the specified object.
456 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
457 cNameAllAlias[strName] = true
460 -- Add reference and name.
461 if cAccessTag[cObject] then
466 cAccessTag[cObject] = true
468 -- Dump table key and value.
469 for k, v in pairs(cObject) do
471 local strKeyType = type(k)
472 if "table" == strKeyType then
474 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
478 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
480 elseif "function" == strKeyType then
482 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
486 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
488 elseif "thread" == strKeyType then
490 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
494 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
496 elseif "userdata" == strKeyType then
498 CollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
502 CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
505 CollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
511 CollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
513 elseif "function" == strType then
514 -- Get function info.
515 local cDInfo = debug.getinfo(cObject, "Su")
516 local cCombinedName = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
518 -- Check if the specified object.
519 if cExistTag[cObject] and (not cNameAllAlias[cCombinedName]) then
520 cNameAllAlias[cCombinedName] = true
524 if cAccessTag[cObject] then
529 cAccessTag[cObject] = true
532 local nUpsNum = cDInfo.nups
533 for i = 1, nUpsNum do
534 local strUpName, cUpValue = debug.getupvalue(cObject, i)
535 local strUpValueType = type(cUpValue)
536 --print(strUpName, cUpValue)
537 if "table" == strUpValueType then
538 CollectSingleObjectReferenceInMemory(strName .. ".[ups:table:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
539 elseif "function" == strUpValueType then
540 CollectSingleObjectReferenceInMemory(strName .. ".[ups:function:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
541 elseif "thread" == strUpValueType then
542 CollectSingleObjectReferenceInMemory(strName .. ".[ups:thread:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
543 elseif "userdata" == strUpValueType then
544 CollectSingleObjectReferenceInMemory(strName .. ".[ups:userdata:" .. strUpName .. "]", cUpValue, cDumpInfoContainer)
548 -- Dump environment table.
549 local getfenv = debug.getfenv
551 local cEnv = getfenv(cObject)
553 CollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
556 elseif "thread" == strType then
557 -- Check if the specified object.
558 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
559 cNameAllAlias[strName] = true
562 -- Add reference and name.
563 if cAccessTag[cObject] then
568 cAccessTag[cObject] = true
570 -- Dump environment table.
571 local getfenv = debug.getfenv
573 local cEnv = getfenv(cObject)
575 CollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
580 local cMt = getmetatable(cObject)
582 CollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
584 elseif "userdata" == strType then
585 -- Check if the specified object.
586 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
587 cNameAllAlias[strName] = true
590 -- Add reference and name.
591 if cAccessTag[cObject] then
596 cAccessTag[cObject] = true
598 -- Dump environment table.
599 local getfenv = debug.getfenv
601 local cEnv = getfenv(cObject)
603 CollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
608 local cMt = getmetatable(cObject)
610 CollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
612 elseif "string" == strType then
613 -- Check if the specified object.
614 if cExistTag[cObject] and (not cNameAllAlias[strName]) then
615 cNameAllAlias[strName] = true
618 -- Add reference and name.
619 if cAccessTag[cObject] then
624 cAccessTag[cObject] = true
626 -- For "number" and "boolean" type, they are not object type, skip.
630 -- The base method to dump a mem ref info result into a file.
631 -- 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.
632 -- 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 "".
633 -- 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.
634 -- strRootObjectName - The header info to show the root object name, can be nil.
635 -- cRootObject - The header info to show the root object address, can be nil.
636 -- cDumpInfoResultsBase - The base dumped mem info result, nil means no compare and only output cDumpInfoResults, otherwise to compare with cDumpInfoResults.
637 -- cDumpInfoResults - The compared dumped mem info result, dump itself only if cDumpInfoResultsBase is nil, otherwise dump compared results with cDumpInfoResultsBase.
638 local function OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, cDumpInfoResultsBase, cDumpInfoResults)
640 if not cDumpInfoResults then
644 -- Get time format string.
645 local strDateTime = FormatDateTimeNow()
647 -- Collect memory info.
648 local cRefInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectReferenceCount) or nil
649 local cNameInfoBase = (cDumpInfoResultsBase and cDumpInfoResultsBase.m_cObjectAddressToName) or nil
650 local cRefInfo = cDumpInfoResults.m_cObjectReferenceCount
651 local cNameInfo = cDumpInfoResults.m_cObjectAddressToName
653 -- Create a cache result to sort by ref count.
656 for k in pairs(cRefInfo) do
662 table.sort(cRes, function (l, r)
663 return cRefInfo[l] > cRefInfo[r]
666 -- Save result to file.
667 local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
668 local cOutputHandle = nil
669 local cOutputEntry = print
672 -- Check save path affix.
673 local strAffix = string.sub(strSavePath, -1)
674 if ("/" ~= strAffix) and ("\\" ~= strAffix) then
675 strSavePath = strSavePath .. "/"
678 -- Combine file name.
679 local strFileName = strSavePath .. "LuaMemRefInfo-All"
680 if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then
681 if cDumpInfoResultsBase then
682 if cConfig.m_bComparedMemoryRefFileAddTime then
683 strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
685 strFileName = strFileName .. ".txt"
688 if cConfig.m_bAllMemoryRefFileAddTime then
689 strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
691 strFileName = strFileName .. ".txt"
695 if cDumpInfoResultsBase then
696 if cConfig.m_bComparedMemoryRefFileAddTime then
697 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
699 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
702 if cConfig.m_bAllMemoryRefFileAddTime then
703 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
705 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
710 local cFile = assert(io.open(strFileName, "w"))
711 cOutputHandle = cFile
712 cOutputEntry = cFile.write
715 local cOutputer = function (strContent)
716 if cOutputHandle then
717 cOutputEntry(cOutputHandle, strContent)
719 cOutputEntry(strContent)
723 -- Write table header.
724 if cDumpInfoResultsBase then
725 cOutputer("--------------------------------------------------------\n")
726 cOutputer("-- This is compared memory information.\n")
728 cOutputer("--------------------------------------------------------\n")
729 cOutputer("-- Collect base memory reference at line:" .. tostring(cDumpInfoResultsBase.m_nCurrentLine) .. "@file:" .. cDumpInfoResultsBase.m_strShortSrc .. "\n")
730 cOutputer("-- Collect compared memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
732 cOutputer("--------------------------------------------------------\n")
733 cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
736 cOutputer("--------------------------------------------------------\n")
737 cOutputer("-- [Table/Function/String Address/Name]\t[Reference Path]\t[Reference Count]\n")
738 cOutputer("--------------------------------------------------------\n")
740 if strRootObjectName and cRootObject then
741 if "string" == type(cRootObject) then
742 cOutputer("-- From Root Object: \"" .. tostring(cRootObject) .. "\" (" .. strRootObjectName .. ")\n")
744 cOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n")
749 for i, v in ipairs(cRes) do
750 if (not cDumpInfoResultsBase) or (not cRefInfoBase[v]) then
751 if (nMaxRescords > 0) then
752 if (i <= nMaxRescords) then
753 if "string" == type(v) then
754 local strOrgString = tostring(v)
755 local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")
756 if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then
757 local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")
758 cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
760 cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
763 cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
767 if "string" == type(v) then
768 local strOrgString = tostring(v)
769 local nPattenBegin, nPattenEnd = string.find(strOrgString, "string: \".*\"")
770 if ((not cDumpInfoResultsBase) and ((nil == nPattenBegin) or (nil == nPattenEnd))) then
771 local strRepString = string.gsub(strOrgString, "([\n\r])", "\\n")
772 cOutputer("string: \"" .. strRepString .. "\"\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
774 cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
777 cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
784 io.close(cOutputHandle)
789 -- The base method to dump a mem ref info result of a single object into a file.
790 -- 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.
791 -- 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 "".
792 -- 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.
793 -- cDumpInfoResults - The dumped results.
794 local function OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoResults)
796 if not cDumpInfoResults then
800 -- Get time format string.
801 local strDateTime = FormatDateTimeNow()
803 -- Collect memory info.
804 local cObjectAliasName = cDumpInfoResults.m_cObjectAliasName
806 -- Save result to file.
807 local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
808 local cOutputHandle = nil
809 local cOutputEntry = print
812 -- Check save path affix.
813 local strAffix = string.sub(strSavePath, -1)
814 if ("/" ~= strAffix) and ("\\" ~= strAffix) then
815 strSavePath = strSavePath .. "/"
818 -- Combine file name.
819 local strFileName = strSavePath .. "LuaMemRefInfo-Single"
820 if (not strExtraFileName) or (0 == string.len(strExtraFileName)) then
821 if cConfig.m_bSingleMemoryRefFileAddTime then
822 strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
824 strFileName = strFileName .. ".txt"
827 if cConfig.m_bSingleMemoryRefFileAddTime then
828 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
830 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
834 local cFile = assert(io.open(strFileName, "w"))
835 cOutputHandle = cFile
836 cOutputEntry = cFile.write
839 local cOutputer = function (strContent)
840 if cOutputHandle then
841 cOutputEntry(cOutputHandle, strContent)
843 cOutputEntry(strContent)
847 -- Write table header.
848 cOutputer("--------------------------------------------------------\n")
849 cOutputer("-- Collect single object memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
850 cOutputer("--------------------------------------------------------\n")
852 -- Calculate reference count.
854 for k in pairs(cObjectAliasName) do
858 -- Output reference count.
859 cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n")
860 cOutputer("--------------------------------------------------------\n")
863 for k in pairs(cObjectAliasName) do
864 if (nMaxRescords > 0) then
865 if (i <= nMaxRescords) then
874 io.close(cOutputHandle)
879 -- Fileter an existing result file and output it.
880 -- strFilePath - The existing result file.
881 -- strFilter - The filter string.
882 -- bIncludeFilter - Include(true) or exclude(false) the filter.
883 -- bOutputFile - Output to file(true) or console(false).
884 local function OutputFilteredResult(strFilePath, strFilter, bIncludeFilter, bOutputFile)
885 if (not strFilePath) or (0 == string.len(strFilePath)) then
886 print("You need to specify a file path.")
890 if (not strFilter) or (0 == string.len(strFilter)) then
891 print("You need to specify a filter string.")
896 local cFilteredResult = {}
897 local cReadFile = assert(io.open(strFilePath, "rb"))
898 for strLine in cReadFile:lines() do
899 local nBegin, nEnd = string.find(strLine, strFilter)
900 if nBegin and nEnd then
901 if bIncludeFilter then
902 nBegin, nEnd = string.find(strLine, "[\r\n]")
903 if nBegin and nEnd and (string.len(strLine) == nEnd) then
904 table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))
906 table.insert(cFilteredResult, strLine)
910 if not bIncludeFilter then
911 nBegin, nEnd = string.find(strLine, "[\r\n]")
912 if nBegin and nEnd and (string.len(strLine) == nEnd) then
913 table.insert(cFilteredResult, string.sub(strLine, 1, nBegin - 1))
915 table.insert(cFilteredResult, strLine)
921 -- Close and clear read file handle.
925 -- Write filtered result.
926 local cOutputHandle = nil
927 local cOutputEntry = print
930 -- Combine file name.
931 local _, _, strResFileName = string.find(strFilePath, "(.*)%.txt")
932 strResFileName = strResFileName .. "-Filter-" .. ((bIncludeFilter and "I") or "E") .. "-[" .. strFilter .. "].txt"
934 local cFile = assert(io.open(strResFileName, "w"))
935 cOutputHandle = cFile
936 cOutputEntry = cFile.write
939 local cOutputer = function (strContent)
940 if cOutputHandle then
941 cOutputEntry(cOutputHandle, strContent)
943 cOutputEntry(strContent)
948 for i, v in ipairs(cFilteredResult) do
953 io.close(cOutputHandle)
958 -- Dump memory reference at current time.
959 -- 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.
960 -- 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 "".
961 -- 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.
962 -- strRootObjectName - The root object name that start to search, default is "_G" if leave this to nil.
963 -- cRootObject - The root object that start to search, default is _G if leave this to nil.
964 local function DumpMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject)
965 -- Get time format string.
966 local strDateTime = FormatDateTimeNow()
968 -- Check root object.
970 if (not strRootObjectName) or (0 == string.len(strRootObjectName)) then
971 strRootObjectName = tostring(cRootObject)
974 cRootObject = debug.getregistry()
975 strRootObjectName = "registry"
979 local cDumpInfoContainer = CreateObjectReferenceInfoContainer()
980 local cStackInfo = debug.getinfo(2, "Sl")
982 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
983 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
986 -- Collect memory info.
987 CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer)
990 OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, strRootObjectName, cRootObject, nil, cDumpInfoContainer)
993 -- Dump compared memory reference results generated by DumpMemorySnapshot.
994 -- 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.
995 -- 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 "".
996 -- 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.
997 -- cResultBefore - The base dumped results.
998 -- cResultAfter - The compared dumped results.
999 local function DumpMemorySnapshotCompared(strSavePath, strExtraFileName, nMaxRescords, cResultBefore, cResultAfter)
1001 OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)
1004 -- Dump compared memory reference file results generated by DumpMemorySnapshot.
1005 -- 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.
1006 -- 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 "".
1007 -- 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.
1008 -- strResultFilePathBefore - The base dumped results file.
1009 -- strResultFilePathAfter - The compared dumped results file.
1010 local function DumpMemorySnapshotComparedFile(strSavePath, strExtraFileName, nMaxRescords, strResultFilePathBefore, strResultFilePathAfter)
1011 -- Read results from file.
1012 local cResultBefore = CreateObjectReferenceInfoContainerFromFile(strResultFilePathBefore)
1013 local cResultAfter = CreateObjectReferenceInfoContainerFromFile(strResultFilePathAfter)
1016 OutputMemorySnapshot(strSavePath, strExtraFileName, nMaxRescords, nil, nil, cResultBefore, cResultAfter)
1019 -- Dump memory reference of a single object at current time.
1020 -- 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.
1021 -- 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 "".
1022 -- 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.
1023 -- strObjectName - The object name reference you want to dump.
1024 -- cObject - The object reference you want to dump.
1025 local function DumpMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, strObjectName, cObject)
1031 if (not strObjectName) or (0 == string.len(strObjectName)) then
1032 strObjectName = GetOriginalToStringResult(cObject)
1035 -- Get time format string.
1036 local strDateTime = FormatDateTimeNow()
1038 -- Create container.
1039 local cDumpInfoContainer = CreateSingleObjectReferenceInfoContainer(strObjectName, cObject)
1040 local cStackInfo = debug.getinfo(2, "Sl")
1042 cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
1043 cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
1046 -- Collect memory info.
1047 CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer)
1050 OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer)
1054 local cPublications = {m_cConfig = nil, m_cMethods = {}, m_cHelpers = {}, m_cBases = {}}
1056 cPublications.m_cConfig = cConfig
1058 cPublications.m_cMethods.DumpMemorySnapshot = DumpMemorySnapshot
1059 cPublications.m_cMethods.DumpMemorySnapshotCompared = DumpMemorySnapshotCompared
1060 cPublications.m_cMethods.DumpMemorySnapshotComparedFile = DumpMemorySnapshotComparedFile
1061 cPublications.m_cMethods.DumpMemorySnapshotSingleObject = DumpMemorySnapshotSingleObject
1063 cPublications.m_cHelpers.FormatDateTimeNow = FormatDateTimeNow
1064 cPublications.m_cHelpers.GetOriginalToStringResult = GetOriginalToStringResult
1066 cPublications.m_cBases.CreateObjectReferenceInfoContainer = CreateObjectReferenceInfoContainer
1067 cPublications.m_cBases.CreateObjectReferenceInfoContainerFromFile = CreateObjectReferenceInfoContainerFromFile
1068 cPublications.m_cBases.CreateSingleObjectReferenceInfoContainer = CreateSingleObjectReferenceInfoContainer
1069 cPublications.m_cBases.CollectObjectReferenceInMemory = CollectObjectReferenceInMemory
1070 cPublications.m_cBases.CollectSingleObjectReferenceInMemory = CollectSingleObjectReferenceInMemory
1071 cPublications.m_cBases.OutputMemorySnapshot = OutputMemorySnapshot
1072 cPublications.m_cBases.OutputMemorySnapshotSingleObject = OutputMemorySnapshotSingleObject
1073 cPublications.m_cBases.OutputFilteredResult = OutputFilteredResult
1075 return cPublications