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.IO;
using System.Linq;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo namespace Ryujinx.Common.SystemInfo
{ {
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
internal class LinuxSystemInfo : SystemInfo class LinuxSystemInfo : SystemInfo
{ {
public override string CpuName { get; } internal LinuxSystemInfo()
public override ulong RamSize { get; }
public LinuxSystemInfo()
{ {
CpuName = File.ReadAllLines("/proc/cpuinfo").Where(line => line.StartsWith("model name")).ToList()[0].Split(":")[1].Trim(); string cpuName = GetCpuidCpuName();
RamSize = ulong.Parse(File.ReadAllLines("/proc/meminfo")[0].Split(":")[1].Trim().Split(" ")[0]) * 1024;
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 namespace Ryujinx.Common.SystemInfo
{ {
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
internal class MacOSSystemInfo : SystemInfo class MacOSSystemInfo : SystemInfo
{ {
public override string CpuName { get; } internal MacOSSystemInfo()
public override ulong RamSize { get; } {
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)] [DllImport("libSystem.dylib", CharSet = CharSet.Ansi, SetLastError = true)]
private static extern int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); 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) 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; return 0;
@ -64,36 +85,5 @@ namespace Ryujinx.Common.SystemInfo
return res; 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;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Text;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo namespace Ryujinx.Common.SystemInfo
{ {
public class SystemInfo public class SystemInfo
{ {
public virtual string OsDescription => $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})"; public string OsDescription { get; protected set; }
public virtual string CpuName => "Unknown"; public string CpuName { get; protected set; }
public virtual ulong RamSize => 0; public ulong RamTotal { get; protected set; }
public string RamSizeInMB => (RamSize == 0) ? "Unknown" : $"{RamSize / 1024 / 1024} MB"; 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()) if (OperatingSystem.IsWindows())
{ {
Instance = new WindowsSystemInfo(); return new WindowsSystemInfo();
} }
else if (OperatingSystem.IsLinux()) else if (OperatingSystem.IsLinux())
{ {
Instance = new LinuxSystemInfo(); return new LinuxSystemInfo();
} }
else if (OperatingSystem.IsMacOS()) else if (OperatingSystem.IsMacOS())
{ {
Instance = new MacOSSystemInfo(); return new MacOSSystemInfo();
} }
else 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;
using System.Globalization;
using System.Management; using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Ryujinx.Common.Logging;
namespace Ryujinx.Common.SystemInfo namespace Ryujinx.Common.SystemInfo
{ {
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
internal class WindowsSystemInfo : SystemInfo class WindowsSystemInfo : SystemInfo
{ {
public override string CpuName { get; } internal WindowsSystemInfo()
public override ulong RamSize { get; }
public 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 try
{ {
foreach (ManagementBaseObject mObject in new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor").Get()) return new ManagementObjectSearcher(scope, query).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;
}
} }
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) return null;
{
Logger.Error?.Print(LogClass.Application, "WMI isn't available, system informations will use default values.");
CpuName = "Unknown";
}
} }
} }
} }

View file

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