Quantcast

Instrument some profiling code into Ovale using the Profiler module.

Johnny C. Lam [06-01-14 - 05:30]
Instrument some profiling code into Ovale using the Profiler module.

git-svn-id: svn://svn.curseforge.net/wow/ovale/mainline/trunk@1505 d5049fe3-3747-40f7-a4b5-f36d6801af5f
Filename
OvaleActionBar.lua
OvaleAura.lua
OvaleComboPoints.lua
OvaleCompile.lua
OvaleCooldown.lua
OvaleDamageTaken.lua
OvaleEclipse.lua
OvaleEnemies.lua
OvaleEquipement.lua
OvaleFuture.lua
OvalePaperDoll.lua
OvalePool.lua
OvalePoolRefCount.lua
OvalePower.lua
OvaleRunes.lua
OvaleSpellDamage.lua
OvaleStance.lua
diff --git a/OvaleActionBar.lua b/OvaleActionBar.lua
index f7de421..1e21cea 100644
--- a/OvaleActionBar.lua
+++ b/OvaleActionBar.lua
@@ -14,6 +14,14 @@ local OvaleActionBar = Ovale:NewModule("OvaleActionBar", "AceEvent-3.0")
 Ovale.OvaleActionBar = OvaleActionBar

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleActionBar")
+	profiler = Profiler.group["OvaleActionBar"]
+end
+
 local gsub = string.gsub
 local strmatch = string.match
 local tonumber = tonumber
@@ -104,12 +112,11 @@ end

 function OvaleActionBar:UPDATE_BINDINGS(event)
 	Ovale:DebugPrintf(OVALE_ACTIONBAR_DEBUG, "%s: Updating key bindings.", event)
-	for slot = 1, 120 do
-		self.keybind[slot] = GetKeyBinding(slot)
-	end
+	self:UpdateKeyBindings()
 end

 function OvaleActionBar:UpdateActionSlots(event)
+	profiler.Start("OvaleActionBar_UpdateActionSlots")
 	Ovale:DebugPrintf(OVALE_ACTIONBAR_DEBUG, "%s: Updating all action slot mappings.", event)
 	wipe(self.action)
 	wipe(self.item)
@@ -127,9 +134,11 @@ function OvaleActionBar:UpdateActionSlots(event)
 	for slot = start, 72 do
 		self:UpdateActionSlot(slot)
 	end
+	profiler.Stop("OvaleActionBar_UpdateActionSlots")
 end

 function OvaleActionBar:UpdateActionSlot(slot)
+	profiler.Start("OvaleActionBar_UpdateActionSlot")
 	-- Clear old slot and associated actions.
 	local action = self.action[slot]
 	if self.spell[action] == slot then
@@ -197,6 +206,15 @@ function OvaleActionBar:UpdateActionSlot(slot)

 	-- Update the keybind for the slot.
 	self.keybind[slot] = GetKeyBinding(slot)
+	profiler.Stop("OvaleActionBar_UpdateActionSlot")
+end
+
+function OvaleActionBar:UpdateKeyBindings()
+	profiler.Start("OvaleActionBar_UpdateKeyBindings")
+	for slot = 1, 120 do
+		self.keybind[slot] = GetKeyBinding(slot)
+	end
+	profiler.Stop("OvaleActionBar_UpdateKeyBindings")
 end

 -- Get the action slot that matches a spell ID.
diff --git a/OvaleAura.lua b/OvaleAura.lua
index b12af90..8111a8b 100644
--- a/OvaleAura.lua
+++ b/OvaleAura.lua
@@ -16,6 +16,14 @@ local OvaleAura = Ovale:NewModule("OvaleAura", "AceEvent-3.0")
 Ovale.OvaleAura = OvaleAura

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleAura")
+	profiler = Profiler.group["OvaleAura"]
+end
+
 local OvalePool = Ovale.OvalePool

 -- Forward declarations for module dependencies.
@@ -377,6 +385,7 @@ function OvaleAura:IsActiveAura(aura, now)
 end

 function OvaleAura:GainedAuraOnGUID(guid, atTime, auraId, casterGUID, filter, visible, icon, count, debuffType, duration, expirationTime, isStealable, name, value1, value2, value3)
+	profiler.Start("OvaleAura_GainedAuraOnGUID")
 	-- Whose aura is it?
 	casterGUID = casterGUID or UNKNOWN_GUID
 	local mine = (casterGUID == self_guid)
