--[[-------------------------------------------------------------------- Copyright (C) 2014 Johnny C. Lam. See the file LICENSE.txt for copying permission. --]]-------------------------------------------------------------------- --[[ Every time a new function is entered and exited, debugprofilestop() is called and the time between the two timestamps is calculated and attributed to that function. --]] local OVALE, Ovale = ... local OvaleProfiler = Ovale:NewModule("OvaleProfiler") Ovale.OvaleProfiler = OvaleProfiler --<private-static-properties> local AceConfig = LibStub("AceConfig-3.0") local AceConfigDialog = LibStub("AceConfigDialog-3.0") local L = Ovale.L local LibTextDump = LibStub("LibTextDump-1.0") local OvaleOptions = Ovale.OvaleOptions local debugprofilestop = debugprofilestop local format = string.format local ipairs = ipairs local next = next local pairs = pairs local tconcat = table.concat local tinsert = table.insert local tsort = table.sort local wipe = wipe local API_GetTime = GetTime local self_timestamp = debugprofilestop() local self_stack = {} local self_stackSize = 0 local self_timeSpent = {} local self_timesInvoked = {} -- LibTextDump-1.0 object for profiling output. local self_profilingOutput = nil do local actions = { profiling = { name = L["Profiling"], type = "execute", func = function() local appName = OvaleProfiler:GetName() AceConfigDialog:SetDefaultSize(appName, 800, 550) AceConfigDialog:Open(appName) end, }, } -- Insert actions into OvaleOptions. for k, v in pairs(actions) do OvaleOptions.options.args.actions.args[k] = v end -- Add a global data type for debug options. OvaleOptions.defaultDB.global = OvaleOptions.defaultDB.global or {} OvaleOptions.defaultDB.global.profiler = {} OvaleOptions:RegisterOptions(OvaleProfiler) end --</private-static-properties> --<public-static-properties> OvaleProfiler.options = { name = OVALE .. " " .. L["Profiling"], type = "group", args = { profiling = { name = L["Profiling"], type = "group", args = { modules = { name = L["Modules"], type = "group", inline = true, order = 10, args = {}, get = function(info) local name = info[#info] local value = Ovale.db.global.profiler[name] return (value ~= nil) end, set = function(info, value) value = value or nil local name = info[#info] Ovale.db.global.profiler[name] = value if value then OvaleProfiler:EnableProfiling(name) else OvaleProfiler:DisableProfiling(name) end end, }, reset = { name = L["Reset"], desc = L["Reset the profiling statistics."], type = "execute", order = 20, func = function() OvaleProfiler:ResetProfiling() end, }, show = { name = L["Show"], desc = L["Show the profiling statistics."], type = "execute", order = 30, func = function() self_profilingOutput:Clear() local s = OvaleProfiler:GetProfilingInfo() if s then self_profilingOutput:AddLine(s) self_profilingOutput:Display() end end, }, }, }, }, } --</public-static-properties> --<private-static-methods> local function DoNothing() -- no-op end local function StartProfiling(_, tag) local newTimestamp = debugprofilestop() -- Attribute the time spent up to this call to the previous function. if self_stackSize > 0 then local delta = newTimestamp - self_timestamp local previous = self_stack[self_stackSize] local timeSpent = self_timeSpent[previous] or 0 timeSpent = timeSpent + delta self_timeSpent[previous] = timeSpent end -- Add the current function to the call stack. self_timestamp = newTimestamp self_stackSize = self_stackSize + 1 self_stack[self_stackSize] = tag do local timesInvoked = self_timesInvoked[tag] or 0 timesInvoked = timesInvoked + 1 self_timesInvoked[tag] = timesInvoked end end local function StopProfiling(_, tag) if self_stackSize > 0 then local currentTag = self_stack[self_stackSize] if currentTag == tag then local newTimestamp = debugprofilestop() local delta = newTimestamp - self_timestamp local timeSpent = self_timeSpent[currentTag] or 0 timeSpent = timeSpent + delta self_timeSpent[currentTag] = timeSpent self_timestamp = newTimestamp self_stackSize = self_stackSize - 1 end end end --</private-static-methods> --<public-static-methods> function OvaleProfiler:OnInitialize() local appName = self:GetName() AceConfig:RegisterOptionsTable(appName, self.options) AceConfigDialog:AddToBlizOptions(appName, L["Profiling"], OVALE) end function OvaleProfiler:OnEnable() if not self_profilingOutput then self_profilingOutput = LibTextDump:New(OVALE .. " - " .. L["Profiling"], 750, 500) end end function OvaleProfiler:OnDisable() self_profilingOutput:Clear() end function OvaleProfiler:RegisterProfiling(addon, name) name = name or addon:GetName() self.options.args.profiling.args.modules.args[name] = { name = name, desc = format(L["Enable profiling for the %s module."], name), type = "toggle", } self:DisableProfiling(name) end function OvaleProfiler:EnableProfiling(name) local addon = Ovale[name] if addon then addon.StartProfiling = StartProfiling addon.StopProfiling = StopProfiling end end function OvaleProfiler:DisableProfiling(name) local addon = Ovale[name] if addon then addon.StartProfiling = DoNothing addon.StopProfiling = DoNothing end end function OvaleProfiler:ResetProfiling() for tag in pairs(self_timeSpent) do self_timeSpent[tag] = nil end for tag in pairs(self_timesInvoked) do self_timesInvoked[tag] = nil end end do local array = {} function OvaleProfiler:GetProfilingInfo() if next(self_timeSpent) then -- Calculate the width needed to print out the times invoked. local width = 1 do local tenPower = 10 for _, timesInvoked in pairs(self_timesInvoked) do while timesInvoked > tenPower do width = width + 1 tenPower = tenPower * 10 end end end wipe(array) local formatString = format(" %%08.3fms: %%0%dd (%%05f) x %%s", width) for tag, timeSpent in pairs(self_timeSpent) do local timesInvoked = self_timesInvoked[tag] tinsert(array, format(formatString, timeSpent, timesInvoked, timeSpent / timesInvoked, tag)) end if next(array) then tsort(array) local now = API_GetTime() tinsert(array, 1, format("Profiling statistics at %f:", now)) return tconcat(array, "\n") end end end end function OvaleProfiler:DebuggingInfo() Ovale:Print("Profiler stack size = %d", self_stackSize) local index = self_stackSize while index > 0 and self_stackSize - index < 10 do local tag = self_stack[index] Ovale:Print(" [%d] %s", index, tag) index = index - 1 end end --</public-static-methods>