TradeSkillMaster/Core/Development/Profiling.lua

164 lines
5.4 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
if not TSMDEV then return end
local _, TSM = ...
TSMDEV.Profiling = {}
local Profiling = TSMDEV.Profiling
local Math = TSM.Include("Util.Math")
local private = {
startTime = nil,
nodes = {},
nodeRuns = {},
nodeStart = {},
nodeTotal = {},
nodeMaxContext = {},
nodeMaxTime = {},
nodeParent = {},
nodeStack = {},
}
local NODE_PATH_SEP = "`"
-- ============================================================================
-- Module Functions
-- ============================================================================
--- Starts profiling.
function Profiling.Start()
assert(not private.startTime, "Profiling already started")
private.startTime = debugprofilestop()
end
--- Starts profiling of a node.
-- Profiling must have been started for this to have any effect.
-- @tparam string node The name of the profiling node
function Profiling.StartNode(node)
if not private.startTime then
-- profiling is not running
return
end
local nodeStackLen = #private.nodeStack
local parentNode = nodeStackLen > 0 and table.concat(private.nodeStack, NODE_PATH_SEP) or nil
private.nodeStack[nodeStackLen + 1] = node
node = table.concat(private.nodeStack, NODE_PATH_SEP)
if private.nodeStart[node] then
error("Node already started", 2)
end
if not private.nodeTotal[node] then
tinsert(private.nodes, node)
private.nodeTotal[node] = 0
private.nodeRuns[node] = 0
private.nodeMaxContext[node] = nil
private.nodeMaxTime[node] = 0
private.nodeParent[node] = parentNode
elseif private.nodeParent[node] ~= parentNode then
error("Node changed parents", 2)
end
private.nodeStart[node] = debugprofilestop()
end
--- Ends profiling of a node.
-- Profiling of this node must have been started for this to have any effect.
-- @tparam string node The name of the profiling node
-- @param[opt] arg An extra argument which is printed if this invocation represents the max duration for the node
function Profiling.EndNode(node, arg)
if not private.startTime then
-- profiling is not running
return
end
local endTime = debugprofilestop()
local nodeStackLen = #private.nodeStack
if node ~= private.nodeStack[nodeStackLen] then
error("Node isn't at the top of the stack", 2)
end
node = table.concat(private.nodeStack, NODE_PATH_SEP)
if not private.nodeStart[node] then
error("Node hasn't been started", 2)
end
private.nodeStack[nodeStackLen] = nil
local nodeTime = endTime - private.nodeStart[node]
private.nodeRuns[node] = private.nodeRuns[node] + 1
private.nodeTotal[node] = private.nodeTotal[node] + nodeTime
private.nodeStart[node] = nil
if nodeTime > private.nodeMaxTime[node] then
private.nodeMaxContext[node] = arg
private.nodeMaxTime[node] = nodeTime
end
end
--- Ends profiling and prints the results to chat.
-- @tparam[opt=0] number minTotalTime The minimum total time to print the profiling info
function Profiling.End(minTotalTime)
if not private.startTime then
-- profiling is not running
return
end
local totalTime = debugprofilestop() - private.startTime
if totalTime > (minTotalTime or 0) then
print(format("Total: %.03f", Math.Round(totalTime, 0.001)))
for _, node in ipairs(private.nodes) do
local parentNode = private.nodeParent[node]
local parentTotalTime = nil
if parentNode then
parentTotalTime = private.nodeTotal[parentNode]
else
parentTotalTime = totalTime
end
local nodeTotalTime = Math.Round(private.nodeTotal[node], 0.001)
local pctTime = Math.Round(nodeTotalTime * 100 / parentTotalTime)
local nodeRuns = private.nodeRuns[node]
local nodeMaxContext = private.nodeMaxContext[node]
local level = private.GetLevel(node)
local name = strmatch(node, NODE_PATH_SEP.."?([^"..NODE_PATH_SEP.."]+)$")
if nodeMaxContext ~= nil then
local nodeMaxTime = private.nodeMaxTime[node]
print(format("%s%s | %d%% | %.03f | %d | %.03f | %s", strrep(" ", level), name, pctTime, nodeTotalTime, nodeRuns, nodeMaxTime, tostring(nodeMaxContext)))
else
print(format("%s%s | %d%% | %.03f | %d", strrep(" ", level), name, pctTime, nodeTotalTime, nodeRuns))
end
end
end
private.startTime = nil
wipe(private.nodes)
wipe(private.nodeRuns)
wipe(private.nodeStart)
wipe(private.nodeTotal)
wipe(private.nodeMaxContext)
wipe(private.nodeMaxTime)
end
--- Checks whether or not we're currently profiling.
-- @treturn boolean Whether or not we're currently profiling.
function Profiling.IsActive()
return private.startTime and true or false
end
--- Gets the total memory used by TSM.
-- @treturn number The amount of memory being used in bytes
function Profiling.GetMemoryUsage()
collectgarbage()
UpdateAddOnMemoryUsage("TradeSkillMaster")
return GetAddOnMemoryUsage("TradeSkillMaster") * 1024
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.GetLevel(node)
local level = 0
while node do
level = level + 1
node = private.nodeParent[node]
end
return level
end