@@ -476,9 +485,11 @@ function OvaleAura:GainedAuraOnGUID(guid, atTime, auraId, casterGUID, filter, vi
 			Ovale.refreshNeeded[unitId] = true
 		end
 	end
+	profiler.Stop("OvaleAura_GainedAuraOnGUID")
 end

 function OvaleAura:LostAuraOnGUID(guid, atTime, auraId, casterGUID)
+	profiler.Start("OvaleAura_LostAuraOnGUID")
 	local aura = GetAura(self.aura, guid, auraId, casterGUID)
 	if aura then
 		local filter = aura.filter
@@ -522,10 +533,12 @@ function OvaleAura:LostAuraOnGUID(guid, atTime, auraId, casterGUID)
 			Ovale.refreshNeeded[unitId] = true
 		end
 	end
+	profiler.Stop("OvaleAura_LostAuraOnGUID")
 end

 -- Scan auras on the given GUID and update the aura database.
 function OvaleAura:ScanAurasOnGUID(guid)
+	profiler.Start("OvaleAura_ScanAurasOnGUID")
 	if not guid then return end
 	local unitId = OvaleGUID:GetUnitId(guid)
 	if not unitId then return end
@@ -576,6 +589,7 @@ function OvaleAura:ScanAurasOnGUID(guid)
 			end
 		end
 	end
+	profiler.Stop("OvaleAura_ScanAurasOnGUID")
 end

 function OvaleAura:ScanAuras(unitId)
@@ -642,6 +656,7 @@ end

 -- Reset the state to the current conditions.
 function OvaleAura:ResetState(state)
+	profiler.Start("OvaleAura_ResetState")
 	-- Advance age of auras in state machine.
 	state.serial = state.serial + 1

@@ -666,6 +681,7 @@ function OvaleAura:ResetState(state)
 			state.aura[guid] = nil
 		end
 	end
+	profiler.Stop("OvaleAura_ResetState")
 end

 -- Release state resources prior to removing from the simulator.
@@ -677,20 +693,24 @@ end

 -- Apply the effects of the spell on the player's state, assuming the spellcast completes.
 function OvaleAura:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleAura_ApplySpellAfterCast")
 	local si = OvaleData.spellInfo[spellId]
 	-- Apply the auras on the player.
 	if si and si.aura and si.aura.player then
 		state:ApplySpellAuras(spellId, self_guid, startCast, endCast, isChanneled, si.aura.player, spellcast)
 	end
+	profiler.Stop("OvaleAura_ApplySpellAfterCast")
 end

 -- Apply the effects of the spell on the target's state after it lands on the target.
 function OvaleAura:ApplySpellAfterHit(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleAura_ApplySpellAfterHit")
 	local si = OvaleData.spellInfo[spellId]
 	-- Apply the auras on the target.
 	if si and si.aura and si.aura.target then
 		state:ApplySpellAuras(spellId, targetGUID, startCast, endCast, isChanneled, si.aura.target, spellcast)
 	end
+	profiler.Stop("OvaleAura_ApplySpellAfterHit")
 end
 --</public-static-methods>

@@ -915,6 +935,7 @@ statePrototype.IsActiveAura = function(state, aura, atTime)
 end

 statePrototype.ApplySpellAuras = function(state, spellId, guid, startCast, endCast, isChanneled, auraList, spellcast)
+	profiler.Start("OvaleAura_state_ApplySpellAuras")
 	local unitId = OvaleGUID:GetUnitId(guid)
 	for filter, filterInfo in pairs(auraList) do
 		for auraId, spellData in pairs(filterInfo) do
@@ -1042,6 +1063,7 @@ statePrototype.ApplySpellAuras = function(state, spellId, guid, startCast, endCa
 			end
 		end
 	end
+	profiler.Stop("OvaleAura_state_ApplySpellAuras")
 end

 statePrototype.GetAuraByGUID = function(state, guid, auraId, filter, mine)
@@ -1182,6 +1204,7 @@ do
 		the count is more than 0.  If excludeUnitId is given, then that unit is excluded from the count.
 	--]]
 	statePrototype.AuraCount = function(state, auraId, filter, mine, minStacks, excludeUnitId)
+		profiler.Start("OvaleAura_state_AuraCount")
 		-- Initialize.
 		minStacks = minStacks or 1
 		count = 0
@@ -1229,6 +1252,7 @@ do
 		end

 		Ovale:Logf("AuraCount(%d) is %s, %s, %s, %s, %s, %s", auraId, count, stacks, startChangeCount, endingChangeCount, startFirst, endingLast)
+		profiler.Stop("OvaleAura_state_AuraCount")
 		return count, stacks, startChangeCount, endingChangeCount, startFirst, endingLast
 	end
 end
diff --git a/OvaleComboPoints.lua b/OvaleComboPoints.lua
index 3837df8..730ed79 100644
--- a/OvaleComboPoints.lua
+++ b/OvaleComboPoints.lua
@@ -14,6 +14,14 @@ local OvaleComboPoints = Ovale:NewModule("OvaleComboPoints", "AceEvent-3.0")
 Ovale.OvaleComboPoints = OvaleComboPoints

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleComboPoints")
+	profiler = Profiler.group["OvaleComboPoints"]
+end
+
 -- Forward declarations for module dependencies.
 local OvaleAura = nil
 local OvaleData = nil
@@ -174,11 +182,14 @@ end

 -- Reset the state to the current conditions.
 function OvaleComboPoints:ResetState(state)
+	profiler.Start("OvaleComboPoints_ResetState")
 	state.combo = self.combo or 0
+	profiler.Stop("OvaleComboPoints_ResetState")
 end

 -- Apply the effects of the spell on the player's state, assuming the spellcast completes.
 function OvaleComboPoints:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleComboPoints_ApplySpellAfterCast")
 	local si = OvaleData.spellInfo[spellId]
 	if si and si.combo then
 		local cost = state:ComboPointCost(spellId)
@@ -193,12 +204,14 @@ function OvaleComboPoints:ApplySpellAfterCast(state, spellId, targetGUID, startC
 		end
 		state.combo = power
 	end
+	profiler.Stop("OvaleComboPoints_ApplySpellAfterCast")
 end
 --</public-static-methods>

 --<state-methods>
 -- Return the number of combo points required to cast the given spell.
 statePrototype.ComboPointCost = function(state, spellId)
+	profiler.Start("OvaleComboPoints_state_ComboPointCost")
 	local spellCost = 0
 	local si = OvaleData.spellInfo[spellId]
 	if si and si.combo then
@@ -249,6 +262,7 @@ statePrototype.ComboPointCost = function(state, spellId)
 		end
 		spellCost = cost
 	end
+	profiler.Stop("OvaleComboPoints_state_ComboPointCost")
 	return spellCost
 end
 --</state-methods>
diff --git a/OvaleCompile.lua b/OvaleCompile.lua
index 4132976..395c023 100644
--- a/OvaleCompile.lua
+++ b/OvaleCompile.lua
@@ -13,6 +13,14 @@ local OvaleCompile = Ovale:NewModule("OvaleCompile", "AceEvent-3.0")
 Ovale.OvaleCompile = OvaleCompile

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleCompile")
+	profiler = Profiler.group["OvaleCompile"]
+end
+
 local L = Ovale.L
 local OvalePool = Ovale.OvalePool
 local OvaleTimeSpan = Ovale.OvaleTimeSpan
@@ -840,6 +848,7 @@ local function CompileDeclarations(text)
 end

 local function CompileScript(text)
+	profiler.Start("OvaleCompile_CompileScript")
 	local self = OvaleCompile
 	self_compileOnItems = false
 	self_compileOnStances = false
@@ -913,6 +922,7 @@ local function CompileScript(text)
 	for k, v in pairs(self_missingSpellList) do
 		OvaleSpellBook:AddSpell(k, v)
 	end
+	profiler.Stop("OvaleCompile_CompileScript")
 end
 --</private-static-methods>

diff --git a/OvaleCooldown.lua b/OvaleCooldown.lua
index d0ab5d3..cb3b585 100644
--- a/OvaleCooldown.lua
+++ b/OvaleCooldown.lua
@@ -13,6 +13,14 @@ local OvaleCooldown = Ovale:NewModule("OvaleCooldown", "AceEvent-3.0")
 Ovale.OvaleCooldown = OvaleCooldown

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleCooldown")
+	profiler = Profiler.group["OvaleCooldown"]
+end
+
 -- Forward declarations for module dependencies.
 local OvaleData = nil
 local OvaleGUID = nil
@@ -171,6 +179,7 @@ end

 -- Reset the state to the current conditions.
 function OvaleCooldown:ResetState(state)
+	profiler.Start("OvaleCooldown_ResetState")
 	for _, cd in pairs(state.cd) do
 		-- Remove outdated cooldown state.
 		if cd.serial and cd.serial < self_serial then
@@ -179,6 +188,7 @@ function OvaleCooldown:ResetState(state)
 			end
 		end
 	end
+	profiler.Stop("OvaleCooldown_ResetState")
 end

 -- Release state resources prior to removing from the simulator.
@@ -193,6 +203,7 @@ end

 -- Apply the effects of the spell on the player's state, assuming the spellcast completes.
 function OvaleCooldown:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleCooldown_ApplySpellAfterCast")
 	local si = OvaleData.spellInfo[spellId]
 	if si then
 		local cd = state:GetCD(spellId)
@@ -257,6 +268,7 @@ function OvaleCooldown:ApplySpellAfterCast(state, spellId, targetGUID, startCast

 		Ovale:Logf("Spell %d cooldown info: start=%f, duration=%f", spellId, cd.start, cd.duration)
 	end
+	profiler.Stop("OvaleCooldown_ApplySpellAfterCast")
 end
 --</public-static-methods>

@@ -276,6 +288,7 @@ end

 -- Return the table holding the simulator's cooldown information for the given spell.
 statePrototype.GetCD = function(state, spellId)
+	profiler.Start("OvaleCooldown_state_GetCD")
 	local cdName = spellId
 	local si = OvaleData.spellInfo[spellId]
 	if si and si.sharedcd then
@@ -329,6 +342,7 @@ statePrototype.GetCD = function(state, spellId)
 		cd.chargeStart = chargeStart
 	end

+	profiler.Stop("OvaleCooldown_state_GetCD")
 	return cd
 end

diff --git a/OvaleDamageTaken.lua b/OvaleDamageTaken.lua
index 89c3bea..9d0dd9e 100644
--- a/OvaleDamageTaken.lua
+++ b/OvaleDamageTaken.lua
@@ -14,6 +14,14 @@ local OvaleDamageTaken = Ovale:NewModule("OvaleDamageTaken", "AceEvent-3.0")
 Ovale.OvaleDamageTaken = OvaleDamageTaken

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleDamageTaken")
+	profiler = Profiler.group["OvaleDamageTaken"]
+end
+
 local OvalePool = Ovale.OvalePool
 local OvaleQueue = Ovale.OvaleQueue

@@ -60,6 +68,7 @@ function OvaleDamageTaken:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, cleuEven
 	local arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23 = ...

 	if destGUID == self_guid and strsub(cleuEvent, -7) == "_DAMAGE" then
+		profiler.Start("OvaleDamageTaken_COMBAT_LOG_EVENT_UNFILTERED")
 		local now = API_GetTime()
 		local eventPrefix = strsub(cleuEvent, 1, 6)
 		if eventPrefix == "SWING_" then
@@ -71,6 +80,7 @@ function OvaleDamageTaken:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, cleuEven
 			Ovale:DebugPrintf(OVALE_DAMAGE_TAKEN_DEBUG, "%s (%s) caused %d damage.", cleuEvent, spellName, amount)
 			self:AddDamageTaken(now, amount)
 		end
+		profiler.Stop("OvaleDamageTaken_COMBAT_LOG_EVENT_UNFILTERED")
 	end
 end

@@ -79,11 +89,13 @@ function OvaleDamageTaken:PLAYER_REGEN_ENABLED(event)
 end

 function OvaleDamageTaken:AddDamageTaken(timestamp, damage)
+	profiler.Start("OvaleDamageTaken_AddDamageTaken")
 	local event = self_pool:Get()
 	event.timestamp = timestamp
 	event.damage = damage
 	self.damageEvent:InsertFront(event)
 	self:RemoveExpiredEvents(timestamp)
+	profiler.Stop("OvaleDamageTaken_AddDamageTaken")
 end

 -- Return the total damage taken in the previous time interval (in seconds).
@@ -107,6 +119,7 @@ end

 -- Remove all events that are more than DAMAGE_TAKEN_WINDOW seconds before the given timestamp.
 function OvaleDamageTaken:RemoveExpiredEvents(timestamp)
+	profiler.Start("OvaleDamageTaken_RemoveExpiredEvents")
 	while true do
 		local event = self.damageEvent:Back()
 		if not event then break end
@@ -118,6 +131,7 @@ function OvaleDamageTaken:RemoveExpiredEvents(timestamp)
 			self_pool:Release(event)
 		end
 	end
+	profiler.Stop("OvaleDamageTaken_RemoveExpiredEvents")
 end

 function OvaleDamageTaken:Debug()
diff --git a/OvaleEclipse.lua b/OvaleEclipse.lua
index 6d3db1d..2535804 100644
--- a/OvaleEclipse.lua
+++ b/OvaleEclipse.lua
@@ -16,6 +16,14 @@ local OvaleEclipse = Ovale:NewModule("OvaleEclipse", "AceEvent-3.0")
 Ovale.OvaleEclipse = OvaleEclipse

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleEclipse")
+	profiler = Profiler.group["OvaleEclipse"]
+end
+
 -- Forward declarations for module dependencies.
 local OvaleAura = nil
 local OvaleData = nil
@@ -209,29 +217,36 @@ end

 -- Reset the state to the current conditions.
 function OvaleEclipse:ResetState(state)
+	profiler.Start("OvaleEclipse_ResetState")
 	state.eclipseDirection = self.eclipseDirection
+	profiler.Stop("OvaleEclipse_ResetState")
 end

 -- Apply the effects of the spell at the start of the spellcast.
 function OvaleEclipse:ApplySpellStartCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleEclipse_ApplySpellStartCast")
 	-- Channeled spells cost resources at the start of the channel.
 	if isChanneled then
 		state:ApplyEclipseEnergy(spellId, startCast, spellcast.snapshot)
 	end
+	profiler.Stop("OvaleEclipse_ApplySpellStartCast")
 end

 -- Apply the effects of the spell on the player's state, assuming the spellcast completes.
 function OvaleEclipse:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleEclipse_ApplySpellAfterCast")
 	-- Instant or cast-time spells cost resources at the end of the spellcast.
 	if not isChanneled then
 		state:ApplyEclipseEnergy(spellId, endCast, spellcast.snapshot)
 	end
+	profiler.Stop("OvaleEclipse_ApplySpellAfterCast")
 end
 --</public-static-methods>

 --<state-methods>
 -- Update the state of the simulator for the eclipse energy gained by casting the given spell.
 statePrototype.ApplyEclipseEnergy = function(state, spellId, atTime, snapshot)
+	profiler.Start("OvaleEclipse_ApplyEclipseEnergy")
 	if spellId == CELESTIAL_ALIGNMENT then
 		local aura = state:AddAuraToGUID(self_guid, spellId, self_guid, "HELPFUL", atTime, atTime + 15, snapshot)
 		aura.value1 = state:EclipseBonusDamage(atTime, snapshot)
@@ -283,6 +298,7 @@ statePrototype.ApplyEclipseEnergy = function(state, spellId, atTime, snapshot)
 			state.eclipseDirection = direction
 		end
 	end
+	profiler.Stop("OvaleEclipse_ApplyEclipseEnergy")
 end

 statePrototype.EclipseEnergy = function(state, spellId)
diff --git a/OvaleEnemies.lua b/OvaleEnemies.lua
index 9b45aff..d7a2942 100644
--- a/OvaleEnemies.lua
+++ b/OvaleEnemies.lua
@@ -15,6 +15,14 @@ local OvaleEnemies = Ovale:NewModule("OvaleEnemies", "AceEvent-3.0", "AceTimer-3
 Ovale.OvaleEnemies = OvaleEnemies

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleEnemies")
+	profiler = Profiler.group["OvaleEnemies"]
+end
+
 local bit_band = bit.band
 local pairs = pairs
 local tostring = tostring
@@ -84,15 +92,18 @@ end
 -- These enemies are not in combat with your group, out of range, or
 -- incapacitated and shouldn't count toward the number of active enemies.
 function OvaleEnemies:RemoveInactiveEnemies()
+	profiler.Start("OvaleEnemies_RemoveInactiveEnemies")
 	local now = API_GetTime()
 	for guid, timestamp in pairs(self.enemyLastSeen) do
 		if now - timestamp > REAP_INTERVAL then
 			self:RemoveEnemy(guid)
 		end
 	end
+	profiler.Stop("OvaleEnemies_RemoveInactiveEnemies")
 end

 function OvaleEnemies:AddEnemy(guid, name, timestamp)
+	profiler.Start("OvaleEnemies_AddEnemy")
 	if not guid then return end
 	local seen = self.enemyLastSeen[guid]
 	self.enemyLastSeen[guid] = timestamp
@@ -102,9 +113,11 @@ function OvaleEnemies:AddEnemy(guid, name, timestamp)
 		Ovale:DebugPrintf(OVALE_ENEMIES_DEBUG, "New enemy (%d total): %s (%s)", self.activeEnemies, guid, name)
 		Ovale.refreshNeeded["player"] = true
 	end
+	profiler.Stop("OvaleEnemies_AddEnemy")
 end

 function OvaleEnemies:RemoveEnemy(guid, isDead)
+	profiler.Start("OvaleEnemies_RemoveEnemy")
 	if not guid then return end
 	local seen = self.enemyLastSeen[guid]
 	local name = self.enemyName[guid]
@@ -121,6 +134,7 @@ function OvaleEnemies:RemoveEnemy(guid, isDead)
 		self:SendMessage("Ovale_InactiveUnit", guid)
 		Ovale.refreshNeeded["player"] = true
 	end
+	profiler.Stop("OvaleEnemies_RemoveEnemy")
 end

 function OvaleEnemies:Debug()
diff --git a/OvaleEquipement.lua b/OvaleEquipement.lua
index 6c028be..94b59c7 100644
--- a/OvaleEquipement.lua
+++ b/OvaleEquipement.lua
@@ -13,6 +13,14 @@ local OvaleEquipement = Ovale:NewModule("OvaleEquipement", "AceEvent-3.0")
 Ovale.OvaleEquipement = OvaleEquipement

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleEquipement")
+	profiler = Profiler.group["OvaleEquipement"]
+end
+
 local pairs = pairs
 local strgsub = string.gsub
 local strmatch = string.match
@@ -415,6 +423,7 @@ function OvaleEquipement:OnDisable()
 end

 function OvaleEquipement:PLAYER_EQUIPMENT_CHANGED(event, slotId, hasItem)
+	profiler.Start("OvaleEquipement_PLAYER_EQUIPMENT_CHANGED")
 	if hasItem then
 		self.equippedItems[slotId] = API_GetInventoryItemID("player", slotId)
 		self.equippedItemLevels[slotId] = GetItemLevel(slotId)
@@ -437,6 +446,7 @@ function OvaleEquipement:PLAYER_EQUIPMENT_CHANGED(event, slotId, hasItem)

 	self:UpdateArmorSetCount()
 	self:SendMessage("Ovale_EquipmentChanged")
+	profiler.Stop("OvaleEquipement_PLAYER_EQUIPMENT_CHANGED")
 end

 do
@@ -575,6 +585,7 @@ function OvaleEquipement:HasOneHandedWeapon(slotId)
 end

 function OvaleEquipement:UpdateArmorSetCount()
+	profiler.Start("OvaleEquipement_UpdateArmorSetCount")
 	wipe(self.armorSetCount)
 	for i = 1, #OVALE_ARMORSET_SLOT_IDS do
 		local itemId = self:GetEquippedItem(OVALE_ARMORSET_SLOT_IDS[i])
@@ -589,9 +600,11 @@ function OvaleEquipement:UpdateArmorSetCount()
 			end
 		end
 	end
+	profiler.Stop("OvaleEquipement_UpdateArmorSetCount")
 end

 function OvaleEquipement:UpdateEquippedItems()
+	profiler.Start("OvaleEquipement_UpdateEquippedItems")
 	local changed = false
 	local item
 	for slotId = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do
@@ -615,9 +628,11 @@ function OvaleEquipement:UpdateEquippedItems()
 		self:SendMessage("Ovale_EquipmentChanged")
 	end
 	self.ready = true
+	profiler.Stop("OvaleEquipement_UpdateEquippedItems")
 end

 function OvaleEquipement:UpdateEquippedItemLevels()
+	profiler.Start("OvaleEquipement_UpdateEquippedItemLevels")
 	local changed = false
 	local itemLevel
 	for slotId = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do
@@ -630,10 +645,12 @@ function OvaleEquipement:UpdateEquippedItemLevels()
 	if changed then
 		self:SendMessage("Ovale_EquipmentChanged")
 	end
+	profiler.Stop("OvaleEquipement_UpdateEquippedItemLevels")
 	return changed
 end

 function OvaleEquipement:UpdateMetaGem()
+	profiler.Start("OvaleEquipement_UpdateMetaGem")
 	local changed = false
 	local gemId = API_GetInventoryItemGems(INVSLOT_HEAD)
 	if gemId then
@@ -650,6 +667,7 @@ function OvaleEquipement:UpdateMetaGem()
 			changed = true
 		end
 	end
+	profiler.Stop("OvaleEquipement_UpdateMetaGem")
 	return changed
 end

diff --git a/OvaleFuture.lua b/OvaleFuture.lua
index 141a183..b055676 100644
--- a/OvaleFuture.lua
+++ b/OvaleFuture.lua
@@ -15,6 +15,14 @@ local OvaleFuture = Ovale:NewModule("OvaleFuture", "AceEvent-3.0")
 Ovale.OvaleFuture = OvaleFuture

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleFuture")
+	profiler = Profiler.group["OvaleFuture"]
+end
+
 local OvalePool = Ovale.OvalePool

 -- Forward declarations for module dependencies.
@@ -161,6 +169,7 @@ local function GetDamageMultiplier(spellId, snapshot, auraObject)
 end

 local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, allowRemove)
+	profiler.Start("OvaleFuture_AddSpellToQueue")
 	local self = OvaleFuture
 	local spellcast = self_pool:Get()
 	spellcast.spellId = spellId
@@ -251,9 +260,11 @@ local function AddSpellToQueue(spellId, lineId, startTime, endTime, channeled, a

 	OvaleScore:ScoreSpell(spellId)
 	Ovale.refreshNeeded["player"] = true
+	profiler.Stop("OvaleFuture_AddSpellToQueue")
 end

 local function RemoveSpellFromQueue(spellId, lineId)
+	profiler.Start("OvaleFuture_RemoveSpellFromQueue")
 	local self = OvaleFuture
 	for index, spellcast in ipairs(self_activeSpellcast) do
 		if spellcast.lineId == lineId then
@@ -264,6 +275,7 @@ local function RemoveSpellFromQueue(spellId, lineId)
 		end
 	end
 	Ovale.refreshNeeded["player"] = true
+	profiler.Stop("OvaleFuture_RemoveSpellFromQueue")
 end

 -- UpdateLastSpellcast() is called at the end of the event handler for CLEU_SUCCESSFUL_SPELLCAST_EVENT[].
@@ -271,6 +283,7 @@ end
 -- snapshot values are correctly adjusted for buffs that are added or cleared simultaneously with the
 -- spellcast.
 local function UpdateLastSpellcast(spellcast)
+	profiler.Start("OvaleFuture_UpdateLastSpellcast")
 	local self = OvaleFuture
 	local targetGUID = spellcast.target
 	local spellId = spellcast.spellId
@@ -301,6 +314,7 @@ local function UpdateLastSpellcast(spellcast)
 			end
 		end
 	end
+	profiler.Stop("OvaleFuture_UpdateLastSpellcast")
 end
 --</private-static-methods>

@@ -446,6 +460,7 @@ end
 ]]--
 function OvaleFuture:UNIT_SPELLCAST_SUCCEEDED(event, unit, name, rank, lineId, spellId)
 	if unit == "player" then
+		profiler.Start("OvaleFuture_UNIT_SPELLCAST_SUCCEEDED")
 		TracePrintf(spellId, "%s: %d, lineId=%d", event, spellId, lineId)

 		-- Search for a cast-time spell matching this spellcast that was added by UNIT_SPELLCAST_START.
@@ -458,6 +473,7 @@ function OvaleFuture:UNIT_SPELLCAST_SUCCEEDED(event, unit, name, rank, lineId, s
 				end
 				spellcast.snapshot = OvalePaperDoll:GetSnapshot()
 				spellcast.damageMultiplier = GetDamageMultiplier(spellId, spellcast.snapshot)
+				profiler.Stop("OvaleFuture_UNIT_SPELLCAST_SUCCEEDED")
 				return
 			end
 		end
@@ -473,6 +489,7 @@ function OvaleFuture:UNIT_SPELLCAST_SUCCEEDED(event, unit, name, rank, lineId, s
 			local now = API_GetTime()
 			AddSpellToQueue(spellId, lineId, now, now, false, true)
 		end
+		profiler.Stop("OvaleFuture_UNIT_SPELLCAST_SUCCEEDED")
 	end
 end

@@ -522,6 +539,7 @@ function OvaleFuture:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, cleuEvent, hi
 	if sourceGUID == self_guid then
 		local success = CLEU_SUCCESSFUL_SPELLCAST_EVENT[cleuEvent]
 		if success then
+			profiler.Start("OvaleFuture_COMBAT_LOG_EVENT_UNFILTERED")
 			local spellId, spellName = arg12, arg13
 			TracePrintf(spellId, "%s: %s (%d)", cleuEvent, spellName, spellId)
 			for index, spellcast in ipairs(self_activeSpellcast) do
@@ -538,6 +556,7 @@ function OvaleFuture:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, cleuEvent, hi
 					break
 				end
 			end
+			profiler.Stop("OvaleFuture_COMBAT_LOG_EVENT_UNFILTERED")
 		end
 	end
 end
@@ -545,12 +564,10 @@ end
 -- Apply the effects of spells that are being cast or are in flight, allowing us to
 -- ignore lag or missile travel time.
 function OvaleFuture:ApplyInFlightSpells(state)
+	profiler.Start("OvaleFuture_ApplyInFlightSpells")
 	local now = API_GetTime()
-	local index = 0
-	while true do
-		index = index + 1
-		if index > #self_activeSpellcast then return end
-
+	local index = 1
+	while index <= #self_activeSpellcast do
 		local spellcast = self_activeSpellcast[index]
 		Ovale:Logf("now = %f, spellId = %d, endCast = %f", now, spellcast.spellId, spellcast.stop)
 		if now - spellcast.stop < 5 then
@@ -561,7 +578,9 @@ function OvaleFuture:ApplyInFlightSpells(state)
 			-- Decrement current index since item was removed and rest of items shifted up.
 			index = index - 1
 		end
+		index = index + 1
 	end
+	profiler.Stop("OvaleFuture_ApplyInFlightSpells")
 end

 function OvaleFuture:LastInFlightSpell()
@@ -572,6 +591,7 @@ function OvaleFuture:LastInFlightSpell()
 end

 function OvaleFuture:UpdateSnapshotFromSpellcast(dest, spellcast)
+	profiler.Start("OvaleFuture_UpdateSnapshotFromSpellcast")
 	if dest.snapshot then
 		OvalePaperDoll:ReleaseSnapshot(dest.snapshot)
 	end
@@ -587,6 +607,7 @@ function OvaleFuture:UpdateSnapshotFromSpellcast(dest, spellcast)
 			tbl.UpdateFromSpellcast(dest, spellcast)
 		end
 	end
+	profiler.Stop("OvaleFuture_UpdateSnapshotFromSpellcast")
 end

 function OvaleFuture:GetLastSpellInfo(guid, spellId, statName)
@@ -667,6 +688,7 @@ end

 -- Reset the state to the current conditions.
 function OvaleFuture:ResetState(state)
+	profiler.Start("OvaleFuture_ResetState")
 	local now = API_GetTime()
 	state.currentTime = now
 	Ovale:Logf("Reset state with current time = %f", state.currentTime)
@@ -679,6 +701,7 @@ function OvaleFuture:ResetState(state)
 	for k, v in pairs(self.counter) do
 		state.counter[k] = self.counter[k]
 	end
+	profiler.Stop("OvaleFuture_ResetState")
 end

 -- Release state resources prior to removing from the simulator.
@@ -698,6 +721,7 @@ end

 -- Apply the effects of the spell at the start of the spellcast.
 function OvaleFuture:ApplySpellStartCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleFuture_ApplySpellStartCast")
 	local si = OvaleData.spellInfo[spellId]
 	if si then
 		-- Increment and reset spell counters.
@@ -711,6 +735,7 @@ function OvaleFuture:ApplySpellStartCast(state, spellId, targetGUID, startCast,
 			state.counter[id] = 0
 		end
 	end
+	profiler.Stop("OvaleFuture_ApplySpellStartCast")
 end
 --</public-static-methods>

@@ -737,61 +762,61 @@ end
 		spellcast	(optional) Table of spellcast information, including a snapshot of player's stats.
 --]]
 statePrototype.ApplySpell = function(state, ...)
+	profiler.Start("OvaleFuture_state_ApplySpell")
 	local spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast = ...
-	if not spellId or not targetGUID then
-		return
-	end
-
-	-- Handle missing start/end/next cast times.
-	if not startCast or not endCast or not nextCast then
-		local _, _, _, _, _, _, castTime = API_GetSpellInfo(spellId)
-		castTime = castTime and (castTime / 1000) or 0
-		local gcd = OvaleCooldown:GetGCD(spellId)
-
-		startCast = startCast or state.nextCast
-		endCast = endCast or (startCast + castTime)
-		nextCast = (castTime > gcd) and endCast or (startCast + gcd)
-	end
-
-	-- Update the latest spell cast in the simulator.
-	state.currentSpellId = spellId
-	state.startCast = startCast
-	state.endCast = endCast
-	state.nextCast = nextCast
-	state.isChanneling = isChanneled
-	state.lastSpellId = spellId
+	if spellId and targetGUID then
+		-- Handle missing start/end/next cast times.
+		if not startCast or not endCast or not nextCast then
+			local _, _, _, _, _, _, castTime = API_GetSpellInfo(spellId)
+			castTime = castTime and (castTime / 1000) or 0
+			local gcd = OvaleCooldown:GetGCD(spellId)
+
+			startCast = startCast or state.nextCast
+			endCast = endCast or (startCast + castTime)
+			nextCast = (castTime > gcd) and endCast or (startCast + gcd)
+		end

-	-- Set the current time in the simulator to a little after the start of the current cast,
-	-- or to now if in the past.
-	local now = API_GetTime()
-	if startCast >= now then
-		state.currentTime = startCast + 0.1
-	else
-		state.currentTime = now
-	end
+		-- Update the latest spell cast in the simulator.
+		state.currentSpellId = spellId
+		state.startCast = startCast
+		state.endCast = endCast
+		state.nextCast = nextCast
+		state.isChanneling = isChanneled
+		state.lastSpellId = spellId
+
+		-- Set the current time in the simulator to a little after the start of the current cast,
+		-- or to now if in the past.
+		local now = API_GetTime()
+		if startCast >= now then
+			state.currentTime = startCast + 0.1
+		else
+			state.currentTime = now
+		end

-	Ovale:Logf("Apply spell %d at %f currentTime=%f nextCast=%f endCast=%f targetGUID=%s", spellId, startCast, state.currentTime, nextCast, endCast, targetGUID)
+		Ovale:Logf("Apply spell %d at %f currentTime=%f nextCast=%f endCast=%f targetGUID=%s", spellId, startCast, state.currentTime, nextCast, endCast, targetGUID)

-	--[[
-		Apply the effects of the spellcast in four phases.
-			1. Effects at the beginning of the spellcast.
-			2. Effects when the spell has been cast.
-			3. Effects when the spellcast hits the target.
-			4. Effects after the spellcast hits the target (possibly due to server lag).
-	--]]
-	-- If the spellcast has already started, then the effects have already occurred.
-	if startCast > now then
-		OvaleState:InvokeMethod("ApplySpellStartCast", state, ...)
-	end
-	-- If the spellcast has already ended, then the effects have already occurred.
-	if endCast > now then
-		OvaleState:InvokeMethod("ApplySpellAfterCast", state, ...)
-	end
-	if not spellcast or not spellcast.success then
-		OvaleState:InvokeMethod("ApplySpellOnHit", state, ...)
-	end
-	if not spellcast or not spellcast.success or spellcast.success == "hit" then
-		OvaleState:InvokeMethod("ApplySpellAfterHit", state, ...)
+		--[[
+			Apply the effects of the spellcast in four phases.
+				1. Effects at the beginning of the spellcast.
+				2. Effects when the spell has been cast.
+				3. Effects when the spellcast hits the target.
+				4. Effects after the spellcast hits the target (possibly due to server lag).
+		--]]
+		-- If the spellcast has already started, then the effects have already occurred.
+		if startCast > now then
+			OvaleState:InvokeMethod("ApplySpellStartCast", state, ...)
+		end
+		-- If the spellcast has already ended, then the effects have already occurred.
+		if endCast > now then
+			OvaleState:InvokeMethod("ApplySpellAfterCast", state, ...)
+		end
+		if not spellcast or not spellcast.success then
+			OvaleState:InvokeMethod("ApplySpellOnHit", state, ...)
+		end
+		if not spellcast or not spellcast.success or spellcast.success == "hit" then
+			OvaleState:InvokeMethod("ApplySpellAfterHit", state, ...)
+		end
 	end
+	profiler.Stop("OvaleFuture_state_ApplySpell")
 end
 --</state-methods>
diff --git a/OvalePaperDoll.lua b/OvalePaperDoll.lua
index c68825a..834d7aa 100644
--- a/OvalePaperDoll.lua
+++ b/OvalePaperDoll.lua
@@ -14,6 +14,14 @@ local OvalePaperDoll = Ovale:NewModule("OvalePaperDoll", "AceEvent-3.0")
 Ovale.OvalePaperDoll = OvalePaperDoll

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvalePaperDoll")
+	profiler = Profiler.group["OvalePaperDoll"]
+end
+
 local OvalePool = Ovale.OvalePoolRefCount

 -- Forward declarations for module dependencies.
@@ -221,6 +229,7 @@ function OvalePaperDoll:OnDisable()
 end

 function OvalePaperDoll:COMBAT_RATING_UPDATE(event)
+	profiler.Start("OvalePaperDoll_UpdateStats")
 	local snapshot = UpdateCurrentSnapshot()
 	snapshot.meleeCrit = API_GetCritChance()
 	snapshot.rangedCrit = API_GetRangedCritChance()
@@ -231,9 +240,11 @@ function OvalePaperDoll:COMBAT_RATING_UPDATE(event)
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["meleeCrit"].description, snapshot.meleeCrit)
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["rangedCrit"].description, snapshot.rangedCrit)
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["spellCrit"].description, snapshot.spellCrit)
+	profiler.Stop("OvalePaperDoll_UpdateStats")
 end

 function OvalePaperDoll:MASTERY_UPDATE(event)
