Skip to content

Commit 2caa821

Browse files
authored
Merge pull request #897 from twpol/feature/telemetry
feat: Improved system information collection
2 parents 9ad34eb + 42f1dd9 commit 2caa821

File tree

1 file changed

+141
-166
lines changed

1 file changed

+141
-166
lines changed

Source/ORTS.Common/SystemInfo.cs

Lines changed: 141 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,232 +1,178 @@
1-
// COPYRIGHT 2015 by the Open Rails project.
2-
//
1+
// COPYRIGHT 2009 - 2023 by the Open Rails project.
2+
//
33
// This file is part of Open Rails.
4-
//
4+
//
55
// Open Rails is free software: you can redistribute it and/or modify
66
// it under the terms of the GNU General Public License as published by
77
// the Free Software Foundation, either version 3 of the License, or
88
// (at your option) any later version.
9-
//
9+
//
1010
// Open Rails is distributed in the hope that it will be useful,
1111
// but WITHOUT ANY WARRANTY; without even the implied warranty of
1212
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1313
// GNU General Public License for more details.
14-
//
14+
//
1515
// You should have received a copy of the GNU General Public License
1616
// along with Open Rails. If not, see <http://www.gnu.org/licenses/>.
1717

18-
using Microsoft.Win32;
1918
using System;
20-
using System.Diagnostics;
2119
using System.IO;
20+
using System.Linq;
2221
using System.Management;
2322
using System.Runtime.InteropServices;
24-
using System.Text;
25-
using System.Windows.Forms;
26-
using Microsoft.Xna.Framework.Graphics;
23+
using System.Text.RegularExpressions;
24+
using System.Collections.Generic;
25+
using System.Globalization;
26+
using System.Diagnostics;
27+
using SharpDX.DXGI;
2728

