From 98969ebc3e5fa9d896f0942a68fb2b8b27cba1ac Mon Sep 17 00:00:00 2001 From: Campbell Date: Sun, 27 Jan 2019 22:34:04 +1300 Subject: [PATCH] Added basic Nvidia NVML library support and power usage sensor (#60) * Added basic Nvidia NVML library support and power usage sensor * Added basic Nvidia NVML library support and power usage sensor * Added Initialised check * Made NvmlDevice internal * Code formatting * Move NvmlDevice and NvmlReturn to separate files * Move code from NvidiaNVMLGPU class to NvidiaGPU class * Replace call to SetDllDirectory with LoadLibrary/FreeLibrary and code formatting changes * Made native methods private * Fixed p/invoke definitions, removed unnecessary try/catch blocks. Code style cleanup --- Hardware/Nvidia/NVML.cs | 171 +++++++++++++++++++++++++++++++++ Hardware/Nvidia/NvidiaGPU.cs | 23 ++++- Hardware/Nvidia/NvidiaGroup.cs | 8 +- Hardware/Nvidia/NvmlDevice.cs | 9 ++ Hardware/Nvidia/NvmlReturn.cs | 84 ++++++++++++++++ OpenHardwareMonitorLib.csproj | 5 +- 6 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 Hardware/Nvidia/NVML.cs create mode 100644 Hardware/Nvidia/NvmlDevice.cs create mode 100644 Hardware/Nvidia/NvmlReturn.cs diff --git a/Hardware/Nvidia/NVML.cs b/Hardware/Nvidia/NVML.cs new file mode 100644 index 0000000..0514daa --- /dev/null +++ b/Hardware/Nvidia/NVML.cs @@ -0,0 +1,171 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace OpenHardwareMonitor.Hardware.Nvidia { + internal class NVML { + private const string LinuxDllName = "nvidia-ml"; + + private delegate NvmlReturn WindowsNvmlDelegate(); + private WindowsNvmlDelegate WindowsNvmlInit; + private WindowsNvmlDelegate WindowsNvmlShutdown; + + private delegate NvmlReturn WindowsNvmlGetHandleDelegate(int index, out NvmlDevice device); + private WindowsNvmlGetHandleDelegate WindowsNvmlDeviceGetHandleByIndex; + + private delegate NvmlReturn WindowsNvmlGetPowerUsageDelegate(NvmlDevice device, out int power); + private WindowsNvmlGetPowerUsageDelegate WindowsNvmlDeviceGetPowerUsage; + + private readonly IntPtr windowsDll; + + internal bool Initialised { get; } + + internal NVML() { + if (Software.OperatingSystem.IsLinux) { + try { + Initialised = (LinuxNvmlInit() == NvmlReturn.Success); + } + catch (DllNotFoundException) { + return; + } + catch (EntryPointNotFoundException) { + try { + Initialised = (LinuxNvmlInitLegacy() == NvmlReturn.Success); + } + catch (EntryPointNotFoundException) { + return; + } + } + } + else if (IsNvmlCompatibleWindowsVersion()) { + var programFilesDirectory = Environment.ExpandEnvironmentVariables("%ProgramW6432%"); + var dllPath = Path.Combine(programFilesDirectory, @"NVIDIA Corporation\NVSMI\nvml.dll"); + + windowsDll = LoadLibrary(dllPath); + + if (windowsDll == IntPtr.Zero) + return; + + Initialised = InitialiseDelegates() && (WindowsNvmlInit() == NvmlReturn.Success); + } + } + + private static bool IsNvmlCompatibleWindowsVersion() + { + return Software.OperatingSystem.Is64Bit && + ((Environment.OSVersion.Version.Major > 6) || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 1)); + } + + private bool InitialiseDelegates() + { + var nvmlInit = GetProcAddress(windowsDll, "nvmlInit_v2"); + if (nvmlInit != IntPtr.Zero) + WindowsNvmlInit = (WindowsNvmlDelegate)Marshal.GetDelegateForFunctionPointer(nvmlInit, typeof(WindowsNvmlDelegate)); + else { + nvmlInit = GetProcAddress(windowsDll, "nvmlInit"); + if (nvmlInit != IntPtr.Zero) + WindowsNvmlInit = (WindowsNvmlDelegate)Marshal.GetDelegateForFunctionPointer(nvmlInit, typeof(WindowsNvmlDelegate)); + else + return false; + } + + var nvmlShutdown = GetProcAddress(windowsDll, "nvmlShutdown"); + if (nvmlShutdown != IntPtr.Zero) + WindowsNvmlShutdown = (WindowsNvmlDelegate)Marshal.GetDelegateForFunctionPointer(nvmlShutdown, typeof(WindowsNvmlDelegate)); + else + return false; + + var nvmlGetHandle = GetProcAddress(windowsDll, "nvmlDeviceGetHandleByIndex_v2"); + if (nvmlGetHandle != IntPtr.Zero) + WindowsNvmlDeviceGetHandleByIndex = (WindowsNvmlGetHandleDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetHandle, typeof(WindowsNvmlGetHandleDelegate)); + else { + nvmlGetHandle = GetProcAddress(windowsDll, "nvmlDeviceGetHandleByIndex"); + if (nvmlGetHandle != IntPtr.Zero) + WindowsNvmlDeviceGetHandleByIndex = (WindowsNvmlGetHandleDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetHandle, typeof(WindowsNvmlGetHandleDelegate)); + else + return false; + } + + var nvmlGetPowerUsage = GetProcAddress(windowsDll, "nvmlDeviceGetPowerUsage"); + if (nvmlGetPowerUsage != IntPtr.Zero) + WindowsNvmlDeviceGetPowerUsage = (WindowsNvmlGetPowerUsageDelegate)Marshal.GetDelegateForFunctionPointer(nvmlGetPowerUsage, typeof(WindowsNvmlGetPowerUsageDelegate)); + else + return false; + + return true; + } + + internal void Close() { + if (Initialised) { + if (Software.OperatingSystem.IsLinux) + LinuxNvmlShutdown(); + else if (windowsDll != IntPtr.Zero) { + WindowsNvmlShutdown(); + FreeLibrary(windowsDll); + } + } + } + + internal NvmlDevice? NvmlDeviceGetHandleByIndex(int index) { + if (Initialised) { + NvmlDevice nvmlDevice; + if (Software.OperatingSystem.IsLinux) { + try { + if (LinuxNvmlDeviceGetHandleByIndex(index, out nvmlDevice) == NvmlReturn.Success) + return nvmlDevice; + } + catch (EntryPointNotFoundException) { + if (LinuxNvmlDeviceGetHandleByIndexLegacy(index, out nvmlDevice) == NvmlReturn.Success) + return nvmlDevice; + } + } + else if (WindowsNvmlDeviceGetHandleByIndex(index, out nvmlDevice) == NvmlReturn.Success) + return nvmlDevice; + } + + return null; + } + + internal int? NvmlDeviceGetPowerUsage(NvmlDevice nvmlDevice) { + if (Initialised) { + int powerUsage; + if (Software.OperatingSystem.IsLinux) { + if (LinuxNvmlDeviceGetPowerUsage(nvmlDevice, out powerUsage) == NvmlReturn.Success) + return powerUsage; + } + else if (WindowsNvmlDeviceGetPowerUsage(nvmlDevice, out powerUsage) == NvmlReturn.Success) + return powerUsage; + } + + return null; + } + + [DllImport("kernel32")] + private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string dllPath); + + [DllImport("kernel32", ExactSpelling = true)] + private static extern IntPtr GetProcAddress(IntPtr module, string methodName); + + [DllImport("kernel32")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool FreeLibrary(IntPtr module); + + [DllImport(LinuxDllName, EntryPoint = "nvmlInit_v2", ExactSpelling = true)] + private static extern NvmlReturn LinuxNvmlInit(); + + [DllImport(LinuxDllName, EntryPoint = "nvmlInit", ExactSpelling = true)] + private static extern NvmlReturn LinuxNvmlInitLegacy(); + + [DllImport(LinuxDllName, EntryPoint = "nvmlShutdown", ExactSpelling = true)] + private static extern NvmlReturn LinuxNvmlShutdown(); + + [DllImport(LinuxDllName, EntryPoint = "nvmlDeviceGetHandleByIndex_v2", ExactSpelling = true)] + private static extern NvmlReturn LinuxNvmlDeviceGetHandleByIndex(int index, out NvmlDevice device); + + [DllImport(LinuxDllName, EntryPoint = "nvmlDeviceGetHandleByIndex", ExactSpelling = true)] + private static extern NvmlReturn LinuxNvmlDeviceGetHandleByIndexLegacy(int index, out NvmlDevice device); + + [DllImport(LinuxDllName, EntryPoint = "nvmlDeviceGetPowerUsage", ExactSpelling = true)] + private static extern NvmlReturn LinuxNvmlDeviceGetPowerUsage(NvmlDevice device, out int power); + } +} diff --git a/Hardware/Nvidia/NvidiaGPU.cs b/Hardware/Nvidia/NvidiaGPU.cs index 556f4f0..b7f5fe3 100644 --- a/Hardware/Nvidia/NvidiaGPU.cs +++ b/Hardware/Nvidia/NvidiaGPU.cs @@ -19,6 +19,8 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { private readonly int adapterIndex; private readonly NvPhysicalGpuHandle handle; private readonly NvDisplayHandle? displayHandle; + private readonly NVML nvml; + private readonly NvmlDevice? nvmlDevice; private readonly Sensor[] temperatures; private readonly Sensor fan; @@ -29,15 +31,17 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { private readonly Sensor memoryUsed; private readonly Sensor memoryFree; private readonly Sensor memoryAvail; + private readonly Sensor powerUsage; private readonly Control fanControl; public NvidiaGPU(int adapterIndex, NvPhysicalGpuHandle handle, - NvDisplayHandle? displayHandle, ISettings settings) + NvDisplayHandle? displayHandle, ISettings settings, NVML nvml) : base(GetName(handle), new Identifier("nvidiagpu", adapterIndex.ToString(CultureInfo.InvariantCulture)), settings) { this.adapterIndex = adapterIndex; this.handle = handle; this.displayHandle = displayHandle; + this.nvml = nvml; NvGPUThermalSettings thermalSettings = GetThermalSettings(); temperatures = new Sensor[thermalSettings.Count]; @@ -93,6 +97,15 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { ControlModeChanged(fanControl); control.Control = fanControl; } + + if (nvml.Initialised) + { + nvmlDevice = nvml.NvmlDeviceGetHandleByIndex(adapterIndex); + if (nvmlDevice.HasValue) { + powerUsage = new Sensor("GPU Package", 0, SensorType.Power, this, settings); + } + } + Update(); } @@ -219,6 +232,14 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { ActivateSensor(memoryFree); ActivateSensor(memoryLoad); } + + if (nvml.Initialised && nvmlDevice.HasValue) { + var result = nvml.NvmlDeviceGetPowerUsage(nvmlDevice.Value); + if (result.HasValue) { + powerUsage.Value = (float)result.Value / 1000; + ActivateSensor(powerUsage); + } + } } public override string GetReport() { diff --git a/Hardware/Nvidia/NvidiaGroup.cs b/Hardware/Nvidia/NvidiaGroup.cs index 001e462..a14ac4f 100644 --- a/Hardware/Nvidia/NvidiaGroup.cs +++ b/Hardware/Nvidia/NvidiaGroup.cs @@ -15,9 +15,9 @@ using System.Text; namespace OpenHardwareMonitor.Hardware.Nvidia { internal class NvidiaGroup : IGroup { - private readonly List hardware = new List(); private readonly StringBuilder report = new StringBuilder(); + private readonly NVML nvml = new NVML(); public NvidiaGroup(ISettings settings) { if (!NVAPI.IsAvailable) @@ -82,7 +82,7 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { for (int i = 0; i < count; i++) { NvDisplayHandle displayHandle; displayHandles.TryGetValue(handles[i], out displayHandle); - hardware.Add(new NvidiaGPU(i, handles[i], displayHandle, settings)); + hardware.Add(new NvidiaGPU(i, handles[i], displayHandle, settings, nvml)); } report.AppendLine(); @@ -96,7 +96,9 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { public void Close() { foreach (Hardware gpu in hardware) - gpu.Close(); + gpu.Close(); + + nvml.Close(); } } } diff --git a/Hardware/Nvidia/NvmlDevice.cs b/Hardware/Nvidia/NvmlDevice.cs new file mode 100644 index 0000000..85369da --- /dev/null +++ b/Hardware/Nvidia/NvmlDevice.cs @@ -0,0 +1,9 @@ +using System; +using System.Runtime.InteropServices; + +namespace OpenHardwareMonitor.Hardware.Nvidia { + [StructLayout(LayoutKind.Sequential)] + internal struct NvmlDevice { + public IntPtr Handle; + } +} diff --git a/Hardware/Nvidia/NvmlReturn.cs b/Hardware/Nvidia/NvmlReturn.cs new file mode 100644 index 0000000..bcd19c3 --- /dev/null +++ b/Hardware/Nvidia/NvmlReturn.cs @@ -0,0 +1,84 @@ +namespace OpenHardwareMonitor.Hardware.Nvidia { + internal enum NvmlReturn { + /// + /// The operation was successful + /// + Success = 0, + /// + /// NVML was not first initialized with nvmlInit() + /// + Uninitialized = 1, + /// + /// A supplied argument is invalid + /// + InvalidArgument = 2, + /// + /// The requested operation is not available on target device + /// + NotSupported = 3, + /// + /// The current user does not have permission for operation + /// + NoPermission = 4, + /// + /// A query to find an object was unsuccessful + /// + NotFound = 6, + /// + /// An input argument is not large enough + /// + InsufficientSize = 7, + /// + /// A device's external power cables are not properly attached + /// + InsufficientPower = 8, + /// + /// NVIDIA driver is not loaded + /// + DriverNotLoaded = 9, + /// + /// User provided timeout passed + /// + TimeOut = 10, + /// + /// NVIDIA Kernel detected an interrupt issue with a GPU + /// + IRQIssue = 11, + /// + /// NVML Shared Library couldn't be found or loaded + /// + LibraryNotFound = 12, + /// + /// Local version of NVML doesn't implement this function + /// + FunctionNotFound = 13, + /// + /// infoROM is corrupted + /// + CorruptedInfoROM = 14, + /// + /// The GPU has fallen off the bus or has otherwise become inaccessible + /// + GPUIsLost = 15, + /// + /// The GPU requires a reset before it can be used again + /// + ResetRequired = 16, + /// + /// The GPU control device has been blocked by the operating system/cgroups + /// + OperatingSystem = 17, + /// + /// RM detects a driver/library version mismatch + /// + LibRMVersionMismatch = 18, + /// + /// An operation cannot be performed because the GPU is currently in use + /// + InUse = 19, + /// + /// An internal driver error occurred + /// + Unknown = 999 + } +} diff --git a/OpenHardwareMonitorLib.csproj b/OpenHardwareMonitorLib.csproj index a42eb8a..441f30b 100644 --- a/OpenHardwareMonitorLib.csproj +++ b/OpenHardwareMonitorLib.csproj @@ -65,7 +65,10 @@ - + + + +