Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Sandbox/Aidan9382/Benchmarker

From Wikipedia, the free encyclopedia

-- 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 = SeenModules[i]
			local t = dp(ModuleTotalTimes[name])
			mw.log(name .. ": " .. t*1000 .. "ms (" .. dp(t/TotalTimeTaken, 3)*100 .. "%) in " .. ModuleCallCount[name] .. " 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[name])
			mw.log(name .. ": " .. t*1000 .. "ms (" .. dp(t/TotalTimeTaken, 3)*100 .. "%) in " .. FunctionCallCount[name] .. " calls")
		end
		mw.log("") -- extra newline
	end
	CompleteCalls = {}
end

local function HookFunction(f, fname, origin)
	if not NoHookZone[f] then
		local out = function(...)
			-- Caller stats aren't used right now, so keep performance up
			-- 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 HookGeneric(obj, origin)
	assert(type(origin) == "string", "Attempted to init benchmarking with a non-string origin")
	-- safety catch since we export this function
	if type(obj) == "function" then
		return HookFunction(obj, "<main>", origin)
	elseif type(obj) == "table" then
		for a, b in next, obj do
			if type(b) == "function" then
				obj[a] = HookFunction(b, a, origin)
			end
		end
		return obj
	else
		error("Attempted to init benchmarking on an invalid object type")
	end
end
rawset(_G, "_BenchmarkerHooker", HookGeneric)

--== 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
			HookGeneric(out, source)
		elseif type(out) == "function" then
			out = HookFunction(out, "<main>", source)
		end
	end
	return out
end
rawset(_G, "require", hookedrequire)

return HookGeneric