Module:Sandbox/Aidan9382/Benchmarker

-- 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 FunctionTotalTimes = {} 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 SeenModules[#SeenModules+1] = Call.Origin end ModuleTotalTimes[Call.Origin] = ModuleTotalTimes[Call.Origin] + CallTime local UniqueName = Call.Origin .. "." .. Call.Name if not FunctionTotalTimes[UniqueName] then FunctionTotalTimes[UniqueName] = 0 SeenFunctions[#SeenFunctions+1] = UniqueName end FunctionTotalTimes[UniqueName] = FunctionTotalTimes[UniqueName] + CallTime end if TotalTimeTaken > .01 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 t = dp(ModuleTotalTimes[SeenModules[i]]) mw.log(SeenModules[i] .. ": " .. t*1000 .. "ms (" .. dp(t/TotalTimeTaken, 3)*100 .. "%)")		end mw.log("\nTop 5 functions by time taken:") for i = 1, math.min(5, #SeenFunctions) do			local t = dp(FunctionTotalTimes[SeenFunctions[i]]) mw.log(SeenFunctions[i] .. ": " .. t*1000 .. "ms (" .. dp(t/TotalTimeTaken, 3)*100 .. "%)")		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, " ", 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, " ", source) end end return out end rawset(_G, "require", hookedrequire)

return HookTable