Module:Sandbox/Aidan9382/Benchmarker: Difference between revisions – Wikipedia

Line 39: Line 39:

local TotalTimeTaken = 0

local TotalTimeTaken = 0

local ModuleTotalTimes = {}

local ModuleTotalTimes = {}

local ModuleCallCount = {}

local FunctionTotalTimes = {}

local FunctionTotalTimes = {}

local FunctionCallCount = {}

local SeenModules = {}

local SeenModules = {}

local SeenFunctions = {}

local SeenFunctions = {}

Line 47: Line 49:

if not ModuleTotalTimes[Call.Origin] then

if not ModuleTotalTimes[Call.Origin] then

ModuleTotalTimes[Call.Origin] = 0

ModuleTotalTimes[Call.Origin] = 0

ModuleCallCount[Call.Origin] = 0

SeenModules[#SeenModules+1] = Call.Origin

SeenModules[#SeenModules+1] = Call.Origin

end

end

ModuleTotalTimes[Call.Origin] = ModuleTotalTimes[Call.Origin] + CallTime

ModuleTotalTimes[Call.Origin] = ModuleTotalTimes[Call.Origin] + CallTime

ModuleCallCount[Call.Origin] = ModuleCallCount[Call.Origin] + 1

local UniqueName = Call.Origin .. “.” .. Call.Name

local UniqueName = Call.Origin .. “.” .. Call.Name

if not FunctionTotalTimes[UniqueName] then

if not FunctionTotalTimes[UniqueName] then

FunctionTotalTimes[UniqueName] = 0

FunctionTotalTimes[UniqueName] = 0

FunctionCallCount[UniqueName] = 0

SeenFunctions[#SeenFunctions+1] = UniqueName

SeenFunctions[#SeenFunctions+1] = UniqueName

end

end

FunctionTotalTimes[UniqueName] = FunctionTotalTimes[UniqueName] + CallTime

FunctionTotalTimes[UniqueName] = FunctionTotalTimes[UniqueName] + CallTime

FunctionCallCount[UniqueName] = FunctionCallCount[UniqueName] + 1

end

end

local MinTimeTaken = rawget(_G, “_MinTimeTaken”) or 0.01

local MinTimeTaken = rawget(_G, “_MinTimeTaken”) or 0.01

Line 69: Line 75:

mw.log(“\nTop 5 modules by time taken:”)

mw.log(“\nTop 5 modules by time taken:”)

for i = 1, math.min(5, #SeenModules) do

for i = 1, math.min(5, #SeenModules) do

local t = dp(ModuleTotalTimes[SeenModules[i]])

local = [i]

local t = dp(ModuleTotalTimes[name])

mw.log(SeenModules[i] .. “: ” .. t*1000 .. “ms (” .. dp(t/TotalTimeTaken, 3)*100 .. “%)”)

mw.log( .. “: ” .. t*1000 .. “ms (” .. dp(t/TotalTimeTaken, 3)*100 .. “%)”)

end

end

mw.log(“\nTop 5 functions by time taken:”)

mw.log(“\nTop 5 functions by time taken:”)

for i = 1, math.min(5, #SeenFunctions) do

for i = 1, math.min(5, #SeenFunctions) do

local name = SeenFunctions[i]

local t = dp(FunctionTotalTimes[SeenFunctions[i]])

local t = dp(FunctionTotalTimes[SeenFunctions[i]])

mw.log(SeenFunctions[i] .. “: ” .. t*1000 .. “ms (” .. dp(t/TotalTimeTaken, 3)*100 .. “%)”)

mw.log( .. “: ” .. t*1000 .. “ms (” .. dp(t/TotalTimeTaken, 3)*100 .. “%)”)

end

end

mw.log(“”) — extra newline

mw.log(“”) — extra newline

-- In-depth execution speed benchmarker - read the /doc for more info

-- =================================================================== --
-- This is a meta-module that hooks globals, which could be disruptive --
--        Be careful including this module outside of sandboxes        --
-- =================================================================== --

-- Always use rawget/rawset on _G to bypass strict
local ActiveHooker = rawget(_G, "_BenchmarkerHooker")
if ActiveHooker ~= nil then
	return ActiveHooker
end

--== Personal stuff ==--
local function dp(x, n)
	n = n or 4
	return math.floor(x*10^n+0.5) / 10^n
end

local function GetVarargInfo(...)
	return {...}, select("#", ...)
end

local function DetermineCaller(stacktrace)
	for line in stacktrace:gmatch("[^\n]+") do
		if not line:find("^stack traceback:") and not line:find("Aidan9382/Benchmarker") then
			local f, l = line:match("^%s*([^:]+):([^:]+)")
			return {Function=f, Line=tonumber(l)}
		end
	end
end

local CompleteCalls = {}
local FunctionCallStack = {}
local NoHookZone = {}

local function FinishUp()
	-- Note: Don't currently use caller stats. Eh, whatever
	local TotalTimeTaken = 0
	local ModuleTotalTimes = {}
	local ModuleCallCount = {}
	local FunctionTotalTimes = {}
	local FunctionCallCount = {}
	local SeenModules = {}
	local SeenFunctions = {}
	for _, Call in next, CompleteCalls do
		local CallTime = Call.TimeTaken - Call.Offset
		TotalTimeTaken = TotalTimeTaken + CallTime
		if not ModuleTotalTimes[Call.Origin] then
			ModuleTotalTimes[Call.Origin] = 0
			ModuleCallCount[Call.Origin] = 0
			SeenModules[#SeenModules+1] = Call.Origin
		end
		ModuleTotalTimes[Call.Origin] = ModuleTotalTimes[Call.Origin] + CallTime
		ModuleCallCount[Call.Origin] = ModuleCallCount[Call.Origin] + 1
		local UniqueName = Call.Origin .. "." .. Call.Name
		if not FunctionTotalTimes[UniqueName] then
			FunctionTotalTimes[UniqueName] = 0
			FunctionCallCount[UniqueName] = 0
			SeenFunctions[#SeenFunctions+1] = UniqueName
		end
		FunctionTotalTimes[UniqueName] = FunctionTotalTimes[UniqueName] + CallTime
		FunctionCallCount[UniqueName] = FunctionCallCount[UniqueName] + 1
	end
	local MinTimeTaken = rawget(_G, "_MinTimeTaken") or 0.01
	if TotalTimeTaken > MinTimeTaken then
		table.sort(SeenModules, function(a, b)
			return ModuleTotalTimes[a] > ModuleTotalTimes[b]
		end)
		table.sort(SeenFunctions, function(a, b)
			return FunctionTotalTimes[a] > FunctionTotalTimes[b]
		end)
		mw.log("\n-- Benchmarker Finished --")
		mw.log("Total time taken: " .. dp(TotalTimeTaken)*1000 .. "ms")
		mw.log("\nTop 5 modules by time taken:")
		for i = 1, math.min(5, #SeenModules) do
			local name = SeenFunctions[i]
			local t = dp(ModuleTotalTimes[name])
			mw.log(name .. ": " .. t*1000 .. "ms (" .. dp(t/TotalTimeTaken, 3)*100 .. "%) in " .. ModuleCallCount[i] .. " calls")
		end
		mw.log("\nTop 5 functions by time taken:")
		for i = 1, math.min(5, #SeenFunctions) do
			local name = SeenFunctions[i]
			local t = dp(FunctionTotalTimes[SeenFunctions[i]])
			mw.log(name .. ": " .. t*1000 .. "ms (" .. dp(t/TotalTimeTaken, 3)*100 .. "%) in " .. FunctionCallCount[i] .. " calls")
		end
		mw.log("") -- extra newline
	end
	CompleteCalls = {}
end

local function HookFunction(f, fname, origin)
	if not NoHookZone[f] then
		local out = function(...)
			local callerinfo = DetermineCaller(debug.traceback())
			local StackObject = {
				Name=fname, Origin=origin, Offset=0,
				Caller=callerinfo.Function, CallLine=callerinfo.Line
			}
			FunctionCallStack[#FunctionCallStack+1] = StackObject
			local s = os.clock()
			local response, length = GetVarargInfo(f(...))
			local timetaken = os.clock() - s
			StackObject.TimeTaken = timetaken
			CompleteCalls[#CompleteCalls+1] = StackObject
			local maxi = #FunctionCallStack
			FunctionCallStack[maxi] = nil
			if maxi == 1 then
				FinishUp()
			else
				FunctionCallStack[maxi-1].Offset = FunctionCallStack[maxi-1].Offset + timetaken
			end
			return unpack(response, 1, length)
		end
		NoHookZone[out] = true
		return out
	else
		return f
	end
end
local function HookTable(obj, origin)
	-- safety catch since we export this function
	if type(obj) == "function" then
		return HookFunction(obj, "<main>", origin)
	end
	for a, b in next, obj do
		if type(b) == "function" then
			obj[a] = HookFunction(b, a, origin)
		end
	end
	return obj
end
rawset(_G, "_BenchmarkerHooker", HookTable)

--== Global hooking ==--
local require = require
local function hookedrequire(source)
	local out = require(source)
	if source ~= "strict" and source ~= "Module:Sandbox/Aidan9382/Benchmarker" then
		if type(out) == "table" then
			HookTable(out, source)
		elseif type(out) == "function" then
			out = HookFunction(out, "<main>", source)
		end
	end
	return out
end
rawset(_G, "require", hookedrequire)

return HookTable

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top