+	profiler.Start("OvalePaperDoll_UpdateStats")
 	local snapshot = UpdateCurrentSnapshot()
 	snapshot.masteryRating = API_GetMastery()
 	if self.level < 80 then
@@ -243,18 +254,23 @@ function OvalePaperDoll:MASTERY_UPDATE(event)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %f%%",
 			event, self.SNAPSHOT_STATS["masteryEffect"].description, snapshot.masteryEffect)
 	end
+	profiler.Stop("OvalePaperDoll_UpdateStats")
 end

 function OvalePaperDoll:PLAYER_LEVEL_UP(event, level, ...)
+	profiler.Start("OvalePaperDoll_UpdateStats")
 	self.level = tonumber(level) or API_UnitLevel("player")
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: level = %d", event, self.level)
+	profiler.Stop("OvalePaperDoll_UpdateStats")
 end

 function OvalePaperDoll:PLAYER_DAMAGE_DONE_MODS(event, unitId)
+	profiler.Start("OvalePaperDoll_UpdateStats")
 	local snapshot = UpdateCurrentSnapshot()
 	snapshot.spellBonusHealing = API_GetSpellBonusHealing()
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
 		event, self.SNAPSHOT_STATS["spellBonusHealing"].description, snapshot.spellBonusHealing)
+	profiler.Stop("OvalePaperDoll_UpdateStats")
 end

 function OvalePaperDoll:PLAYER_REGEN_DISABLED(event)
