Skip to content

Commit

Permalink
Merge pull request #329 from RagnarokResearchLab/perf-metrics-upgrade
Browse files Browse the repository at this point in the history
Add CPU and memory usage to the performance metrics overlay
  • Loading branch information
rdw-software authored Jan 25, 2024
2 parents abd1d67 + 5a692f9 commit 576b7f0
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 1 deletion.
26 changes: 25 additions & 1 deletion Core/NativeClient/Interface/PerformanceMetricsOverlay.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local tinsert = table.insert

local PerformanceMetricsOverlay = {
samples = {},
formatOverrides = {},
isEnabled = false,
messageStrings = {
NO_SAMPLES_AVAILABLE = "No performance metrics available at this time",
Expand Down Expand Up @@ -53,10 +54,33 @@ function PerformanceMetricsOverlay:GetFormattedMetricsString()

local isLastMetric = (index == #self.samples)
local separator = isLastMetric and "" or " | "
tinsert(sampleStrings, format("%s: %.2f ms%s", name, avg, separator))
local formatString = self.formatOverrides[name] or "%s: %.2f ms%s"
tinsert(sampleStrings, format(formatString, name, avg, separator))
end

return tconcat(sampleStrings, "")
end

local function toMicroseconds(time)
return time.sec * 1E6 + time.usec
end

function PerformanceMetricsOverlay:ComputeResourceUsageForInterval(
initialResourceUsage,
finalUsage,
measuredIntervalInMilliseconds
)
if measuredIntervalInMilliseconds <= 0 then
return 0
end

local initialTotal = toMicroseconds(initialResourceUsage.utime)
local finalTotal = toMicroseconds(finalUsage.utime)

local cpuTimeUsedInMicroseconds = finalTotal - initialTotal
local elapsedTimeInMicroseconds = measuredIntervalInMilliseconds * 1E3

return (cpuTimeUsedInMicroseconds / elapsedTimeInMicroseconds) * 100
end

return PerformanceMetricsOverlay
16 changes: 16 additions & 0 deletions Core/NativeClient/NativeClient.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ function NativeClient:CreateMainWindow()
end

function NativeClient:StartRenderLoop()
PerformanceMetricsOverlay.formatOverrides.CPU = "%s: %.2f %%%s"
PerformanceMetricsOverlay.formatOverrides.Memory = "%s: %d MB%s"
PerformanceMetricsOverlay:StartMeasuring()
local initialResourceUsage = uv.getrusage()

-- Should probably replace with RML data binding or a similar approach later?
self.fpsDisplayTicker = C_Timer.NewTicker(2500, function()
Expand Down Expand Up @@ -115,14 +118,27 @@ function NativeClient:StartRenderLoop()
local frameEndTime = uv.hrtime()

local frameTimeInMilliseconds = (frameEndTime - frameStartTime) / 10E5
local lastMeasuredResourceUsage = uv.getrusage()

local cpuUsage = PerformanceMetricsOverlay:ComputeResourceUsageForInterval(
initialResourceUsage,
lastMeasuredResourceUsage,
frameTimeInMilliseconds
)
initialResourceUsage = lastMeasuredResourceUsage

local sample = {
CPU = cpuUsage,
Memory = collectgarbage("count") / 1024,
Frame = frameTimeInMilliseconds,
Render = cpuFrameTime / 10E5,
World = worldRenderTime / 10E5,
UI = uiRenderTime / 10E5,
Submit = commandSubmissionTime / 10E5,
UV = uvPollingTime / 10E5,
GLFW = (glfwPollingTime + replayTime) / 10E5,
"Memory",
"CPU",
"Frame",
"Render",
"World",
Expand Down
97 changes: 97 additions & 0 deletions Tests/NativeClient/Interface/PerformanceMetricsOverlay.spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,102 @@ describe("PerformanceMetricsOverlay", function()
"totalFrameTime: 100.00 ms | cpuRenderTime: nan ms | worldRenderTime: 200.00 ms | interfaceRenderTime: 300.00 ms | commandSubmissionTime: 600.00 ms | uvPollingTime: 400.00 ms | glfwPollingTime: 500.00 ms"
assertEquals(actual, expected)
end)

it("should allow overriding the format for non-standard types of metrics", function()
PerformanceMetricsOverlay:StartMeasuring()

local metricsEntry = {
Memory = 1024,
Percentage = 56.75345,
Time = 250,
"Memory",
"Percentage",
"Time",
}
PerformanceMetricsOverlay:AddSample(metricsEntry)
PerformanceMetricsOverlay:AddSample(metricsEntry)

PerformanceMetricsOverlay.formatOverrides.Memory = "%s: %d MB%s"
PerformanceMetricsOverlay.formatOverrides.Percentage = "%s: %.2f %%%s"
PerformanceMetricsOverlay.formatOverrides.Time = "%s: %d milliseconds%s"

local actual = PerformanceMetricsOverlay:GetFormattedMetricsString()
local expected = "Memory: 1024 MB | Percentage: 56.75 % | Time: 250 milliseconds"
assertEquals(actual, expected)
end)

describe("ComputeResourceUsageForInterval", function()
local function uvMakeResourceUsage(seconds, microseconds)
return {
utime = { sec = seconds, usec = microseconds },
stime = { sec = 0, usec = 0 }, -- stime is ignored since it may be async background tasks etc.
}
end

it("should compute the resource usage if the measured interval is zero", function()
local initialUsage = uvMakeResourceUsage(1, 500000) -- 1.5 seconds
local finalUsage = uvMakeResourceUsage(1, 500000) -- 1.5 seconds
local measuredIntervalInMilliseconds = 0 -- 1 second
local expected = 0
local actual = PerformanceMetricsOverlay:ComputeResourceUsageForInterval(
initialUsage,
finalUsage,
measuredIntervalInMilliseconds
)
assertEquals(actual, expected)
end)

it("should compute the resource usage correctly if it's 0%", function()
local initialUsage = uvMakeResourceUsage(1, 500000) -- 1.5 seconds
local finalUsage = uvMakeResourceUsage(1, 500000) -- 1.5 seconds
local measuredIntervalInMilliseconds = 1000 -- 1 second
local expected = 0
local actual = PerformanceMetricsOverlay:ComputeResourceUsageForInterval(
initialUsage,
finalUsage,
measuredIntervalInMilliseconds
)
assertEquals(actual, expected)
end)

it("should compute the resource usage correctly if it's 100%", function()
local initialUsage = uvMakeResourceUsage(0, 500000) -- 0.5 seconds
local finalUsage = uvMakeResourceUsage(1, 0) -- 1 second
local measuredIntervalInMilliseconds = 500 -- 0.5 second
local expected = 100 -- 100% CPU usage
local actual = PerformanceMetricsOverlay:ComputeResourceUsageForInterval(
initialUsage,
finalUsage,
measuredIntervalInMilliseconds
)
assertEquals(actual, expected)
end)

it("should compute the resource usage correctly if it's more than 100%", function()
local initialUsage = uvMakeResourceUsage(0, 500000) -- 0.5 seconds
local finalUsage = uvMakeResourceUsage(2, 0) -- 1 second
local measuredIntervalInMilliseconds = 500 -- 0.5 second
local expected = 300 -- 300% CPU usage (probably a measurement error or timer inaccuracy - ignore it)
local actual = PerformanceMetricsOverlay:ComputeResourceUsageForInterval(
initialUsage,
finalUsage,
measuredIntervalInMilliseconds
)
assertEquals(actual, expected)
end)

it("should compute the resource usage correctly over the provided duration", function()
local initialUsage = uvMakeResourceUsage(1, 0) -- 1 second
local finalUsage = uvMakeResourceUsage(2, 0) -- 2 seconds
local measuredIntervalInMilliseconds = 2000 -- 2 seconds
local expected = 50 -- 50% CPU usage
local actual = PerformanceMetricsOverlay:ComputeResourceUsageForInterval(
initialUsage,
finalUsage,
measuredIntervalInMilliseconds
)
assertEquals(actual, expected)
end)
end)
end)
end)

0 comments on commit 576b7f0

Please sign in to comment.