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

KSLeaksImp.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2011 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.

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

--[[

KSLeaks:

	This module provides support for leak tracking. It provides a single
	API to call to announce the existence of an object of a particular
	kind. It will generate debug log entries that track when the count goes
	up. This only works with table-based objects.
	
	function KSLeaks.registerObject( kind, obj )
		kind must be a string.
		obj must be nil or a table.

	[scouten 2012-06-20]: Split out the body of KSLeaks.lua. In non-debug builds
	we don't use this code, so no point in storing the compiled code.

]]

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

local assert = assert
local pairs = pairs
local type = type
local setmetatable = setmetatable

local string_format = string.format
local debug_print = debug.print
local debug_getinfo = debug.getinfo
local string_match = string.match

local KSTableUtils = require 'KSTableUtils'
local KSTableUtils_makeWithMode = assert( KSTableUtils.makeWithMode )

local KSFunctionUtils = require 'KSFunctionUtils'

local WFCurrentThread = require 'WFCurrentThread'

local BASE_THRESHOLD = 100

local CHECK_FACTOR = 1.5 -- Factor by which we need to exceed the report threshold
local GROWTH_FACTOR = 1.1 -- Factor by which we raise the threshold

local PRINT_ALL = false -- Show all uses when we generate a report (otherwise print max)

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

-- Skip these source files when computing where the object was allocated

local kUtilitySources = {
	[ 'Class.lua' ] = true, -- Rx.Class
	[ 'KSReactive.lua' ] = true,
	[ 'KSRDirectedAcyclicGraph.lua' ] = true,
	[ 'KSLeaks.lua' ] = true,
	[ 'WFCloseSupport.lua' ] = true,
	[ 'KSFunctionUtils.lua' ] = true,
	[ 'WFWorkerThread.lua' ] = true,
	[ 'WFRequestDispatcher.lua' ] = true,
}

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

local KSLeaks = {}

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

-- Information for a particular kind. We track the objects (and their creation
-- location), the count of objects added to the objects set, and the reporting
-- threshold.

local function makeKindInfo()
	return {
		objects = KSTableUtils_makeWithMode( "k" ),
		count = 0,
		threshold = BASE_THRESHOLD,
		checkThreshold = BASE_THRESHOLD * CHECK_FACTOR
	}
end

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

-- Mapping from kind to info

local kindInfos = setmetatable( { }, {
	__index = function( t, k ) t[ k ] = makeKindInfo(); return t[ k ] end
} )

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

-- Getting the trailing name from a source path

local shortSourceName = KSFunctionUtils.memoizeFn( function( longName )
	local shortName = string_match( longName, '.*/([^/]+)$' )
	return tostring( shortName or longName )
end )
	
--------------------------------------------------------------------------------

local function formatLocation( source, line )
	return string_format( "%s:%d", source, line )
end
	
--------------------------------------------------------------------------------

local function computeWhere()

	local index = 3
	local prevSource = ""
	local prevLine = -1
	
	while true do
		local info = debug_getinfo( index, "Sl" )
		local sourceName = info and info.source
		if not sourceName then
			return formatLocation( prevSource, prevLine )
		end
		if info.what ~= "C" then
			prevSource = shortSourceName( info.source )
			prevLine = info.currentline or -1
			if not kUtilitySources[ prevSource ] then
				return formatLocation( prevSource, prevLine )
			end
		end
		index = index + 1
	end
	
end

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

local function report( kind, info )

	local locCounts = setmetatable( { }, {
		__index = function( t, k ) t[ k ] = 0; return 0 end
	} )
	
	for obj, loc in pairs( info.objects ) do
		locCounts[ loc ] = locCounts[ loc ] + 1
	end
	
	debug_print( string_format( "Leak report for thread: %s", WFCurrentThread.DEBUG_getThreadName() ) )
	
	if PRINT_ALL then
	
		debug_print( string_format( "New count (%s): %d", kind, info.count ) )
		
		for loc, count in pairs( locCounts ) do
			debug_print( string_format( "> [ %s ]: %d", loc, count ) )
		end
		
	else
	
		local maxLoc, maxCount = "", 0
		
		for loc, count in pairs( locCounts ) do
			if maxCount < count then
				maxLoc, maxCount = loc, count
			end
		end
	
		debug_print( string_format( "New count (%s): %d (max: %s @ %d)",
				kind, info.count, maxLoc, maxCount ) )

	end

end

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

function KSLeaks.registerObject( kind, obj )

	if not obj then
		return
	end
	
	assert( type( obj ) == 'table', "Table expected" )
	
	assert( type( kind ) == 'string', "String expected" )
	
	local where = computeWhere()
	
	local info = kindInfos[ kind ]
	
	info.objects[ obj ] = where
	
	local count = info.count + 1
	
	info.count = count

	if info.checkThreshold <= count then
		count = 0
		for _ in pairs( info.objects ) do
			count = count + 1
		end
		info.count = count
		local threshold = info.threshold
		if threshold <= count then
			report( kind, info )
			threshold = threshold * GROWTH_FACTOR
			info.threshold = threshold
			info.checkThreshold = threshold * CHECK_FACTOR
		end
	end

end

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

return KSLeaks