@@ -262,60 +278,73 @@ function OvalePaperDoll:PLAYER_REGEN_DISABLED(event)
 end

 function OvalePaperDoll:PLAYER_REGEN_ENABLED(event)
+	profiler.Start("OvalePaperDoll_UpdateStats")
 	local now = API_GetTime()
 	if Ovale.enCombat and Ovale.combatStartTime then
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%d snapshots in %f seconds.",
 			self_snapshotCount, now - Ovale.combatStartTime)
 	end
 	self_pool:Drain()
+	profiler.Stop("OvalePaperDoll_UpdateStats")
 end

 function OvalePaperDoll:SPELL_POWER_CHANGED(event)
+	profiler.Start("OvalePaperDoll_UpdateStats")
 	local snapshot = UpdateCurrentSnapshot()
 	snapshot.spellBonusDamage = API_GetSpellBonusDamage(OVALE_SPELLDAMAGE_SCHOOL[self_class])
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
 		event, self.SNAPSHOT_STATS["spellBonusDamage"].description, snapshot.spellBonusDamage)
+	profiler.Stop("OvalePaperDoll_UpdateStats")
 end

 function OvalePaperDoll:UNIT_ATTACK_POWER(event, unitId)
 	if unitId == "player" then
+		profiler.Start("OvalePaperDoll_UpdateStats")
 		local snapshot = UpdateCurrentSnapshot()
 		local base, posBuff, negBuff = API_UnitAttackPower(unitId)
 		snapshot.attackPower = base + posBuff + negBuff
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
 			event, self.SNAPSHOT_STATS["attackPower"].description, snapshot.attackPower)
 		self:UpdateDamage(event)
