From 24f9904a9de9f8872ee36acd321090d8128d09fb Mon Sep 17 00:00:00 2001 From: Ionite Date: Fri, 1 Dec 2023 04:00:16 -0500 Subject: [PATCH] Add settings system info, HardwareInfo reorganize --- .../Converters/CustomStringFormatConverter.cs | 28 ++ .../Converters/IndexPlusOneConverter.cs | 36 +++ .../Converters/MemoryBytesFormatter.cs | 41 +++ .../Converters/StringFormatConverters.cs | 8 +- .../Languages/Resources.Designer.cs | 9 + .../Languages/Resources.resx | 3 + .../ViewModels/FirstLaunchSetupViewModel.cs | 1 + .../Settings/MainSettingsViewModel.cs | 42 +++ .../Views/Settings/MainSettingsPage.axaml | 145 ++++++++++- StabilityMatrix.Core/Helper/HardwareHelper.cs | 172 ------------- .../Helper/HardwareInfo/CpuInfo.cs | 7 + .../Helper/HardwareInfo/GpuInfo.cs | 31 +++ .../Helper/HardwareInfo/HardwareHelper.cs | 241 ++++++++++++++++++ .../Helper/HardwareInfo/MemoryInfo.cs | 10 + .../Helper/HardwareInfo/MemoryLevel.cs | 9 + .../HardwareInfo/Win32MemoryStatusEx.cs | 17 ++ StabilityMatrix.Core/Helper/Size.cs | 55 +++- .../Models/Packages/A3WebUI.cs | 5 +- .../Models/Packages/BasePackage.cs | 1 + .../Models/Packages/ComfyUI.cs | 5 +- .../Models/Packages/Fooocus.cs | 5 +- .../Models/Packages/KohyaSs.cs | 1 + .../Models/Packages/StableDiffusionUx.cs | 5 +- .../Models/Packages/VladAutomatic.cs | 5 +- .../StabilityMatrix.Core.csproj | 2 + 25 files changed, 684 insertions(+), 200 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs create mode 100644 StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs create mode 100644 StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs delete mode 100644 StabilityMatrix.Core/Helper/HardwareHelper.cs create mode 100644 StabilityMatrix.Core/Helper/HardwareInfo/CpuInfo.cs create mode 100644 StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs create mode 100644 StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs create mode 100644 StabilityMatrix.Core/Helper/HardwareInfo/MemoryInfo.cs create mode 100644 StabilityMatrix.Core/Helper/HardwareInfo/MemoryLevel.cs create mode 100644 StabilityMatrix.Core/Helper/HardwareInfo/Win32MemoryStatusEx.cs diff --git a/StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs b/StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs new file mode 100644 index 00000000..8f41f8e1 --- /dev/null +++ b/StabilityMatrix.Avalonia/Converters/CustomStringFormatConverter.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace StabilityMatrix.Avalonia.Converters; + +public class CustomStringFormatConverter([StringSyntax("CompositeFormat")] string format) + : IValueConverter + where T : IFormatProvider, new() +{ + /// + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value is null ? null : string.Format(new T(), format, value); + } + + /// + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture + ) + { + return value is null ? null : throw new NotImplementedException(); + } +} diff --git a/StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs b/StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs new file mode 100644 index 00000000..e462c8ee --- /dev/null +++ b/StabilityMatrix.Avalonia/Converters/IndexPlusOneConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace StabilityMatrix.Avalonia.Converters; + +/// +/// Converts an index to index + 1 +/// +public class IndexPlusOneConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is int i) + { + return i + 1; + } + + return value; + } + + public object? ConvertBack( + object? value, + Type targetType, + object? parameter, + CultureInfo culture + ) + { + if (value is int i) + { + return i - 1; + } + + return value; + } +} diff --git a/StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs b/StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs new file mode 100644 index 00000000..18dba05f --- /dev/null +++ b/StabilityMatrix.Avalonia/Converters/MemoryBytesFormatter.cs @@ -0,0 +1,41 @@ +using System; +using Size = StabilityMatrix.Core.Helper.Size; + +namespace StabilityMatrix.Avalonia.Converters; + +public class MemoryBytesFormatter : ICustomFormatter, IFormatProvider +{ + /// + public object? GetFormat(Type? formatType) + { + return formatType == typeof(ICustomFormatter) ? this : null; + } + + /// + public string Format(string? format, object? arg, IFormatProvider? formatProvider) + { + if (format == null || !format.Trim().StartsWith('M')) + { + if (arg is IFormattable formatArg) + { + return formatArg.ToString(format, formatProvider); + } + + return arg?.ToString() ?? string.Empty; + } + + var value = Convert.ToUInt64(arg); + + var result = format.Trim().EndsWith("10", StringComparison.OrdinalIgnoreCase) + ? Size.FormatBase10Bytes(value) + : Size.FormatBytes(value); + + // Strip i if not Mi + if (!format.Trim().Contains('I', StringComparison.OrdinalIgnoreCase)) + { + result = result.Replace("i", string.Empty, StringComparison.OrdinalIgnoreCase); + } + + return result; + } +} diff --git a/StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs b/StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs index e4ed2455..6402747f 100644 --- a/StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs +++ b/StabilityMatrix.Avalonia/Converters/StringFormatConverters.cs @@ -1,4 +1,5 @@ -using Avalonia.Data.Converters; +using System; +using Avalonia.Data.Converters; namespace StabilityMatrix.Avalonia.Converters; @@ -7,4 +8,9 @@ public static class StringFormatConverters private static StringFormatValueConverter? _decimalConverter; public static StringFormatValueConverter Decimal => _decimalConverter ??= new StringFormatValueConverter("{0:D}", null); + + private static readonly Lazy MemoryBytesConverterLazy = + new(() => new CustomStringFormatConverter("{0:M}")); + + public static IValueConverter MemoryBytes => MemoryBytesConverterLazy.Value; } diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs index 23ffdba3..b5468fdf 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs +++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs @@ -1193,6 +1193,15 @@ namespace StabilityMatrix.Avalonia.Languages { } } + /// + /// Looks up a localized string similar to Information. + /// + public static string Label_Information { + get { + return ResourceManager.GetString("Label_Information", resourceCulture); + } + } + /// /// Looks up a localized string similar to Inner exception. /// diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx index be55b0f1..2c3ff57f 100644 --- a/StabilityMatrix.Avalonia/Languages/Resources.resx +++ b/StabilityMatrix.Avalonia/Languages/Resources.resx @@ -843,4 +843,7 @@ Tags file to use for suggesting completions (Supports the a1111-sd-webui-tagcomplete .csv format) + + Information + diff --git a/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs index 54463c21..ac795ca6 100644 --- a/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/FirstLaunchSetupViewModel.cs @@ -7,6 +7,7 @@ using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.HardwareInfo; namespace StabilityMatrix.Avalonia.ViewModels; diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs index 47408a22..bcc4ff00 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs @@ -44,6 +44,7 @@ using StabilityMatrix.Avalonia.Views.Settings; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Python; @@ -116,6 +117,23 @@ public partial class MainSettingsViewModel : PageViewModelBase [ObservableProperty] private string? debugGpuInfo; + #region System Info + + private static Lazy> GpuInfosLazy { get; } = + new(() => HardwareHelper.IterGpuInfo().ToImmutableArray()); + + public static IReadOnlyList GpuInfos => GpuInfosLazy.Value; + + [ObservableProperty] + private MemoryInfo memoryInfo; + + private readonly DispatcherTimer hardwareInfoUpdateTimer = + new() { Interval = TimeSpan.FromSeconds(2.627) }; + + public Task CpuInfoAsync => HardwareHelper.GetCpuInfoAsync(); + + #endregion + // Info section private const int VersionTapCountThreshold = 7; @@ -186,6 +204,25 @@ public partial class MainSettingsViewModel : PageViewModelBase notificationService, LogLevel.Warn ); + + hardwareInfoUpdateTimer.Tick += OnHardwareInfoUpdateTimerTick; + } + + /// + public override void OnLoaded() + { + base.OnLoaded(); + + hardwareInfoUpdateTimer.Start(); + OnHardwareInfoUpdateTimerTick(null, null!); + } + + /// + public override void OnUnloaded() + { + base.OnUnloaded(); + + hardwareInfoUpdateTimer.Stop(); } /// @@ -199,6 +236,11 @@ public partial class MainSettingsViewModel : PageViewModelBase accountsService.RefreshAsync().SafeFireAndForget(); } + private void OnHardwareInfoUpdateTimerTick(object? sender, EventArgs e) + { + MemoryInfo = HardwareHelper.GetMemoryInfo(); + } + partial void OnSelectedThemeChanged(string? value) { // In case design / tests diff --git a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml index 52da78bb..e35b1bbc 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/Settings/MainSettingsPage.axaml @@ -7,6 +7,8 @@ xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" + xmlns:hardwareInfo="clr-namespace:StabilityMatrix.Core.Helper.HardwareInfo;assembly=StabilityMatrix.Core" + xmlns:helper="clr-namespace:StabilityMatrix.Core.Helper;assembly=StabilityMatrix.Core" xmlns:inference="clr-namespace:StabilityMatrix.Avalonia.Models.Inference" xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -28,10 +30,17 @@ + + + + + @@ -104,14 +113,14 @@ - + - + - + - + @@ -294,6 +300,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Core/Helper/HardwareHelper.cs b/StabilityMatrix.Core/Helper/HardwareHelper.cs deleted file mode 100644 index b50fe42d..00000000 --- a/StabilityMatrix.Core/Helper/HardwareHelper.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System.Diagnostics; -using System.Runtime.Versioning; -using System.Text.RegularExpressions; -using Microsoft.Win32; - -namespace StabilityMatrix.Core.Helper; - -public static partial class HardwareHelper -{ - private static IReadOnlyList? cachedGpuInfos; - - private static string RunBashCommand(string command) - { - var processInfo = new ProcessStartInfo("bash", "-c \"" + command + "\"") - { - UseShellExecute = false, - RedirectStandardOutput = true - }; - - var process = Process.Start(processInfo); - - process.WaitForExit(); - - var output = process.StandardOutput.ReadToEnd(); - - return output; - } - - [SupportedOSPlatform("windows")] - private static IEnumerable IterGpuInfoWindows() - { - const string gpuRegistryKeyPath = - @"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}"; - - using var baseKey = Registry.LocalMachine.OpenSubKey(gpuRegistryKeyPath); - - if (baseKey == null) yield break; - - foreach (var subKeyName in baseKey.GetSubKeyNames().Where(k => k.StartsWith("0"))) - { - using var subKey = baseKey.OpenSubKey(subKeyName); - if (subKey != null) - { - yield return new GpuInfo - { - Name = subKey.GetValue("DriverDesc")?.ToString(), - MemoryBytes = Convert.ToUInt64(subKey.GetValue("HardwareInformation.qwMemorySize")), - }; - } - } - } - - [SupportedOSPlatform("linux")] - private static IEnumerable IterGpuInfoLinux() - { - var output = RunBashCommand("lspci | grep VGA"); - var gpuLines = output.Split("\n"); - - foreach (var line in gpuLines) - { - if (string.IsNullOrWhiteSpace(line)) continue; - - var gpuId = line.Split(' ')[0]; // The GPU ID is the first part of the line - var gpuOutput = RunBashCommand($"lspci -v -s {gpuId}"); - - ulong memoryBytes = 0; - string? name = null; - - // Parse output with regex - var match = Regex.Match(gpuOutput, @"VGA compatible controller: ([^\n]*)"); - if (match.Success) - { - name = match.Groups[1].Value.Trim(); - } - - match = Regex.Match(gpuOutput, @"prefetchable\) \[size=(\\d+)M\]"); - if (match.Success) - { - memoryBytes = ulong.Parse(match.Groups[1].Value) * 1024 * 1024; - } - - yield return new GpuInfo { Name = name, MemoryBytes = memoryBytes }; - } - } - - /// - /// Yields GpuInfo for each GPU in the system. - /// - public static IEnumerable IterGpuInfo() - { - if (Compat.IsWindows) - { - return IterGpuInfoWindows(); - } - else if (Compat.IsLinux) - { - // Since this requires shell commands, fetch cached value if available. - if (cachedGpuInfos is not null) - { - return cachedGpuInfos; - } - - // No cache, fetch and cache. - cachedGpuInfos = IterGpuInfoLinux().ToList(); - return cachedGpuInfos; - } - // TODO: Implement for macOS - return Enumerable.Empty(); - } - - /// - /// Return true if the system has at least one Nvidia GPU. - /// - public static bool HasNvidiaGpu() - { - return IterGpuInfo().Any(gpu => gpu.IsNvidia); - } - - /// - /// Return true if the system has at least one AMD GPU. - /// - public static bool HasAmdGpu() - { - return IterGpuInfo().Any(gpu => gpu.IsAmd); - } - - // Set ROCm for default if AMD and Linux - public static bool PreferRocm() => !HasNvidiaGpu() - && HasAmdGpu() - && Compat.IsLinux; - - // Set DirectML for default if AMD and Windows - public static bool PreferDirectML() => !HasNvidiaGpu() - && HasAmdGpu() - && Compat.IsWindows; -} - -public enum Level -{ - Unknown, - Low, - Medium, - High -} - -public record GpuInfo -{ - public string? Name { get; init; } = string.Empty; - public ulong MemoryBytes { get; init; } - public Level? MemoryLevel => MemoryBytes switch - { - <= 0 => Level.Unknown, - < 4 * Size.GiB => Level.Low, - < 8 * Size.GiB => Level.Medium, - _ => Level.High - }; - - public bool IsNvidia - { - get - { - var name = Name?.ToLowerInvariant(); - - if (string.IsNullOrEmpty(name)) return false; - - return name.Contains("nvidia") - || name.Contains("tesla"); - } - } - - public bool IsAmd => Name?.ToLowerInvariant().Contains("amd") ?? false; -} diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/CpuInfo.cs b/StabilityMatrix.Core/Helper/HardwareInfo/CpuInfo.cs new file mode 100644 index 00000000..7ea9b2b6 --- /dev/null +++ b/StabilityMatrix.Core/Helper/HardwareInfo/CpuInfo.cs @@ -0,0 +1,7 @@ +namespace StabilityMatrix.Core.Helper.HardwareInfo; + +public readonly record struct CpuInfo +{ + public string ProcessorCaption { get; init; } + public string ProcessorName { get; init; } +} diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs new file mode 100644 index 00000000..faabd558 --- /dev/null +++ b/StabilityMatrix.Core/Helper/HardwareInfo/GpuInfo.cs @@ -0,0 +1,31 @@ +namespace StabilityMatrix.Core.Helper.HardwareInfo; + +public record GpuInfo +{ + public int Index { get; init; } + public string? Name { get; init; } = string.Empty; + public ulong MemoryBytes { get; init; } + public MemoryLevel? MemoryLevel => + MemoryBytes switch + { + <= 0 => HardwareInfo.MemoryLevel.Unknown, + < 4 * Size.GiB => HardwareInfo.MemoryLevel.Low, + < 8 * Size.GiB => HardwareInfo.MemoryLevel.Medium, + _ => HardwareInfo.MemoryLevel.High + }; + + public bool IsNvidia + { + get + { + var name = Name?.ToLowerInvariant(); + + if (string.IsNullOrEmpty(name)) + return false; + + return name.Contains("nvidia") || name.Contains("tesla"); + } + } + + public bool IsAmd => Name?.Contains("amd", StringComparison.OrdinalIgnoreCase) ?? false; +} diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs b/StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs new file mode 100644 index 00000000..b86216a5 --- /dev/null +++ b/StabilityMatrix.Core/Helper/HardwareInfo/HardwareHelper.cs @@ -0,0 +1,241 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text.RegularExpressions; +using Hardware.Info; +using Microsoft.Win32; + +namespace StabilityMatrix.Core.Helper.HardwareInfo; + +public static partial class HardwareHelper +{ + private static IReadOnlyList? cachedGpuInfos; + + private static readonly Lazy HardwareInfoLazy = + new(() => new Hardware.Info.HardwareInfo()); + + public static IHardwareInfo HardwareInfo => HardwareInfoLazy.Value; + + private static string RunBashCommand(string command) + { + var processInfo = new ProcessStartInfo("bash", "-c \"" + command + "\"") + { + UseShellExecute = false, + RedirectStandardOutput = true + }; + + var process = Process.Start(processInfo); + + process.WaitForExit(); + + var output = process.StandardOutput.ReadToEnd(); + + return output; + } + + [SupportedOSPlatform("windows")] + private static IEnumerable IterGpuInfoWindows() + { + const string gpuRegistryKeyPath = + @"SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}"; + + using var baseKey = Registry.LocalMachine.OpenSubKey(gpuRegistryKeyPath); + + if (baseKey == null) + yield break; + + var gpuIndex = 0; + + foreach (var subKeyName in baseKey.GetSubKeyNames().Where(k => k.StartsWith("0"))) + { + using var subKey = baseKey.OpenSubKey(subKeyName); + if (subKey != null) + { + yield return new GpuInfo + { + Index = gpuIndex++, + Name = subKey.GetValue("DriverDesc")?.ToString(), + MemoryBytes = Convert.ToUInt64( + subKey.GetValue("HardwareInformation.qwMemorySize") + ), + }; + } + } + } + + [SupportedOSPlatform("linux")] + private static IEnumerable IterGpuInfoLinux() + { + var output = RunBashCommand("lspci | grep VGA"); + var gpuLines = output.Split("\n"); + + var gpuIndex = 0; + + foreach (var line in gpuLines) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + + var gpuId = line.Split(' ')[0]; // The GPU ID is the first part of the line + var gpuOutput = RunBashCommand($"lspci -v -s {gpuId}"); + + ulong memoryBytes = 0; + string? name = null; + + // Parse output with regex + var match = Regex.Match(gpuOutput, @"VGA compatible controller: ([^\n]*)"); + if (match.Success) + { + name = match.Groups[1].Value.Trim(); + } + + match = Regex.Match(gpuOutput, @"prefetchable\) \[size=(\\d+)M\]"); + if (match.Success) + { + memoryBytes = ulong.Parse(match.Groups[1].Value) * 1024 * 1024; + } + + yield return new GpuInfo + { + Index = gpuIndex++, + Name = name, + MemoryBytes = memoryBytes + }; + } + } + + /// + /// Yields GpuInfo for each GPU in the system. + /// + public static IEnumerable IterGpuInfo() + { + if (Compat.IsWindows) + { + return IterGpuInfoWindows(); + } + else if (Compat.IsLinux) + { + // Since this requires shell commands, fetch cached value if available. + if (cachedGpuInfos is not null) + { + return cachedGpuInfos; + } + + // No cache, fetch and cache. + cachedGpuInfos = IterGpuInfoLinux().ToList(); + return cachedGpuInfos; + } + // TODO: Implement for macOS + return Enumerable.Empty(); + } + + /// + /// Return true if the system has at least one Nvidia GPU. + /// + public static bool HasNvidiaGpu() + { + return IterGpuInfo().Any(gpu => gpu.IsNvidia); + } + + /// + /// Return true if the system has at least one AMD GPU. + /// + public static bool HasAmdGpu() + { + return IterGpuInfo().Any(gpu => gpu.IsAmd); + } + + // Set ROCm for default if AMD and Linux + public static bool PreferRocm() => !HasNvidiaGpu() && HasAmdGpu() && Compat.IsLinux; + + // Set DirectML for default if AMD and Windows + public static bool PreferDirectML() => !HasNvidiaGpu() && HasAmdGpu() && Compat.IsWindows; + + /// + /// Gets the total and available physical memory in bytes. + /// + public static MemoryInfo GetMemoryInfo() => + Compat.IsWindows ? GetMemoryInfoImplWindows() : GetMemoryInfoImplGeneric(); + + [SupportedOSPlatform("windows")] + private static MemoryInfo GetMemoryInfoImplWindows() + { + var memoryStatus = new Win32MemoryStatusEx(); + + if (!GlobalMemoryStatusEx(ref memoryStatus)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + if (!GetPhysicallyInstalledSystemMemory(out var installedMemoryKb)) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + return new MemoryInfo + { + TotalInstalledBytes = (ulong)installedMemoryKb * 1024, + TotalPhysicalBytes = memoryStatus.UllTotalPhys, + AvailablePhysicalBytes = memoryStatus.UllAvailPhys + }; + } + + private static MemoryInfo GetMemoryInfoImplGeneric() + { + HardwareInfo.RefreshMemoryList(); + + return new MemoryInfo + { + TotalPhysicalBytes = HardwareInfo.MemoryStatus.TotalPhysical, + AvailablePhysicalBytes = HardwareInfo.MemoryStatus.AvailablePhysical + }; + } + + /// + /// Gets cpu info + /// + public static Task GetCpuInfoAsync() => + Compat.IsWindows ? Task.FromResult(GetCpuInfoImplWindows()) : GetCpuInfoImplGenericAsync(); + + [SupportedOSPlatform("windows")] + private static CpuInfo GetCpuInfoImplWindows() + { + var info = new CpuInfo(); + + using var processorKey = Registry.LocalMachine.OpenSubKey( + @"Hardware\Description\System\CentralProcessor\0", + RegistryKeyPermissionCheck.ReadSubTree + ); + + if (processorKey?.GetValue("ProcessorNameString") is string processorName) + { + info = info with { ProcessorCaption = processorName.Trim() }; + } + + return info; + } + + private static Task GetCpuInfoImplGenericAsync() + { + return Task.Run(() => + { + HardwareInfo.RefreshCPUList(); + + return new CpuInfo + { + ProcessorCaption = HardwareInfo.CpuList.FirstOrDefault()?.Caption.Trim() ?? "" + }; + }); + } + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GetPhysicallyInstalledSystemMemory(out long totalMemoryInKilobytes); + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GlobalMemoryStatusEx(ref Win32MemoryStatusEx lpBuffer); +} diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/MemoryInfo.cs b/StabilityMatrix.Core/Helper/HardwareInfo/MemoryInfo.cs new file mode 100644 index 00000000..821f35fd --- /dev/null +++ b/StabilityMatrix.Core/Helper/HardwareInfo/MemoryInfo.cs @@ -0,0 +1,10 @@ +namespace StabilityMatrix.Core.Helper.HardwareInfo; + +public readonly record struct MemoryInfo +{ + public ulong TotalInstalledBytes { get; init; } + + public ulong TotalPhysicalBytes { get; init; } + + public ulong AvailablePhysicalBytes { get; init; } +} diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/MemoryLevel.cs b/StabilityMatrix.Core/Helper/HardwareInfo/MemoryLevel.cs new file mode 100644 index 00000000..4f66acbf --- /dev/null +++ b/StabilityMatrix.Core/Helper/HardwareInfo/MemoryLevel.cs @@ -0,0 +1,9 @@ +namespace StabilityMatrix.Core.Helper.HardwareInfo; + +public enum MemoryLevel +{ + Unknown, + Low, + Medium, + High +} diff --git a/StabilityMatrix.Core/Helper/HardwareInfo/Win32MemoryStatusEx.cs b/StabilityMatrix.Core/Helper/HardwareInfo/Win32MemoryStatusEx.cs new file mode 100644 index 00000000..43e399b9 --- /dev/null +++ b/StabilityMatrix.Core/Helper/HardwareInfo/Win32MemoryStatusEx.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace StabilityMatrix.Core.Helper.HardwareInfo; + +[StructLayout(LayoutKind.Sequential)] +public struct Win32MemoryStatusEx +{ + public uint DwLength = (uint)Marshal.SizeOf(typeof(Win32MemoryStatusEx)); + public uint DwMemoryLoad = 0; + public ulong UllTotalPhys = 0; + public ulong UllAvailPhys = 0; + public ulong UllTotalPageFile = 0; + public ulong UllAvailPageFile = 0; + public ulong UllTotalVirtual = 0; + public ulong UllAvailVirtual = 0; + public ulong UllAvailExtendedVirtual = 0; +} diff --git a/StabilityMatrix.Core/Helper/Size.cs b/StabilityMatrix.Core/Helper/Size.cs index 4f31ba60..e1249b7f 100644 --- a/StabilityMatrix.Core/Helper/Size.cs +++ b/StabilityMatrix.Core/Helper/Size.cs @@ -9,25 +9,60 @@ public static class Size public const ulong MiB = KiB * 1024; public const ulong GiB = MiB * 1024; - public static string FormatBytes(ulong bytes) + private static string TrimZero(string value) + { + return value.TrimEnd('0').TrimEnd('.'); + } + + public static string FormatBytes(ulong bytes, bool trimZero = false) { return bytes switch { - < KiB => $"{bytes} B", - < MiB => $"{bytes / (double)KiB:0.0} KiB", - < GiB => $"{bytes / (double)MiB:0.0} MiB", - _ => $"{bytes / (double)GiB:0.0} GiB" + < KiB => $"{bytes:0} Bytes", + < MiB + => ( + trimZero + ? $"{bytes / (double)KiB:0.0}".TrimEnd('0').TrimEnd('.') + : $"{bytes / (double)KiB:0.0}" + ) + " KiB", + < GiB + => ( + trimZero + ? $"{bytes / (double)MiB:0.0}".TrimEnd('0').TrimEnd('.') + : $"{bytes / (double)MiB:0.0}" + ) + " MiB", + _ + => ( + trimZero + ? $"{bytes / (double)GiB:0.0}".TrimEnd('0').TrimEnd('.') + : $"{bytes / (double)GiB:0.0}" + ) + " GiB" }; } - public static string FormatBase10Bytes(ulong bytes) + public static string FormatBase10Bytes(ulong bytes, bool trimZero = false) { return bytes switch { - < KiB => $"{bytes} Bytes", - < MiB => $"{bytes / (double)KiB:0.0} KB", - < GiB => $"{bytes / (double)MiB:0.0} MB", - _ => $"{bytes / (double)GiB:0.00} GB" + < KiB => $"{bytes:0} Bytes", + < MiB + => ( + trimZero + ? $"{bytes / (double)KiB:0.0}".TrimEnd('0').TrimEnd('.') + : $"{bytes / (double)KiB:0.0}" + ) + " KB", + < GiB + => ( + trimZero + ? $"{bytes / (double)MiB:0.0}".TrimEnd('0').TrimEnd('.') + : $"{bytes / (double)MiB:0.0}" + ) + " MB", + _ + => ( + trimZero + ? $"{bytes / (double)GiB:0.00}".TrimEnd('0').TrimEnd('.') + : $"{bytes / (double)GiB:0.00}" + ) + " GB" }; } diff --git a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs index bad8db64..3d6ce702 100644 --- a/StabilityMatrix.Core/Models/Packages/A3WebUI.cs +++ b/StabilityMatrix.Core/Models/Packages/A3WebUI.cs @@ -5,6 +5,7 @@ using NLog; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -103,8 +104,8 @@ public class A3WebUI : BaseGitPackage .Select(gpu => gpu.MemoryLevel) .Max() switch { - Level.Low => "--lowvram", - Level.Medium => "--medvram", + MemoryLevel.Low => "--lowvram", + MemoryLevel.Medium => "--medvram", _ => null }, Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" } diff --git a/StabilityMatrix.Core/Models/Packages/BasePackage.cs b/StabilityMatrix.Core/Models/Packages/BasePackage.cs index 29ff06b9..ec20e75f 100644 --- a/StabilityMatrix.Core/Models/Packages/BasePackage.cs +++ b/StabilityMatrix.Core/Models/Packages/BasePackage.cs @@ -1,5 +1,6 @@ using Octokit; using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.Database; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index c7a824b7..10999218 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -4,6 +4,7 @@ using NLog; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -92,8 +93,8 @@ public class ComfyUI : BaseGitPackage .Select(gpu => gpu.MemoryLevel) .Max() switch { - Level.Low => "--lowvram", - Level.Medium => "--normalvram", + MemoryLevel.Low => "--lowvram", + MemoryLevel.Medium => "--normalvram", _ => null }, Options = { "--highvram", "--normalvram", "--lowvram", "--novram" } diff --git a/StabilityMatrix.Core/Models/Packages/Fooocus.cs b/StabilityMatrix.Core/Models/Packages/Fooocus.cs index b25e2ed1..d55f4d69 100644 --- a/StabilityMatrix.Core/Models/Packages/Fooocus.cs +++ b/StabilityMatrix.Core/Models/Packages/Fooocus.cs @@ -3,6 +3,7 @@ using System.Text.RegularExpressions; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -84,8 +85,8 @@ public class Fooocus : BaseGitPackage .Select(gpu => gpu.MemoryLevel) .Max() switch { - Level.Low => "--lowvram", - Level.Medium => "--normalvram", + MemoryLevel.Low => "--lowvram", + MemoryLevel.Medium => "--normalvram", _ => null }, Options = { "--highvram", "--normalvram", "--lowvram", "--novram" } diff --git a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs index 99b2a6a3..ca4535fd 100644 --- a/StabilityMatrix.Core/Models/Packages/KohyaSs.cs +++ b/StabilityMatrix.Core/Models/Packages/KohyaSs.cs @@ -4,6 +4,7 @@ using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; diff --git a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs index 9b4a9b45..7ff9f792 100644 --- a/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs +++ b/StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs @@ -5,6 +5,7 @@ using NLog; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -104,8 +105,8 @@ public class StableDiffusionUx : BaseGitPackage .Select(gpu => gpu.MemoryLevel) .Max() switch { - Level.Low => "--lowvram", - Level.Medium => "--medvram", + MemoryLevel.Low => "--lowvram", + MemoryLevel.Medium => "--medvram", _ => null }, Options = new() { "--lowvram", "--medvram", "--medvram-sdxl" } diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index 6a5c6308..7a300516 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -7,6 +7,7 @@ using NLog; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; +using StabilityMatrix.Core.Helper.HardwareInfo; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; @@ -110,8 +111,8 @@ public class VladAutomatic : BaseGitPackage .Select(gpu => gpu.MemoryLevel) .Max() switch { - Level.Low => "--lowvram", - Level.Medium => "--medvram", + MemoryLevel.Low => "--lowvram", + MemoryLevel.Medium => "--medvram", _ => null }, Options = new() { "--lowvram", "--medvram" } diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj index 254f8efe..504924d6 100644 --- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj +++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj @@ -7,6 +7,7 @@ enable true true + true @@ -24,6 +25,7 @@ +