Skip to content

Commit

Permalink
Merge pull request #568 from openrails/release/1.4
Browse files Browse the repository at this point in the history
Merge pull request #556 from twpol/feature/detailed-memory-usage
  • Loading branch information
twpol committed Jan 13, 2022
2 parents 7347a80 + c113a5d commit 950f1a1
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 30 deletions.
61 changes: 39 additions & 22 deletions Source/Documentation/Manual/driving.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2226,35 +2226,52 @@ This applies to both Timetable Mode and Activity Mode.
Extended HUD for Debug Information
----------------------------------

The last extended HUD display shows Debug information.
The last extended HUD display shows Debug information containing:

- Logging enabled: :ref:`logging status <driving-logfile>`
- Build: date and time Open Rails was compiled
- CPU: processor utilization by Open Rails
- GPU: frame rate, frame time percentiles and graphics feature level
- Memory: number of core memory objects loaded (textures, materials, shapes, and tiles) plus garbage collection statistics
- CPU Memory:

- Private: virtual memory allocated just for Open Rails (not shared with other applications)
- Working set: physical memory in use by Open Rails (including any shared with other applications)
- Private working set: physical memory in use just for Open Rails
- Managed: virtual memory allocated by the .NET runtime (CLR)
- Virtual: virtual memory allocated for any purpose (private + shared + others)

- GPU Memory:

- Committed: all graphics memory allocated for Open Rails
- Dedicated: physical graphics card memory in use by Open Rails
- Shared: system memory shared with the graphics card in use by Open Rails

- Adapter: name and dedicated physical memory of graphics card
- Shadow maps: distance and size of each shadow map level (and texture size)
- Shadow primitives: total primitives (rendered items) and breakdown by shadow map level
- Render primitives: total primitives (rendered items) and breakdown by rendering sequence (``RenderPrimitiveSequence`` in the code)
- Render/Updater/Loader/Sound process: percentage of time each process is active and waiting
- Camera: tile X, tile Z, X, Y, Z, altitude, LOD bias, effective viewing distance, effective distant mountain viewing distance

The first line (``Logging enabled``) refers to logging as described in
paragraphs 6.6 and 6.7.
.. image:: images/driving-hud-debug.png

A wide variety of parameters is shown, from frame wait and render speeds
in milliseconds, to number of primitives, Process Thread resource
utilization and number of Logical CPUs from the system's bios. They are
very useful in case of OR stuttering, to find out where the bottleneck is.
The primary measurements for comparing and analysing performance are the first column of values (some lines are skipped), so for the image above we have:

.. index::
single: tile
- CPU: 10% - *very low (warning at 75%)*
- GPU: 58 FPS - *good (warning at <55 FPS with 60 Hz vertical sync)*
- CPU Memory: 211 MB private - *very low (~3% of 8 GB)*
- GPU Memory: 493 MB committed - *low (~24% of 2 GB)*
- Highest process (Render): 47% - *moderate (warning at 75%)*

The values in the ``Camera`` line refer to the two tile coordinates and to
the three coordinates within the tile.
Also shown are the following graphs of:

.. image:: images/driving-hud-debug.png
:align: center
:scale: 80%
- Memory: working set
- GCs: garbage collections
- Frame time: how long each frame takes to render
- Render/Updater/Loader/Sound process: same as textual display above

.. image:: images/driving-hud-debug-graphs.png
:align: center
:scale: 80%

At the bottom of the picture, some moving graphs are displayed that show
the actual load of the computer.

Referring to memory use, about at least 400 MB must remain free to
avoid out-of-memory exceptions

Viewing Interactive Track Items
-------------------------------
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Source/Documentation/Manual/images/driving-hud-debug.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions Source/Launcher/app.manifest
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
Expand Down
105 changes: 97 additions & 8 deletions Source/RunActivity/Viewer3D/Popups/HUDWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,30 @@ public class HUDWindow : LayeredWindow

readonly int ProcessorCount = System.Environment.ProcessorCount;