+		profiler.Stop("OvalePaperDoll_UpdateStats")
 	end
 end

 function OvalePaperDoll:UNIT_LEVEL(event, unitId)
 	if unitId == "player" then
+		profiler.Start("OvalePaperDoll_UpdateStats")
 		self.level = API_UnitLevel(unitId)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: level = %d", event, self.level)
+		profiler.Stop("OvalePaperDoll_UpdateStats")
 	end
 end

 function OvalePaperDoll:UNIT_RANGEDDAMAGE(event, unitId)
 	if unitId == "player" then
+		profiler.Start("OvalePaperDoll_UpdateStats")
 		local snapshot = UpdateCurrentSnapshot()
 		snapshot.rangedHaste = API_GetRangedHaste()
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %f%%",
 			event, self.SNAPSHOT_STATS["rangedHaste"].description, snapshot.rangedHaste)
+		profiler.Stop("OvalePaperDoll_UpdateStats")
 	end
 end

 function OvalePaperDoll:UNIT_RANGED_ATTACK_POWER(event, unitId)
 	if unitId == "player" then
+		profiler.Start("OvalePaperDoll_UpdateStats")
 		local base, posBuff, negBuff = API_UnitRangedAttackPower(unitId)
 		local snapshot = UpdateCurrentSnapshot()
 		snapshot.rangedAttackPower = base + posBuff + negBuff
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, true, "%s: %s = %d",
 			event, self.SNAPSHOT_STATS["rangedAttackPower"].description, snapshot.rangedAttackPower)
