register a new add-on tool
[view.love.git] / MemoryReferenceInfo.lua.unused
blobad9402eb02aa914185ddf5991f634549be3a434a
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       print('tostring overridden:', tostring(cObject))
41 --?         rawset(cMt, "__tostring", nil)
42 --?         strName = tostring(cObject)
43 --?         rawset(cMt, "__tostring", cToString)
44 --?     else
45 --?         strName = tostring(cObject)
46     end
47     strName = tostring(cObject)
49     return strName
50 end
52 -- Create a container to collect the mem ref info results.
53 local function CreateObjectReferenceInfoContainer()
54     -- Create new container.
55     local cContainer = {}
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"})
65     -- Set members.
66     cContainer.m_cObjectReferenceCount = cObjectReferenceCount
67     cContainer.m_cObjectAddressToName = cObjectAddressToName
69     -- For stack info.
70     cContainer.m_nStackLevel = -1
71     cContainer.m_strShortSrc = "None"
72     cContainer.m_nCurrentLine = -1
74     return cContainer
75 end
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
84     -- Cache ref info.
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+)")
94             if strAddr then
95                 cRefInfo[strAddr] = strRefCount
96                 cNameInfo[strAddr] = strName
97             end
98         end
99     end
101     -- Close and clear file handler.
102     io.close(cFile)
103     cFile = nil
105     return cContainer
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"})
126     -- Set members.
127     cContainer.m_cObjectExistTag = cObjectExistTag
128     cContainer.m_cObjectAliasName = cObjectAliasName
129     cContainer.m_cObjectAccessTag = cObjectAccessTag
131     -- For stack info.
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
141     return cContainer
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)
149     if not cObject then
150         return
151     end
153     if not strName then
154         strName = ""
155     end
157     -- Check container.
158     if (not cDumpInfoContainer) then
159         cDumpInfoContainer = CreateObjectReferenceInfoContainer()
160     end
162     -- Check stack.
163     if cDumpInfoContainer.m_nStackLevel > 0 then
164         local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
165         if cStackInfo then
166             cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
167             cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
168         end
170         cDumpInfoContainer.m_nStackLevel = -1
171     end
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 .. "]"
183             end
184         elseif rawget(cObject, "class") then
185             if "string" == type(cObject.class) then
186                 strName = strName .. "[class:" .. cObject.class .. "]"
187             end
188         elseif rawget(cObject, "_className") then
189             if "string" == type(cObject._className) then
190                 strName = strName .. "[class:" .. cObject._className .. "]"
191             end
192         end
194         -- Check if table is _G.
195         if cObject == _G then
196             strName = strName .. "[_G]"
197         end
199         -- Get metatable.
200         local bWeakK = false
201         local bWeakV = false
202         local cMt = getmetatable(cObject)
203         if cMt then
204             -- Check mode.
205             local strMode = rawget(cMt, "__mode")
206             if strMode then
207                 if "k" == strMode then
208                     bWeakK = true
209                 elseif "v" == strMode then
210                     bWeakV = true
211                 elseif "kv" == strMode then
212                     bWeakK = true
213                     bWeakV = true
214                 end
215             end
216         end
218         -- Add reference and name.
219         cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
220         if cNameInfoContainer[cObject] then
221             return
222         end
224         -- Set name.
225         cNameInfoContainer[cObject] = strName
227         -- Dump table key and value.
228         for k, v in pairs(cObject) do
229             -- Check key type.
230             local strKeyType = type(k)
231             if "table" == strKeyType then
232                 if not bWeakK then
233                     CollectObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
234                 end
236                 if not bWeakV then
237                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
238                 end
239             elseif "function" == strKeyType then
240                 if not bWeakK then
241                     CollectObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
242                 end
244                 if not bWeakV then
245                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
246                 end
247             elseif "thread" == strKeyType then
248                 if not bWeakK then
249                     CollectObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
250                 end
252                 if not bWeakV then
253                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
254                 end
255             elseif "userdata" == strKeyType then
256                 if not bWeakK then
257                     CollectObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
258                 end
260                 if not bWeakV then
261                     CollectObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
262                 end
263             else
264                 CollectObjectReferenceInMemory(strName .. "." .. tostring(k), v, cDumpInfoContainer)
265             end
266         end
268         -- Dump metatable.
269         if cMt then
270             CollectObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
271         end
272     elseif "function" == strType then
273         -- Get function info.
274         local cDInfo = debug.getinfo(cObject, "Su")
276         -- Write this info.
277         cRefInfoContainer[cObject] = (cRefInfoContainer[cObject] and (cRefInfoContainer[cObject] + 1)) or 1
278         if cNameInfoContainer[cObject] then
279             return
280         end
282         -- Set name.
283         cNameInfoContainer[cObject] = strName .. "[line:" .. tostring(cDInfo.linedefined) .. "@file:" .. cDInfo.short_src .. "]"
285         -- Get upvalues.
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)
299             end
300         end
302         -- Dump environment table.
303         local getfenv = debug.getfenv
304         if getfenv then
305             local cEnv = getfenv(cObject)
306             if cEnv then
307                 CollectObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
308             end
309         end
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
314             return
315         end
317         -- Set name.
318         cNameInfoContainer[cObject] = strName
320         -- Dump environment table.
321         local getfenv = debug.getfenv
322         if getfenv then
323             local cEnv = getfenv(cObject)
324             if cEnv then
325                 CollectObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
326             end
327         end
329         -- Dump metatable.
330         local cMt = getmetatable(cObject)
331         if cMt then
332             CollectObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
333         end
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
338             return
339         end
341         -- Set name.
342         cNameInfoContainer[cObject] = strName
344         -- Dump environment table.
345         local getfenv = debug.getfenv
346         if getfenv then
347             local cEnv = getfenv(cObject)
348             if cEnv then
349                 CollectObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
350             end
351         end
353         -- Dump metatable.
354         local cMt = getmetatable(cObject)
355         if cMt then
356             CollectObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
357         end
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
362             return
363         end
365         -- Set name.
366         cNameInfoContainer[cObject] = strName .. "[" .. strType .. "]"
367     else
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
373         --  return
374         -- end
376         -- -- Set name.
377         -- cNameInfoContainer[cObject] = strName .. "[" .. strType .. ":" .. tostring(cObject) .. "]"
378     end
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)
386     if not cObject then
387         return
388     end
390     if not strName then
391         strName = ""
392     end
394     -- Check container.
395     if (not cDumpInfoContainer) then
396         cDumpInfoContainer = CreateObjectReferenceInfoContainer()
397     end
399     -- Check stack.
400     if cDumpInfoContainer.m_nStackLevel > 0 then
401         local cStackInfo = debug.getinfo(cDumpInfoContainer.m_nStackLevel, "Sl")
402         if cStackInfo then
403             cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
404             cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
405         end
407         cDumpInfoContainer.m_nStackLevel = -1
408     end
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 .. "]"
420             end
421         elseif rawget(cObject, "class") then
422             if "string" == type(cObject.class) then
423                 strName = strName .. "[class:" .. cObject.class .. "]"
424             end
425         elseif rawget(cObject, "_className") then
426             if "string" == type(cObject._className) then
427                 strName = strName .. "[class:" .. cObject._className .. "]"
428             end
429         end
431         -- Check if table is _G.
432         if cObject == _G then
433             strName = strName .. "[_G]"
434         end
436         -- Get metatable.
437         local bWeakK = false
438         local bWeakV = false
439         local cMt = getmetatable(cObject)
440         if cMt then
441             -- Check mode.
442             local strMode = rawget(cMt, "__mode")
443             if strMode then
444                 if "k" == strMode then
445                     bWeakK = true
446                 elseif "v" == strMode then
447                     bWeakV = true
448                 elseif "kv" == strMode then
449                     bWeakK = true
450                     bWeakV = true
451                 end
452             end
453         end
455         -- Check if the specified object.
456         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
457             cNameAllAlias[strName] = true
458         end
460         -- Add reference and name.
461         if cAccessTag[cObject] then
462             return
463         end
465         -- Get this name.
466         cAccessTag[cObject] = true
468         -- Dump table key and value.
469         for k, v in pairs(cObject) do
470             -- Check key type.
471             local strKeyType = type(k)
472             if "table" == strKeyType then
473                 if not bWeakK then
474                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.table]", k, cDumpInfoContainer)
475                 end
477                 if not bWeakV then
478                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
479                 end
480             elseif "function" == strKeyType then
481                 if not bWeakK then
482                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.function]", k, cDumpInfoContainer)
483                 end
485                 if not bWeakV then
486                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
487                 end
488             elseif "thread" == strKeyType then
489                 if not bWeakK then
490                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.thread]", k, cDumpInfoContainer)
491                 end
493                 if not bWeakV then
494                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
495                 end
496             elseif "userdata" == strKeyType then
497                 if not bWeakK then
498                     CollectSingleObjectReferenceInMemory(strName .. ".[table:key.userdata]", k, cDumpInfoContainer)
499                 end
501                 if not bWeakV then
502                     CollectSingleObjectReferenceInMemory(strName .. ".[table:value]", v, cDumpInfoContainer)
503                 end
504             else
505                 CollectSingleObjectReferenceInMemory(strName .. "." .. k, v, cDumpInfoContainer)
506             end
507         end
509         -- Dump metatable.
510         if cMt then
511             CollectSingleObjectReferenceInMemory(strName ..".[metatable]", cMt, cDumpInfoContainer)
512         end
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
521         end
523         -- Write this info.
524         if cAccessTag[cObject] then
525             return
526         end
528         -- Set name.
529         cAccessTag[cObject] = true
531         -- Get upvalues.
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)
545             end
546         end
548         -- Dump environment table.
549         local getfenv = debug.getfenv
550         if getfenv then
551             local cEnv = getfenv(cObject)
552             if cEnv then
553                 CollectSingleObjectReferenceInMemory(strName ..".[function:environment]", cEnv, cDumpInfoContainer)
554             end
555         end
556     elseif "thread" == strType then
557         -- Check if the specified object.
558         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
559             cNameAllAlias[strName] = true
560         end
562         -- Add reference and name.
563         if cAccessTag[cObject] then
564             return
565         end
567         -- Get this name.
568         cAccessTag[cObject] = true
570         -- Dump environment table.
571         local getfenv = debug.getfenv
572         if getfenv then
573             local cEnv = getfenv(cObject)
574             if cEnv then
575                 CollectSingleObjectReferenceInMemory(strName ..".[thread:environment]", cEnv, cDumpInfoContainer)
576             end
577         end
579         -- Dump metatable.
580         local cMt = getmetatable(cObject)
581         if cMt then
582             CollectSingleObjectReferenceInMemory(strName ..".[thread:metatable]", cMt, cDumpInfoContainer)
583         end
584     elseif "userdata" == strType then
585         -- Check if the specified object.
586         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
587             cNameAllAlias[strName] = true
588         end
590         -- Add reference and name.
591         if cAccessTag[cObject] then
592             return
593         end
595         -- Get this name.
596         cAccessTag[cObject] = true
598         -- Dump environment table.
599         local getfenv = debug.getfenv
600         if getfenv then
601             local cEnv = getfenv(cObject)
602             if cEnv then
603                 CollectSingleObjectReferenceInMemory(strName ..".[userdata:environment]", cEnv, cDumpInfoContainer)
604             end
605         end
607         -- Dump metatable.
608         local cMt = getmetatable(cObject)
609         if cMt then
610             CollectSingleObjectReferenceInMemory(strName ..".[userdata:metatable]", cMt, cDumpInfoContainer)
611         end
612     elseif "string" == strType then
613         -- Check if the specified object.
614         if cExistTag[cObject] and (not cNameAllAlias[strName]) then
615             cNameAllAlias[strName] = true
616         end
618         -- Add reference and name.
619         if cAccessTag[cObject] then
620             return
621         end
623         -- Get this name.
624         cAccessTag[cObject] = true
625     else
626         -- For "number" and "boolean" type, they are not object type, skip.
627     end
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)
639     -- Check results.
640     if not cDumpInfoResults then
641         return
642     end
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.
654     local cRes = {}
655     local nIdx = 0
656     for k in pairs(cRefInfo) do
657         nIdx = nIdx + 1
658         cRes[nIdx] = k
659     end
661     -- Sort result.
662     table.sort(cRes, function (l, r)
663         return cRefInfo[l] > cRefInfo[r]
664     end)
666     -- Save result to file.
667     local bOutputFile = strSavePath and (string.len(strSavePath) > 0)
668     local cOutputHandle = nil
669     local cOutputEntry = print
671     if bOutputFile then
672         -- Check save path affix.
673         local strAffix = string.sub(strSavePath, -1)
674         if ("/" ~= strAffix) and ("\\" ~= strAffix) then
675             strSavePath = strSavePath .. "/"
676         end
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"
684                 else
685                     strFileName = strFileName .. ".txt"
686                 end
687             else
688                 if cConfig.m_bAllMemoryRefFileAddTime then
689                     strFileName = strFileName .. "-[" .. strDateTime .. "].txt"
690                 else
691                     strFileName = strFileName .. ".txt"
692                 end
693             end
694         else
695             if cDumpInfoResultsBase then
696                 if cConfig.m_bComparedMemoryRefFileAddTime then
697                     strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
698                 else
699                     strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
700                 end
701             else
702                 if cConfig.m_bAllMemoryRefFileAddTime then
703                     strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
704                 else
705                     strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
706                 end
707             end
708         end
710         local cFile = assert(io.open(strFileName, "w"))
711         cOutputHandle = cFile
712         cOutputEntry = cFile.write
713     end
715     local cOutputer = function (strContent)
716         if cOutputHandle then
717             cOutputEntry(cOutputHandle, strContent)
718         else
719             cOutputEntry(strContent)
720         end
721     end
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")
731     else
732         cOutputer("--------------------------------------------------------\n")
733         cOutputer("-- Collect memory reference at line:" .. tostring(cDumpInfoResults.m_nCurrentLine) .. "@file:" .. cDumpInfoResults.m_strShortSrc .. "\n")
734     end
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")
743         else
744             cOutputer("-- From Root Object: " .. GetOriginalToStringResult(cRootObject) .. " (" .. strRootObjectName .. ")\n")
745         end
746     end
748     -- Save each info.
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")
759                         else
760                             cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
761                         end
762                     else
763                         cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
764                     end
765                 end
766             else
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")
773                     else
774                         cOutputer(tostring(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
775                     end
776                 else
777                     cOutputer(GetOriginalToStringResult(v) .. "\t" .. cNameInfo[v] .. "\t" .. tostring(cRefInfo[v]) .. "\n")
778                 end
779             end
780         end
781     end
783     if bOutputFile then
784         io.close(cOutputHandle)
785         cOutputHandle = nil
786     end
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)
795     -- Check results.
796     if not cDumpInfoResults then
797         return
798     end
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
811     if bOutputFile then
812         -- Check save path affix.
813         local strAffix = string.sub(strSavePath, -1)
814         if ("/" ~= strAffix) and ("\\" ~= strAffix) then
815             strSavePath = strSavePath .. "/"
816         end
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"
823             else
824                 strFileName = strFileName .. ".txt"
825             end
826         else
827             if cConfig.m_bSingleMemoryRefFileAddTime then
828                 strFileName = strFileName .. "-[" .. strDateTime .. "]-[" .. strExtraFileName .. "].txt"
829             else
830                 strFileName = strFileName .. "-[" .. strExtraFileName .. "].txt"
831             end
832         end
834         local cFile = assert(io.open(strFileName, "w"))
835         cOutputHandle = cFile
836         cOutputEntry = cFile.write
837     end
839     local cOutputer = function (strContent)
840         if cOutputHandle then
841             cOutputEntry(cOutputHandle, strContent)
842         else
843             cOutputEntry(strContent)
844         end
845     end
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.
853     local nCount = 0
854     for k in pairs(cObjectAliasName) do
855         nCount = nCount + 1
856     end
858     -- Output reference count.
859     cOutputer("-- For Object: " .. cDumpInfoResults.m_strAddressName .. " (" .. cDumpInfoResults.m_strObjectName .. "), have " .. tostring(nCount) .. " reference in total.\n")
860     cOutputer("--------------------------------------------------------\n")
862     -- Save each info.
863     for k in pairs(cObjectAliasName) do
864         if (nMaxRescords > 0) then
865             if (i <= nMaxRescords) then
866                 cOutputer(k .. "\n")
867             end
868         else
869             cOutputer(k .. "\n")
870         end
871     end
873     if bOutputFile then
874         io.close(cOutputHandle)
875         cOutputHandle = nil
876     end
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.")
887         return
888     end
890     if (not strFilter) or (0 == string.len(strFilter)) then
891         print("You need to specify a filter string.")
892         return
893     end
895     -- Read file.
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))
905                 else
906                     table.insert(cFilteredResult, strLine)
907                 end
908             end
909         else
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))
914                 else
915                     table.insert(cFilteredResult, strLine)
916                 end
917             end
918         end
919     end
921     -- Close and clear read file handle.
922     io.close(cReadFile)
923     cReadFile = nil
925     -- Write filtered result.
926     local cOutputHandle = nil
927     local cOutputEntry = print
929     if bOutputFile then
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
937     end
939     local cOutputer = function (strContent)
940         if cOutputHandle then
941             cOutputEntry(cOutputHandle, strContent)
942         else
943             cOutputEntry(strContent)
944         end
945     end
947     -- Output result.
948     for i, v in ipairs(cFilteredResult) do
949         cOutputer(v .. "\n")
950     end
952     if bOutputFile then
953         io.close(cOutputHandle)
954         cOutputHandle = nil
955     end
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.
969     if cRootObject then
970         if (not strRootObjectName) or (0 == string.len(strRootObjectName)) then
971             strRootObjectName = tostring(cRootObject)
972         end
973     else
974         cRootObject = debug.getregistry()
975         strRootObjectName = "registry"
976     end
978     -- Create container.
979     local cDumpInfoContainer = CreateObjectReferenceInfoContainer()
980     local cStackInfo = debug.getinfo(2, "Sl")
981     if cStackInfo then
982         cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
983         cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
984     end
986     -- Collect memory info.
987     CollectObjectReferenceInMemory(strRootObjectName, cRootObject, cDumpInfoContainer)
989     -- Dump the result.
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)
1000     -- Dump the result.
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)
1015     -- Dump the result.
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)
1026     -- Check object.
1027     if not cObject then
1028         return
1029     end
1031     if (not strObjectName) or (0 == string.len(strObjectName)) then
1032         strObjectName = GetOriginalToStringResult(cObject)
1033     end
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")
1041     if cStackInfo then
1042         cDumpInfoContainer.m_strShortSrc = cStackInfo.short_src
1043         cDumpInfoContainer.m_nCurrentLine = cStackInfo.currentline
1044     end
1046     -- Collect memory info.
1047     CollectSingleObjectReferenceInMemory("registry", debug.getregistry(), cDumpInfoContainer)
1049     -- Dump the result.
1050     OutputMemorySnapshotSingleObject(strSavePath, strExtraFileName, nMaxRescords, cDumpInfoContainer)
1053 -- Return methods.
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