2829
namespace ORTS.Common
2930
{
3031
public static class SystemInfo
3132
{
32-
public static void WriteSystemDetails(TextWriter output)
33+
static readonly Regex NameAndVersionRegex = new Regex("^(.*?) +([0-9.]+)$");
34+
static readonly NativeStructs.MemoryStatusExtended MemoryStatusExtended = new NativeStructs.MemoryStatusExtended()
3335
{
34-
output.WriteLine("Date/time = {0} ({1:u})", DateTime.Now, DateTime.UtcNow);
35-
WriteEnvironment(output);
36-
WriteAvailableRuntimes(output);
37-
output.WriteLine("Runtime = {0} ({1}bit)", Environment.Version, IntPtr.Size * 8);
38-
WriteGraphicsAdapter(output);
39-
}
36+
Size = 64
37+
};
4038

41-
static void WriteEnvironment(TextWriter output)
39+
static SystemInfo()
4240
{
43-
var buffer = new NativeStructs.MemoryStatusExtended { Size = 64 };
44-
NativeMethods.GlobalMemoryStatusEx(buffer);
45-
try
41+
Application = new Platform
4642
{
47-
foreach (ManagementObject bios in new ManagementClass("Win32_BIOS").GetInstances())
48-
{
49-
output.WriteLine("BIOS = {0} ({1})", (string)bios["Description"], (string)bios["Manufacturer"]);
50-
}
51-
}
52-
catch (Exception error)
53-
{
54-
Trace.WriteLine(error);
55-
}
56-
try
57-
{
58-
foreach (ManagementObject processor in new ManagementClass("Win32_Processor").GetInstances())
59-
{
60-
output.Write("Processor = {0} ({2} threads, {1} cores, {3:F1} GHz)", (string)processor["Name"], (uint)processor["NumberOfCores"], (uint)processor["NumberOfLogicalProcessors"], (float)(uint)processor["MaxClockSpeed"] / 1000);
61-
foreach (ManagementObject cpuCache in processor.GetRelated("Win32_CacheMemory"))
62-
{
63-
output.Write(" ({0} {1:F0} KB)", (string)cpuCache["Purpose"], (float)(uint)cpuCache["InstalledSize"]);
64-
}
65-
output.WriteLine();
66-
}
67-
}
68-
catch (Exception error)
69-
{
70-
Trace.WriteLine(error);
71-
}
72-
output.WriteLine("Memory = {0:F1} GB", (float)buffer.TotalPhysical / 1024 / 1024 / 1024);
73-
try
74-
{
75-
foreach (ManagementObject display in new ManagementClass("Win32_VideoController").GetInstances())
76-
{
77-
// ? used as display["AdapterRAM"] may be null on a virtual machine (e.g. VMWare)
78-
output.WriteLine("Video = {0} ({1:F1} GB RAM){2}", (string)display["Description"], (float?)(uint?)display["AdapterRAM"] / 1024 / 1024 / 1024, GetPnPDeviceDrivers(display));
79-
}
80-
}
81-
catch (Exception error)
82-
{
83-
Trace.WriteLine(error);
84-
}
85-
try
86-
{
87-
foreach (var screen in Screen.AllScreens)
88-
{
89-
output.WriteLine("Display = {0} ({3} x {4}, {5}-bit{6}, {1} x {2})", screen.DeviceName, screen.Bounds.X, screen.Bounds.Y, screen.Bounds.Width, screen.Bounds.Height, screen.BitsPerPixel, screen.Primary ? ", primary" : "");
90-
}
91-
}
92-
catch (Exception error)
43+
Name = System.Windows.Forms.Application.ProductName,
44+
Version = VersionInfo.VersionOrBuild,
45+
Architecture = RuntimeInformation.ProcessArchitecture.ToString(),
46+
};
47+
48+
var runtime = NameAndVersionRegex.Match(RuntimeInformation.FrameworkDescription.Trim());
49+
Runtime = new Platform
9350
{
94-
Trace.WriteLine(error);
95-
}
51+
Name = runtime.Groups[1].Value,
52+
Version = runtime.Groups[2].Value,
53+
};
54+
9655
try
9756
{
98-
foreach (ManagementObject sound in new ManagementClass("Win32_SoundDevice").GetInstances())
57+
// Almost nothing will correctly identify Windows 11 at this point, so we have to use WMI.
58+
var operatingSystem = new ManagementClass("Win32_OperatingSystem").GetInstances().Cast<ManagementObject>().First();
59+
OperatingSystem = new Platform
9960
{
100-
output.WriteLine("Sound = {0}{1}", (string)sound["Description"], GetPnPDeviceDrivers(sound));
101-
}
61+
Name = (string)operatingSystem["Caption"],
62+
Version = (string)operatingSystem["Version"],
63+
Architecture = RuntimeInformation.OSArchitecture.ToString(),
64+
Language = CultureInfo.CurrentUICulture.IetfLanguageTag,
65+
Languages = (string[])operatingSystem["MUILanguages"],
66+
};
10267
}
103-
catch (Exception error)
68+
catch (ManagementException error)
10469
{
10570
Trace.WriteLine(error);
10671
}
72+
73+
NativeMethods.GlobalMemoryStatusEx(MemoryStatusExtended);
74+
InstalledMemoryMB = (int)(MemoryStatusExtended.TotalPhysical / 1024 / 1024);
75+
10776
try
10877
{
109-
foreach (ManagementObject disk in new ManagementClass("Win32_LogicalDisk").GetInstances())
78+
CPUs = new ManagementClass("Win32_Processor").GetInstances().Cast<ManagementObject>().Select(processor => new CPU
11079
{
111-
output.Write("Disk = {0} ({1}, {2}", (string)disk["Name"], (string)disk["Description"], (string)disk["FileSystem"]);
112-
if (disk["Size"] != null && disk["FreeSpace"] != null)
113-
output.WriteLine(", {0:F1} GB, {1:F1} GB free)", (float)(ulong)disk["Size"] / 1024 / 1024 / 1024, (float)(ulong)disk["FreeSpace"] / 1024 / 1024 / 1024);
114-
else
115-
output.WriteLine(")");
116-
}
80+
Name = (string)processor["Name"],
81+
Manufacturer = (string)processor["Manufacturer"],
82+
ThreadCount = (uint)processor["ThreadCount"],
83+
MaxClockMHz = (uint)processor["MaxClockSpeed"],
84+
}).ToList();
11785
}
118-
catch (Exception error)
86+
catch (ManagementException error)
11987
{
12088
Trace.WriteLine(error);
12189
}
90+
91+
// The WMI data for AdapterRAM is unreliable, so we have to use DXGI to get the real numbers.
92+
// Alas, DXGI doesn't give us the manufacturer name for the adapter, so we combine it with WMI.
93+
var descriptions = new Factory1().Adapters.Select(adapter => adapter.Description).ToArray();
12294
try
12395
{
124-
foreach (ManagementObject os in new ManagementClass("Win32_OperatingSystem").GetInstances())
96+
GPUs = new ManagementClass("Win32_VideoController").GetInstances().Cast<ManagementObject>().Select(adapter => new GPU
12597
{
126-
output.WriteLine("OS = {0} {1} ({2})", (string)os["Caption"], (string)os["OSArchitecture"], (string)os["Version"]);
127-
}
98+
Name = (string)adapter["Name"],
99+
Manufacturer = (string)adapter["AdapterCompatibility"],
100+
MemoryMB = (uint)((long)descriptions.FirstOrDefault(desc => desc.Description == (string)adapter["Name"]).DedicatedVideoMemory / 1024 / 1024),
101+
}).ToList();
128102
}
129-
catch (Exception error)
103+
catch (ManagementException error)
130104
{
131105
Trace.WriteLine(error);
132106
}
133-
}
134107