+		profiler.Stop("OvalePaperDoll_UpdateStats")
 	end
 end

 function OvalePaperDoll:UNIT_SPELL_HASTE(event, unitId)
 	if unitId == "player" then
+		profiler.Start("OvalePaperDoll_UpdateStats")
 		local snapshot = UpdateCurrentSnapshot()
 		snapshot.meleeHaste = API_GetMeleeHaste()
 		snapshot.spellHaste = API_UnitSpellHaste(unitId)
@@ -323,11 +352,13 @@ function OvalePaperDoll:UNIT_SPELL_HASTE(event, unitId)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["meleeHaste"].description, snapshot.meleeHaste)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f%%", self.SNAPSHOT_STATS["spellHaste"].description, snapshot.spellHaste)
 		self:UpdateDamage(event)
+		profiler.Stop("OvalePaperDoll_UpdateStats")
 	end
 end

 function OvalePaperDoll:UNIT_STATS(event, unitId)
 	if unitId == "player" then
+		profiler.Start("OvalePaperDoll_UpdateStats")
 		local snapshot = UpdateCurrentSnapshot()
 		snapshot.strength = API_UnitStat(unitId, 1)
 		snapshot.agility = API_UnitStat(unitId, 2)
@@ -341,10 +372,12 @@ function OvalePaperDoll:UNIT_STATS(event, unitId)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["stamina"].description, snapshot.stamina)
 		Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %d", self.SNAPSHOT_STATS["strength"].description, snapshot.strength)
 		self:COMBAT_RATING_UPDATE(event)
+		profiler.Stop("OvalePaperDoll_UpdateStats")
 	end
 end

 function OvalePaperDoll:UpdateDamage(event)
+	profiler.Start("OvalePaperDoll_UpdateDamage")
 	local minDamage, maxDamage, minOffHandDamage, maxOffHandDamage, _, _, damageMultiplier = API_UnitDamage("player")
 	local mainHandAttackSpeed, offHandAttackSpeed = API_UnitAttackSpeed("player")

@@ -398,15 +431,18 @@ function OvalePaperDoll:UpdateDamage(event)
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["baseDamageMultiplier"].description, snapshot.baseDamageMultiplier)
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["mainHandWeaponDamage"].description, snapshot.mainHandWeaponDamage)
 	Ovale:DebugPrintf(OVALE_PAPERDOLL_DEBUG, "    %s = %f", self.SNAPSHOT_STATS["offHandWeaponDamage"].description, snapshot.offHandWeaponDamage)
+	profiler.Stop("OvalePaperDoll_UpdateDamage")
 end

 function OvalePaperDoll:UpdateSpecialization(event)
+	profiler.Start("OvalePaperDoll_UpdateSpecialization")
 	local newSpecialization = API_GetSpecialization()
 	if self.specialization ~= newSpecialization then
 		local oldSpecialization = self.specialization
 		self.specialization = newSpecialization
 		self:SendMessage("Ovale_SpecializationChanged", self:GetSpecialization(newSpecialization), self:GetSpecialization(oldSpecialization))
 	end
+	profiler.Stop("OvalePaperDoll_UpdateSpecialization")
 end

 function OvalePaperDoll:UpdateStats(event)
@@ -463,10 +499,12 @@ end

 -- Copy the current snapshot into the given snapshot table.
 function OvalePaperDoll:UpdateSnapshot(snapshot)
+	profiler.Start("OvalePaperDoll:UpdateSnapshot")
 	local snapshot = self:CurrentSnapshot()
 	for k in pairs(self.SNAPSHOT_STATS) do
 		snapshot[k] = self.snapshot[k]
 	end
+	profiler.Stop("OvalePaperDoll:UpdateSnapshot")
 	return snapshot
 end

diff --git a/OvalePool.lua b/OvalePool.lua
index 10fd20c..bb69786 100644
--- a/OvalePool.lua
+++ b/OvalePool.lua
@@ -13,6 +13,14 @@ local OvalePool = {}
 Ovale.OvalePool = OvalePool

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvalePool")
+	profiler = Profiler.group["OvalePool"]
+end
+
 local assert = assert
 local setmetatable = setmetatable
 local tinsert = table.insert
@@ -26,6 +34,7 @@ OvalePool.name = "OvalePool"
 OvalePool.pool = nil
 OvalePool.size = 0
 OvalePool.unused = 0
+OvalePool.profiler = nil
 OvalePool.__index = OvalePool
 --</public-static-properties>

@@ -43,6 +52,7 @@ function OvalePool:NewPool(name)
 end

 function OvalePool:Get()
+	profiler.Start(self.name)
 	assert(self.pool)
 	local item = tremove(self.pool)
 	if item then
@@ -51,15 +61,18 @@ function OvalePool:Get()
 		self.size = self.size + 1
 		item = {}
 	end
+	profiler.Stop(self.name)
 	return item
 end

 function OvalePool:Release(item)
+	profiler.Start(self.name)
 	assert(self.pool)
 	self:Clean(item)
 	wipe(item)
 	tinsert(self.pool, item)
 	self.unused = self.unused + 1
+	profiler.Stop(self.name)
 end

 function OvalePool:GetReference(item)
@@ -75,9 +88,11 @@ function OvalePool:Clean(item)
 end

 function OvalePool:Drain()
