ClassicAPI's frame CPU numbers exceeded wall-clock time, so they are no longer shown. `/bs cpu clamp on|off` toggles SetClampedToScreen on the inventory windows for the session to test the engine-side drag stalls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019W3rwt5PZSpEDkXYzRuRhH
179 lines
5.8 KiB
Lua
179 lines
5.8 KiB
Lua
-- Bagshui CPU Profile
|
|
-- Exposes: Bagshui.components.CpuProfile
|
|
--
|
|
-- `/bs cpu` reads ClassicAPI's script profiler and prints where CPU time went since the last
|
|
-- reset (or /reload): the busiest addons and Bagshui's busiest functions.
|
|
-- `/bs cpu reset` starts a new measurement.
|
|
-- `/bs cpu clamp on|off` switches the windows' keep-on-screen clamp for this session only,
|
|
-- to test whether it causes the engine-side stalls while dragging.
|
|
--
|
|
-- Needs ClassicAPI and the scriptProfile CVar (`/console scriptProfile 1`, then /reload).
|
|
-- Profiling slows every addon down a little, so turn it off again when done.
|
|
|
|
Bagshui:AddComponent(function()
|
|
local CpuProfile = {}
|
|
Bagshui.components.CpuProfile = CpuProfile
|
|
|
|
local TOP_ADDONS = 5
|
|
local TOP_FUNCTIONS = 8
|
|
|
|
--- Print one line to chat with a consistent prefix.
|
|
local function Out(text)
|
|
_G.DEFAULT_CHAT_FRAME:AddMessage("|cffdb4e97Bagshui CPU|r " .. text)
|
|
end
|
|
|
|
--- Format milliseconds for display.
|
|
local function Ms(ms)
|
|
if ms >= 1000 then
|
|
return string.format("%.2fs", ms / 1000)
|
|
end
|
|
return string.format("%dms", math.floor(ms + 0.5))
|
|
end
|
|
|
|
--- Is the profiler available and switched on?
|
|
---@return boolean
|
|
function CpuProfile:IsActive()
|
|
if type(_G.GetFunctionCPUUsage) ~= "function" or type(_G.UpdateAddOnCPUUsage) ~= "function" then
|
|
return false
|
|
end
|
|
local ok, value = pcall(_G.GetCVar, "scriptProfile")
|
|
return ok and tostring(value) == "1"
|
|
end
|
|
|
|
--- Sort helper: biggest `ms` first.
|
|
local function ByMs(a, b)
|
|
return a.ms > b.ms
|
|
end
|
|
|
|
--- CPU time per addon since the last reset.
|
|
---@return table list { name, ms }
|
|
local function AddOnUsage()
|
|
local list = {}
|
|
pcall(_G.UpdateAddOnCPUUsage)
|
|
for i = 1, _G.GetNumAddOns() do
|
|
local name = _G.GetAddOnInfo(i)
|
|
local ok, ms = pcall(_G.GetAddOnCPUUsage, i)
|
|
if ok and type(ms) == "number" and ms > 0 then
|
|
table.insert(list, { name = name, ms = ms })
|
|
end
|
|
end
|
|
table.sort(list, ByMs)
|
|
return list
|
|
end
|
|
|
|
--- CPU time of every function reachable from Bagshui's tables.
|
|
--- `total` includes the functions it calls; `self` is time spent in the function itself.
|
|
---@return table byTotal, table bySelf Lists of { name, ms }.
|
|
local function FunctionUsage()
|
|
local seen = {}
|
|
local byTotal, bySelf = {}, {}
|
|
|
|
local function Scan(prefix, tbl)
|
|
if type(tbl) ~= "table" then
|
|
return
|
|
end
|
|
for key, value in pairs(tbl) do
|
|
if type(value) == "function" and type(key) == "string" and not seen[value] then
|
|
seen[value] = true
|
|
local okTotal, total = pcall(_G.GetFunctionCPUUsage, value, true)
|
|
local okSelf, own = pcall(_G.GetFunctionCPUUsage, value, false)
|
|
local name = prefix .. key
|
|
if okTotal and type(total) == "number" and total > 0 then
|
|
table.insert(byTotal, { name = name, ms = total })
|
|
end
|
|
if okSelf and type(own) == "number" and own > 0 then
|
|
table.insert(bySelf, { name = name, ms = own })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Prototypes first, so shared methods are named after the class that defines them.
|
|
for name, prototype in pairs(Bagshui.prototypes) do
|
|
Scan(name .. ":", prototype)
|
|
end
|
|
for name, component in pairs(Bagshui.components) do
|
|
Scan(name .. ":", component)
|
|
end
|
|
Scan("Bagshui:", Bagshui)
|
|
Scan("BsUtil.", BsUtil)
|
|
|
|
table.sort(byTotal, ByMs)
|
|
table.sort(bySelf, ByMs)
|
|
return byTotal, bySelf
|
|
end
|
|
|
|
--- Print up to `count` entries of `list`, prefixed by `heading`.
|
|
local function PrintTop(heading, list, count)
|
|
if table.getn(list) == 0 then
|
|
return
|
|
end
|
|
local parts = {}
|
|
for i = 1, math.min(count, table.getn(list)) do
|
|
table.insert(parts, list[i].name .. " " .. Ms(list[i].ms))
|
|
end
|
|
Out("|cffffd100" .. heading .. ":|r " .. table.concat(parts, ", "))
|
|
end
|
|
|
|
--- Print the report.
|
|
function CpuProfile:Report()
|
|
if not self:IsActive() then
|
|
Out("profiling is off. Type |cffffffff/console scriptProfile 1|r and |cffffffff/reload|r (needs ClassicAPI).")
|
|
return
|
|
end
|
|
|
|
local since = self.resetAt and string.format("%.0fs", _G.GetTime() - self.resetAt) or "the last /reload"
|
|
Out("CPU time since " .. since .. " (/bs cpu reset to start over)")
|
|
|
|
PrintTop("Addons", AddOnUsage(), TOP_ADDONS)
|
|
local byTotal, bySelf = FunctionUsage()
|
|
PrintTop("Bagshui functions, own time", bySelf, TOP_FUNCTIONS)
|
|
PrintTop("Bagshui functions, including calls", byTotal, TOP_FUNCTIONS)
|
|
end
|
|
|
|
--- Start a new measurement.
|
|
function CpuProfile:Reset()
|
|
if not self:IsActive() then
|
|
self:Report()
|
|
return
|
|
end
|
|
pcall(_G.ResetCPUUsage)
|
|
self.resetAt = _G.GetTime()
|
|
Out("counters reset. Reproduce the hitch, then type /bs cpu.")
|
|
end
|
|
|
|
--- Switch the keep-on-screen clamp on all inventory windows (not saved; /reload restores it).
|
|
---@param enable boolean
|
|
function CpuProfile:SetClamp(enable)
|
|
local count = 0
|
|
for _, component in pairs(Bagshui.components) do
|
|
if
|
|
type(component) == "table"
|
|
and component.inventoryType
|
|
and type(component.uiFrame) == "table"
|
|
and component.uiFrame.SetClampedToScreen
|
|
then
|
|
component.uiFrame:SetClampedToScreen(enable)
|
|
count = count + 1
|
|
end
|
|
end
|
|
Out(string.format("keep-on-screen clamp %s for %d windows until /reload.", enable and "ON" or "OFF", count))
|
|
end
|
|
|
|
BsSlash:AddHandler("CPU", function(tokens)
|
|
local verb = tokens[2] and string.lower(tokens[2])
|
|
if verb == "reset" then
|
|
CpuProfile:Reset()
|
|
elseif verb == "clamp" then
|
|
local state = tokens[3] and string.lower(tokens[3])
|
|
if state == "on" or state == "off" then
|
|
CpuProfile:SetClamp(state == "on")
|
|
else
|
|
Out("usage: /bs cpu clamp on|off")
|
|
end
|
|
else
|
|
CpuProfile:Report()
|
|
end
|
|
end, true)
|
|
end)
|