Skip to content

Commit

Permalink
✨ NV GPUs: Read "thermal settings" from the official API.
Browse files Browse the repository at this point in the history
  • Loading branch information
hexawyz committed Apr 18, 2024
1 parent 6dec35d commit 8df426d
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 170 deletions.
4 changes: 2 additions & 2 deletions Exo.Devices.Corsair.PowerSupplies/CorsairLinkDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ public override async ValueTask DisposeAsync()
void ISensorsGroupedQueryFeature.AddSensor(IPolledSensor sensor)
{
if (sensor is not Sensor s || s.Driver != this) throw new ArgumentException();
if (s.GroupedQueryMode != GroupedQueryMode.Enabled)
if (!s.IsGroupQueryEnabled)
{
s.IsGroupQueryEnabled = true;
_groupQueriedSensorCount++;
Expand All @@ -305,7 +305,7 @@ void ISensorsGroupedQueryFeature.AddSensor(IPolledSensor sensor)
void ISensorsGroupedQueryFeature.RemoveSensor(IPolledSensor sensor)
{
if (sensor is not Sensor s || s.Driver != this) throw new ArgumentException();
if (s.GroupedQueryMode == GroupedQueryMode.Enabled)
if (s.IsGroupQueryEnabled)
{
s.IsGroupQueryEnabled = false;
_groupQueriedSensorCount--;
Expand Down
59 changes: 59 additions & 0 deletions Exo.Devices.NVidia/NVidiaGpuDriver.ThermalTargetSensor.cs
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;
}
}
52 changes: 52 additions & 0 deletions Exo.Devices.NVidia/NVidiaGpuDriver.UtilizationSensor.cs
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 Exo.Devices.NVidia/NVidiaGpuDriver.UtilizationWatcher.cs
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);
}
}
}
}
Loading

0 comments on commit 8df426d

Please sign in to comment.