+	profiler.Start(self.name)
 	self.pool = {}
 	self.size = self.size - self.unused
 	self.unused = 0
+	profiler.Stop(self.name)
 end

 function OvalePool:Debug()
diff --git a/OvalePoolRefCount.lua b/OvalePoolRefCount.lua
index cd271ef..c9c917b 100644
--- a/OvalePoolRefCount.lua
+++ b/OvalePoolRefCount.lua
@@ -24,6 +24,14 @@ local OvalePoolRefCount = {}
 Ovale.OvalePoolRefCount = OvalePoolRefCount

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvalePoolRefCount")
+	profiler = Profiler.group["OvalePoolRefCount"]
+end
+
 local assert = assert
 local setmetatable = setmetatable
 local tinsert = table.insert
@@ -38,6 +46,7 @@ OvalePoolRefCount.pool = nil
 OvalePoolRefCount.refcount = nil
 OvalePoolRefCount.size = 0
 OvalePoolRefCount.unused = 0
+OvalePoolRefCount.profiler = nil
 OvalePoolRefCount.__index = OvalePoolRefCount
 --</public-static-properties>

@@ -47,14 +56,18 @@ local function ReferenceCount(item)
 end

 local function GetReference(item)
+	local poolObject = item._refcount_pool_object
+	profiler.Start(poolObject.name)
 	local refcount = item:ReferenceCount()
-	item._refcount_pool_object.refcount[item] = refcount + 1
+	poolObject.refcount[item] = refcount + 1
+	profiler.Stop(poolObject.name)
 	return item
 end

 local function ReleaseReference(item)
-	local refcount = item:ReferenceCount()
 	local poolObject = item._refcount_pool_object
+	profiler.Start(poolObject.name)
+	local refcount = item:ReferenceCount()
 	if refcount > 1 then
 		poolObject.refcount[item] = refcount - 1
 	else
@@ -64,6 +77,7 @@ local function ReleaseReference(item)
 		tinsert(poolObject.pool, item)
 		poolObject.unused = poolObject.unused + 1
 	end
+	profiler.Stop(poolObject.name)
 	return item
 end
 --</private-static-methods>
@@ -92,6 +106,7 @@ function OvalePoolRefCount:NewPool(name)
 end

 function OvalePoolRefCount:Get()
+	profiler.Start(self.name)
 	assert(self.pool and self.refcount)
 	local item = tremove(self.pool)
 	if item then
@@ -104,6 +119,7 @@ function OvalePoolRefCount:Get()
 		item[name] = method
 	end
 	item._refcount_pool_object = self
+	profiler.Stop(self.name)
 	return item:GetReference()
 end

@@ -124,9 +140,11 @@ function OvalePoolRefCount:Clean(item)
 end

 function OvalePoolRefCount:Drain()
+	profiler.Start(self.name)
 	self.pool = {}
 	self.size = self.size - self.unused
 	self.unused = 0
+	profiler.Stop(self.name)
 end

 function OvalePoolRefCount:Debug()
diff --git a/OvalePower.lua b/OvalePower.lua
index 883e4c0..ee70d7f 100644
--- a/OvalePower.lua
+++ b/OvalePower.lua
@@ -13,6 +13,14 @@ local OvalePower = Ovale:NewModule("OvalePower", "AceEvent-3.0")
 Ovale.OvalePower = OvalePower

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvalePower")
+	profiler = Profiler.group["OvalePower"]
+end
+
 -- Forward declarations for module dependencies.
 local OvaleAura = nil
 local OvaleFuture = nil
@@ -227,6 +235,7 @@ function OvalePower:UNIT_RANGEDDAMAGE(event, unitId)
 end

 function OvalePower:UpdateMaxPower(powerType)
+	profiler.Start("OvalePower_UpdateMaxPower")
 	if powerType then
 		local powerInfo = self.POWER_INFO[powerType]
 		self.maxPower[powerType] = API_UnitPowerMax("player", powerInfo.id, powerInfo.segments)
@@ -235,9 +244,11 @@ function OvalePower:UpdateMaxPower(powerType)
 			self.maxPower[powerType] = API_UnitPowerMax("player", powerInfo.id, powerInfo.segments)
 		end
 	end
+	profiler.Stop("OvalePower_UpdateMaxPower")
 end

 function OvalePower:UpdatePower(powerType)
+	profiler.Start("OvalePower_UpdatePower")
 	if powerType then
 		local powerInfo = self.POWER_INFO[powerType]
 		self.power[powerType] = API_UnitPower("player", powerInfo.id, powerInfo.segments)
@@ -246,15 +257,20 @@ function OvalePower:UpdatePower(powerType)
 			self.power[powerType] = API_UnitPower("player", powerInfo.id, powerInfo.segments)
 		end
 	end
+	profiler.Stop("OvalePower_UpdatePower")
 end

 function OvalePower:UpdatePowerRegen()
+	profiler.Start("OvalePower_UpdatePowerRegen")
 	self.inactiveRegen, self.activeRegen = API_GetPowerRegen()
+	profiler.Stop("OvalePower_UpdatePowerRegen")
 end

 function OvalePower:UpdatePowerType()
+	profiler.Start("OvalePower_UpdatePowerType")
 	local currentType, currentToken = API_UnitPowerType("player")
 	self.powerType = self.POWER_TYPE[currentType]
+	profiler.Stop("OvalePower_UpdatePowerType")
 end

 function OvalePower:Debug()
@@ -313,6 +329,7 @@ end

 -- Reset the state to the current conditions.
 function OvalePower:ResetState(state)
+	profiler.Start("OvalePower_ResetState")
 	-- Power levels for each resource.
 	for powerType in pairs(self.POWER_INFO) do
 		state[powerType] = self.power[powerType] or 0
@@ -327,6 +344,7 @@ function OvalePower:ResetState(state)
 	else
 		state.powerRate[self.powerType] = self.inactiveRegen
 	end
+	profiler.Stop("OvalePower_ResetState")
 end

 -- Release state resources prior to removing from the simulator.
@@ -341,24 +359,29 @@ end

 -- Apply the effects of the spell at the start of the spellcast.
 function OvalePower:ApplySpellStartCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvalePower_ApplySpellStartCast")
 	-- Channeled spells cost resources at the start of the channel.
 	if isChanneled then
 		state:ApplyPowerCost(spellId)
 	end
+	profiler.Stop("OvalePower_ApplySpellStartCast")
 end

 -- Apply the effects of the spell on the player's state, assuming the spellcast completes.
 function OvalePower:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvalePower_ApplySpellAfterCast")
 	-- Instant or cast-time spells cost resources at the end of the spellcast.
 	if not isChanneled then
 		state:ApplyPowerCost(spellId)
 	end
+	profiler.Stop("OvalePower_ApplySpellAfterCast")
 end
 --</public-static-methods>

 --<state-methods>
 -- Update the state of the simulator for the power cost of the given spell.
 statePrototype.ApplyPowerCost = function(state, spellId)
+	profiler.Start("OvalePower_state_ApplyPowerCost")
 	local si = OvaleData.spellInfo[spellId]

 	-- Update power using information from GetSpellInfo() if there is no SpellInfo() for the spell's cost.
@@ -394,6 +417,7 @@ statePrototype.ApplyPowerCost = function(state, spellId)
 			end
 		end
 	end
+	profiler.Stop("OvalePower_state_ApplyPowerCost")
 end

 -- Return the number of seconds before all of the primary resources needed by a spell are available.
@@ -423,6 +447,7 @@ do
 	}

 	statePrototype.PowerCost = function(state, spellId, powerType)
+		profiler.Start("OvalePower_state_PowerCost")
 		local buffParam = "buff_" .. powerType
 		local spellCost = 0
 		local si = OvaleData.spellInfo[spellId]
@@ -501,6 +526,7 @@ do
 				spellCost = cost
 			end
 		end
+		profiler.Stop("OvalePower_state_PowerCost")
 		return spellCost
 	end
 end
diff --git a/OvaleRunes.lua b/OvaleRunes.lua
index c59b11a..3e1cd3f 100644
--- a/OvaleRunes.lua
+++ b/OvaleRunes.lua
@@ -19,6 +19,14 @@ local OvaleRunes = Ovale:NewModule("OvaleRunes", "AceEvent-3.0")
 Ovale.OvaleRunes = OvaleRunes

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleRunes")
+	profiler = Profiler.group["OvaleRunes"]
+end
+
 -- Forward declarations for module dependencies.
 local OvaleData = nil
 local OvalePower = nil
