From 06a2b03cc91b38e3798d8cc1c57c186778c7e666 Mon Sep 17 00:00:00 2001 From: mageven <62494521+mageven@users.noreply.github.com> Date: Mon, 1 Mar 2021 09:52:00 +0530 Subject: [PATCH] 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 --- Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs | 77 +++++++++++++-- Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs | 60 +++++------- Ryujinx.Common/SystemInfo/SystemInfo.cs | 65 +++++++++++-- .../SystemInfo/WindowsSystemInfo.cs | 94 ++++++++++++++----- Ryujinx/Program.cs | 4 +- 5 files changed, 218 insertions(+), 82 deletions(-) diff --git a/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs b/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs index f067083e47..069cd5aa25 100644 --- a/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs +++ b/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs @@ -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(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(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 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; + } + } + } } } } \ No newline at end of file diff --git a/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs b/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs index ec069ca427..92b54902d1 100644 --- a/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs +++ b/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs @@ -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"; - } - } } } \ No newline at end of file diff --git a/Ryujinx.Common/SystemInfo/SystemInfo.cs b/Ryujinx.Common/SystemInfo/SystemInfo.cs index feb6b8f8a8..98f520bc11 100644 --- a/Ryujinx.Common/SystemInfo/SystemInfo.cs +++ b/Ryujinx.Common/SystemInfo/SystemInfo.cs @@ -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(regs)).Replace('\0', ' ').Trim(); + + return string.IsNullOrEmpty(name) ? null : name; + } } } \ No newline at end of file diff --git a/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs b/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs index 479dd25fef..fdc8fb6a71 100644 --- a/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs +++ b/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs @@ -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; } } } \ No newline at end of file diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 24642b6111..a3137cc1a8 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -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 ? "" : string.Join(", ", enabledLogs))}");