Skip to content

Commit a6c0f04

Browse files
authored
Merge pull request #669 from twpol/feature/performance-counter-thread
refactor: Separate host performance data collection and processing to another thread
2 parents 93555b6 + 1ab5f58 commit a6c0f04

File tree

7 files changed

+260
-195
lines changed

7 files changed

+260
-195
lines changed

Source/RunActivity/RunActivity.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
<Compile Include="Viewer3D\Processes\GameState.cs" />
160160
<Compile Include="Viewer3D\Processes\GameStateRunActivity.cs" />
161161
<Compile Include="Viewer3D\Processes\GameStateViewer3D.cs" />
162+
<Compile Include="Viewer3D\Processes\HostProcess.cs" />
162163
<Compile Include="Viewer3D\Processes\LoaderProcess.cs" />
163164
<Compile Include="Viewer3D\Processes\RenderProcess.cs" />
164165
<Compile Include="Viewer3D\Processes\SoundProcess.cs" />

Source/RunActivity/Viewer3D/Materials.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ where material is SceneryMaterial
383383
{
384384
count = 0;
385385
// retest if there is enough free memory left;
386-
var remainingMemorySpace = Viewer.LoadMemoryThreshold - Viewer.HUDWindow.GetWorkingSetSize();
386+
var remainingMemorySpace = Viewer.LoadMemoryThreshold - Viewer.Game.HostProcess.CPUMemoryWorkingSet;
387387
if (remainingMemorySpace < 0)
388388
{
389389
return false; // too bad, no more space, other night textures won't be loaded
@@ -406,7 +406,7 @@ where material is SceneryMaterial
406406
{
407407
count = 0;
408408
// retest if there is enough free memory left;
409-
var remainingMemorySpace = Viewer.LoadMemoryThreshold - Viewer.HUDWindow.GetWorkingSetSize();
409+
var remainingMemorySpace = Viewer.LoadMemoryThreshold - Viewer.Game.HostProcess.CPUMemoryWorkingSet;
410410
if (remainingMemorySpace < 0)
411411
{
412412
return false; // too bad, no more space, other night textures won't be loaded

Source/RunActivity/Viewer3D/Popups/HUDWindow.cs

Lines changed: 8 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,6 @@ public class HUDWindow : LayeredWindow
4646
// Set to distance from top-left corner to place text.
4747
const int TextOffset = 10;
4848

49-
readonly int ProcessorCount = System.Environment.ProcessorCount;
50-
51-
readonly Version Windows10 = new Version(10, 0);
52-
const int PerformanceCounterUpdateTimeS = 10;
53-
float PerformanceCounterElapsedTimeS = PerformanceCounterUpdateTimeS;
54-
55-
readonly PerformanceCounter CLRMemoryAllocatedBytesPerSecCounter; // \.NET CLR Memory(*)\Allocated Bytes/sec
56-
float CLRMemoryAllocatedBytesPerSec;
57-
58-
readonly PerformanceCounter CPUMemoryPrivateCounter; // \Process(*)\Private Bytes
59-
readonly PerformanceCounter CPUMemoryWorkingSetCounter; // \Process(*)\Working Set
60-
readonly PerformanceCounter CPUMemoryWorkingSetPrivateCounter; // \Process(*)\Working Set - Private
61-
readonly PerformanceCounter CPUMemoryVirtualCounter; // \Process(*)\Virtual Bytes
62-
63-
float CPUMemoryPrivate;
64-
float CPUMemoryWorkingSet;
65-
float CPUMemoryWorkingSetPrivate;
66-
float CPUMemoryVirtual;
67-
68-
readonly List<PerformanceCounter> GPUMemoryCommittedCounters = new List<PerformanceCounter>(); // \GPU Process Memory(*)\Total Committed
69-
readonly List<PerformanceCounter> GPUMemoryDedicatedCounters = new List<PerformanceCounter>(); // \GPU Process Memory(*)\Dedicated Usage
70-
readonly List<PerformanceCounter> GPUMemorySharedCounters = new List<PerformanceCounter>(); // \GPU Process Memory(*)\Shared Usage
71-
72-
float GPUMemoryCommitted;
73-
float GPUMemoryDedicated;
74-
float GPUMemoryShared;
75-
7649
readonly Viewer Viewer;
7750
readonly Action<TableData>[] TextPages;
7851
readonly WindowTextFont TextFont;
@@ -102,86 +75,14 @@ public class HUDWindow : LayeredWindow
10275
HUDGraphMesh DebugGraphProcessLoader;
10376
HUDGraphMesh DebugGraphProcessSound;
10477

78+
HostProcess Host => Viewer.Game.HostProcess;
79+
10580
public HUDWindow(WindowManager owner)
10681
: base(owner, TextOffset, TextOffset, "HUD")
10782
{
10883
Viewer = owner.Viewer;
10984
LastTextPage = LocomotivePage;
11085

111-
ProcessHandle = OpenProcess(0x410 /* PROCESS_QUERY_INFORMATION | PROCESS_VM_READ */, false, Process.GetCurrentProcess().Id);
112-
ProcessMemoryCounters = new PROCESS_MEMORY_COUNTERS() { Size = 40 };
113-
ProcessVirtualAddressLimit = GetVirtualAddressLimit();
114-
115-
var processId = Process.GetCurrentProcess().Id;
116-
try
117-
{
118-
var counterDotNetClrMemory = new PerformanceCounterCategory(".NET CLR Memory");
119-
foreach (InstanceData instance in counterDotNetClrMemory.ReadCategory()["Process ID"].Values)
120-
{
121-
if (instance.RawValue == processId)
122-
{
123-
var process = instance.InstanceName;
124-
CLRMemoryAllocatedBytesPerSecCounter = new PerformanceCounter(".NET CLR Memory", "Allocated Bytes/sec", process);
125-
Trace.TraceInformation($"Found Microsoft .NET Framework performance counter {process}");
126-
break;
127-
}
128-
}
129-
}
130-
catch (Exception error)
131-
{
132-
Trace.WriteLine(error);
133-
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");
134-
}
135-
136-
try
137-
{
138-
var counterProcess = new PerformanceCounterCategory("Process");
139-
foreach (InstanceData instance in counterProcess.ReadCategory()["ID Process"].Values)
140-
{
141-
if (instance.RawValue == processId)
142-
{
143-
var process = instance.InstanceName;
144-
CPUMemoryPrivateCounter = new PerformanceCounter("Process", "Private Bytes", process);
145-
CPUMemoryWorkingSetCounter = new PerformanceCounter("Process", "Working Set", process);
146-
CPUMemoryWorkingSetPrivateCounter = new PerformanceCounter("Process", "Working Set - Private", process);
147-
CPUMemoryVirtualCounter = new PerformanceCounter("Process", "Virtual Bytes", process);
148-
Trace.TraceInformation($"Found Windows Process performance counter {process}");
149-
break;
150-
}
151-
}
152-
}
153-
catch (Exception error)
154-
{
155-
Trace.WriteLine(error);
156-
Trace.TraceWarning("Unable to access Windows Process performance counters. This may be resolved by following the instructions at http://support.microsoft.com/kb/300956");
157-
}
158-
159-
if (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= Windows10)
160-
{
161-
try
162-
{
163-
var instancePrefix = $"pid_{Process.GetCurrentProcess().Id}_";
164-
var counterProcess = new PerformanceCounterCategory("GPU Process Memory");
165-
foreach (var process in counterProcess.GetInstanceNames())
166-
{
167-
if (process.StartsWith(instancePrefix))
168-
{
169-
GPUMemoryCommittedCounters.Add(new PerformanceCounter("GPU Process Memory", "Total Committed", process));
170-
GPUMemoryDedicatedCounters.Add(new PerformanceCounter("GPU Process Memory", "Dedicated Usage", process));
171-
GPUMemorySharedCounters.Add(new PerformanceCounter("GPU Process Memory", "Shared Usage", process));
172-
Trace.TraceInformation($"Found Windows GPU Process Memory performance counter {process}");
173-
}
174-
}
175-
}
176-
catch (Exception error)
177-
{
178-
Trace.WriteLine(error);
179-
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");
180-
}
181-
}
182-
183-
Debug.Assert(GC.MaxGeneration == 2, "Runtime is expected to have a MaxGeneration of 2.");
184-
18586
var textPages = new List<Action<TableData>>();
18687
textPages.Add(TextPageCommon);
18788
textPages.Add(TextPageConsistInfo);
@@ -211,7 +112,7 @@ public HUDWindow(WindowManager owner)
211112
ForceGraphNumOfSubsteps = ForceGraphs.Add(Viewer.Catalog.GetString("Num of substeps"), "0", "50", Color.Blue, 25);
212113

213114
DebugGraphs = new HUDGraphSet(Viewer, HUDGraphMaterial);
214-
DebugGraphMemory = DebugGraphs.Add(Viewer.Catalog.GetString("Memory"), "0GB", String.Format("{0:F0}GB", (float)ProcessVirtualAddressLimit / 1024 / 1024 / 1024), Color.Orange, 50);
115+
DebugGraphMemory = DebugGraphs.Add(Viewer.Catalog.GetString("Memory"), "0GB", String.Format("{0:F0}GB", (float)Host.CPUMemoryVirtualLimit / 1024 / 1024 / 1024), Color.Orange, 50);
215116
DebugGraphGCs = DebugGraphs.Add(Viewer.Catalog.GetString("GCs"), "0", "2", Color.Magenta, 20); // Multiple of 4
216117
DebugGraphFrameTime = DebugGraphs.Add(Viewer.Catalog.GetString("Frame time"), "0.0s", "0.1s", Color.LightGreen, 50);
217118
DebugGraphProcessRender = DebugGraphs.Add(Viewer.Catalog.GetString("Render process"), "0%", "100%", Color.Red, 20);
@@ -332,7 +233,7 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime, bo
332233
if (Visible && TextPages[TextPage] == TextPageDebugInfo)
333234
{
334235
var gcCounts = new[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) };
335-
DebugGraphMemory.AddSample((float)GetWorkingSetSize() / ProcessVirtualAddressLimit);
236+
DebugGraphMemory.AddSample((float)Host.CPUMemoryWorkingSet / Host.CPUMemoryVirtualLimit);
336237
DebugGraphGCs.AddSample(gcCounts[2] > lastGCCounts[2] ? 1.0f : gcCounts[1] > lastGCCounts[1] ? 0.5f : gcCounts[0] > lastGCCounts[0] ? 0.25f : 0);
337238
DebugGraphFrameTime.AddSample(Viewer.RenderProcess.FrameTime.Value * 10);
338239
DebugGraphProcessRender.AddSample(Viewer.RenderProcess.Profiler.Wall.Value / 100);
@@ -348,13 +249,6 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull)
348249
{
349250
base.PrepareFrame(elapsedTime, updateFull);
350251

351-
PerformanceCounterElapsedTimeS += elapsedTime.RealSeconds;
352-
if (PerformanceCounterElapsedTimeS >= PerformanceCounterUpdateTimeS)
353-
{
354-
UpdatePerformanceCounters();
355-
PerformanceCounterElapsedTimeS = 0;
356-
}
357-
358252
if (updateFull)
359253
{
360254
var table = new TableData() { Cells = new string[TextTable.Cells.GetLength(0), TextTable.Cells.GetLength(1)] };
@@ -1437,11 +1331,11 @@ void TextPageDebugInfo(TableData table)
14371331

14381332
TableAddLabelValue(table, Viewer.Catalog.GetString("Logging enabled"), Viewer.Settings.DataLogger ? Viewer.Catalog.GetString("Yes") : Viewer.Catalog.GetString("No"));
14391333
TableAddLabelValue(table, Viewer.Catalog.GetString("Build"), VersionInfo.Build);
1440-
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)));
1334+
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) / Host.ProcessorCount, Viewer.Catalog.GetPluralStringFmt("{0} logical processor", "{0} logical processors", Host.ProcessorCount)));
14411335
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));
1442-
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));
1443-
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));
1444-
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));
1336+
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(), Host.CLRMemoryAllocatedBytesPerSec / Viewer.RenderProcess.FrameRate.SmoothedValue / 1024));
1337+
if (Host.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", Host.CPUMemoryPrivate / 1024 / 1024, Host.CPUMemoryWorkingSet / 1024 / 1024, Host.CPUMemoryWorkingSetPrivate / 1024 / 1024, GC.GetTotalMemory(false) / 1024 / 1024, Host.CPUMemoryVirtual / 1024 / 1024));
1338+
if (Host.GPUMemoryCommitted > 0) TableAddLabelValue(table, Viewer.Catalog.GetString("GPU Memory"), Viewer.Catalog.GetStringFmt("{0:F0} MB committed, {1:F0} MB dedicated, {2:F0} MB shared", Host.GPUMemoryCommitted / 1024 / 1024, Host.GPUMemoryDedicated / 1024 / 1024, Host.GPUMemoryShared / 1024 / 1024));
14451339
TableAddLabelValue(table, Viewer.Catalog.GetString("Adapter"), Viewer.Catalog.GetStringFmt("{0} ({1:F0} MB)", Viewer.AdapterDescription, Viewer.AdapterMemory / 1024 / 1024));
14461340
if (Viewer.Settings.DynamicShadows)
14471341
{
@@ -1467,78 +1361,6 @@ static void TextPageHeading(TableData table, string name)
14671361
TableAddLine(table);
14681362
TableAddLine(table, name);
14691363
}
1470-
1471-
#region Native code
1472-
[StructLayout(LayoutKind.Sequential, Size = 64)]
1473-
public class MEMORYSTATUSEX
1474-
{
1475-
public uint Size;
1476-
public uint MemoryLoad;
1477-
public ulong TotalPhysical;
1478-
public ulong AvailablePhysical;
1479-
public ulong TotalPageFile;
1480-
public ulong AvailablePageFile;
1481-
public ulong TotalVirtual;
1482-
public ulong AvailableVirtual;
1483-
public ulong AvailableExtendedVirtual;
1484-
}
1485-
1486-
[StructLayout(LayoutKind.Sequential, Size = 40)]
1487-
struct PROCESS_MEMORY_COUNTERS
1488-
{
1489-
public int Size;
1490-
public int PageFaultCount;
1491-
public int PeakWorkingSetSize;
1492-
public int WorkingSetSize;
1493-
public int QuotaPeakPagedPoolUsage;
1494-
public int QuotaPagedPoolUsage;
1495-
public int QuotaPeakNonPagedPoolUsage;
1496-
public int QuotaNonPagedPoolUsage;
1497-
public int PagefileUsage;
1498-
public int PeakPagefileUsage;
1499-
}
1500-
1501-
[DllImport("kernel32.dll", SetLastError = true)]
1502-
static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX buffer);
1503-
1504-
[DllImport("psapi.dll", SetLastError = true)]
1505-
static extern bool GetProcessMemoryInfo(IntPtr hProcess, out PROCESS_MEMORY_COUNTERS counters, int size);
1506-
1507-
[DllImport("kernel32.dll")]
1508-
static extern IntPtr OpenProcess(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
1509-
1510-
readonly IntPtr ProcessHandle;
1511-
PROCESS_MEMORY_COUNTERS ProcessMemoryCounters;
1512-
readonly ulong ProcessVirtualAddressLimit;
1513-
#endregion
1514-
1515-
public ulong GetWorkingSetSize()
1516-
{
1517-
// Get memory usage (working set).
1518-
GetProcessMemoryInfo(ProcessHandle, out ProcessMemoryCounters, ProcessMemoryCounters.Size);
1519-
return (ulong)ProcessMemoryCounters.WorkingSetSize;
1520-
}
1521-
1522-
public ulong GetVirtualAddressLimit()
1523-
{
1524-
var buffer = new MEMORYSTATUSEX { Size = 64 };
1525-
GlobalMemoryStatusEx(buffer);
1526-
return Math.Min(buffer.TotalVirtual, buffer.TotalPhysical);
1527-
}
1528-
1529-
void UpdatePerformanceCounters()
1530-
{
1531-
// Only update CLRMemoryAllocatedBytesPerSec with non-zero values
1532-
var clrMemoryAllocatedBytesPerSec = CLRMemoryAllocatedBytesPerSecCounter?.NextValue() ?? 0;
1533-
if (clrMemoryAllocatedBytesPerSec >= 1) CLRMemoryAllocatedBytesPerSec = clrMemoryAllocatedBytesPerSec;
1534-
CPUMemoryPrivate = CPUMemoryPrivateCounter?.NextValue() ?? 0;
1535-
CPUMemoryWorkingSet = CPUMemoryWorkingSetCounter?.NextValue() ?? 0;
1536-
CPUMemoryWorkingSetPrivate = CPUMemoryWorkingSetPrivateCounter?.NextValue() ?? 0;
1537-
CPUMemoryVirtual = CPUMemoryVirtualCounter?.NextValue() ?? 0;
1538-
GPUMemoryCommitted = GPUMemoryCommittedCounters.Sum(counter => counter.NextValue());
1539-
GPUMemoryDedicated = GPUMemoryDedicatedCounters.Sum(counter => counter.NextValue());
1540-
GPUMemoryShared = GPUMemorySharedCounters.Sum(counter => counter.NextValue());
1541-
}
15421364
}
15431365

