-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ NV GPUs: Read "thermal settings" from the official API.
- Loading branch information
Showing
8 changed files
with
369 additions
and
170 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
using Exo.Sensors; | ||
|
||
namespace Exo.Devices.NVidia; | ||
|
||
public partial class NVidiaGpuDriver | ||
{ | ||
private sealed class ThermalTargetSensor : GroupQueriedSensor, IPolledSensor<short> | ||
{ | ||
private static Guid GetGuidForThermalTarget(NvApi.Gpu.ThermalTarget thermalTarget) | ||
=> thermalTarget switch | ||
{ | ||
NvApi.Gpu.ThermalTarget.Gpu => GpuThermalSensorId, | ||
NvApi.Gpu.ThermalTarget.Memory => MemoryThermalSensorId, | ||
NvApi.Gpu.ThermalTarget.PowerSupply => PowerSupplyThermalSensorId, | ||
NvApi.Gpu.ThermalTarget.Board => BoardThermalSensorId, | ||
_ => throw new InvalidOperationException("Unsupported thermal target.") | ||
}; | ||
|
||
private short _currentValue; | ||
private readonly short _minValue; | ||
private readonly short _maxValue; | ||
private readonly sbyte _thermalTarget; | ||
private readonly byte _sensorIndex; | ||
|
||
public Guid SensorId => GetGuidForThermalTarget((NvApi.Gpu.ThermalTarget)_thermalTarget); | ||
|
||
public ThermalTargetSensor(NvApi.PhysicalGpu gpu, NvApi.Gpu.ThermalSensor thermalSensor, byte sensorIndex) | ||
: base(gpu) | ||
{ | ||
_currentValue = (short)thermalSensor.CurrentTemp; | ||
_minValue = (short)thermalSensor.DefaultMinTemp; | ||
_maxValue = (short)thermalSensor.DefaultMaxTemp; | ||
_thermalTarget = (sbyte)thermalSensor.Target; | ||
_sensorIndex = sensorIndex; | ||
} | ||
|
||
public short? ScaleMinimumValue => _minValue; | ||
public short? ScaleMaximumValue => _maxValue; | ||
|
||
public SensorUnit Unit => SensorUnit.Celsius; | ||
|
||
public ValueTask<short> GetValueAsync(CancellationToken cancellationToken) | ||
=> ValueTask.FromResult(GroupedQueryMode == GroupedQueryMode.Enabled ? _currentValue : QueryValue()); | ||
|
||
public bool TryGetLastValue(out short lastValue) | ||
{ | ||
lastValue = _currentValue; | ||
return true; | ||
} | ||
|
||
private short QueryValue() | ||
{ | ||
var result = Gpu.GetThermalSettings(1); | ||
return (short)result.CurrentTemp; | ||
} | ||
|
||
public void OnValueRead(short value) => _currentValue = value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using System.Threading.Channels; | ||
using Exo.Sensors; | ||
|
||
namespace Exo.Devices.NVidia; | ||
|
||
public partial class NVidiaGpuDriver | ||
{ | ||
private sealed class UtilizationSensor : IStreamedSensor<float> | ||
{ | ||
private ChannelWriter<SensorDataPoint<float>>? _listener; | ||
private readonly UtilizationWatcher _watcher; | ||
|
||
public Guid SensorId { get; } | ||
|
||
public UtilizationSensor(UtilizationWatcher watcher, Guid sensorId) | ||
{ | ||
_watcher = watcher; | ||
SensorId = sensorId; | ||
} | ||
|
||
public float? ScaleMinimumValue => 0; | ||
public float? ScaleMaximumValue => 100; | ||
public SensorUnit Unit => SensorUnit.Percent; | ||
|
||
public async IAsyncEnumerable<SensorDataPoint<float>> EnumerateValuesAsync(CancellationToken cancellationToken) | ||
{ | ||
var channel = Channel.CreateUnbounded<SensorDataPoint<float>>(SharedOptions.ChannelOptions); | ||
if (Interlocked.CompareExchange(ref _listener, channel, null) is not null) throw new InvalidOperationException("An enumeration is already running."); | ||
try | ||
{ | ||
_watcher.Acquire(); | ||
try | ||
{ | ||
await foreach (var dataPoint in channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) | ||
{ | ||
yield return dataPoint; | ||
} | ||
} | ||
finally | ||
{ | ||
_watcher.Release(); | ||
} | ||
} | ||
finally | ||
{ | ||
Volatile.Write(ref _listener, null); | ||
} | ||
} | ||
|
||
public void OnDataReceived(DateTime dateTime, uint value) => Volatile.Read(ref _listener)?.TryWrite(new SensorDataPoint<float>(dateTime, value * 0.01f)); | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
Exo.Devices.NVidia/NVidiaGpuDriver.UtilizationWatcher.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
namespace Exo.Devices.NVidia; | ||
|
||
public partial class NVidiaGpuDriver | ||
{ | ||
private sealed class UtilizationWatcher : IAsyncDisposable | ||
{ | ||
private readonly NvApi.PhysicalGpu _gpu; | ||
private readonly UtilizationSensor _graphicsSensor; | ||
private readonly UtilizationSensor _frameBufferSensor; | ||
private readonly UtilizationSensor _videoSensor; | ||
private int _referenceCount; | ||
private readonly object _lock; | ||
private TaskCompletionSource _enableSignal; | ||
private CancellationTokenSource? _disableCancellationTokenSource; | ||
private CancellationTokenSource? _disposeCancellationTokenSource; | ||
private readonly Task _runTask; | ||
|
||
public UtilizationSensor GraphicsSensor => _graphicsSensor; | ||
public UtilizationSensor FrameBufferSensor => _frameBufferSensor; | ||
public UtilizationSensor VideoSensor => _videoSensor; | ||
|
||
public UtilizationWatcher(NvApi.PhysicalGpu gpu) | ||
{ | ||
_gpu = gpu; | ||
_graphicsSensor = new(this, GraphicsUtilizationSensorId); | ||
_frameBufferSensor = new(this, FrameBufferUtilizationSensorId); | ||
_videoSensor = new(this, VideoUtilizationSensorId); | ||
_lock = new(); | ||
_enableSignal = new(); | ||
_disposeCancellationTokenSource = new(); | ||
_runTask = RunAsync(_disposeCancellationTokenSource.Token); | ||
} | ||
|
||
public async ValueTask DisposeAsync() | ||
{ | ||
if (Interlocked.Exchange(ref _disposeCancellationTokenSource, null) is { } cts) | ||
{ | ||
cts.Cancel(); | ||
Volatile.Read(ref _enableSignal).TrySetResult(); | ||
await _runTask.ConfigureAwait(false); | ||
cts.Dispose(); | ||
} | ||
} | ||
|
||
public void Acquire() | ||
{ | ||
lock (_lock) | ||
{ | ||
if (_referenceCount++ == 0) | ||
{ | ||
_enableSignal.TrySetResult(); | ||
} | ||
} | ||
} | ||
|
||
// This function is called by a sensor state to cancel grouped querying for it. | ||
// NB: The sensor state *WILL* ensure that this method is never called twice in succession for a given sensor. | ||
public void Release() | ||
{ | ||
lock (_lock) | ||
{ | ||
if (--_referenceCount == 0) | ||
{ | ||
if (Interlocked.Exchange(ref _disableCancellationTokenSource, null) is { } cts) | ||
{ | ||
cts.Cancel(); | ||
cts.Dispose(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private async Task RunAsync(CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
while (true) | ||
{ | ||
await _enableSignal.Task.ConfigureAwait(false); | ||
if (cancellationToken.IsCancellationRequested) return; | ||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | ||
var queryCancellationToken = cts.Token; | ||
Volatile.Write(ref _disableCancellationTokenSource, cts); | ||
try | ||
{ | ||
await WatchValuesAsync(queryCancellationToken).ConfigureAwait(false); | ||
} | ||
catch (OperationCanceledException) when (queryCancellationToken.IsCancellationRequested) | ||
{ | ||
} | ||
if (cancellationToken.IsCancellationRequested) return; | ||
cts = Interlocked.Exchange(ref _disableCancellationTokenSource, null); | ||
cts?.Dispose(); | ||
Volatile.Write(ref _enableSignal, new()); | ||
} | ||
} | ||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) | ||
{ | ||
} | ||
catch (Exception ex) | ||
{ | ||
// TODO: Log | ||
} | ||
} | ||
|
||
private async ValueTask WatchValuesAsync(CancellationToken cancellationToken) | ||
{ | ||
await foreach (var utilizationValue in _gpu.WatchUtilizationAsync(500, cancellationToken).ConfigureAwait(false)) | ||
{ | ||
var sensor = utilizationValue.Domain switch | ||
{ | ||
NvApi.Gpu.Client.UtilizationDomain.Graphics => _graphicsSensor, | ||
NvApi.Gpu.Client.UtilizationDomain.FrameBuffer => _frameBufferSensor, | ||
NvApi.Gpu.Client.UtilizationDomain.Video => _videoSensor, | ||
_ => null, | ||
}; | ||
sensor?.OnDataReceived(utilizationValue.DateTime, utilizationValue.PerTenThousandValue); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.