use new tree constructor for other Nvidia nodes

This commit is contained in:
Jussi Kuokkanen 2023-10-04 11:08:54 +03:00
parent 1d3479480b
commit d3dc4a9ee5

View File

@ -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)