readonly PerformanceCounter AllocatedBytesPerSecCounter; // \.NET CLR Memory(*)\Allocated Bytes/sec
float AllocatedBytesPerSecLastValue;
readonly Version Windows10 = new Version(10, 0);
const int PerformanceCounterUpdateTimeS = 10;
float PerformanceCounterElapsedTimeS = PerformanceCounterUpdateTimeS;

readonly PerformanceCounter CLRMemoryAllocatedBytesPerSecCounter; // \.NET CLR Memory(*)\Allocated Bytes/sec
float CLRMemoryAllocatedBytesPerSec;

readonly PerformanceCounter CPUMemoryPrivateCounter; // \Process(*)\Private Bytes
readonly PerformanceCounter CPUMemoryWorkingSetCounter; // \Process(*)\Working Set
readonly PerformanceCounter CPUMemoryWorkingSetPrivateCounter; // \Process(*)\Working Set - Private
readonly PerformanceCounter CPUMemoryVirtualCounter; // \Process(*)\Virtual Bytes

float CPUMemoryPrivate;
float CPUMemoryWorkingSet;
float CPUMemoryWorkingSetPrivate;
float CPUMemoryVirtual;

readonly List<PerformanceCounter> GPUMemoryCommittedCounters = new List<PerformanceCounter>(); // \GPU Process Memory(*)\Total Committed
readonly List<PerformanceCounter> GPUMemoryDedicatedCounters = new List<PerformanceCounter>(); // \GPU Process Memory(*)\Dedicated Usage
readonly List<PerformanceCounter> GPUMemorySharedCounters = new List<PerformanceCounter>(); // \GPU Process Memory(*)\Shared Usage

float GPUMemoryCommitted;
float GPUMemoryDedicated;
float GPUMemoryShared;

readonly Viewer Viewer;
readonly Action<TableData>[] TextPages;
Expand Down Expand Up @@ -98,7 +120,8 @@ public HUDWindow(WindowManager owner)
var processId = new PerformanceCounter(".NET CLR Memory", "Process ID", process);
if (processId.NextValue() == Process.GetCurrentProcess().Id)
{
AllocatedBytesPerSecCounter = new PerformanceCounter(".NET CLR Memory", "Allocated Bytes/sec", process);
CLRMemoryAllocatedBytesPerSecCounter = new PerformanceCounter(".NET CLR Memory", "Allocated Bytes/sec", process);
Trace.TraceInformation($"Found Microsoft .NET Framework performance counter {process}");
break;
}
}
Expand All @@ -109,6 +132,53 @@ public HUDWindow(WindowManager owner)
Trace.TraceWarning("Unable to access Microsoft .NET Framework performance counters. This may be resolved by following the instructions at http://support.microsoft.com/kb/300956");
}

try
{
var counterProcess = new PerformanceCounterCategory("Process");
foreach (var process in counterProcess.GetInstanceNames())
{
var processId = new PerformanceCounter("Process", "ID Process", process);
if (processId.NextValue() == Process.GetCurrentProcess().Id)
{
CPUMemoryPrivateCounter = new PerformanceCounter("Process", "Private Bytes", process);
CPUMemoryWorkingSetCounter = new PerformanceCounter("Process", "Working Set", process);
CPUMemoryWorkingSetPrivateCounter = new PerformanceCounter("Process", "Working Set - Private", process);
CPUMemoryVirtualCounter = new PerformanceCounter("Process", "Virtual Bytes", process);
Trace.TraceInformation($"Found Windows Process performance counter {process}");
break;
}
}
}
catch (Exception error)
{
Trace.WriteLine(error);
Trace.TraceWarning("Unable to access Windows Process performance counters. This may be resolved by following the instructions at http://support.microsoft.com/kb/300956");
}

