mirror of
https://github.com/Lurkki14/tuxclocker.git
synced 2025-02-25 18:55:24 -06:00
use new tree constructor for other Nvidia nodes
This commit is contained in:
parent
1d3479480b
commit
d3dc4a9ee5
@ -23,6 +23,7 @@ using namespace mpark::patterns;
|
||||
// This data is used to construct the tree
|
||||
struct NvidiaGPUData {
|
||||
nvmlDevice_t devHandle;
|
||||
Display *dpy;
|
||||
uint index;
|
||||
std::string uuid;
|
||||
std::optional<uint> maxPerfState;
|
||||
@ -45,19 +46,19 @@ std::optional<AssignmentError> fromNVMLRet(nvmlReturn_t ret) {
|
||||
return AssignmentError::UnknownError;
|
||||
}
|
||||
|
||||
std::optional<uint> nvmlMaxPerfState(nvmlDevice_t dev) {
|
||||
nvmlPstates_t pstates[NVML_MAX_GPU_PERF_PSTATES];
|
||||
if (nvmlDeviceGetSupportedPerformanceStates(dev, pstates, NVML_MAX_GPU_PERF_PSTATES)
|
||||
!= NVML_SUCCESS)
|
||||
return std::nullopt;
|
||||
|
||||
uint max = 0;
|
||||
for (int i = 0; i < NVML_MAX_GPU_PERF_PSTATES; i++) {
|
||||
int state = pstates[i];
|
||||
if (state > max && state != NVML_PSTATE_UNKNOWN)
|
||||
max = state;
|
||||
uint nvctrlPerfModes(Display *dpy, uint index) {
|
||||
// TODO: NVML has a function to get these but is borked
|
||||
// Thanks to Artifth for original code
|
||||
char *result;
|
||||
if (XNVCTRLQueryTargetStringAttribute(dpy, NV_CTRL_TARGET_TYPE_GPU, index, 0,
|
||||
NV_CTRL_STRING_PERFORMANCE_MODES, &result)) {
|
||||
auto s = std::string(result);
|
||||
auto modes = std::count(s.begin(), s.end(), ';');
|
||||
delete result;
|
||||
return modes;
|
||||
}
|
||||
return max;
|
||||
// Usually there's 3 perf modes
|
||||
return 3;
|
||||
}
|
||||
|
||||
uint nvmlFanCount(nvmlDevice_t dev) {
|
||||
@ -71,7 +72,7 @@ bool hasReadableValue(ReadResult res) {
|
||||
return std::holds_alternative<ReadableValue>(res);
|
||||
}
|
||||
|
||||
std::optional<NvidiaGPUData> fromIndex(uint i) {
|
||||
std::optional<NvidiaGPUData> fromIndex(Display *dpy, uint i) {
|
||||
nvmlDevice_t dev;
|
||||
if (nvmlDeviceGetHandleByIndex_v2(i, &dev) != NVML_SUCCESS) {
|
||||
std::cout << "nvidia: couldn't get nvml handle for index " << i << "\n";
|
||||
@ -85,7 +86,7 @@ std::optional<NvidiaGPUData> fromIndex(uint i) {
|
||||
.devHandle = dev,
|
||||
.index = i,
|
||||
.uuid = uuid,
|
||||
.maxPerfState = nvmlMaxPerfState(dev),
|
||||
.maxPerfState = nvctrlPerfModes(dpy, i),
|
||||
.fanCount = nvmlFanCount(dev),
|
||||
};
|
||||
}
|
||||
@ -109,13 +110,107 @@ std::vector<TreeNode<DeviceNode>> getGPUName(NvidiaGPUData data) {
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getMemClockWrite(NvidiaGPUData data) {
|
||||
if (!data.maxPerfState.has_value())
|
||||
return {};
|
||||
|
||||
auto maxPerfState = data.maxPerfState.value();
|
||||
// TODO: getting current offset and available range doesn't work properly through NVML
|
||||
// but setting does
|
||||
NVCTRLAttributeValidValuesRec values;
|
||||
if (!XNVCTRLQueryValidTargetAttributeValues(data.dpy, NV_CTRL_TARGET_TYPE_GPU, data.index,
|
||||
maxPerfState, NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET, &values))
|
||||
return {};
|
||||
|
||||
// Transfer rate -> clock speed
|
||||
Range<int> range{values.u.range.min / 2, values.u.range.max / 2};
|
||||
|
||||
|
||||
|
||||
auto getFunc = [=]() -> std::optional<AssignmentArgument> {
|
||||
int value;
|
||||
if (!XNVCTRLQueryTargetAttribute(data.dpy, NV_CTRL_TARGET_TYPE_GPU,
|
||||
data.index, maxPerfState, NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET, &value))
|
||||
return std::nullopt;
|
||||
return value / 2;
|
||||
};
|
||||
|
||||
auto setFunc = [=](AssignmentArgument a) -> std::optional<AssignmentError> {
|
||||
if (!std::holds_alternative<int>(a))
|
||||
return AssignmentError::InvalidType;
|
||||
auto target = std::get<int>(a);
|
||||
if (target < range.min || target > range.max)
|
||||
return AssignmentError::OutOfRange;
|
||||
|
||||
// Don't need to convert since the function deals with clocks already
|
||||
auto ret = nvmlDeviceSetMemClkVfOffset(data.devHandle, target);
|
||||
return fromNVMLRet(ret);
|
||||
};
|
||||
|
||||
Assignable a{
|
||||
setFunc,
|
||||
range,
|
||||
getFunc,
|
||||
"MHz"
|
||||
};
|
||||
|
||||
if (getFunc().has_value())
|
||||
return {DeviceNode{
|
||||
.name = "Memory Clock Offset",
|
||||
.interface = a,
|
||||
.hash = md5(data.uuid + "Memory Clock Offset"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getCoreClockWrite(NvidiaGPUData data) {
|
||||
if (!data.maxPerfState.has_value())
|
||||
return {};
|
||||
|
||||
auto maxPerfState = data.maxPerfState.value();
|
||||
// TODO: NVML has functions for core clock as well but they're borked as of writing
|
||||
NVCTRLAttributeValidValuesRec values;
|
||||
if (!XNVCTRLQueryValidTargetAttributeValues(data.dpy, NV_CTRL_TARGET_TYPE_GPU, data.index,
|
||||
maxPerfState, NV_CTRL_GPU_NVCLOCK_OFFSET, &values)) {
|
||||
std::cout << "b" << maxPerfState << "\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Transfer rate -> clock speed
|
||||
Range<int> range{values.u.range.min, values.u.range.max};
|
||||
|
||||
auto getFunc = [=]() -> std::optional<AssignmentArgument> {
|
||||
int value;
|
||||
if (!XNVCTRLQueryTargetAttribute(data.dpy, NV_CTRL_TARGET_TYPE_GPU,
|
||||
data.index, maxPerfState, NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET, &value))
|
||||
return std::nullopt;
|
||||
return value;
|
||||
};
|
||||
|
||||
auto setFunc = [=](AssignmentArgument a) -> std::optional<AssignmentError> {
|
||||
if (!std::holds_alternative<int>(a))
|
||||
return AssignmentError::InvalidType;
|
||||
auto target = std::get<int>(a);
|
||||
if (target < range.min || target > range.max)
|
||||
return AssignmentError::OutOfRange;
|
||||
|
||||
if (!XNVCTRLSetTargetAttributeAndGetStatus(data.dpy, NV_CTRL_TARGET_TYPE_GPU,
|
||||
data.index, maxPerfState, NV_CTRL_GPU_NVCLOCK_OFFSET, target))
|
||||
return AssignmentError::UnknownError;
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
Assignable a{
|
||||
setFunc,
|
||||
range,
|
||||
getFunc,
|
||||
"MHz"
|
||||
};
|
||||
|
||||
return {DeviceNode{
|
||||
.name = "Core Clock Offset",
|
||||
.interface = a,
|
||||
.hash = md5(data.uuid + "Core Clock Offset"),
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getCoreClockRead(NvidiaGPUData data) {
|
||||
auto func = [data]() -> ReadResult{
|
||||
@ -127,6 +222,50 @@ std::vector<TreeNode<DeviceNode>> getCoreClockRead(NvidiaGPUData data) {
|
||||
return fromNVMLError(ret);
|
||||
};
|
||||
|
||||
DynamicReadable dr{func, "MHz"};
|
||||
|
||||
if (hasReadableValue(func()))
|
||||
return {DeviceNode{
|
||||
.name = "Core Clock",
|
||||
.interface = dr,
|
||||
.hash = md5(data.uuid + "Core Clock"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getMemClockRead(NvidiaGPUData data) {
|
||||
auto func = [data]() -> ReadResult{
|
||||
uint clock;
|
||||
nvmlReturn_t ret;
|
||||
ret = nvmlDeviceGetClockInfo(data.devHandle, NVML_CLOCK_MEM, &clock);
|
||||
if (ret == NVML_SUCCESS)
|
||||
return clock;
|
||||
return fromNVMLError(ret);
|
||||
};
|
||||
|
||||
DynamicReadable dr{func, "MHz"};
|
||||
|
||||
if (hasReadableValue(func()))
|
||||
return {DeviceNode{
|
||||
.name = "Memory Clock",
|
||||
.interface = dr,
|
||||
.hash = md5(data.uuid + "Memory Clock"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getClocksRoot(NvidiaGPUData data) {
|
||||
// TODO: this gets added unconditionally
|
||||
// If leaf nodes without an interface become a problem, we can remove them
|
||||
// centrally in the daemon.
|
||||
// Another option would be to check from here if any children would get added,
|
||||
// but that's spaghetti and inefficient.
|
||||
return {DeviceNode{
|
||||
.name = "Clocks",
|
||||
.interface = std::nullopt,
|
||||
.hash = md5(data.uuid + "Clocks"),
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getFanSpeedRead(NvidiaGPUData data) {
|
||||
static uint fanId = 0;
|
||||
@ -230,6 +369,217 @@ std::vector<TreeNode<DeviceNode>> getFanMode(NvidiaGPUData data) {
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getCoreUtilization(NvidiaGPUData data) {
|
||||
auto func = [data]() -> ReadResult{
|
||||
nvmlUtilization_t value;
|
||||
auto ret = nvmlDeviceGetUtilizationRates(data.devHandle, &value);
|
||||
if (ret != NVML_SUCCESS)
|
||||
return fromNVMLError(ret);
|
||||
return value.gpu;
|
||||
};
|
||||
|
||||
DynamicReadable dr{func, "%"};
|
||||
|
||||
if (hasReadableValue(func()))
|
||||
return {DeviceNode{
|
||||
.name = "Core Utilization",
|
||||
.interface = dr,
|
||||
.hash = md5(data.uuid + "Core Utilization"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getMemoryUtilization(NvidiaGPUData data) {
|
||||
auto func = [data]() -> ReadResult{
|
||||
nvmlUtilization_t value;
|
||||
auto ret = nvmlDeviceGetUtilizationRates(data.devHandle, &value);
|
||||
if (ret != NVML_SUCCESS)
|
||||
return fromNVMLError(ret);
|
||||
return value.memory;
|
||||
};
|
||||
|
||||
DynamicReadable dr{func, "%"};
|
||||
|
||||
if (hasReadableValue(func()))
|
||||
return {DeviceNode{
|
||||
.name = "Memory Utilization",
|
||||
.interface = dr,
|
||||
.hash = md5(data.uuid + "Memory Utilization"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getPowerUsage(NvidiaGPUData data) {
|
||||
auto func = [data]() -> ReadResult{
|
||||
uint value;
|
||||
auto ret = nvmlDeviceGetPowerUsage(data.devHandle, &value);
|
||||
if (ret != NVML_SUCCESS)
|
||||
return fromNVMLError(ret);
|
||||
return static_cast<double>(value) / 1000;
|
||||
};
|
||||
|
||||
DynamicReadable dr{func, "W"};
|
||||
|
||||
if (hasReadableValue(func()))
|
||||
return {DeviceNode{
|
||||
.name = "Power Usage",
|
||||
.interface = dr,
|
||||
.hash = md5(data.uuid + "Power Usage"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getPowerLimit(NvidiaGPUData data) {
|
||||
uint min, max;
|
||||
if (nvmlDeviceGetPowerManagementLimitConstraints(data.devHandle, &min, &max)
|
||||
!= NVML_SUCCESS)
|
||||
return {};
|
||||
|
||||
// mW -> W
|
||||
Range<double> range{static_cast<double>(min) / 1000, static_cast<double>(max) / 1000};
|
||||
|
||||
auto getFunc = [data]() -> std::optional<AssignmentArgument> {
|
||||
uint limit;
|
||||
auto ret = nvmlDeviceGetPowerManagementLimit(data.devHandle, &limit);
|
||||
if (ret != NVML_SUCCESS)
|
||||
return std::nullopt;
|
||||
return static_cast<double>(limit) / 1000;
|
||||
};
|
||||
|
||||
auto setFunc = [=](AssignmentArgument a) -> std::optional<AssignmentError> {
|
||||
if (!std::holds_alternative<double>(a))
|
||||
return AssignmentError::InvalidType;
|
||||
|
||||
auto target = std::get<double>(a);
|
||||
if (target < range.min || target > range.max)
|
||||
return AssignmentError::OutOfRange;
|
||||
|
||||
// W -> mW
|
||||
auto ret = nvmlDeviceSetPowerManagementLimit(data.devHandle, round(target * 1000));
|
||||
return fromNVMLRet(ret);
|
||||
};
|
||||
|
||||
Assignable a{setFunc, range, getFunc, "W"};
|
||||
|
||||
return {DeviceNode{
|
||||
.name = "Power Limit",
|
||||
.interface = a,
|
||||
.hash = md5(data.uuid + "Power Limit"),
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getTemperature(NvidiaGPUData data) {
|
||||
auto func = [data]() -> ReadResult {
|
||||
uint temp;
|
||||
auto ret = nvmlDeviceGetTemperature(data.devHandle, NVML_TEMPERATURE_GPU, &temp);
|
||||
if (ret != NVML_SUCCESS)
|
||||
return fromNVMLError(ret);
|
||||
return temp;
|
||||
};
|
||||
|
||||
DynamicReadable dr{func, "°C"};
|
||||
|
||||
if (hasReadableValue(func()))
|
||||
return {DeviceNode{
|
||||
.name = "Temperature",
|
||||
.interface = dr,
|
||||
.hash = md5(data.uuid + "Temperature"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getSlowdownTemperature(NvidiaGPUData data) {
|
||||
uint temp;
|
||||
if (nvmlDeviceGetTemperatureThreshold(data.devHandle,
|
||||
NVML_TEMPERATURE_THRESHOLD_SLOWDOWN, &temp) != NVML_SUCCESS)
|
||||
return {};
|
||||
|
||||
StaticReadable sr{temp, "°C"};
|
||||
|
||||
return {DeviceNode{
|
||||
.name = "Slowdown Temperature",
|
||||
.interface = sr,
|
||||
.hash = md5(data.uuid + "Slowdown Temperature"),
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getShutdownTemperature(NvidiaGPUData data) {
|
||||
uint temp;
|
||||
if (nvmlDeviceGetTemperatureThreshold(data.devHandle,
|
||||
NVML_TEMPERATURE_THRESHOLD_SHUTDOWN, &temp) != NVML_SUCCESS)
|
||||
return {};
|
||||
|
||||
StaticReadable sr{temp, "°C"};
|
||||
|
||||
return {DeviceNode{
|
||||
.name = "Shutdown Temperature",
|
||||
.interface = sr,
|
||||
.hash = md5(data.uuid + "Shutdown Temperature"),
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getVoltage(NvidiaGPUData data) {
|
||||
auto func = [data]() -> ReadResult {
|
||||
int value;
|
||||
if (!XNVCTRLQueryTargetAttribute(data.dpy, NV_CTRL_TARGET_TYPE_GPU, data.index,
|
||||
0, NV_CTRL_GPU_CURRENT_CORE_VOLTAGE, &value))
|
||||
return ReadError::UnknownError;
|
||||
return static_cast<double>(value) / 1000;
|
||||
};
|
||||
|
||||
DynamicReadable dr{func, "mV"};
|
||||
|
||||
if (hasReadableValue(func()))
|
||||
return {DeviceNode{
|
||||
.name = "Core Voltage",
|
||||
.interface = dr,
|
||||
.hash = md5(data.uuid + "Core Voltage"),
|
||||
}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getVoltageOffset(NvidiaGPUData data) {
|
||||
NVCTRLAttributeValidValuesRec values;
|
||||
if (!XNVCTRLQueryValidTargetAttributeValues(data.dpy, NV_CTRL_TARGET_TYPE_GPU,
|
||||
data.index, 0, NV_CTRL_GPU_OVER_VOLTAGE_OFFSET, &values))
|
||||
return {};
|
||||
|
||||
// uV -> mV
|
||||
Range<int> range{values.u.range.min / 1000, values.u.range.max / 1000};
|
||||
|
||||
auto getFunc = [data]() -> std::optional<AssignmentArgument> {
|
||||
int value;
|
||||
if (!XNVCTRLQueryTargetAttribute(data.dpy, NV_CTRL_TARGET_TYPE_GPU,
|
||||
data.index, 0, NV_CTRL_GPU_OVER_VOLTAGE_OFFSET, &value))
|
||||
return std::nullopt;
|
||||
// uV -> mV
|
||||
return value / 1000;
|
||||
};
|
||||
|
||||
auto setFunc = [=](AssignmentArgument a) -> std::optional<AssignmentError> {
|
||||
if (!std::holds_alternative<int>(a))
|
||||
return AssignmentError::InvalidType;
|
||||
|
||||
auto target = std::get<int>(a);
|
||||
if (target < range.min || target > range.max)
|
||||
return AssignmentError::OutOfRange;
|
||||
|
||||
// mV -> uV
|
||||
if (!XNVCTRLSetTargetAttributeAndGetStatus(data.dpy, NV_CTRL_TARGET_TYPE_GPU,
|
||||
data.index, 0, NV_CTRL_GPU_OVER_VOLTAGE_OFFSET, target * 1000))
|
||||
return AssignmentError::UnknownError;
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
Assignable a{setFunc, range, getFunc, "mV"};
|
||||
|
||||
return {DeviceNode{
|
||||
.name = "Core Voltage Offset",
|
||||
.interface = a,
|
||||
.hash = md5(data.uuid + "Core Voltage Offset"),
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getMultiFanRoots(NvidiaGPUData data) {
|
||||
if (data.fanCount < 2)
|
||||
return {};
|
||||
@ -268,6 +618,13 @@ std::vector<TreeNode<DeviceNode>> getSingleFanMode(NvidiaGPUData data) {
|
||||
return getFanMode(data);
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getTemperaturesRoot(NvidiaGPUData data) {
|
||||
return {DeviceNode{
|
||||
.name = "Temperatures",
|
||||
.interface = std::nullopt,
|
||||
.hash = md5(data.uuid + "Temperatures"),
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getFanRoot(NvidiaGPUData data) {
|
||||
return {DeviceNode{
|
||||
@ -277,8 +634,31 @@ std::vector<TreeNode<DeviceNode>> getFanRoot(NvidiaGPUData data) {
|
||||
}};
|
||||
}
|
||||
|
||||
std::vector<TreeNode<DeviceNode>> getUtilizationsRoot(NvidiaGPUData data) {
|
||||
return {DeviceNode{
|
||||
.name = "Utilizations",
|
||||
.interface = std::nullopt,
|
||||
.hash = md5(data.uuid + "Utilizations"),
|
||||
}};
|
||||
}
|
||||
|
||||
auto gpuTree = TreeConstructor<NvidiaGPUData, DeviceNode>{
|
||||
getGPUName, {
|
||||
{getUtilizationsRoot, {
|
||||
{getCoreUtilization, {}},
|
||||
{getMemoryUtilization, {}},
|
||||
}},
|
||||
{getTemperaturesRoot, {
|
||||
{getTemperature, {}},
|
||||
{getSlowdownTemperature, {}},
|
||||
{getShutdownTemperature, {}}
|
||||
}},
|
||||
{getClocksRoot, {
|
||||
{getCoreClockRead, {}},
|
||||
{getCoreClockWrite, {}},
|
||||
{getMemClockRead, {}},
|
||||
{getMemClockWrite, {}}
|
||||
}},
|
||||
{getFanRoot, {
|
||||
{getMultiFanRoots, {
|
||||
{getFanSpeedWrite, {}},
|
||||
@ -288,7 +668,11 @@ auto gpuTree = TreeConstructor<NvidiaGPUData, DeviceNode>{
|
||||
{getSingleFanSpeedRead, {}},
|
||||
{getSingleFanSpeedWrite, {}},
|
||||
{getSingleFanMode, {}}
|
||||
}}
|
||||
}},
|
||||
{getPowerUsage, {}},
|
||||
{getPowerLimit, {}},
|
||||
{getVoltage, {}},
|
||||
{getVoltageOffset, {}}
|
||||
}
|
||||
};
|
||||
|
||||
@ -328,9 +712,11 @@ TreeNode<DeviceNode> NvidiaPlugin::deviceRootNode() {
|
||||
}
|
||||
std::vector<NvidiaGPUData> gpuData;
|
||||
for (uint i = 0; i < gpuCount; i++) {
|
||||
auto data = fromIndex(i);
|
||||
if (data.has_value())
|
||||
auto data = fromIndex(m_dpy, i);
|
||||
if (data.has_value()) {
|
||||
data->dpy = m_dpy;
|
||||
gpuData.push_back(data.value());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &datum : gpuData)
|
||||
|
Loading…
Reference in New Issue
Block a user