-
-
Notifications
You must be signed in to change notification settings - Fork 222
Developer Stuff
syrifgit edited this page Nov 15, 2024
·
10 revisions
page to put things and stuff
If you want generic plug-and-play, you need from below:
- The macros
- Updated ProfileCPU function:
function Hekili:ProfileCPU( name, func )
- Data storage function:
function Hekili:AddProfileData( name, timeSpent )
- Printer function:
function Hekili:PrintProfileSummary()
- Storage cleanup:
function Hekili:ClearProfileData()
Anything else is optional
/run Hekili:PrintProfileSummary()
/run Hekili:PrintNextPredictionProfile()
/run Hekili:ClearProfileData()
/run Hekili:ResetNextPredictionProfile()
This is the function that allows you to write stuff like this to have a function added to the profiler
Hekili:ProfileCPU( "QueueEvent", state.QueueEvent )
Hekili:ProfileCPU( "ThreadedUpdate", Hekili.Update )
Hekili:ProfileCPU( "GetNextPrediction", Hekili.GetNextPrediction )
Theoretically, you can add any function you want.
local cpuProfileDB = {}
Hekili.CPUProfileResults = {}
function Hekili:ProfileCPU(name, func)
-- Store the original function for reference
print("Profiling function:", name)
cpuProfileDB[name] = func
-- Initialize profiling data if it doesn’t already exist
if not self.CPUProfileResults[name] then
self.CPUProfileResults[name] = {
totalCPUTime = 0,
calls = 0,
maxCPU = 0,
minCPU = math.huge
}
end
-- Redefine the function with a profiling wrapper
self[name] = function(...)
local startCPU = debugprofilestop()
local result = { func(...) }
local endCPU = debugprofilestop()
-- Calculate CPU time for this call
local cpuTime = endCPU - startCPU
-- Ensure profileData is available
local profileData = self.CPUProfileResults[name]
if profileData then
profileData.totalCPUTime = profileData.totalCPUTime + cpuTime
profileData.calls = profileData.calls + 1
profileData.maxCPU = math.max(profileData.maxCPU, cpuTime)
profileData.minCPU = math.min(profileData.minCPU, cpuTime)
else
self:Debug("Warning: profileData is nil for function " .. name)
end
-- Return the original function's results
return unpack(result)
end
end
Hekili:AddProfileData( name, timeSpent )
- Function for storing data over time (live profiling), scalable
function Hekili:AddProfileData( name, timeSpent )
self.ProfileResults = self.ProfileResults or {}
if not self.ProfileResults[ name ] then
self.ProfileResults[ name ] = { totalTime = 0, count = 0, max = 0, min = math.huge }
end
local result = self.ProfileResults[ name ]
result.totalTime = result.totalTime + timeSpent
result.count = result.count + 1
result.max = math.max( result.max, timeSpent )
result.min = math.min( result.min, timeSpent )
end
function Hekili:PrintProfileSummary()
-- Ensure there is profiling data to print
if not self.ProfileResults or next(self.ProfileResults) == nil then
print("No profiling data recorded.")
return
end
-- Prepare a list of keys sorted by total time in descending order
local sortedEntries = {}
for name, data in pairs(self.ProfileResults) do
table.insert(sortedEntries, { name = name, data = data })
end
-- Sort by totalTime in descending order
table.sort(sortedEntries, function(a, b)
return a.data.totalTime > b.data.totalTime
end)
-- General profiling summary
print("\nProfiling Summary:")
print(string.format("%-25s | %12s | %8s | %10s | %10s | %10s", "Function Name", "Total Time", "Calls", "Avg Time", "Max Time", "Min Time"))
print(string.rep("-", 85))
for _, entry in ipairs(sortedEntries) do
local name, data = entry.name, entry.data
local avg = data.totalTime / data.count
print(string.format("%-25s | %10.3f ms | %6d | %8.3f ms | %8.3f ms | %8.3f ms",
name, data.totalTime, data.count, avg, data.max, data.min))
end
-- Specific section for entries containing "reset" or "Reset"
print("\nReset Profiling Summary:")
print(string.format("%-25s | %12s | %8s | %10s | %10s | %10s", "Reset Entry", "Total Time", "Calls", "Avg Time", "Max Time", "Min Time"))
print(string.rep("-", 85))
for _, entry in ipairs(sortedEntries) do
local name, data = entry.name, entry.data
if name:match("[rR]eset") then -- Match any entry containing "reset" or "Reset"
local avg = data.totalTime / data.count
print(string.format("%-25s | %10.3f ms | %6d | %8.3f ms | %8.3f ms | %8.3f ms",
name, data.totalTime, data.count, avg, data.max, data.min))
end
end
-- Check for CPU profiling data
if Hekili.CPUProfileResults then
print("\nCPU Profile Summary:")
-- Sort CPU profiling data by total CPU time in descending order
local cpuSortedEntries = {}
for funcName, data in pairs(Hekili.CPUProfileResults) do
table.insert(cpuSortedEntries, { funcName = funcName, data = data })
end
table.sort(cpuSortedEntries, function(a, b)
return a.data.totalCPUTime > b.data.totalCPUTime
end)
-- Print all entries where the call count is greater than 0
for i = 1, #cpuSortedEntries do
local entry = cpuSortedEntries[i]
local funcName = entry.funcName
local data = entry.data
if data.calls > 0 then -- Only print entries with calls > 0
local avgCPU = data.totalCPUTime / data.calls
-- Top row: function name
print(string.format("Function: %-22s", funcName))
-- Second row: table format for profiling data
print(string.format("Calls | Total CPU Time | Avg CPU | Max CPU | Min CPU"))
print(string.format(" %6d | %14.3f ms | %9.3f ms | %9.3f ms | %9.3f ms",
data.calls, data.totalCPUTime, avgCPU, data.maxCPU, data.minCPU))
print(string.rep("-", 60)) -- Separator line for better readability
end
end
else
print("No CPU profiling data recorded.")
end
-- Add engine performance data if available
if Hekili.Engine and Hekili.Engine.threadUpdates then
local engineData = Hekili.Engine.threadUpdates
print("\nEngine Thread Performance Summary:")
print(string.format("%-25s | %-4s | %-4s", "Metric", "Mean", "Peak"))
print(string.rep("-", 55)) -- Reduced width for separator line
print(string.format("%-18s | %7.3f ms | %7.3f ms", "Mean Clock Time", engineData.meanClockTime or 0, engineData.peakClockTime or 0))
print(string.format("%-18s | %7.3f ms | %7.3f ms", "Mean Work Time", engineData.meanWorkTime or 0, engineData.peakWorkTime or 0))
print(string.format("%-20s | %7.3f | %7.3f", "Mean Frames", engineData.meanFrames or 0, engineData.peakFrames or 0))
print(string.format("%-18s | %7.3f ms | %7.3f ms", "Total Wasted Time", engineData.totalWasted or 0, engineData.peakWasted or 0))
print(string.format("%-14s | %7.3f ms", "Mean Wasted Time", engineData.meanWasted or 0))
else
print("No engine performance data recorded.")
end
end
-- Function to clear all profiling data
function Hekili:ClearProfileData()
-- Ensure the ProfileResults and CPUProfileResults tables exist
self.ProfileResults = self.ProfileResults or {}
self.CPUProfileResults = self.CPUProfileResults or {}
-- Wipe the ProfileResults table
wipe(self.ProfileResults)
print("Profiling data cleared.")
-- Wipe the CPUProfileResults table
wipe(self.CPUProfileResults)
print("CPU profiling data cleared.")
-- Clear the engine performance data if available
if Hekili.Engine and Hekili.Engine.threadUpdates then
Hekili.Engine.threadUpdates = {
meanClockTime = 0,
meanWorkTime = 0,
meanFrames = 0,
meanWasted = 0,
firstUpdate = 0,
updates = 0,
updatesPerSec = 0,
peakClockTime = 0,
peakWorkTime = 0,
peakFrames = 0,
peakWasted = 0,
totalWasted = 0
}
print("Engine performance data cleared.")
else
print("No engine performance data to clear.")
end
end
function scripts:CheckScript(scriptID, action, elem)
-- Capture the action we're checking.
local prev_action = state.this_action
if action then state.this_action = action end
-- Retrieve the script from the database.
local script = self.DB[scriptID]
if not script then
state.this_action = prev_action
return false
end
-- Profiling for the entire CheckScript function.
local startCheckScript
if InCombatLockdown() then
startCheckScript = debugprofilestop()
end
-- Initialize the result variables.
local result, errorMessage
if not elem then
-- Profiling for general Conditions.
local startConditions, timeSpentConditions
if InCombatLockdown() then
startConditions = debugprofilestop()
end
if script.Error then
result = false
errorMessage = script.Error
elseif not script.Conditions then
result = true
else
-- Error handling for Conditions to catch potential issues.
local success, conditionsResult = pcall(script.Conditions)
if success then
result = conditionsResult
else
result = false
errorMessage = "Error in Conditions: " .. conditionsResult
end
end
-- Record time spent on Conditions evaluation.
if InCombatLockdown() and startConditions then
timeSpentConditions = debugprofilestop() - startConditions
Hekili:AddProfileData("CheckScript:Conditions:" .. scriptID, timeSpentConditions)
end
else
-- Profiling for Modifier checks.
local startModifier, timeSpentModifier
if InCombatLockdown() then
startModifier = debugprofilestop()
end
if not script.Modifiers[elem] then
result = nil
errorMessage = elem .. " not set."
else
local success, value = pcall(script.Modifiers[elem])
if success then
result = value
else
result = false
errorMessage = "Error in Modifier " .. elem .. ": " .. value
end
end
-- Record time spent on the Modifier evaluation.
if InCombatLockdown() and startModifier then
timeSpentModifier = debugprofilestop() - startModifier
Hekili:AddProfileData("CheckScript:Modifier:" .. scriptID .. ":" .. elem, timeSpentModifier)
end
end
-- Restore the previous action state.
state.this_action = prev_action
-- Record the total time for CheckScript if applicable.
if InCombatLockdown() and startCheckScript then
local timeSpentTotal = debugprofilestop() - startCheckScript
Hekili:AddProfileData("CheckScript:Total:" .. scriptID, timeSpentTotal)
end
-- Return the result with potential error message.
return result, errorMessage
end
function scripts:GetModifiers(scriptID, out)
print("GetModifiers called for scriptID:", scriptID) -- Debug print
-- Start profiling for the whole GetModifiers function
local startTotal
if InCombatLockdown() then
print("In combat, starting profiling for GetModifiers") -- Debug print
startTotal = debugprofilestop()
end
out = out or {}
local script = self.DB[scriptID]
if not script then
print("No script found for scriptID:", scriptID) -- Debug print
-- End profiling if exiting early
if InCombatLockdown() and startTotal then
local endTotal = debugprofilestop() - startTotal
Hekili:AddProfileData("GetModifiers:" .. scriptID, endTotal)
end
return out
end
for k, v in pairs(script.Modifiers) do
local start, timeSpent
if InCombatLockdown() then
start = debugprofilestop()
print("Profiling modifier:", k) -- Debug print
end
local success, value = pcall(v)
if success then
out[k] = value
else
print("Failed to execute modifier:", k) -- Debug print for failure
end
-- Record profiling data for each modifier
if InCombatLockdown() and start then
timeSpent = debugprofilestop() - start
print("Time spent on modifier:", k, "=", timeSpent) -- Debug print
Hekili:AddProfileData("Modifier:" .. k, timeSpent)
end
end
-- End profiling for the entire function
if InCombatLockdown() and startTotal then
local endTotal = debugprofilestop() - startTotal
Hekili:AddProfileData("GetModifiers:" .. scriptID, endTotal)
print("Total time spent on GetModifiers:", endTotal) -- Debug print
end
return out
end
function scripts:GetConditionsAndValues(scriptID, listName, actID, recheck)
-- Skip if snapshot or debug is not enabled
if troubleshootingSnapshotTimes or not Hekili.ActiveDebug then return "[no data]" end
-- Adjust scriptID if listName and actID are provided
if listName and actID then
scriptID = scriptID .. ":" .. listName .. ":" .. actID
end
-- Start profiling for the whole GetConditionsAndValues function
local startTotal = debugprofilestop()
-- Retrieve the script from the database
local script = self.DB[scriptID]
-- Check if this is a recheck and profile accordingly
if recheck then
local startRecheck = debugprofilestop()
local result = embedConditionsAndValues(script.RecheckScript, script.RecheckElements)
local endRecheck = debugprofilestop() - startRecheck
Hekili:AddProfileData("RecheckConditions:" .. scriptID, endRecheck)
-- End profiling for total function and return
local endTotal = debugprofilestop() - startTotal
Hekili:AddProfileData("GetConditionsAndValues:" .. scriptID, endTotal)
return result
end
-- Profile if there is a custom Print function
if script.Print then
local startPrint = debugprofilestop()
local result = script.Print()
local endPrint = debugprofilestop() - startPrint
Hekili:AddProfileData("PrintFunction:" .. scriptID, endPrint)
-- End profiling for total function and return
local endTotal = debugprofilestop() - startTotal
Hekili:AddProfileData("GetConditionsAndValues:" .. scriptID, endTotal)
return result
end
-- Profile standard condition evaluation with embedConditionsAndValues
local startEmbed = debugprofilestop()
local result = embedConditionsAndValues(script.SimC, script.Elements)
local endEmbed = debugprofilestop() - startEmbed
Hekili:AddProfileData("ConditionEvaluation:" .. scriptID, endEmbed)
-- End profiling for total function
local endTotal = debugprofilestop() - startTotal
Hekili:AddProfileData("GetConditionsAndValues:" .. scriptID, endTotal)
return result
end
Has a lot of potential, needs work.
ns.refreshableAuraCount = function(auraName)
local startTime = debugprofilestop() -- Start timing
local aura = class.auras[auraName]
if not aura then
return 0
end
local auraID = aura.id
-- Handle dynamic duration (if duration is a function, evaluate it)
local duration = type(aura.duration) == "function" and aura.duration() or aura.duration or 30
local refreshThreshold = 0.3 * duration -- Calculate threshold as 30% of the duration
local refreshableCount = 0
-- Iterate over each nameplate unit (up to 40)
for i = 1, 40 do
local unit = "nameplate" .. i
if not UnitExists(unit) then break end -- Stop if no more nameplates are present
local auraFound = false
-- Check each debuff slot on the unit to find the specific aura
for j = 1, 40 do
local auraData = C_UnitAuras.GetAuraDataBySlot(unit, j)
if auraData and auraData.spellId == auraID then
auraFound = true
local currentTime = GetTime()
local expirationTime = auraData.expirationTime or 0
local remainingTime = expirationTime - currentTime
if remainingTime <= refreshThreshold then
refreshableCount = refreshableCount + 1
end
break
end
end
if not auraFound then
-- If the aura is not found on the unit, consider it refreshable.
refreshableCount = refreshableCount + 1
end
end
-- Calculate elapsed time and add it to the profiling data
local timeSpent = debugprofilestop() - startTime
Hekili:AddProfileData("Refreshable", timeSpent)
return refreshableCount
end
-- Count of refreshable Garrotes
spec:RegisterStateExpr( "refreshable_garrotes", function ()
return ns.refreshableAuraCount("garrote")
end )
-- Count of refreshable Ruptures
spec:RegisterStateExpr( "refreshable_ruptures", function ()
return ns.refreshableAuraCount("rupture")
end )