diff --git a/GUI/SensorNode.cs b/GUI/SensorNode.cs index 10347ce..8c0251a 100644 --- a/GUI/SensorNode.cs +++ b/GUI/SensorNode.cs @@ -8,11 +8,9 @@ */ +using OpenHardwareMonitor.Hardware; using System; using System.Drawing; -using System.Collections.Generic; -using OpenHardwareMonitor.Hardware; -using OpenHardwareMonitor.Utilities; namespace OpenHardwareMonitor.GUI { public class SensorNode : Node { @@ -20,18 +18,26 @@ namespace OpenHardwareMonitor.GUI { private ISensor sensor; private PersistentSettings settings; private UnitManager unitManager; - private string format; + private string fixedFormat; private bool plot = false; private Color? penColor = null; public string ValueToString(float? value) { if (value.HasValue) { - if (sensor.SensorType == SensorType.Temperature && - unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit) { - return string.Format("{0:F1} °F", value * 1.8 + 32); - } else { - return string.Format(format, value); - } + switch (sensor.SensorType) { + case SensorType.Temperature: + if (unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit) + return string.Format("{0:F1} °F", value * 1.8 + 32); + else + return string.Format("{0:F1} °C", value); + case SensorType.Throughput: + if (value < 1) + return string.Format("{0:F1} KB/s", value * 0x400); + else + return string.Format("{0:F1} MB/s", value); + default: + return string.Format(fixedFormat, value); + } } else return "-"; } @@ -42,18 +48,18 @@ namespace OpenHardwareMonitor.GUI { this.settings = settings; this.unitManager = unitManager; switch (sensor.SensorType) { - case SensorType.Voltage: format = "{0:F3} V"; break; - case SensorType.Clock: format = "{0:F1} MHz"; break; - case SensorType.Load: format = "{0:F1} %"; break; - case SensorType.Temperature: format = "{0:F1} °C"; break; - case SensorType.Fan: format = "{0:F0} RPM"; break; - case SensorType.Flow: format = "{0:F0} L/h"; break; - case SensorType.Control: format = "{0:F1} %"; break; - case SensorType.Level: format = "{0:F1} %"; break; - case SensorType.Power: format = "{0:F1} W"; break; - case SensorType.Data: format = "{0:F1} GB"; break; - case SensorType.SmallData: format = "{0:F1} MB"; break; - case SensorType.Factor: format = "{0:F3}"; break; + case SensorType.Voltage: fixedFormat = "{0:F3} V"; break; + case SensorType.Clock: fixedFormat = "{0:F1} MHz"; break; + case SensorType.Load: fixedFormat = "{0:F1} %"; break; + case SensorType.Fan: fixedFormat = "{0:F0} RPM"; break; + case SensorType.Flow: fixedFormat = "{0:F0} L/h"; break; + case SensorType.Control: fixedFormat = "{0:F1} %"; break; + case SensorType.Level: fixedFormat = "{0:F1} %"; break; + case SensorType.Power: fixedFormat = "{0:F1} W"; break; + case SensorType.Data: fixedFormat = "{0:F1} GB"; break; + case SensorType.SmallData: fixedFormat = "{0:F1} MB"; break; + case SensorType.Factor: fixedFormat = "{0:F3}"; break; + default: fixedFormat = ""; break; } bool hidden = settings.GetValue(new Identifier(sensor.Identifier, diff --git a/GUI/TypeNode.cs b/GUI/TypeNode.cs index b57d58b..420c750 100644 --- a/GUI/TypeNode.cs +++ b/GUI/TypeNode.cs @@ -69,6 +69,10 @@ namespace OpenHardwareMonitor.GUI { this.Image = Utilities.EmbeddedResources.GetImage("factor.png"); this.Text = "Factors"; break; + case SensorType.Throughput: + this.Image = Utilities.EmbeddedResources.GetImage("throughput.png"); + this.Text = "Throughput"; + break; } NodeAdded += new NodeEventHandler(TypeNode_NodeAdded); diff --git a/Hardware/ISensor.cs b/Hardware/ISensor.cs index e157949..e8862a0 100644 --- a/Hardware/ISensor.cs +++ b/Hardware/ISensor.cs @@ -27,6 +27,7 @@ namespace OpenHardwareMonitor.Hardware { Power, // W Data, // GB = 2^30 Bytes SmallData, // MB = 2^20 Bytes + Throughput, // MB/s = 2^20 Bytes/s } public struct SensorValue { diff --git a/Hardware/Nvidia/NVAPI.cs b/Hardware/Nvidia/NVAPI.cs index d81baf7..c0c8d6d 100644 --- a/Hardware/Nvidia/NVAPI.cs +++ b/Hardware/Nvidia/NVAPI.cs @@ -4,7 +4,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - Copyright (C) 2009-2012 Michael Möller + Copyright (C) 2009-2020 Michael Möller Copyright (C) 2011 Christian Vallières */ @@ -307,6 +307,10 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { NvPhysicalGpuHandle gpuHandle, out uint deviceId, out uint subSystemId, out uint revisionId, out uint extDeviceId); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvStatus NvAPI_GPU_GetBusIdDelegate( + NvPhysicalGpuHandle gpuHandle, out uint busId); + private static readonly bool available; private static readonly nvapi_QueryInterfaceDelegate nvapi_QueryInterface; private static readonly NvAPI_InitializeDelegate NvAPI_Initialize; @@ -341,6 +345,8 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { NvAPI_GetDisplayDriverVersion; public static readonly NvAPI_GPU_GetPCIIdentifiersDelegate NvAPI_GPU_GetPCIIdentifiers; + public static readonly NvAPI_GPU_GetBusIdDelegate + NvAPI_GPU_GetBusId; private NVAPI() { } @@ -416,6 +422,7 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { GetDelegate(0xF951A4D1, out NvAPI_GetDisplayDriverVersion); GetDelegate(0x01053FA5, out _NvAPI_GetInterfaceVersionString); GetDelegate(0x2DDFB66E, out NvAPI_GPU_GetPCIIdentifiers); + GetDelegate(0x1BE0B8E5, out NvAPI_GPU_GetBusId); available = true; } diff --git a/Hardware/Nvidia/NVML.cs b/Hardware/Nvidia/NVML.cs new file mode 100644 index 0000000..92b0cb8 --- /dev/null +++ b/Hardware/Nvidia/NVML.cs @@ -0,0 +1,243 @@ +/* + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + Copyright (C) 2020 Michael Möller + +*/ + +using System; +using System.Runtime.InteropServices; + +namespace OpenHardwareMonitor.Hardware.Nvidia { + internal class NVML { + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate NvmlReturn nvmlInitDelegate(); + private static readonly nvmlInitDelegate nvmlInit; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate NvmlReturn nvmlInit_v2Delegate(); + private static readonly nvmlInit_v2Delegate nvmlInit_v2; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvmlReturn nvmlShutdownDelegate(); + public static readonly nvmlShutdownDelegate NvmlShutdown; + + public delegate NvmlReturn nvmlDeviceGetHandleByPciBusIdDelegate( + string pciBusId, out NvmlDevice device); + public static readonly nvmlDeviceGetHandleByPciBusIdDelegate + NvmlDeviceGetHandleByPciBusId; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvmlReturn nvmlDeviceGetPowerUsageDelegate( + NvmlDevice device, out int power); + public static readonly nvmlDeviceGetPowerUsageDelegate + NvmlDeviceGetPowerUsage; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate NvmlReturn nvmlDeviceGetPcieThroughputDelegate( + NvmlDevice devicee, NvmlPcieUtilCounter counter, out uint value); + public static readonly nvmlDeviceGetPcieThroughputDelegate + NvmlDeviceGetPcieThroughput; + + + public static NvmlReturn NvmlInit() { + try { + var result = nvmlInit_v2(); + initialized = result == NvmlReturn.Success; + return result; + } catch { + try { + var result = nvmlInit(); + initialized = result == NvmlReturn.Success; + return result; + } catch { + return NvmlReturn.ErrorLibraryNotFound; + } + } + } + + private static string GetDllName() { + int p = (int)Environment.OSVersion.Platform; + if ((p == 4) || (p == 128)) + return "libnvidia-ml.so"; + else + return "nvml.dll"; + } + + private static T CreateDelegate(string entryPoint) where T : Delegate { + var attribute = new DllImportAttribute(GetDllName()) { + CallingConvention = CallingConvention.Cdecl, + PreserveSig = true, + EntryPoint = entryPoint + }; + PInvokeDelegateFactory.CreateDelegate(attribute, out T newDelegate); + return newDelegate; + } + + static NVML() { + nvmlInit = + CreateDelegate( + "nvmlInit"); + nvmlInit_v2 = + CreateDelegate( + "nvmlInit_v2"); + NvmlShutdown = + CreateDelegate( + "nvmlShutdown"); + NvmlDeviceGetHandleByPciBusId = + CreateDelegate( + "nvmlDeviceGetHandleByPciBusId_v2"); + NvmlDeviceGetPowerUsage = + CreateDelegate( + "nvmlDeviceGetPowerUsage"); + NvmlDeviceGetPcieThroughput = + CreateDelegate( + "nvmlDeviceGetPcieThroughput"); + } + + private static bool initialized; + public static bool IsInitialized { + get { return initialized; } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NvmlDevice { + private readonly IntPtr ptr; + } + + internal enum NvmlPcieUtilCounter { + TxBytes = 0, + RxBytes = 1 + } + + internal enum NvmlReturn { + /// + /// The operation was successful + /// + Success = 0, + + /// + /// NVML was not first initialized with nvmlInit() + /// + ErrorUninitialized = 1, + + /// + /// A supplied argument is invalid + /// + ErrorInvalidArgument = 2, + + /// + /// The requested operation is not available on target device + /// + ErrorNotSupported = 3, + + /// + /// The current user does not have permission for operation + /// + ErrorNoPermission = 4, + + /// + /// Deprecated: Multiple initializations are now allowed through ref + /// counting + /// + ErrorAlreadyInitialized = 5, + + /// + /// A query to find an object was unsuccessful + /// + ErrorNotFound = 6, + + /// + /// An input argument is not large enough + /// + ErrorInsufficientSize = 7, + + /// + /// A device's external power cables are not properly attached + /// + ErrorInsufficientPower = 8, + + /// + /// NVIDIA driver is not loaded + /// + ErrorDriverNotLoaded = 9, + + /// + /// User provided timeout passed + /// + ErrorTimeout = 10, + + /// + /// NVIDIA Kernel detected an interrupt issue with a GPU + /// + ErrorIrqIssue = 11, + + /// + /// NVML Shared Library couldn't be found or loaded + /// + ErrorLibraryNotFound = 12, + + /// + /// Local version of NVML doesn't implement this function + /// + ErrorFunctionNotFound = 13, + + /// + /// infoROM is corruptedinfoROM is corrupted + /// + ErrorCorruptedInforom = 14, + + /// + /// The GPU has fallen off the bus or has otherwise become inaccessible + /// + ErrorGpuIsLost = 15, + + /// + /// The GPU requires a reset before it can be used again + /// + ErrorResetRequired = 16, + + /// + /// The GPU control device has been blocked by the operating + /// system/cgroups + /// + ErrorOperatingSystem = 17, + + /// + /// RM detects a driver/library version mismatch + /// + ErrorLibRmVersionMismatch = 18, + + /// + /// An operation cannot be performed because the GPU is currently in use + /// + ErrorInUse = 19, + + /// + /// Insufficient memory + /// + ErrorMemory = 20, + + /// + /// No data + /// + ErrorNoData = 21, + + /// + /// The requested vgpu operation is not available on target device, + /// becasue ECC is enabled + /// + ErrorvGpuEccNotSupported = 22, + + /// + /// An internal driver error occurred + /// + ErrorUnknown = 999 + }; + + } +} diff --git a/Hardware/Nvidia/NvidiaGPU.cs b/Hardware/Nvidia/NvidiaGPU.cs index 556f4f0..0dfa542 100644 --- a/Hardware/Nvidia/NvidiaGPU.cs +++ b/Hardware/Nvidia/NvidiaGPU.cs @@ -4,7 +4,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - Copyright (C) 2009-2015 Michael Möller + Copyright (C) 2009-2020 Michael Möller Copyright (C) 2011 Christian Vallières */ @@ -19,6 +19,7 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { private readonly int adapterIndex; private readonly NvPhysicalGpuHandle handle; private readonly NvDisplayHandle? displayHandle; + private readonly NVML.NvmlDevice? device; private readonly Sensor[] temperatures; private readonly Sensor fan; @@ -29,12 +30,16 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { private readonly Sensor memoryUsed; private readonly Sensor memoryFree; private readonly Sensor memoryAvail; + private readonly Sensor power; + private readonly Sensor pcieThroughputRx; + private readonly Sensor pcieThroughputTx; private readonly Control fanControl; public NvidiaGPU(int adapterIndex, NvPhysicalGpuHandle handle, NvDisplayHandle? displayHandle, ISettings settings) : base(GetName(handle), new Identifier("nvidiagpu", - adapterIndex.ToString(CultureInfo.InvariantCulture)), settings) { + adapterIndex.ToString(CultureInfo.InvariantCulture)), settings) + { this.adapterIndex = adapterIndex; this.handle = handle; this.displayHandle = displayHandle; @@ -57,13 +62,11 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { ActivateSensor(temperatures[i]); } - int value; if (NVAPI.NvAPI_GPU_GetTachReading != null && - NVAPI.NvAPI_GPU_GetTachReading(handle, out value) == NvStatus.OK) { - if (value >= 0) { - fan = new Sensor("GPU", 0, SensorType.Fan, this, settings); - ActivateSensor(fan); - } + NVAPI.NvAPI_GPU_GetTachReading(handle, out _) == NvStatus.OK) + { + fan = new Sensor("GPU", 0, SensorType.Fan, this, settings); + ActivateSensor(fan); } clocks = new Sensor[3]; @@ -93,6 +96,24 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { ControlModeChanged(fanControl); control.Control = fanControl; } + + if (NVML.IsInitialized) { + if (NVAPI.NvAPI_GPU_GetBusId != null && + NVAPI.NvAPI_GPU_GetBusId(handle, out uint busId) == NvStatus.OK) { + if (NVML.NvmlDeviceGetHandleByPciBusId( + "0000:" + busId.ToString("X2") + ":00.0", out var result) + == NVML.NvmlReturn.Success) + { + device = result; + power = new Sensor("GPU Power", 0, SensorType.Power, this, settings); + pcieThroughputRx = new Sensor("GPU PCIE Rx", 0, + SensorType.Throughput, this, settings); + pcieThroughputTx = new Sensor("GPU PCIE Tx", 1, + SensorType.Throughput, this, settings); + } + } + } + Update(); } @@ -152,10 +173,10 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { foreach (Sensor sensor in temperatures) sensor.Value = settings.Sensor[sensor.Index].CurrentTemp; - if (fan != null) { - int value = 0; - NVAPI.NvAPI_GPU_GetTachReading(handle, out value); - fan.Value = value; + if (fan != null && NVAPI.NvAPI_GPU_GetTachReading(handle, out int fanValue) + == NvStatus.OK) + { + fan.Value = fanValue; } uint[] values = GetClocks(); @@ -219,6 +240,34 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { ActivateSensor(memoryFree); ActivateSensor(memoryLoad); } + + if (power != null) { + if (NVML.NvmlDeviceGetPowerUsage(device.Value, out int powerValue) + == NVML.NvmlReturn.Success) + { + power.Value = powerValue * 0.001f; + ActivateSensor(power); + } + } + + if (pcieThroughputRx != null) { + if (NVML.NvmlDeviceGetPcieThroughput(device.Value, + NVML.NvmlPcieUtilCounter.RxBytes, out uint value) + == NVML.NvmlReturn.Success) + { + pcieThroughputRx.Value = value * (1.0f / 0x400); + ActivateSensor(pcieThroughputRx); + } + } + + if (pcieThroughputTx != null) { + if (NVML.NvmlDeviceGetPcieThroughput(device.Value, + NVML.NvmlPcieUtilCounter.TxBytes, out uint value) + == NVML.NvmlReturn.Success) { + pcieThroughputTx.Value = value * (1.0f / 0x400); + ActivateSensor(pcieThroughputTx); + } + } } public override string GetReport() { diff --git a/Hardware/Nvidia/NvidiaGroup.cs b/Hardware/Nvidia/NvidiaGroup.cs index dc2847f..632212b 100644 --- a/Hardware/Nvidia/NvidiaGroup.cs +++ b/Hardware/Nvidia/NvidiaGroup.cs @@ -4,7 +4,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - Copyright (C) 2009-2011 Michael Möller + Copyright (C) 2009-2020 Michael Möller */ @@ -28,7 +28,7 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { string version; if (NVAPI.NvAPI_GetInterfaceVersionString(out version) == NvStatus.OK) { - report.Append("Version: "); + report.Append(" Version: "); report.AppendLine(version); } @@ -36,18 +36,26 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { new NvPhysicalGpuHandle[NVAPI.MAX_PHYSICAL_GPUS]; int count; if (NVAPI.NvAPI_EnumPhysicalGPUs == null) { - report.AppendLine("Error: NvAPI_EnumPhysicalGPUs not available"); + report.AppendLine(" Error: NvAPI_EnumPhysicalGPUs not available"); report.AppendLine(); return; } else { NvStatus status = NVAPI.NvAPI_EnumPhysicalGPUs(handles, out count); if (status != NvStatus.OK) { - report.AppendLine("Status: " + status); + report.AppendLine(" Status: " + status); report.AppendLine(); return; } } + var result = NVML.NvmlInit(); + + report.AppendLine(); + report.AppendLine("NVML"); + report.AppendLine(); + report.AppendLine(" Status: " + result); + report.AppendLine(); + IDictionary displayHandles = new Dictionary(); @@ -100,7 +108,11 @@ namespace OpenHardwareMonitor.Hardware.Nvidia { public void Close() { foreach (Hardware gpu in hardware) - gpu.Close(); + gpu.Close(); + + if (NVML.IsInitialized) { + NVML.NvmlShutdown(); + } } } } diff --git a/OpenHardwareMonitor.csproj b/OpenHardwareMonitor.csproj index 03561a1..7d2462d 100644 --- a/OpenHardwareMonitor.csproj +++ b/OpenHardwareMonitor.csproj @@ -295,6 +295,9 @@ + + + diff --git a/OpenHardwareMonitorLib.csproj b/OpenHardwareMonitorLib.csproj index fd6bf2f..b1c4818 100644 --- a/OpenHardwareMonitorLib.csproj +++ b/OpenHardwareMonitorLib.csproj @@ -94,6 +94,7 @@ + diff --git a/Resources/throughput.png b/Resources/throughput.png new file mode 100644 index 0000000..ca8b5f0 Binary files /dev/null and b/Resources/throughput.png differ