if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= Windows10)
{
try
{
var instancePrefix = $"pid_{Process.GetCurrentProcess().Id}_";
var counterProcess = new PerformanceCounterCategory("GPU Process Memory");
foreach (var process in counterProcess.GetInstanceNames())
{
if (process.StartsWith(instancePrefix))
{
GPUMemoryCommittedCounters.Add(new PerformanceCounter("GPU Process Memory", "Total Committed", process));
GPUMemoryDedicatedCounters.Add(new PerformanceCounter("GPU Process Memory", "Dedicated Usage", process));
GPUMemorySharedCounters.Add(new PerformanceCounter("GPU Process Memory", "Shared Usage", process));
Trace.TraceInformation($"Found Windows GPU Process Memory performance counter {process}");
}
}
}
catch (Exception error)
{
Trace.WriteLine(error);
Trace.TraceWarning("Unable to access Windows GPU Process Memory performance counters. This may be resolved by following the instructions at http://support.microsoft.com/kb/300956");
}
}

Debug.Assert(GC.MaxGeneration == 2, "Runtime is expected to have a MaxGeneration of 2.");

var textPages = new List<Action<TableData>>();
Expand Down Expand Up @@ -258,6 +328,13 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull)
{
base.PrepareFrame(elapsedTime, updateFull);

PerformanceCounterElapsedTimeS += elapsedTime.RealSeconds;
if (PerformanceCounterElapsedTimeS >= PerformanceCounterUpdateTimeS)
{
UpdatePerformanceCounters();
PerformanceCounterElapsedTimeS = 0;
}

if (updateFull)
{
var table = new TableData() { Cells = new string[TextTable.Cells.GetLength(0), TextTable.Cells.GetLength(1)] };
Expand Down Expand Up @@ -1346,15 +1423,13 @@ void TextPageDebugInfo(TableData table)
TableSetLabelValueColumns(table, 0, 2);
TextPageHeading(table, Viewer.Catalog.GetString("DEBUG INFORMATION"));

var allocatedBytesPerSecond = AllocatedBytesPerSecCounter == null ? 0 : AllocatedBytesPerSecCounter.NextValue();
if (allocatedBytesPerSecond >= 1 && AllocatedBytesPerSecLastValue != allocatedBytesPerSecond)
AllocatedBytesPerSecLastValue = allocatedBytesPerSecond;

TableAddLabelValue(table, Viewer.Catalog.GetString("Logging enabled"), Viewer.Settings.DataLogger ? Viewer.Catalog.GetString("Yes") : Viewer.Catalog.GetString("No"));
TableAddLabelValue(table, Viewer.Catalog.GetString("Build"), VersionInfo.Build);
TableAddLabelValue(table, Viewer.Catalog.GetString("Memory"), Viewer.Catalog.GetStringFmt("{0:F0} MB ({5}, {6}, {7}, {8}, {1:F0} MB managed, {9:F0} kB/frame allocated, {2:F0}/{3:F0}/{4:F0} GCs)", GetWorkingSetSize() / 1024 / 1024, GC.GetTotalMemory(false) / 1024 / 1024, GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2), Viewer.TextureManager.GetStatus(), Viewer.MaterialManager.GetStatus(), Viewer.ShapeManager.GetStatus(), Viewer.World.Terrain.GetStatus(), AllocatedBytesPerSecLastValue / Viewer.RenderProcess.FrameRate.SmoothedValue / 1024));
TableAddLabelValue(table, Viewer.Catalog.GetString("CPU"), Viewer.Catalog.GetStringFmt("{0:F0}% ({1})", (Viewer.RenderProcess.Profiler.CPU.SmoothedValue + Viewer.UpdaterProcess.Profiler.CPU.SmoothedValue + Viewer.LoaderProcess.Profiler.CPU.SmoothedValue + Viewer.SoundProcess.Profiler.CPU.SmoothedValue) / ProcessorCount, Viewer.Catalog.GetPluralStringFmt("{0} logical processor", "{0} logical processors", ProcessorCount)));
TableAddLabelValue(table, Viewer.Catalog.GetString("GPU"), Viewer.Catalog.GetStringFmt("{0:F0} FPS (50th/95th/99th percentiles {1:F1} / {2:F1} / {3:F1} ms, DirectX feature level >= {4})", Viewer.RenderProcess.FrameRate.SmoothedValue, Viewer.RenderProcess.FrameTime.SmoothedP50 * 1000, Viewer.RenderProcess.FrameTime.SmoothedP95 * 1000, Viewer.RenderProcess.FrameTime.SmoothedP99 * 1000, Viewer.Settings.DirectXFeatureLevel));
TableAddLabelValue(table, Viewer.Catalog.GetString("Memory"), Viewer.Catalog.GetStringFmt("{3}, {4}, {5}, {6} ({7:F0} kB/frame allocated, {0:F0}/{1:F0}/{2:F0} GCs)", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2), Viewer.TextureManager.GetStatus(), Viewer.MaterialManager.GetStatus(), Viewer.ShapeManager.GetStatus(), Viewer.World.Terrain.GetStatus(), CLRMemoryAllocatedBytesPerSec / Viewer.RenderProcess.FrameRate.SmoothedValue / 1024));
if (CPUMemoryPrivate > 0) TableAddLabelValue(table, Viewer.Catalog.GetString("CPU Memory"), Viewer.Catalog.GetStringFmt("{0:F0} MB private, {1:F0} MB working set, {2:F0} MB private working set, {3:F0} MB managed, {4:F0} MB virtual", CPUMemoryPrivate / 1024 / 1024, CPUMemoryWorkingSet / 1024 / 1024, CPUMemoryWorkingSetPrivate / 1024 / 1024, GC.GetTotalMemory(false) / 1024 / 1024, CPUMemoryVirtual / 1024 / 1024));
if (GPUMemoryCommitted > 0) TableAddLabelValue(table, Viewer.Catalog.GetString("GPU Memory"), Viewer.Catalog.GetStringFmt("{0:F0} MB committed, {1:F0} MB dedicated, {2:F0} MB shared", GPUMemoryCommitted / 1024 / 1024, GPUMemoryDedicated / 1024 / 1024, GPUMemoryShared / 1024 / 1024));
TableAddLabelValue(table, Viewer.Catalog.GetString("Adapter"), Viewer.Catalog.GetStringFmt("{0} ({1:F0} MB)", Viewer.AdapterDescription, Viewer.AdapterMemory / 1024 / 1024));
if (Viewer.Settings.DynamicShadows)
{
Expand Down Expand Up @@ -1438,6 +1513,20 @@ public ulong GetVirtualAddressLimit()
GlobalMemoryStatusEx(buffer);
return Math.Min(buffer.TotalVirtual, buffer.TotalPhysical);
}

