437 lines
13 KiB
Lua
437 lines
13 KiB
Lua
--[[-------------------------------------------------------------------------
|
|
The panel that contains the realm switcher
|
|
---------------------------------------------------------------------------]]
|
|
local REALMPANEL = {}
|
|
|
|
function REALMPANEL:Init()
|
|
self:DockPadding(0, 0, 0, 0)
|
|
self:DockMargin(0, 0, 5, 0)
|
|
|
|
self.realmLabel = vgui.Create("DLabel", self)
|
|
self.realmLabel:SetDark(true)
|
|
self.realmLabel:SetText("Realm:")
|
|
|
|
self.realmLabel:SizeToContents()
|
|
self.realmLabel:Dock(TOP)
|
|
|
|
self.realmbox = vgui.Create("DComboBox", self)
|
|
self.realmbox:AddChoice("Client")
|
|
self.realmbox:AddChoice("Server")
|
|
self.realmbox:Dock(TOP)
|
|
|
|
FProfiler.UI.onModelUpdate("realm", function(new)
|
|
self.realmbox.selected = new == "client" and 1 or 2
|
|
self.realmbox:SetText(new == "client" and "Client" or "Server")
|
|
end)
|
|
|
|
FProfiler.UI.onModelUpdate("serverAccess", function(hasAccess)
|
|
self.realmbox:SetDisabled(not hasAccess)
|
|
|
|
if not hasAccess and self.realmbox.selected == 2 then
|
|
FProfiler.UI.updateModel("realm", "client")
|
|
end
|
|
end)
|
|
|
|
self.realmbox.OnSelect = function(_, _, value) FProfiler.UI.updateModel("realm", string.lower(value)) end
|
|
end
|
|
|
|
function REALMPANEL:PerformLayout()
|
|
self.realmLabel:SizeToContents()
|
|
local top = ( self:GetTall() - self.realmLabel:GetTall() - self.realmbox:GetTall()) * 0.5
|
|
self:DockPadding(0, top, 0, 0)
|
|
end
|
|
|
|
derma.DefineControl("FProfileRealmPanel", "", REALMPANEL, "Panel")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The little red or green indicator that indicates whether the focussing
|
|
function is correct
|
|
---------------------------------------------------------------------------]]
|
|
local FUNCINDICATOR = {}
|
|
|
|
function FUNCINDICATOR:Init()
|
|
self:SetTall(5)
|
|
self.color = Color(0, 0, 0, 0)
|
|
end
|
|
|
|
function FUNCINDICATOR:Paint()
|
|
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.color)
|
|
end
|
|
|
|
derma.DefineControl("FProfileFuncIndicator", "", FUNCINDICATOR, "DPanel")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The panel that contains the focus text entry and the focus indicator
|
|
---------------------------------------------------------------------------]]
|
|
local FOCUSPANEL = {}
|
|
|
|
function FOCUSPANEL:Init()
|
|
self:DockPadding(0, 0, 0, 0)
|
|
self:DockMargin(0, 0, 5, 0)
|
|
|
|
self.focusLabel = vgui.Create("DLabel", self)
|
|
self.focusLabel:SetDark(true)
|
|
self.focusLabel:SetText("Profiling Focus:")
|
|
|
|
self.focusLabel:SizeToContents()
|
|
self.focusLabel:Dock(TOP)
|
|
|
|
self.funcIndicator = vgui.Create("FProfileFuncIndicator", self)
|
|
self.funcIndicator:Dock(BOTTOM)
|
|
|
|
self.focusBox = vgui.Create("DTextEntry", self)
|
|
self.focusBox:SetText("")
|
|
self.focusBox:SetWidth(150)
|
|
self.focusBox:Dock(BOTTOM)
|
|
self.focusBox:SetTooltip("Focus the profiling on a single function.\nEnter a global function name here (like player.GetAll)\nYou're not allowed to call functions in here (e.g. hook.GetTable() is not allowed)")
|
|
|
|
function self.focusBox:OnChange()
|
|
FProfiler.UI.updateCurrentRealm("focusStr", self:GetText())
|
|
end
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("focusObj", function(new)
|
|
self.funcIndicator.color = FProfiler.UI.getCurrentRealmValue("focusStr") == "" and Color(0, 0, 0, 0) or new and Color(80, 255, 80, 255) or Color(255, 80, 80, 255)
|
|
end)
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("focusStr", function(new, old)
|
|
if self.focusBox:GetText() == new then return end
|
|
|
|
self.focusBox:SetText(tostring(new))
|
|
end)
|
|
end
|
|
|
|
function FOCUSPANEL:PerformLayout()
|
|
self.focusBox:SetWide(200)
|
|
self.focusLabel:SizeToContents()
|
|
end
|
|
|
|
derma.DefineControl("FProfileFocusPanel", "", FOCUSPANEL, "Panel")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The timer that keeps track of for how long the profiling has been going on
|
|
---------------------------------------------------------------------------]]
|
|
local TIMERPANEL = {}
|
|
|
|
function TIMERPANEL:Init()
|
|
self:DockPadding(0, 5, 0, 5)
|
|
self:DockMargin(5, 0, 5, 0)
|
|
|
|
self.timeLabel = vgui.Create("DLabel", self)
|
|
self.timeLabel:SetDark(true)
|
|
self.timeLabel:SetText("Total profiling time:")
|
|
|
|
self.timeLabel:SizeToContents()
|
|
self.timeLabel:Dock(TOP)
|
|
|
|
self.counter = vgui.Create("DLabel", self)
|
|
self.counter:SetDark(true)
|
|
self.counter:SetText("00:00:00")
|
|
self.counter:SizeToContents()
|
|
self.counter:Dock(RIGHT)
|
|
|
|
function self.counter:Think()
|
|
local recordTime, sessionStart = FProfiler.UI.getCurrentRealmValue("recordTime"), FProfiler.UI.getCurrentRealmValue("sessionStart")
|
|
|
|
local totalTime = recordTime + (sessionStart and (CurTime() - sessionStart) or 0)
|
|
|
|
self:SetText(string.FormattedTime(totalTime, "%02i:%02i:%02i"))
|
|
end
|
|
end
|
|
|
|
function TIMERPANEL:PerformLayout()
|
|
self.timeLabel:SizeToContents()
|
|
self.counter:SizeToContents()
|
|
end
|
|
|
|
derma.DefineControl("FProfileTimerPanel", "", TIMERPANEL, "Panel")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The top bar
|
|
---------------------------------------------------------------------------]]
|
|
local MAGICBAR = {}
|
|
|
|
function MAGICBAR:Init()
|
|
self:DockPadding(5, 5, 5, 5)
|
|
self.realmpanel = vgui.Create("FProfileRealmPanel", self)
|
|
|
|
-- (Re)Start profiling
|
|
self.restartProfiling = vgui.Create("DButton", self)
|
|
self.restartProfiling:SetText(" (Re)Start\n Profiling")
|
|
self.restartProfiling:DockMargin(0, 0, 5, 0)
|
|
self.restartProfiling:Dock(LEFT)
|
|
|
|
self.restartProfiling.DoClick = function()
|
|
FProfiler.UI.updateCurrentRealm("shouldReset", true)
|
|
FProfiler.UI.updateCurrentRealm("status", "Started")
|
|
end
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
|
|
self.restartProfiling:SetDisabled(new == "Started")
|
|
end)
|
|
|
|
-- Stop profiling
|
|
self.stopProfiling = vgui.Create("DButton", self)
|
|
self.stopProfiling:SetText(" Stop\n Profiling")
|
|
self.stopProfiling:DockMargin(0, 0, 5, 0)
|
|
self.stopProfiling:Dock(LEFT)
|
|
|
|
self.stopProfiling.DoClick = function()
|
|
FProfiler.UI.updateCurrentRealm("status", "Stopped")
|
|
end
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
|
|
self.stopProfiling:SetDisabled(new == "Stopped")
|
|
end)
|
|
|
|
-- Continue profiling
|
|
self.continueProfiling = vgui.Create("DButton", self)
|
|
self.continueProfiling:SetText(" Continue\n Profiling")
|
|
self.continueProfiling:DockMargin(0, 0, 5, 0)
|
|
self.continueProfiling:Dock(LEFT)
|
|
|
|
self.continueProfiling.DoClick = function()
|
|
FProfiler.UI.updateCurrentRealm("shouldReset", false)
|
|
FProfiler.UI.updateCurrentRealm("status", "Started")
|
|
end
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("status", function(new)
|
|
self.continueProfiling:SetDisabled(new == "Started")
|
|
end)
|
|
|
|
self.realmpanel:Dock(LEFT)
|
|
|
|
self.focuspanel = vgui.Create("FProfileFocusPanel", self)
|
|
self.focuspanel:Dock(LEFT)
|
|
|
|
-- Timer
|
|
self.timerpanel = vgui.Create("FProfileTimerPanel", self)
|
|
self.timerpanel:Dock(RIGHT)
|
|
end
|
|
|
|
function MAGICBAR:PerformLayout()
|
|
self.realmpanel:SizeToChildren(true, false)
|
|
self.focuspanel:SizeToChildren(true, false)
|
|
self.timerpanel:SizeToChildren(true, false)
|
|
end
|
|
|
|
|
|
derma.DefineControl("FProfileMagicBar", "", MAGICBAR, "DPanel")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The Bottlenecks tab's contents
|
|
---------------------------------------------------------------------------]]
|
|
local BOTTLENECKTAB = {}
|
|
|
|
function BOTTLENECKTAB:Init()
|
|
self:SetMultiSelect(false)
|
|
self:AddColumn("Name")
|
|
self:AddColumn("Path")
|
|
self:AddColumn("Lines")
|
|
self:AddColumn("Amount of times called")
|
|
self:AddColumn("Total time in ms (inclusive)")
|
|
self:AddColumn("Average time in ms (inclusive)")
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("bottlenecks", function(new)
|
|
self:Clear()
|
|
|
|
for _, row in ipairs(new) do
|
|
local names = {}
|
|
local path = row.info.short_src
|
|
local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A"
|
|
local amountCalled = row.total_called
|
|
local totalTime = row.total_time * 100
|
|
local avgTime = row.average_time * 100
|
|
|
|
for _, fname in ipairs(row.names or {}) do
|
|
if fname.namewhat == "" and fname.name == "" then continue end
|
|
table.insert(names, fname.namewhat .. " " .. fname.name)
|
|
end
|
|
|
|
if #names == 0 then names[1] = "Unknown" end
|
|
|
|
local line = self:AddLine(table.concat(names, "/"), path, lines, amountCalled, totalTime, avgTime)
|
|
line.data = row
|
|
end
|
|
end)
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old)
|
|
if new == old then return end
|
|
|
|
for _, line in pairs(self.Lines) do
|
|
line:SetSelected(line.data.func == new.func)
|
|
end
|
|
end)
|
|
end
|
|
|
|
|
|
function BOTTLENECKTAB:OnRowSelected(id, line)
|
|
FProfiler.UI.updateCurrentRealm("currentSelected", line.data)
|
|
end
|
|
|
|
|
|
derma.DefineControl("FProfileBottleNecks", "", BOTTLENECKTAB, "DListView")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The Top n lag spikes tab's contents
|
|
---------------------------------------------------------------------------]]
|
|
local TOPTENTAB = {}
|
|
|
|
function TOPTENTAB:Init()
|
|
self:SetMultiSelect(false)
|
|
self:AddColumn("Name")
|
|
self:AddColumn("Path")
|
|
self:AddColumn("Lines")
|
|
self:AddColumn("Runtime in ms")
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("topLagSpikes", function(new)
|
|
self:Clear()
|
|
|
|
for _, row in ipairs(new) do
|
|
if not row.func then break end
|
|
|
|
local name = row.info.name and row.info.name ~= "" and (row.info.namewhat .. " " .. row.info.name) or "Unknown"
|
|
local path = row.info.short_src
|
|
local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A"
|
|
local runtime = row.runtime * 100
|
|
|
|
local line = self:AddLine(name, path, lines, runtime)
|
|
line.data = row
|
|
end
|
|
end)
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old)
|
|
if new == old then return end
|
|
|
|
for _, line in pairs(self.Lines) do
|
|
line:SetSelected(line.data.func == new.func)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function TOPTENTAB:OnRowSelected(id, line)
|
|
FProfiler.UI.updateCurrentRealm("currentSelected", line.data)
|
|
end
|
|
|
|
derma.DefineControl("FProfileTopTen", "", TOPTENTAB, "DListView")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The Tab panel of the bottlenecks and top n lag spikes
|
|
---------------------------------------------------------------------------]]
|
|
local RESULTSHEET = {}
|
|
|
|
function RESULTSHEET:Init()
|
|
self:DockMargin(0, 10, 0, 0)
|
|
self:SetPadding(0)
|
|
|
|
self.bottlenecksTab = vgui.Create("FProfileBottleNecks")
|
|
self:AddSheet("Bottlenecks", self.bottlenecksTab)
|
|
|
|
self.toptenTab = vgui.Create("FProfileTopTen")
|
|
self:AddSheet("Top 50 most expensive function calls", self.toptenTab)
|
|
|
|
end
|
|
|
|
|
|
derma.DefineControl("FProfileResultSheet", "", RESULTSHEET, "DPropertySheet")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The function details panel
|
|
---------------------------------------------------------------------------]]
|
|
local FUNCDETAILS = {}
|
|
|
|
function FUNCDETAILS:Init()
|
|
self.titleLabel = vgui.Create("DLabel", self)
|
|
self.titleLabel:SetDark(true)
|
|
self.titleLabel:SetFont("DermaLarge")
|
|
self.titleLabel:SetText("Function Details")
|
|
self.titleLabel:SizeToContents()
|
|
-- self.titleLabel:Dock(TOP)
|
|
|
|
self.focus = vgui.Create("DButton", self)
|
|
self.focus:SetText("Focus")
|
|
self.focus:SetTall(50)
|
|
self.focus:SetFont("DermaDefaultBold")
|
|
self.focus:Dock(BOTTOM)
|
|
|
|
function self.focus:DoClick()
|
|
local sel = FProfiler.UI.getCurrentRealmValue("currentSelected")
|
|
if not sel then return end
|
|
|
|
FProfiler.UI.updateCurrentRealm("focusStr", sel.func)
|
|
end
|
|
|
|
self.source = vgui.Create("DTextEntry", self)
|
|
self.source:SetKeyboardInputEnabled(false)
|
|
self.source:DockMargin(0, 40, 0, 0)
|
|
self.source:SetMultiline(true)
|
|
self.source:Dock(FILL)
|
|
|
|
FProfiler.UI.onCurrentRealmUpdate("sourceText", function(new)
|
|
self.source:SetText(string.Replace(new, "\t", " "))
|
|
end)
|
|
|
|
self.toConsole = vgui.Create("DButton", self)
|
|
self.toConsole:SetText("Print Details to Console")
|
|
self.toConsole:SetTall(50)
|
|
self.toConsole:SetFont("DermaDefaultBold")
|
|
self.toConsole:Dock(BOTTOM)
|
|
|
|
function self.toConsole:DoClick()
|
|
FProfiler.UI.updateCurrentRealm("toConsole", FProfiler.UI.getCurrentRealmValue("currentSelected"))
|
|
end
|
|
end
|
|
|
|
function FUNCDETAILS:PerformLayout()
|
|
self.titleLabel:CenterHorizontal()
|
|
end
|
|
derma.DefineControl("FProfileFuncDetails", "", FUNCDETAILS, "DPanel")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The actual frame
|
|
---------------------------------------------------------------------------]]
|
|
local FRAME = {}
|
|
|
|
local frameInstance
|
|
function FRAME:Init()
|
|
self:SetTitle("FProfiler profiling tool")
|
|
self:SetSize(ScrW() * 0.8, ScrH() * 0.8)
|
|
self:Center()
|
|
self:SetVisible(true)
|
|
self:MakePopup()
|
|
self:SetDeleteOnClose(false)
|
|
|
|
self.magicbar = vgui.Create("FProfileMagicBar", self)
|
|
self.magicbar:SetTall(math.max(self:GetTall() * 0.07, 48))
|
|
self.magicbar:Dock(TOP)
|
|
|
|
self.resultsheet = vgui.Create("FProfileResultSheet", self)
|
|
self.resultsheet:SetWide(self:GetWide() * 0.8)
|
|
self.resultsheet:Dock(LEFT)
|
|
|
|
self.details = vgui.Create("FProfileFuncDetails", self)
|
|
self.details:SetWide(self:GetWide() * 0.2 - 12)
|
|
self.details:DockMargin(5, 31, 0, 0)
|
|
self.details:Dock(RIGHT)
|
|
end
|
|
|
|
function FRAME:OnClose()
|
|
FProfiler.UI.updateModel("frameVisible", false)
|
|
end
|
|
|
|
derma.DefineControl("FProfileFrame", "", FRAME, "DFrame")
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
The command to start the profiler
|
|
---------------------------------------------------------------------------]]
|
|
concommand.Add("FProfiler",
|
|
function()
|
|
CAMI.PlayerHasAccess(LocalPlayer(), "FProfiler", function(b, _)
|
|
if not b then return end
|
|
|
|
frameInstance = frameInstance or vgui.Create("FProfileFrame")
|
|
frameInstance:SetVisible(true)
|
|
|
|
FProfiler.UI.updateModel("frameVisible", true)
|
|
end)
|
|
end,
|
|
nil, "Starts FProfiler")
|