135-
static string GetPnPDeviceDrivers(ManagementObject device)
136-
{
137-
var output = new StringBuilder();
138-
foreach (ManagementObject pnpDevice in device.GetRelated("Win32_PnPEntity"))
139-
{
140-
foreach (ManagementObject dataFile in pnpDevice.GetRelated("CIM_DataFile"))
108+
var featureLevels = new uint[] {
109+
NativeMethods.D3D_FEATURE_LEVEL_12_2,
110+
NativeMethods.D3D_FEATURE_LEVEL_12_1,
111+
NativeMethods.D3D_FEATURE_LEVEL_12_0,
112+
NativeMethods.D3D_FEATURE_LEVEL_11_1,
113+
NativeMethods.D3D_FEATURE_LEVEL_11_0,
114+
NativeMethods.D3D_FEATURE_LEVEL_10_1,
115+
NativeMethods.D3D_FEATURE_LEVEL_10_0,
116+
NativeMethods.D3D_FEATURE_LEVEL_9_3,
117+
NativeMethods.D3D_FEATURE_LEVEL_9_2,
118+
NativeMethods.D3D_FEATURE_LEVEL_9_1,
119+
};
120+
foreach (var featureLevel in featureLevels)
121+
{
122+
var levels = new uint[] { featureLevel };
123+
try
141124
{
142-
output.AppendFormat(" ({0} {1})", (string)dataFile["FileName"], (string)dataFile["Version"]);
125+
var rv = NativeMethods.D3D11CreateDevice(IntPtr.Zero, NativeMethods.D3D_DRIVER_TYPE_HARDWARE, IntPtr.Zero, 0, levels, levels.Length, NativeMethods.D3D11_SDK_VERSION, IntPtr.Zero, out uint level, IntPtr.Zero);
126+
if (level == featureLevel) Direct3DFeatureLevels.Add(string.Format("{0}_{1}", level >> 12 & 0xF, level >> 8 & 0xF));
143127
}
128+
catch (EntryPointNotFoundException) { }
129+
catch (DllNotFoundException) { }
144130
}
145-
return output.ToString();
146131
}
147132

148-
static void WriteAvailableRuntimes(TextWriter output)
133+
public static readonly Platform Application;
134+
public static readonly Platform Runtime;
135+
public static readonly Platform OperatingSystem;
136+
public static readonly int InstalledMemoryMB;
137+
public static readonly List<CPU> CPUs = new List<CPU>();
138+
public static readonly List<GPU> GPUs = new List<GPU>();
139+
public static readonly List<string> Direct3DFeatureLevels = new List<string>();
140+
141+
public static void WriteSystemDetails(TextWriter output)
149142
{
150-
output.Write("Runtimes =");
151-
try
152-
{
153-
// This remote access is necessary to ensure we get the correct bitness view of the registry.
154-
using (var frameworksKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "").OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP", false))
155-
{
156-
foreach (var versionKeyName in frameworksKey.GetSubKeyNames())
157-
{
158-
if (!versionKeyName.StartsWith("v"))
159-
continue;
160-
161-
using (var versionKey = frameworksKey.OpenSubKey(versionKeyName))
162-
{
163-
var fullVersion = WriteInstalledRuntimes(output, versionKeyName, versionKey);
164-
if (fullVersion != "")
165-
continue;
166-
167-
foreach (var skuKeyName in versionKey.GetSubKeyNames())
168-
{
169-
using (var skuKey = versionKey.OpenSubKey(skuKeyName))
170-
{
171-
WriteInstalledRuntimes(output, versionKeyName + " " + skuKeyName, skuKey);
172-
}
173-
}
174-
}
175-
}
176-
}
177-
output.WriteLine();
178-
}
179-
catch (Exception error)
180-
{
181-
Trace.WriteLine(error);
182-
}
143+
output.WriteLine("Date/time = {0} ({1:u})",
144+
DateTime.Now, DateTime.UtcNow);
145+
output.WriteLine("Application = {0} {1} ({2})", Application.Name, Application.Version, Application.Architecture);
146+
output.WriteLine("Runtime = {0} {1}", Runtime.Name, Runtime.Version);
147+
output.WriteLine("System = {0} {1} ({2}; {3}; {4})", OperatingSystem.Name, OperatingSystem.Version, OperatingSystem.Architecture, OperatingSystem.Language, string.Join(",", OperatingSystem.Languages ?? new string[0]));
148+
output.WriteLine("Memory = {0:N0} MB", InstalledMemoryMB);
149+
foreach (var cpu in CPUs) output.WriteLine("CPU = {0} ({1}; {2} threads; {3:N0} MHz)", cpu.Name, cpu.Manufacturer, cpu.ThreadCount, cpu.MaxClockMHz);
150+
foreach (var gpu in GPUs) output.WriteLine("GPU = {0} ({1}; {2:N0} MB)", gpu.Name, gpu.Manufacturer, gpu.MemoryMB);
151+
output.WriteLine("Direct3D = {0}", string.Join(",", Direct3DFeatureLevels));
183152
}
184153

185-
static string WriteInstalledRuntimes(TextWriter output, string versionKeyName, RegistryKey versionKey)
154+
public struct Platform
186155
{
187-
var installed = SafeReadKey(versionKey, "Install", -1);
188-
var fullVersion = SafeReadKey(versionKey, "Version", "");
189-
var servicePack = SafeReadKey(versionKey, "SP", -1);
190-
191-
if (installed == 1 && servicePack != -1)
192-
{
193-
output.Write(" {0} SP{2} ", versionKeyName.Substring(1), fullVersion, servicePack);
194-
}
195-
else if (installed == 1)
196-
{
197-
output.Write(" {0} ", versionKeyName.Substring(1), fullVersion);
198-
}
199-
return fullVersion;
156+
public string Name;
157+
public string Version;
158+
public string Architecture;
159+
public string Language;
160+
public string[] Languages;
200161
}
201162

202-
static void WriteGraphicsAdapter(TextWriter output)
163+
public struct CPU
203164
{
204-
try {
205-
foreach (var adapter in GraphicsAdapter.Adapters)
206-
{
207-
try
208-
{
209-
output.WriteLine("{0} = {1}", adapter.DeviceName, adapter.Description);
210-
}
211-
catch (Exception) { }
212-
}
213-
}
214-
catch (Exception error)
215-
{
216-
output.WriteLine(error);
217-
}
165+
public string Name;
166+
public string Manufacturer;
167+
public uint ThreadCount;
168+
public uint MaxClockMHz;
218169
}
219170

220-
static T SafeReadKey<T>(RegistryKey key, string name, T defaultValue)
171+
public struct GPU
221172
{
222-
try
223-
{
224-
return (T)key.GetValue(name, defaultValue);
225-
}
226-
catch
227-
{
228-
return defaultValue;
229-
}
173+
public string Name;
174+
public string Manufacturer;
175+
public uint MemoryMB;
230176
}
231177

232178
static class NativeStructs
@@ -250,6 +196,35 @@ static class NativeMethods
250196
{
251197
[DllImport("kernel32.dll", SetLastError = true)]
252198
public static extern bool GlobalMemoryStatusEx([In, Out] NativeStructs.MemoryStatusExtended buffer);
199+
200+
public const uint D3D11_SDK_VERSION = 7;
201+
202+
public const uint D3D_DRIVER_TYPE_HARDWARE = 1;
203+
204+
public const uint D3D_FEATURE_LEVEL_9_1 = 0x9100;
205+
public const uint D3D_FEATURE_LEVEL_9_2 = 0x9200;
206+
public const uint D3D_FEATURE_LEVEL_9_3 = 0x9300;
207+
public const uint D3D_FEATURE_LEVEL_10_0 = 0xA000;
208+
public const uint D3D_FEATURE_LEVEL_10_1 = 0xA100;
209+
public const uint D3D_FEATURE_LEVEL_11_0 = 0xB000;
210+
public const uint D3D_FEATURE_LEVEL_11_1 = 0xB100;
211+
public const uint D3D_FEATURE_LEVEL_12_0 = 0xC000;
212+
public const uint D3D_FEATURE_LEVEL_12_1 = 0xC100;
213+
public const uint D3D_FEATURE_LEVEL_12_2 = 0xC200;
214+
215+
[DllImport("d3d11.dll", ExactSpelling = true)]
216+
public static extern uint D3D11CreateDevice(
217+
IntPtr adapter,
218+
uint driverType,
219+
IntPtr software,
220+
uint flags,
221+
[In] uint[] featureLevels,
222+
int featureLevelCount,
223+
uint sdkVersion,
224+
IntPtr device,
225+
out uint featureLevel,
226+
IntPtr immediateContext
227+
);
253228
}
254229
}
255230
}

0 commit comments

Comments
 (0)