void UpdatePerformanceCounters()
{
// Only update CLRMemoryAllocatedBytesPerSec with non-zero values
var clrMemoryAllocatedBytesPerSec = CLRMemoryAllocatedBytesPerSecCounter?.NextValue() ?? 0;
if (clrMemoryAllocatedBytesPerSec >= 1) CLRMemoryAllocatedBytesPerSec = clrMemoryAllocatedBytesPerSec;
CPUMemoryPrivate = CPUMemoryPrivateCounter?.NextValue() ?? 0;
CPUMemoryWorkingSet = CPUMemoryWorkingSetCounter?.NextValue() ?? 0;
CPUMemoryWorkingSetPrivate = CPUMemoryWorkingSetPrivateCounter?.NextValue() ?? 0;
CPUMemoryVirtual = CPUMemoryVirtualCounter?.NextValue() ?? 0;
GPUMemoryCommitted = GPUMemoryCommittedCounters.Sum(counter => counter.NextValue());
GPUMemoryDedicated = GPUMemoryDedicatedCounters.Sum(counter => counter.NextValue());
GPUMemoryShared = GPUMemorySharedCounters.Sum(counter => counter.NextValue());
}
}

public class HUDGraphSet
Expand Down

0 comments on commit 950f1a1

Please sign in to comment.