--[[----------------------------------------------------------------------------

WFLeakCheck.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2009 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

----------------------------------------------------------------------------]]--

local WFFindSymbolInUniverse = require "WFFindSymbolInUniverse"

local WFFileUtils = require "WFFileUtils"
local KSPathUtils = require "KSPathUtils"
local KSDate = require "KSDate"
local WFTimer = require "WFTimer"


--==============================================================================

local WFLeakCheck = {}

-- This namespace is meant to be used with the trio of post-processing
-- scripts found in tools/leaks.
-- For example, run a series of imports using RunImportsAndMonitorLeaks.lua
-- (setup files to import using the script tools/perf_runner/maketree.pl)
-- For each import, a new dump.txt file is created in /leaks.
-- cd to / and run count on the generated files, e.g:
--
-- /Volumes/source/perforce/yorktown_1700/sb1/tools/leaks/count.pl 87 99
--
-- that will count the files starting with dump-87.txt and going thru 99,
-- and stores the results in /counts/87.txt, /counts/88.txt, etc..
-- Next, the tool scan.pl identifies positive linear regression trends with
-- satisfactory covariation:
--
-- /Volumes/source/perforce/yorktown_1700/sb1/tools/leaks/scan.pl 87 99
--
-- This generates a summary text file in /counts/87-99.txt
--
-- You can generate a nice sparkline report from those count files using
--
-- /Volumes/source/perforce/yorktown_1700/sb1/tools/leaks/sparkWord.pl 87 99 counts/87-99.txt
--
-- Any line that shows a persistent upward trend would be a bug.

--------------------------------------------------------------------------------

local weakNameMap = setmetatable( {}, { __mode = 'kv' } )
local objId = 0

--------------------------------------------------------------------------------

local scalars = {
	string = true,
	number = true,
	boolean = true,
	[ 'nil' ] = true,
}

local function isScalar( val )
	return scalars[ type( val ) ]
end

--------------------------------------------------------------------------------

local function pToString( val )
	
	if isScalar( val ) then
		return tostring( val )
	end
	
	local message = weakNameMap[ val ]
	if not message then
	
		objId = objId + 1
		weakNameMap[ val ] = objId
		
		local success
		success, message = pcall( tostring, val )
		
		if not success then
			message = 'cant_tostring'
		end
		
		message = string.gsub( message, "[^0-9A-Za-z_]", "" )
	
		message = string.format( "%s_%d_%s", type( val ), objId, message )
		
		weakNameMap[ val ] = message
	end
	
	return message
	
end

--------------------------------------------------------------------------------

local nameForVal = pToString

--------------------------------------------------------------------------------

local function dumpToStream( stream, path, name, val )
	if path ~= nil then
		stream:write( path )
	end
	stream:write( "\t" )
	stream:write( name )
	stream:write( "\t" )
	stream:write( type( val ) )
	stream:write( "\t" )
	local str = pToString( val )
	str = string.sub( str, 0, 160 )
	str = string.gsub( str, "[\r\n\t ]+", " " )
	stream:write( str )
	stream:write( "\n" )

end

--------------------------------------------------------------------------------

local nextFileName = function( base, extension )

	for index = 1, 10000 do
		
		local result = string.format( "%s-%0.4d.%s", base, index, extension )
		if not WFFileUtils.exists( result ) then
			return result
		end
	end
	
end

--------------------------------------------------------------------------------

local dumpUniverse = function( options, containers )

	local filePath = options.filePath
	local prefix = options.prefix
	local basePath = KSPathUtils.getStandardFilePath( "caches" )
	basePath = KSPathUtils.child( basePath, "leaks.noindex" )
	WFFileUtils.mkdir( basePath )
	local file = filePath or nextFileName( KSPathUtils.child( basePath, "dump" ), "txt" )
	if not filePath then
		debug.print( file )
	end
	local stream = assert( io.open( file, filePath and "a" or "w" ) )
	
	prefix = prefix or ""
	
	dumpToStream( stream, prefix .. "LUABYTES", "lmsize", collectgarbage "count" )
	
