diff options
| author | Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> | 2024-02-11 02:09:18 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-11 03:09:18 +0100 |
| commit | f06d22d6f01e657ebbc0c8ef082739cd468e47b5 (patch) | |
| tree | c10a566438d3801b33c1d7b4eff73ea62b2f1a63 /src/Ryujinx.UI.Common/SystemInfo | |
| parent | 84d6e8d121a1b329d26cc0e462aadd1108d99a04 (diff) | |
Infra: Capitalisation Consistency (#6296)
* Rename Ryujinx.UI.Common
* Rename Ryujinx.UI.LocaleGenerator
* Update in Files
AboutWindow
* Configuration State
* Rename projects
* Ryujinx/UI
* Fix build
* Main remaining inconsistencies
* HLE.UI Namespace
* HLE.UI Files
* Namespace
* Ryujinx.UI.Common.Configuration.UI
* Ryujinx.UI.Common,Configuration.UI Files
* More instances
Diffstat (limited to 'src/Ryujinx.UI.Common/SystemInfo')
| -rw-r--r-- | src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs | 85 | ||||
| -rw-r--r-- | src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs | 164 | ||||
| -rw-r--r-- | src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs | 79 | ||||
| -rw-r--r-- | src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs | 87 |
4 files changed, 415 insertions, 0 deletions
diff --git a/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs new file mode 100644 index 00000000..c7fe05a0 --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs @@ -0,0 +1,85 @@ +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.SystemInfo +{ + [SupportedOSPlatform("linux")] + class LinuxSystemInfo : SystemInfo + { + internal LinuxSystemInfo() + { + 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 KiB + ulong.TryParse(memDict["MemTotal"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong totalKiB); + ulong.TryParse(memDict["MemAvailable"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong availableKiB); + + CpuName = $"{cpuName} ; {LogicalCoreCount} logical"; + RamTotal = totalKiB * 1024; + RamAvailable = availableKiB * 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(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; + } + } + } + } + } +} diff --git a/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs new file mode 100644 index 00000000..36deaf35 --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs @@ -0,0 +1,164 @@ +using Ryujinx.Common.Logging; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; + +namespace Ryujinx.UI.Common.SystemInfo +{ + [SupportedOSPlatform("macos")] + partial class MacOSSystemInfo : SystemInfo + { + internal MacOSSystemInfo() + { + if (SysctlByName("kern.osversion", out string buildRevision) != 0) + { + buildRevision = "Unknown Build"; + } + + OsDescription = $"macOS {Environment.OSVersion.Version} ({buildRevision}) ({RuntimeInformation.OSArchitecture})"; + + 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; + RamAvailable = GetVMInfoAvailableMemory(); + } + + static ulong GetVMInfoAvailableMemory() + { + var port = mach_host_self(); + + uint pageSize = 0; + var result = host_page_size(port, ref pageSize); + + if (result != 0) + { + Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_page_size() error = {result}"); + return 0; + } + + const int Flavor = 4; // HOST_VM_INFO64 + uint count = (uint)(Marshal.SizeOf<VMStatistics64>() / sizeof(int)); // HOST_VM_INFO64_COUNT + VMStatistics64 stats = new(); + result = host_statistics64(port, Flavor, ref stats, ref count); + + if (result != 0) + { + Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_statistics64() error = {result}"); + return 0; + } + + return (ulong)(stats.FreeCount + stats.InactiveCount) * pageSize; + } + + private const string SystemLibraryName = "libSystem.dylib"; + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); + + private static int SysctlByName(string name, IntPtr oldValue, ref ulong oldSize) + { + if (sysctlbyname(name, oldValue, ref oldSize, IntPtr.Zero, 0) == -1) + { + int err = Marshal.GetLastWin32Error(); + + Logger.Error?.Print(LogClass.Application, $"Cannot retrieve '{name}'. Error Code {err}"); + + return err; + } + + return 0; + } + + private static int SysctlByName<T>(string name, ref T oldValue) + { + unsafe + { + ulong oldValueSize = (ulong)Unsafe.SizeOf<T>(); + + return SysctlByName(name, (IntPtr)Unsafe.AsPointer(ref oldValue), ref oldValueSize); + } + } + + private static int SysctlByName(string name, out string oldValue) + { + oldValue = default; + + ulong strSize = 0; + + int res = SysctlByName(name, IntPtr.Zero, ref strSize); + + if (res == 0) + { + byte[] rawData = new byte[strSize]; + + unsafe + { + fixed (byte* rawDataPtr = rawData) + { + res = SysctlByName(name, (IntPtr)rawDataPtr, ref strSize); + } + + if (res == 0) + { + oldValue = Encoding.ASCII.GetString(rawData); + } + } + } + + return res; + } + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial uint mach_host_self(); + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int host_page_size(uint host, ref uint out_page_size); + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + struct VMStatistics64 + { + public uint FreeCount; + public uint ActiveCount; + public uint InactiveCount; + public uint WireCount; + public ulong ZeroFillCount; + public ulong Reactivations; + public ulong Pageins; + public ulong Pageouts; + public ulong Faults; + public ulong CowFaults; + public ulong Lookups; + public ulong Hits; + public ulong Purges; + public uint PurgeableCount; + public uint SpeculativeCount; + public ulong Decompressions; + public ulong Compressions; + public ulong Swapins; + public ulong Swapouts; + public uint CompressorPageCount; + public uint ThrottledCount; + public uint ExternalPageCount; + public uint InternalPageCount; + public ulong TotalUncompressedPagesInCompressor; + } + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int host_statistics64(uint hostPriv, int hostFlavor, ref VMStatistics64 hostInfo64Out, ref uint hostInfo64OutCnt); + } +} diff --git a/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs new file mode 100644 index 00000000..38728b9c --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs @@ -0,0 +1,79 @@ +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Helper; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +using System.Text; + +namespace Ryujinx.UI.Common.SystemInfo +{ + public class SystemInfo + { + 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; + + protected SystemInfo() + { + OsDescription = $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})"; + CpuName = "Unknown"; + } + + private static string ToGBString(ulong bytesValue) => (bytesValue == 0) ? "Unknown" : ValueFormatUtils.FormatFileSize((long)bytesValue, ValueFormatUtils.FileSizeUnits.Gibibytes); + + 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 {ToGBString(RamTotal)} ; Available {ToGBString(RamAvailable)}"); + } + + public static SystemInfo Gather() + { + if (OperatingSystem.IsWindows()) + { + return new WindowsSystemInfo(); + } + else if (OperatingSystem.IsLinux()) + { + return new LinuxSystemInfo(); + } + else if (OperatingSystem.IsMacOS()) + { + return new MacOSSystemInfo(); + } + + 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; + } + } +} diff --git a/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs new file mode 100644 index 00000000..bf49c2a6 --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs @@ -0,0 +1,87 @@ +using Ryujinx.Common.Logging; +using System; +using System.Management; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.SystemInfo +{ + [SupportedOSPlatform("windows")] + partial class WindowsSystemInfo : SystemInfo + { + internal WindowsSystemInfo() + { + CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow + (RamTotal, RamAvailable) = GetMemoryStats(); + } + + private static (ulong Total, ulong Available) GetMemoryStats() + { + MemoryStatusEx memStatus = new(); + if (GlobalMemoryStatusEx(ref memStatus)) + { + return (memStatus.TotalPhys, memStatus.AvailPhys); // Bytes + } + + 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)] + private struct 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<MemoryStatusEx>(); + } + } + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer); + + private static ManagementObjectCollection GetWMIObjects(string scope, string query) + { + try + { + return new ManagementObjectSearcher(scope, query).Get(); + } + catch (PlatformNotSupportedException ex) + { + Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}"); + } + catch (COMException ex) + { + Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}"); + } + + return null; + } + } +} |
