Revise SystemInfo (#2047)

* Revise SystemInfo

Cleans up and adds a bit more info (logical core count and available mem at launch) to logs.

- Extract CPU name from CPUID when supported.
- Linux: Robust parsing of procfs files
- Windows: Prefer native calls to WMI
- Remove unnecessary virtual specifiers

* Address gdkchan's comments

* Address AcK's comments

* Address formatting nits
This commit is contained in:
mageven 2021-03-01 09:52:00 +05:30 committed by GitHub
parent d02eeed9c1
commit 06a2b03cc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 218 additions and 82 deletions

View file

@ -1,19 +1,80 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{
[SupportedOSPlatform("linux")]
internal class LinuxSystemInfo : SystemInfo
class LinuxSystemInfo : SystemInfo
{
public override string CpuName { get; }
public override ulong RamSize { get; }
public LinuxSystemInfo()
internal LinuxSystemInfo()
{
CpuName = File.ReadAllLines("/proc/cpuinfo").Where(line => line.StartsWith("model name")).ToList()[0].Split(":")[1].Trim();
RamSize = ulong.Parse(File.ReadAllLines("/proc/meminfo")[0].Split(":")[1].Trim().Split(" ")[0]) * 1024;
string cpuName = GetCpuidCpuName();
if (cpuName == null)
{
var cpuDict = new Dictionary<string, string>(StringComparer.Ordinal)
{
["model name"] = null,
["Processor"] = null,
["Hardware"] = null
};
ParseKeyValues("/proc/cpuinfo", cpuDict);
cpuName = cpuDict["model name"] ?? cpuDict["Processor"] ?? cpuDict["Hardware"] ?? "Unknown";
}
var memDict = new Dictionary<string, string>(StringComparer.Ordinal)
{
["MemTotal"] = null,
["MemAvailable"] = null
};
ParseKeyValues("/proc/meminfo", memDict);
// Entries are in KB
ulong.TryParse(memDict["MemTotal"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong totalKB);
ulong.TryParse(memDict["MemAvailable"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong availableKB);
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
RamTotal = totalKB * 1024;
RamAvailable = availableKB * 1024;
}
private static void ParseKeyValues(string filePath, Dictionary<string, string> itemDict)
{
if (!File.Exists(filePath))
{
Logger.Error?.Print(LogClass.Application, $"File \"{filePath}\" not found");
return;
}
int count = itemDict.Count;
using (StreamReader file = new StreamReader(filePath))
{
string line;
while ((line = file.ReadLine()) != null)
{
string[] kvPair = line.Split(':', 2, StringSplitOptions.TrimEntries);
if (kvPair.Length < 2) continue;
string key = kvPair[0];
if (itemDict.TryGetValue(key, out string value) && value == null)
{
itemDict[key] = kvPair[1];
if (--count <= 0) break;
}
}
}
}
}
}

View file

@ -8,10 +8,27 @@ using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{
[SupportedOSPlatform("macos")]
internal class MacOSSystemInfo : SystemInfo
class MacOSSystemInfo : SystemInfo
{
public override string CpuName { get; }
public override ulong RamSize { get; }
internal MacOSSystemInfo()
{
string cpuName = GetCpuidCpuName();
if (cpuName == null && sysctlbyname("machdep.cpu.brand_string", out cpuName) != 0)
{
cpuName = "Unknown";
}
ulong totalRAM = 0;
if (sysctlbyname("hw.memsize", ref totalRAM) != 0) // Bytes
{
totalRAM = 0;
};
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
RamTotal = totalRAM;
}
[DllImport("libSystem.dylib", CharSet = CharSet.Ansi, SetLastError = true)]
private static extern int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
@ -20,7 +37,11 @@ namespace Ryujinx.Common.SystemInfo
{
if (sysctlbyname(name, oldValue, ref oldSize, IntPtr.Zero, 0) == -1)
{
return Marshal.GetLastWin32Error();
int err = Marshal.GetLastWin32Error();
Logger.Error?.Print(LogClass.Application, $"Cannot retrieve '{name}'. Error Code {err}");
return err;
}
return 0;
@ -64,36 +85,5 @@ namespace Ryujinx.Common.SystemInfo
return res;
}
public MacOSSystemInfo()
{
ulong ramSize = 0;
int res = sysctlbyname("hw.memsize", ref ramSize);
if (res == 0)
{
RamSize = ramSize;
}
else
{
Logger.Error?.Print(LogClass.Application, $"Cannot get memory size, sysctlbyname error: {res}");
RamSize = 0;
}
res = sysctlbyname("machdep.cpu.brand_string", out string cpuName);
if (res == 0)
{
CpuName = cpuName;
}
else
{
Logger.Error?.Print(LogClass.Application, $"Cannot get CPU name, sysctlbyname error: {res}");
CpuName = "Unknown";
}
}
}
}

View file

@ -1,35 +1,80 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Text;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{
public class SystemInfo
{
public virtual string OsDescription => $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})";
public virtual string CpuName => "Unknown";
public virtual ulong RamSize => 0;
public string RamSizeInMB => (RamSize == 0) ? "Unknown" : $"{RamSize / 1024 / 1024} MB";
public string OsDescription { get; protected set; }
public string CpuName { get; protected set; }
public ulong RamTotal { get; protected set; }
public ulong RamAvailable { get; protected set; }
protected static int LogicalCoreCount => Environment.ProcessorCount;
public static SystemInfo Instance { get; }
protected SystemInfo()
{
OsDescription = $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})";
CpuName = "Unknown";
}
static SystemInfo()
private static string ToMBString(ulong bytesValue) => (bytesValue == 0) ? "Unknown" : $"{bytesValue / 1024 / 1024} MB";
public void Print()
{
Logger.Notice.Print(LogClass.Application, $"Operating System: {OsDescription}");
Logger.Notice.Print(LogClass.Application, $"CPU: {CpuName}");
Logger.Notice.Print(LogClass.Application, $"RAM: Total {ToMBString(RamTotal)} ; Available {ToMBString(RamAvailable)}");
}
public static SystemInfo Gather()
{
if (OperatingSystem.IsWindows())
{
Instance = new WindowsSystemInfo();
return new WindowsSystemInfo();
}
else if (OperatingSystem.IsLinux())
{
Instance = new LinuxSystemInfo();
return new LinuxSystemInfo();
}
else if (OperatingSystem.IsMacOS())
{
Instance = new MacOSSystemInfo();
return new MacOSSystemInfo();
}
else
{
Instance = new SystemInfo();
Logger.Error?.Print(LogClass.Application, "SystemInfo unsupported on this platform");
return new SystemInfo();
}
}
// x86 exposes a 48 byte ASCII "CPU brand" string via CPUID leaves 0x80000002-0x80000004.
internal static string GetCpuidCpuName()
{
if (!X86Base.IsSupported)
{
return null;
}
// Check if CPU supports the query
if ((uint)X86Base.CpuId(unchecked((int)0x80000000), 0).Eax < 0x80000004)
{
return null;
}
int[] regs = new int[12];
for (uint i = 0; i < 3; ++i)
{
(regs[4 * i], regs[4 * i + 1], regs[4 * i + 2], regs[4 * i + 3]) = X86Base.CpuId((int)(0x80000002 + i), 0);
}
string name = Encoding.ASCII.GetString(MemoryMarshal.Cast<int, byte>(regs)).Replace('\0', ' ').Trim();
return string.IsNullOrEmpty(name) ? null : name;
}
}
}