--	local ALLOCATED_STATES = _G.ALLOCATED_STATES
--	if ALLOCATED_STATES then
--		dumpToStream( stream, prefix .. "LUABYTES", "ALLOCATED_STATES", ALLOCATED_STATES )
--	end
	
--	local AgGCCache = import "AgGCCache"
--	dumpToStream( stream, prefix .. "LUABYTES", "gcache_size", AgGCCache.main:totalCachedSize() )
	
	if options.isInitiatingUniverse then
		local now = KSDate.timeNowForAnimation
		
		dumpToStream( stream, prefix .. "LUABYTES", "elapsedSeconds", now() )
	end
	
	if not options.omit_universe then
		options.pToString = pToString
		options.nameForVal = nameForVal
		
		local lastPath
		WFFindSymbolInUniverse.iterateUniverse( function( path, name, value )
			if path == lastPath then
				path = nil
			else
				lastPath = path
				path = prefix .. path
			end
			dumpToStream( stream, path, name, value )
			
		end, containers, options )
	end
	
	stream:close()
	
	return file

end
--
----------------------------------------------------------------------------------
--
--local dumpCoverage = function()
--
--	local allCoverage = import "AgLuaCoverage".currentResults()
--
--	if allCoverage then
--
--		local file = AgFileUtils.ensureFileNameIsUnique( "/leaks.noindex/counts/coverage.txt" )
--		local stream = io.open( file, "w" )
--
--		for k,v in pairs( allCoverage ) do
--
--			for k2, count in pairs( v ) do
--
--				stream:write( pToString( k2 ) )
--				stream:write( ":" )
--				stream:write( pToString( k ) )
--				stream:write( "\t" )
--				stream:write( pToString( count ) )
--				stream:write( "\n" )
--
--			end
--
--		end
--
--	end
--
--end

--------------------------------------------------------------------------------

function WFLeakCheck.dumpThisUniverseWithOptions( options )

	if not options then
		options = {}
	end
	if not options.containers then
			options.containers = {}
	end

	local registry = debug.getregistry()
	if registry and registry._LOADED then
		for k, v in pairs( registry._LOADED ) do
			if v ~= _G and k ~= "_G" then
				k = string.gsub( k, "%..+", "" )
				options.containers[ k ] = v
			end
		end
	end

	if not options.no_purge then
--		local AgCacheControl = import "AgCacheControl"
--		AgCacheControl.purgeAllCachesNow()
--		pause( 10 )
	end

--	local delay = 3
	local mem = collectgarbage "count"
	for i = 1, options.gc_limit do
--		pause( delay )
		collectgarbage( 'collect' )
		local mem2 = collectgarbage "count"
		if mem2 == mem then
			break
		end
		mem = mem2
--		delay = delay / 2
	end
	local containers = options.containers or {}
--	for path, store in pairs( _G.ImportStoreWeakSet or {} ) do
--		containers.import_store = store
--	end

--	if options.includeLibrary then
--		local AgLibrary = import "AgLibrary"
--		containers.library = AgLibrary.activeCatalog
--	end
	
	return dumpUniverse( options, containers )

end

--------------------------------------------------------------------------------

local function dumpWithOptions( options )

	options.gc_limit = options.gc_limit or 3

	debug.print "Dumping main Universe"
	local filePath = WFLeakCheck.dumpThisUniverseWithOptions( options )
	debug.print "DONE Dumping main Universe"
	options.filePath = filePath
	options.containers = nil
	options.pToString = nil
	options.nameForVal = nil
	
	require 'WFDiagnosticLogNative'.dumpAllUniverses( options )

end

--------------------------------------------------------------------------------

function WFLeakCheck.begin( )

	if WFLeakCheck.WALKING then
		debug.print "Stopping leak check"
		WFLeakCheck.WALKING = false
	else
		debug.print "Starting leak check"
		WFLeakCheck.WALKING = true
		local doOnce
		doOnce = function()
			if WFLeakCheck.WALKING then
				dumpWithOptions {}
				WFTimer.afterDelayDo( 15, doOnce )
			end
	
		end
		doOnce()
	end
end

--==============================================================================

return WFLeakCheck