15441366
public class HUDGraphSet

Source/RunActivity/Viewer3D/Processes/Game.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ public class Game : Microsoft.Xna.Framework.Game
6666
/// </summary>
6767
public LoaderProcess LoaderProcess { get; private set; }
6868

69-
70-
7169
/// <summary>
7270
/// Exposes access to the <see cref="SoundProcess"/> for the game.
7371
/// </summary>
@@ -78,6 +76,11 @@ public class Game : Microsoft.Xna.Framework.Game
7876
/// </summary>
7977
public WebServerProcess WebServerProcess { get; private set; }
8078

79+
/// <summary>
80+
/// Exposes access to the <see cref="HostProcess"/> for the game.
81+
/// </summary>
82+
public HostProcess HostProcess { get; private set; }
83+
8184
/// <summary>
8285
/// Gets the current <see cref="GameState"/>, if there is one, or <c>null</c>.
8386
/// </summary>
@@ -100,13 +103,15 @@ public Game(UserSettings settings)
100103
LoaderProcess = new LoaderProcess(this);
101104
SoundProcess = new SoundProcess(this);
102105
WebServerProcess = new WebServerProcess(this);
106+
HostProcess = new HostProcess(this);
103107
States = new Stack<GameState>();
104108
}
105109

106110
[ThreadName("Render")]
107111
protected override void BeginRun()
108112
{
109113
// At this point, GraphicsDevice is initialized and set up.
114+
HostProcess.Start();
110115
WebServerProcess.Start();
111116
SoundProcess.Start();
112117
LoaderProcess.Start();
@@ -160,8 +165,8 @@ protected override void EndRun()
160165
UpdaterProcess.Stop();
161166
LoaderProcess.Stop();
162167
SoundProcess.Stop();
163-
// WebServerProcess.Stop(); Again
164168
WebServerProcess.Stop();
169+
HostProcess.Stop();
165170
}
166171

167172
[ThreadName("Render")]

0 commit comments

Comments
 (0)