View file

@ -1,48 +1,90 @@
using Ryujinx.Common.Logging;
using System;
using System.Globalization;
using System.Management;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo
{
[SupportedOSPlatform("windows")]
internal class WindowsSystemInfo : SystemInfo
class WindowsSystemInfo : SystemInfo
{
public override string CpuName { get; }
public override ulong RamSize { get; }
public WindowsSystemInfo()
internal WindowsSystemInfo()
{
bool wmiNotAvailable = false;
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow
(RamTotal, RamAvailable) = GetMemoryStats();
}
private static (ulong Total, ulong Available) GetMemoryStats()
{
MemoryStatusEx memStatus = new MemoryStatusEx();
if (GlobalMemoryStatusEx(memStatus))
{
return (memStatus.TotalPhys, memStatus.AvailPhys); // Bytes
}
else
{
Logger.Error?.Print(LogClass.Application, $"GlobalMemoryStatusEx failed. Error {Marshal.GetLastWin32Error():X}");
}
return (0, 0);
}
private static string GetCpuNameWMI()
{
ManagementObjectCollection cpuObjs = GetWMIObjects("root\\CIMV2", "SELECT * FROM Win32_Processor");
if (cpuObjs != null)
{
foreach (var cpuObj in cpuObjs)
{
return cpuObj["Name"].ToString().Trim();
}
}
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER").Trim();
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private class MemoryStatusEx
{
public uint Length;
public uint MemoryLoad;
public ulong TotalPhys;
public ulong AvailPhys;
public ulong TotalPageFile;
public ulong AvailPageFile;
public ulong TotalVirtual;
public ulong AvailVirtual;
public ulong AvailExtendedVirtual;
public MemoryStatusEx()
{
Length = (uint)Marshal.SizeOf(typeof(MemoryStatusEx));
}
}
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool GlobalMemoryStatusEx([In, Out] MemoryStatusEx lpBuffer);
private static ManagementObjectCollection GetWMIObjects(string scope, string query)
{
try
{
foreach (ManagementBaseObject mObject in new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor").Get())
{
CpuName = mObject["Name"].ToString();
}
foreach (ManagementBaseObject mObject in new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_OperatingSystem").Get())
{
RamSize = ulong.Parse(mObject["TotalVisibleMemorySize"].ToString()) * 1024;
}
return new ManagementObjectSearcher(scope, query).Get();
}
catch (PlatformNotSupportedException)
catch (PlatformNotSupportedException ex)
{
wmiNotAvailable = true;
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
}
catch (COMException)
catch (COMException ex)
{
wmiNotAvailable = true;
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
}
if (wmiNotAvailable)
{
Logger.Error?.Print(LogClass.Application, "WMI isn't available, system informations will use default values.");
CpuName = "Unknown";
}
return null;
}
}
}

View file

@ -168,9 +168,7 @@ namespace Ryujinx
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
Logger.Notice.Print(LogClass.Application, $"Operating System: {SystemInfo.Instance.OsDescription}");
Logger.Notice.Print(LogClass.Application, $"CPU: {SystemInfo.Instance.CpuName}");
Logger.Notice.Print(LogClass.Application, $"Total RAM: {SystemInfo.Instance.RamSizeInMB}");
SystemInfo.Gather().Print();
var enabledLogs = Logger.GetEnabledLevels();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");