bugfix #3, attempt #2 in search UI
[view.love.git] / MemoryReferenceInfo.lua.0
blob852f01eff9c0be94f998e276215f9a8997805246
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
9 -- The global config of the mri.
10 local cConfig =
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))
22     return strDateTime
23 end
25 -- Get the string result without overrided __tostring.
26 local function GetOriginalToStringResult(cObject)
27     if not cObject then
28         return ""
29     end
31     local cMt = getmetatable(cObject)
32     if not cMt then
33         return tostring(cObject)
34     end
36     -- Check tostring override.
37     local strName = ""
38     local cToString = rawget(cMt, "__tostring")
39     if cToString then
40         rawset(cMt, "__tostring", nil)
41         strName = tostring(cObject)
42         rawset(cMt, "__tostring", cToString)
43     else
44         strName = tostring(cObject)
45     end
47     return strName
48 end
50 -- Create a container to collect the mem ref info results.
51 local function CreateObjectReferenceInfoContainer()
52     -- Create new container.
53     local cContainer = {}
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"})
63     -- Set members.
64     cContainer.m_cObjectReferenceCount = cObjectReferenceCount
65     cContainer.m_cObjectAddressToName = cObjectAddressToName
67     -- For stack info.
68     cContainer.m_nStackLevel = -1
69     cContainer.m_strShortSrc = "None"
70     cContainer.m_nCurrentLine = -1
72     return cContainer
73 end
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
82     -- Cache ref info.
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+)")
92             if strAddr then
93                 cRefInfo[strAddr] = strRefCount
94                 cNameInfo[strAddr] = strName
95             end
96         end
97     end
99     -- Close and clear file handler.
100     io.close(cFile)
101     cFile = nil
103     return cContainer
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"})
124     -- Set members.
125     cContainer.m_cObjectExistTag = cObjectExistTag
126     cContainer.m_cObjectAliasName = cObjectAliasName
127     cContainer.m_cObjectAccessTag = cObjectAccessTag
129     -- For stack info.
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
139     return cContainer
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)
147     if not cObject then
148         return
149     end
151     if not strName then
152         strName = ""
153     end
155     -- Check container.
156     if (not cDumpInfoContainer) then
157         cDumpInfoContainer = CreateObjectReferenceInfoContainer()
158     end
160     -- Check stack.
161     if cDumpInfoContainer.m_nStackLevel > 0 then
162         local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
163         if cStackInfo then
164             cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
165             cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
166         end
168         cDumpInfoContainer.m_nStackLevel = -1
169     end
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 .. "]"
181             end
182         elseif rawget(cObject, "class") then
183             if "string" == type(cObject.class) then
184                 strName = strName .. "[class:" .. cObject.class .. "]"
185             end
186         elseif rawget(cObject, "_className") then
187             if "string" == type(cObject._className) then
188                 strName = strName .. "[class:" .. cObject._className .. "]"
189             end
190         end
192         -- Check if table is _G.
193         if cObject == _G then
194             strName = strName .. "[_G]"
195         end
197         -- Get metatable.
198         local bWeakK = false
199         local bWeakV = false
200         local cMt = getmetatable(cObject)
201         if cMt then
202             -- Check mode.
203             local strMode = rawget(cMt, "__mode")
204             if strMode then
205                 if "k" == strMode then
206                     bWeakK = true
207                 elseif "v" == strMode then
208                     bWeakV = true
209                 elseif "kv" == strMode then
210                     bWeakK = true
211                     bWeakV = true
212                 end
213             end
214         end
216         -- Add reference and name.
217         cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
218         if cNameInfoContainer[cObject] then
219             return
220         end
222         -- Set name.
223         cNameInfoContainer[cObject] = strName
225         -- Dump table key and value.
226         for k, v in pairs(cObject) do
227             -- Check key type.
228             local strKeyType = type(k)
229             if "table" == strKeyType then
230                 if not bWeakK then
231                     CollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
232                 end
234                 if not bWeakV then
235                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
236                 end
237             elseif "function" == strKeyType then
238                 if not bWeakK then
239                     CollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
240                 end
242                 if not bWeakV then
243                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
244                 end
245             elseif "thread" == strKeyType then
246                 if not bWeakK then
247                     CollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
248                 end
250                 if not bWeakV then
251                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
252                 end
253             elseif "userdata" == strKeyType then
254                 if not bWeakK then
255                     CollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
256                 end
258                 if not bWeakV then
259                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
260                 end
261             else
262                 CollectObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
263             end
264         end
266         -- Dump metatable.
267         if cMt then
268             CollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
269         end
270     elseif "function" == strType then
271         -- Get function info.
272         local cDInfo = debug.getinfo(cObject, "Su")
274         -- Write this info.
275         cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
276         if cNameInfoContainer[cObject] then
277             return
278         end
280         -- Set name.
281         cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
283         -- Get upvalues.
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)
297             end
298         end
300         -- Dump environment table.
301         local getfenv = debug.getfenv
302         if getfenv then
303             local cEnv = getfenv(cObject)
304             if cEnv then
305                 CollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
306             end
307         end
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
312             return
313         end
315         -- Set name.
316         cNameInfoContainer[cObject] = strName
318         -- Dump environment table.
319         local getfenv = debug.getfenv
320         if getfenv then
321             local cEnv = getfenv(cObject)
322             if cEnv then
323                 CollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
324             end
325         end
327         -- Dump metatable.
328         local cMt = getmetatable(cObject)
329         if cMt then
330             CollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
331         end
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
336             return
337         end
339         -- Set name.
340         cNameInfoContainer[cObject] = strName
342         -- Dump environment table.
343         local getfenv = debug.getfenv
344         if getfenv then
345             local cEnv = getfenv(cObject)
346             if cEnv then
347                 CollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
348             end
349         end
351         -- Dump metatable.
352         local cMt = getmetatable(cObject)
353         if cMt then
354             CollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
355         end
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
360             return
361         end
363         -- Set name.
364         cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]"
365     else
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
371         --  return
372         -- end
374         -- -- Set name.
375         -- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]"
376     end
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)
384     if not cObject then
385         return
386     end
388     if not strName then
389         strName = ""
390     end
392     -- Check container.
393     if (not cDumpInfoContainer) then
394         cDumpInfoContainer = CreateObjectReferenceInfoContainer()
395     end
397     -- Check stack.
398     if cDumpInfoContainer.m_nStackLevel > 0 then
399         local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
400         if cStackInfo then
401             cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
402             cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
403         end
405         cDumpInfoContainer.m_nStackLevel = -1
406     end
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 .. "]"
418             end
419         elseif rawget(cObject, "class") then
420             if "string" == type(cObject.class) then
421                 strName = strName .. "[class:" .. cObject.class .. "]"
422             end
423         elseif rawget(cObject, "_className") then
424             if "string" == type(cObject._className) then
425                 strName = strName .. "[class:" .. cObject._className .. "]"
426             end
427         end
429         -- Check if table is _G.
430         if cObject == _G then
431             strName = strName .. "[_G]"
432         end
434         -- Get metatable.
435         local bWeakK = false
436         local bWeakV = false
437         local cMt = getmetatable(cObject)
438         if cMt then
439             -- Check mode.
440             local strMode = rawget(cMt, "__mode")
441             if strMode then
442                 if "k" == strMode then
443                     bWeakK = true
444                 elseif "v" == strMode then
445                     bWeakV = true
446                 elseif "kv" == strMode then
447                     bWeakK = true
448                     bWeakV = true
449                 end
450             end
451         end
453         -- Check if the specified object.
454         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
455             cNameAllAlias[strName] = true
456         end
458         -- Add reference and name.
459         if cAccessTag[cObject] then
460             return
461         end
463         -- Get this name.
464         cAccessTag[cObject] = true
466         -- Dump table key and value.
467         for k, v in pairs(cObject) do
468             -- Check key type.
469             local strKeyType = type(k)
470             if "table" == strKeyType then
471                 if not bWeakK then
472                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
473                 end
475                 if not bWeakV then
476                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
477                 end
478             elseif "function" == strKeyType then
479                 if not bWeakK then
480                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
481                 end
483                 if not bWeakV then
484                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
485                 end
486             elseif "thread" == strKeyType then
487                 if not bWeakK then
488                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
489                 end
491                 if not bWeakV then
492                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
493                 end
494             elseif "userdata" == strKeyType then
495                 if not bWeakK then
496                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
497                 end
499                 if not bWeakV then
500                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
501                 end
502             else
503                 CollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
504             end
505         end
507         -- Dump metatable.
508         if cMt then
509             CollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
510         end
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
519         end
521         -- Write this info.
522         if cAccessTag[cObject] then
523             return
524         end
526         -- Set name.
527         cAccessTag[cObject] = true
529         -- Get upvalues.
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)
543             end
544         end
546         -- Dump environment table.
547         local getfenv = debug.getfenv
548         if getfenv then
549             local cEnv = getfenv(cObject)
550             if cEnv then
551                 CollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
552             end
553         end
554     elseif "thread" == strType then
555         -- Check if the specified object.
556         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
557             cNameAllAlias[strName] = true
558         end
560         -- Add reference and name.
561         if cAccessTag[cObject] then
562             return
563         end
565         -- Get this name.
566         cAccessTag[cObject] = true
568         -- Dump environment table.
569         local getfenv = debug.getfenv
570         if getfenv then
571             local cEnv = getfenv(cObject)
572             if cEnv then
573                 CollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
574             end
575         end
577         -- Dump metatable.
578         local cMt = getmetatable(cObject)
579         if cMt then
580             CollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
581         end
582     elseif "userdata" == strType then
583         -- Check if the specified object.
584         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
585             cNameAllAlias[strName] = true
586         end
588         -- Add reference and name.
589         if cAccessTag[cObject] then
590             return
591         end
593         -- Get this name.
594         cAccessTag[cObject] = true
596         -- Dump environment table.
597         local getfenv = debug.getfenv
598         if getfenv then
599             local cEnv = getfenv(cObject)
600             if cEnv then
601                 CollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
602             end
603         end
605         -- Dump metatable.
606         local cMt = getmetatable(cObject)
607         if cMt then
608             CollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
609         end
610     elseif "string" == strType then
611         -- Check if the specified object.
612         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
613             cNameAllAlias[strName] = true
614         end
616         -- Add reference and name.
617         if cAccessTag[cObject] then
618             return
619         end
621         -- Get this name.
622         cAccessTag[cObject] = true
623     else
624         -- For "number" and "boolean" type, they are not object type, skip.
625     end
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)
637     -- Check results.
638     if not cDumpInfoResults then
639         return
640     end
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.
652     local cRes = {}
653     local nIdx = 0
654     for k in pairs(cRefInfo) do
655         nIdx = nIdx + 1
656         cRes[nIdx] = k
657     end
659     -- Sort result.
660     table.sort(cRes, function (l, r)
661         return cRefInfo[l] > cRefInfo[r]
662     end)
664     -- Save result to file.
665     local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
666     local cOutputHandle = nil
667     local cOutputEntry = print
669     if bOutputFile then
670         -- Check save path affix.
671         local strAffix = string.sub(strSavePath, -1)
672         if ("/" ~= strAffix) and ("\\" ~= strAffix) then
673             strSavePath = strSavePath .. "/"
674         end
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"
682                 else
683                     strFileName = strFileName .. ".txt"
684                 end
685             else
686                 if cConfig.m_bAllMemoryRefFileAddTime then
687                     strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
688                 else
689                     strFileName = strFileName .. ".txt"
690                 end
691             end
692         else
693             if cDumpInfoResultsBase then
694                 if cConfig.m_bComparedMemoryRefFileAddTime then
695                     strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
696                 else
697                     strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
698                 end
699             else
700                 if cConfig.m_bAllMemoryRefFileAddTime then
701                     strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
702                 else
703                     strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
704                 end
705             end
706         end
708         local cFile = assert(io.open(strFileName, "w"))
709         cOutputHandle = cFile
710         cOutputEntry = cFile.write
711     end
713     local cOutputer = function (strContent)
714         if cOutputHandle then
715             cOutputEntry(cOutputHandle, strContent)
716         else
717             cOutputEntry(strContent)
718         end
719     end
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")
729     else
730         cOutputer("--------------------------------------------------------\n")
731         cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
732     end
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")
741         else
742             cOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n")
743         end
744     end
746     -- Save each info.
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")
757                         else
758                             cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
759                         end
760                     else
761                         cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
762                     end
763                 end
764             else
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")
771                     else
772                         cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
773                     end
774                 else
775                     cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
776                 end
777             end
778         end
779     end
781     if bOutputFile then
782         io.close(cOutputHandle)
783         cOutputHandle = nil
784     end
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)
793     -- Check results.
794     if not cDumpInfoResults then
795         return
796     end
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
809     if bOutputFile then
810         -- Check save path affix.
811         local strAffix = string.sub(strSavePath, -1)
812         if ("/" ~= strAffix) and ("\\" ~= strAffix) then
813             strSavePath = strSavePath .. "/"
814         end
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"
821             else
822                 strFileName = strFileName .. ".txt"
823             end
824         else
825             if cConfig.m_bSingleMemoryRefFileAddTime then
826                 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
827             else
828                 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
829             end
830         end
832         local cFile = assert(io.open(strFileName, "w"))
833         cOutputHandle = cFile
834         cOutputEntry = cFile.write
835     end
837     local cOutputer = function (strContent)
838         if cOutputHandle then
839             cOutputEntry(cOutputHandle, strContent)
840         else
841             cOutputEntry(strContent)
842         end
843     end
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.
851     local nCount = 0
852     for k in pairs(cObjectAliasName) do
853         nCount = nCount + 1
854     end
856     -- Output reference count.
857     cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n")
858     cOutputer("--------------------------------------------------------\n")
860     -- Save each info.
861     for k in pairs(cObjectAliasName) do
862         if (nMaxRescords > 0) then
863             if (i <= nMaxRescords) then
864                 cOutputer(k .. "\n")
865             end
866         else
867             cOutputer(k .. "\n")
868         end
869     end
871     if bOutputFile then
872         io.close(cOutputHandle)
873         cOutputHandle = nil
874     end
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.")
885         return
886     end
888     if (not strFilter) or (0 == string.len(strFilter)) then
889         print("You need to specify a filter string.")
890         return
891     end
893     -- Read file.
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))
903                 else
904                     table.insert(cFilteredResult, strLine)
905                 end
906             end
907         else
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))
912                 else
913                     table.insert(cFilteredResult, strLine)
914                 end
915             end
916         end
917     end
919     -- Close and clear read file handle.
920     io.close(cReadFile)
921     cReadFile = nil
923     -- Write filtered result.
924     local cOutputHandle = nil
925     local cOutputEntry = print
927     if bOutputFile then
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
935     end
937     local cOutputer = function (strContent)
938         if cOutputHandle then
939             cOutputEntry(cOutputHandle, strContent)
940         else
941             cOutputEntry(strContent)
942         end
943     end
945     -- Output result.
946     for i, v in ipairs(cFilteredResult) do
947         cOutputer(v .. "\n")
948     end
950     if bOutputFile then
951         io.close(cOutputHandle)
952         cOutputHandle = nil
953     end
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.
967     if cRootObject then
968         if (not strRootObjectName) or (0 == string.len(strRootObjectName)) then
969             strRootObjectName = tostring(cRootObject)
970         end
971     else
972         cRootObject = debug.getregistry()
973         strRootObjectName = "registry"
974     end
976     -- Create container.
977     local cDumpInfoContainer = CreateObjectReferenceInfoContainer()
978     local cStackInfo = debug.getinfo(2, "Sl")
979     if cStackInfo then
980         cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
981         cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
982     end
984     -- Collect memory info.
985     CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer)
987     -- Dump the result.
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)
998     -- Dump the result.
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)
1013     -- Dump the result.
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)
1024     -- Check object.
1025     if not cObject then
1026         return
1027     end
1029     if (not strObjectName) or (0 == string.len(strObjectName)) then
1030         strObjectName = GetOriginalToStringResult(cObject)
1031     end
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")
1039     if cStackInfo then
1040         cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
1041         cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
1042     end
1044     -- Collect memory info.
1045     CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer)
1047     -- Dump the result.
1048     OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer)
1051 -- Return methods.
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