mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
Upload
This commit is contained in:
267
lua/fprofiler/gather.lua
Normal file
267
lua/fprofiler/gather.lua
Normal file
@@ -0,0 +1,267 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local timeMeasurementFunc = SysTime
|
||||
|
||||
-- Helper function
|
||||
-- Get all local variables
|
||||
local NIL = {}
|
||||
setmetatable(NIL, {__tostring = function() return "nil" end})
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Call counts:
|
||||
|
||||
registers how often function have been called
|
||||
---------------------------------------------------------------------------]]
|
||||
local callcounts = {}
|
||||
|
||||
|
||||
-- Gets the call counts
|
||||
FProfiler.Internal.getCallCounts = function() return callcounts end
|
||||
|
||||
|
||||
-- Resets the call counts
|
||||
function FProfiler.Internal.resetCallCounts()
|
||||
callcounts = {}
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Inclusive function times
|
||||
|
||||
Keeps track of how long functions take in total
|
||||
i.e. average time between the start and return of a function * times called
|
||||
|
||||
This includes the time that any function called by this function takes
|
||||
(that's what the "inclusive" refers to).
|
||||
Note: recursive calls are not counted double
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local inclusiveTimes = {}
|
||||
|
||||
-- Gets the inclusive times
|
||||
FProfiler.Internal.getInclusiveTimes = function() return inclusiveTimes end
|
||||
|
||||
-- Resets the inclusive times
|
||||
function FProfiler.Internal.resetInclusiveTimes()
|
||||
inclusiveTimes = {}
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Top n most expensive single function calls
|
||||
Keeps track of the functions that took the longest time to run
|
||||
Note: functions can appear in this list at most once
|
||||
---------------------------------------------------------------------------]]
|
||||
local mostExpensiveSingleCalls = {}
|
||||
|
||||
-- Gets most expensive single calls
|
||||
FProfiler.Internal.getMostExpensiveSingleCalls = function() return mostExpensiveSingleCalls end
|
||||
|
||||
-- Dictionary to make sure the same function doesn't appear multiple times
|
||||
-- in the top n
|
||||
local mostExpensiveSingleDict = {}
|
||||
|
||||
function FProfiler.Internal.resetMostExpensiveSingleCalls()
|
||||
for i = 1, 50 do mostExpensiveSingleCalls[i] = {runtime = 0} end
|
||||
mostExpensiveSingleDict = {}
|
||||
end
|
||||
|
||||
-- Initial empty list
|
||||
FProfiler.Internal.resetMostExpensiveSingleCalls()
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Function information
|
||||
Using debug.getinfo on a function object won't give you any function names
|
||||
that's because functions can have multiple names.
|
||||
Luckily, when the functions are called, debug.getinfo(level) gives the
|
||||
function name and scope
|
||||
---------------------------------------------------------------------------]]
|
||||
local functionNames = {}
|
||||
|
||||
FProfiler.Internal.getFunctionNames = function() return functionNames end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Recursion depth
|
||||
|
||||
Used internally to make sure recursive functions' times aren't counted
|
||||
multiple times
|
||||
---------------------------------------------------------------------------]]
|
||||
local recursiveCount = {}
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Function start times
|
||||
|
||||
Used internally to keep track of when functions were called
|
||||
---------------------------------------------------------------------------]]
|
||||
local startTimes = {}
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Lua code event handlers
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
-- The recursion depth of the function that is in focus.
|
||||
-- Only applies when profiling a specific function (i.e. laying focus upon)
|
||||
local focusDepth = 0
|
||||
|
||||
-- Called when a function in the code is called
|
||||
local function registerFunctionCall(funcInfo)
|
||||
local func = funcInfo.func
|
||||
|
||||
-- Update call counts
|
||||
callcounts[func] = (callcounts[func] or 0) + 1
|
||||
|
||||
-- Increase recursion depth for this function
|
||||
recursiveCount[func] = (recursiveCount[func] or 0) + 1
|
||||
|
||||
-- Store function info
|
||||
local funcname = funcInfo.name or ""
|
||||
functionNames[func] = functionNames[func] or {}
|
||||
functionNames[func][funcname] = functionNames[func][funcname] or
|
||||
{ namewhat = funcInfo.namewhat,
|
||||
nparams = funcInfo.nparams
|
||||
}
|
||||
|
||||
local time = timeMeasurementFunc()
|
||||
|
||||
-- Update inclusive function times,
|
||||
-- only when we're on the first recursive call
|
||||
if recursiveCount[func] == 1 then
|
||||
startTimes[func] = time
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Called when a function returns
|
||||
local function registerReturn(funcInfo)
|
||||
local time = timeMeasurementFunc()
|
||||
local func = funcInfo.func
|
||||
local runtime = time - startTimes[func]
|
||||
|
||||
-- Update inclusive function time
|
||||
-- Only update on the topmost call, to prevent recursive
|
||||
-- calls for being counted multiple times.
|
||||
if recursiveCount[func] == 1 then
|
||||
inclusiveTimes[func] = (inclusiveTimes[func] or 0) + runtime
|
||||
end
|
||||
|
||||
-- Maintain recursion depth
|
||||
recursiveCount[func] = recursiveCount[func] - 1
|
||||
|
||||
-- Update top n list
|
||||
-- This path will be taken most often: the function isn't special
|
||||
-- Also only counts the top recursion
|
||||
if runtime <= mostExpensiveSingleCalls[50].runtime or recursiveCount[func] > 1 then return end
|
||||
|
||||
-- If the function already appears in the top 10, replace it or discard the result
|
||||
if mostExpensiveSingleDict[func] then
|
||||
local i = mostExpensiveSingleDict[func]
|
||||
|
||||
-- Discard this info
|
||||
if runtime < mostExpensiveSingleCalls[i].runtime then return end
|
||||
|
||||
-- Update the entry
|
||||
mostExpensiveSingleCalls[i].runtime = runtime
|
||||
mostExpensiveSingleCalls[i].info = funcInfo
|
||||
mostExpensiveSingleCalls[i].func = func
|
||||
|
||||
-- Move the updated entry up the top 10 list if applicable
|
||||
while i > 1 and runtime > mostExpensiveSingleCalls[i - 1].runtime do
|
||||
mostExpensiveSingleDict[mostExpensiveSingleCalls[i - 1].func] = i
|
||||
mostExpensiveSingleCalls[i - 1], mostExpensiveSingleCalls[i] = mostExpensiveSingleCalls[i], mostExpensiveSingleCalls[i - 1]
|
||||
i = i - 1
|
||||
end
|
||||
|
||||
mostExpensiveSingleDict[func] = i
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Knowing that the function belongs in the top n, find its position
|
||||
local i = 50
|
||||
while i >= 1 and runtime > mostExpensiveSingleCalls[i].runtime do
|
||||
-- Update the dictionary
|
||||
-- All functions faster than the current one move down the list
|
||||
if not mostExpensiveSingleCalls[i].func then i = i - 1 continue end
|
||||
mostExpensiveSingleDict[mostExpensiveSingleCalls[i].func] = i + 1
|
||||
|
||||
i = i - 1
|
||||
end
|
||||
|
||||
-- Insert the expensive call in the top n
|
||||
mostExpensiveSingleDict[func] = i + 1
|
||||
table.insert(mostExpensiveSingleCalls, i + 1,
|
||||
{
|
||||
func = func,
|
||||
runtime = runtime,
|
||||
info = funcInfo,
|
||||
})
|
||||
|
||||
|
||||
-- What was previously the 50th most expensive function
|
||||
-- is now kicked out of the top 10
|
||||
if mostExpensiveSingleCalls[51].func then
|
||||
mostExpensiveSingleDict[mostExpensiveSingleCalls[51].func] = nil
|
||||
end
|
||||
mostExpensiveSingleCalls[51] = nil
|
||||
end
|
||||
|
||||
|
||||
-- Called on any Lua event
|
||||
local function onLuaEvent(event, focus)
|
||||
local info = debug.getinfo(3)
|
||||
local func = info.func
|
||||
|
||||
if event == "call" or event == "tail call" then
|
||||
-- Only track the focussed function and the functions
|
||||
-- called by the focussed function
|
||||
if focus == func then focusDepth = focusDepth + 1 end
|
||||
if focus and focusDepth == 0 then return end
|
||||
|
||||
registerFunctionCall(info)
|
||||
else
|
||||
-- Functions that return right after the call to FProfiler.Internal.start()
|
||||
-- are not to be counted
|
||||
if not recursiveCount[func] or recursiveCount[func] == 0 then return end
|
||||
|
||||
if focus == func then focusDepth = focusDepth - 1 end
|
||||
if focus and focusDepth == 0 then return end
|
||||
|
||||
registerReturn(info)
|
||||
end
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Profiling control
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
-- Start profiling
|
||||
-- focus: only measure data of everything that happens within a certain function
|
||||
function FProfiler.Internal.start(focus)
|
||||
-- Empty start times, so unfinished functions aren't
|
||||
-- registered as returns on a second profiling session
|
||||
-- local time = SysTime()
|
||||
-- for k,v in pairs(startTimes) do startTimes[k] = time end
|
||||
table.Empty(startTimes)
|
||||
table.Empty(recursiveCount)
|
||||
|
||||
debug.sethook(function(event) onLuaEvent(event, focus) end, "cr")
|
||||
end
|
||||
|
||||
|
||||
-- Stop profiling
|
||||
function FProfiler.Internal.stop()
|
||||
debug.sethook()
|
||||
end
|
||||
|
||||
-- Reset all profiling data
|
||||
function FProfiler.Internal.reset()
|
||||
FProfiler.Internal.resetCallCounts()
|
||||
FProfiler.Internal.resetInclusiveTimes()
|
||||
FProfiler.Internal.resetMostExpensiveSingleCalls()
|
||||
end
|
||||
Reference in New Issue
Block a user