@@ -176,6 +184,7 @@ function OvaleRunes:UNIT_RANGEDDAMAGE(event, unitId)
 end

 function OvaleRunes:UpdateRune(slot)
+	profiler.Start("OvaleRunes_UpdateRune")
 	local rune = self.rune[slot]
 	local runeType = API_GetRuneType(slot)
 	local start, duration, runeReady = API_GetRuneCooldown(slot)
@@ -189,6 +198,7 @@ function OvaleRunes:UpdateRune(slot)
 		rune.startCooldown = 0
 		rune.endCooldown = 0
 	end
+	profiler.Stop("OvaleRunes_UpdateRune")
 end

 function OvaleRunes:UpdateAllRunes()
@@ -240,12 +250,14 @@ end

 -- Reset the state to the current conditions.
 function OvaleRunes:ResetState(state)
+	profiler.Start("OvaleRunes_ResetState")
 	for slot, rune in ipairs(self.rune) do
 		local stateRune = state.rune[slot]
 		for k, v in pairs(rune) do
 			stateRune[k] = v
 		end
 	end
+	profiler.Stop("OvaleRunes_ResetState")
 end

 -- Release state resources prior to removing from the simulator.
@@ -260,14 +272,17 @@ end

 -- Apply the effects of the spell at the start of the spellcast.
 function OvaleRunes:ApplySpellStartCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleRunes_ApplySpellStartCast")
 	-- Channeled spells cost resources at the start of the channel.
 	if isChanneled then
 		state:ApplyRuneCost(spellId, startCast, spellcast)
 	end
+	profiler.Stop("OvaleRunes_ApplySpellStartCast")
 end

 -- Apply the effects of the spell on the player's state, assuming the spellcast completes.
 function OvaleRunes:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast)
+	profiler.Start("OvaleRunes_ApplySpellAfterCast")
 	-- Instant or cast-time spells cost resources at the end of the spellcast.
 	if not isChanneled then
 		state:ApplyRuneCost(spellId, endCast, spellcast)
@@ -287,6 +302,7 @@ function OvaleRunes:ApplySpellAfterCast(state, spellId, targetGUID, startCast, e
 			end
 		end
 	end
+	profiler.Stop("OvaleRunes_ApplySpellAfterCast")
 end
 --</public-static-methods>

@@ -335,6 +351,7 @@ end

 -- Consume a rune of the given type.  Assume that the required runes are available.
 statePrototype.ConsumeRune = function(state, spellId, atTime, name, snapshot)
+	profiler.Start("OvaleRunes_state_ConsumeRune")
 	--[[
 		Find a usable rune, preferring a regular rune of that rune type over death
 		runes of that rune type over death runes of any rune type.
@@ -418,6 +435,7 @@ statePrototype.ConsumeRune = function(state, spellId, atTime, name, snapshot)
 	else
 		Ovale:FormatPrint("No %s rune available at %f to consume for spell %d!", RUNE_NAME[runeType], atTime, spellId)
 	end
+	profiler.Stop("OvaleRunes_state_ConsumeRune")
 end

 -- Returns a triplet of count, startCooldown, endCooldown:
@@ -425,6 +443,7 @@ end
 --     startCooldown	The time at which the next rune of the given type went on cooldown.
 --     endCooldown		The time at which the next rune of the given type will be active.
 statePrototype.RuneCount = function(state, name, atTime)
+	profiler.Start("OvaleRunes_state_RuneCount")
 	-- Default to checking the rune count at the end of the current spellcast in the
 	-- simulator, or at the current time if no spell is being cast.
 	if not atTime then
@@ -461,6 +480,7 @@ statePrototype.RuneCount = function(state, name, atTime)
 			end
 		end
 	end
+	profiler.Stop("OvaleRunes_state_RuneCount")
 	return count, startCooldown, endCooldown
 end

@@ -472,6 +492,7 @@ do
 	local usedRune = {}

 	statePrototype.GetRunesCooldown = function(state, blood, unholy, frost, death, atTime)
+		profiler.Start("OvaleRunes_state_GetRunesCooldown")
 		-- Default to checking runes at the end of the current spellcast in the
 		-- simulator, or at the current time if no spell is being cast.
 		if not atTime then
@@ -605,10 +626,12 @@ do
 		for _, runeType in pairs(RUNE_TYPE) do
 			if count[runeType] > 0 then
 				Ovale:Logf("Impossible rune count requirements: blood=%d, unholy=%d, frost=%d, death=%d", blood, unholy, frost, death)
+				profiler.Stop("OvaleRunes_state_GetRunesCooldown")
 				return math.huge
 			end
 		end

+		local seconds = 0
 		local maxEndCooldown = 0
 		for rune in pairs(usedRune) do
 			if maxEndCooldown < rune.endCooldown then
@@ -616,9 +639,11 @@ do
 			end
 		end
 		if maxEndCooldown > atTime then
-			return maxEndCooldown - atTime
+			seconds = maxEndCooldown - atTime
 		end
-		return 0
+
+		profiler.Stop("OvaleRunes_state_GetRunesCooldown")
+		return seconds
 	end
 end
 --</state-methods>
diff --git a/OvaleSpellDamage.lua b/OvaleSpellDamage.lua
index 963fdc9..4e11813 100644
--- a/OvaleSpellDamage.lua
+++ b/OvaleSpellDamage.lua
@@ -15,6 +15,14 @@ local OvaleSpellDamage = Ovale:NewModule("OvaleSpellDamage", "AceEvent-3.0")
 Ovale.OvaleSpellDamage = OvaleSpellDamage

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleSpellDamage")
+	profiler = Profiler.group["OvaleSpellDamage"]
+end
+
 local API_UnitGUID = UnitGUID

 local CLEU_DAMAGE_EVENT = {
@@ -44,10 +52,12 @@ function OvaleSpellDamage:COMBAT_LOG_EVENT_UNFILTERED(event, timestamp, cleuEven
 	local arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23 = ...

 	if sourceGUID == self_guid then
+		profiler.Start("OvaleSpellDamage_COMBAT_LOG_EVENT_UNFILTERED")
 		if CLEU_DAMAGE_EVENT[cleuEvent] then
 			local spellId, amount = arg12, arg15
 			self.value[spellId] = amount
 		end
+		profiler.Stop("OvaleSpellDamage_COMBAT_LOG_EVENT_UNFILTERED")
 	end
 end

diff --git a/OvaleStance.lua b/OvaleStance.lua
index d27dee2..7318ccb 100644
--- a/OvaleStance.lua
+++ b/OvaleStance.lua
@@ -14,6 +14,14 @@ local OvaleStance = Ovale:NewModule("OvaleStance", "AceEvent-3.0")
 Ovale.OvaleStance = OvaleStance

 --<private-static-properties>
+-- Profiling set-up.
+local Profiler = Ovale.Profiler
+local profiler = nil
+do
+	Profiler:RegisterProfilingGroup("OvaleStance")
+	profiler = Profiler.group["OvaleStance"]
+end
+
 local ipairs = ipairs
 local pairs = pairs
 local tinsert = table.insert
@@ -112,6 +120,7 @@ end

 -- Fill OvaleStance.stanceList with stance bar index <-> Ovale stance name mappings.
 function OvaleStance:CreateStanceList()
+	profiler.Start("OvaleStance_CreateStanceList")
 	wipe(self.stanceList)
 	local _, name, stanceName
 	for i = 1, API_GetNumShapeshiftForms() do
@@ -121,6 +130,7 @@ function OvaleStance:CreateStanceList()
 			self.stanceList[i] = stanceName
 		end
 	end
+	profiler.Stop("OvaleStance_CreateStanceList")
 end

 -- Print out the list of stances in alphabetical order.
@@ -159,11 +169,13 @@ function OvaleStance:IsStance(name)
 end

 function OvaleStance:ShapeshiftEventHandler()
+	profiler.Start("OvaleStance_ShapeshiftEventHandler")
 	local newStance = API_GetShapeshiftForm()
 	if self.stance ~= newStance then
 		self.stance = newStance
 		self:SendMessage("Ovale_StanceChanged")
 	end
+	profiler.Stop("OvaleStance_ShapeshiftEventHandler")
 end

 function OvaleStance:UpdateStances()