diff --git a/meson.build b/meson.build index b7f25bb0..b831ee2d 100644 --- a/meson.build +++ b/meson.build @@ -54,12 +54,18 @@ app_dependencies = [ meson.get_compiler('c').find_library('pci'), meson.get_compiler('vala').find_library('pci', dirs: vapidir), - # Allow building without nvidia libraries because they're not packaged or does not run - # on some distros due to incompatibility with muslc. Also they're not needed on ARM architecture - meson.get_compiler('c').find_library('XNVCtrl', required: get_option('nvidia').enabled()), - meson.get_compiler('vala').find_library('libxnvctrl', dirs: vapidir, required: get_option('nvidia').enabled()), ] +if get_option('nvidia').enabled() + if not meson.get_compiler('c').has_header('nvml.h') + error('NVIDIA support requested, but nvml.h was not found') + endif + + app_dependencies += [ + meson.get_compiler('c').find_library('nvidia-ml', required: true), + ] +endif + config_data = configuration_data() config_data.set('GETTEXT_PACKAGE', meson.project_name()) diff --git a/src/Resources/GPU/GPUNvidia.vala b/src/Resources/GPU/GPUNvidia.vala index cd62e8b8..5533efee 100644 --- a/src/Resources/GPU/GPUNvidia.vala +++ b/src/Resources/GPU/GPUNvidia.vala @@ -3,163 +3,157 @@ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) */ -public class Monitor.GPUNvidia : IGPU, Object { - - public Gee.HashMap hwmon_temperatures { get; set; } +[Compact] +[CCode (cname = "MonitorNvmlGpu", free_function = "monitor_nvml_gpu_free")] +private class MonitorNvmlGpu { +} - public string hwmon_module_name { get; set; } +[CCode (cname = "MonitorNvmlSample")] +private struct MonitorNvmlSample { + public uint gpu_percent; + public uint64 mem_used; + public uint64 mem_total; + public uint temperature_c; + public bool ok_util; + public bool ok_mem; + public bool ok_temp; +} - public string driver_name { get; set; } +[CCode (cname = "monitor_nvml_gpu_new_from_pci_bus_id")] +private extern MonitorNvmlGpu? monitor_nvml_gpu_new_from_pci_bus_id (string pci_bus_id); - public string name { get; set; } +[CCode (cname = "monitor_nvml_gpu_sample")] +private extern bool monitor_nvml_gpu_sample (MonitorNvmlGpu gpu, out MonitorNvmlSample sample); +public class Monitor.GPUNvidia : IGPU, Object { + public Gee.HashMap hwmon_temperatures { get; set; } + public string hwmon_module_name { get; protected set; } + public string driver_name { get; protected set; } + public string name { get; protected set; } public int percentage { get; protected set; } - public int memory_percentage { get; protected set; } - public int fb_percentage { get; protected set; } - public double memory_vram_used { get; protected set; } - - public double memory_vram_total { get; set; } - + public double memory_vram_total { get; protected set; } public double temperature { get; protected set; } + protected string sysfs_path { get; protected set; } - protected string sysfs_path { get; set; } - - public int nvidia_temperature = 0; - - public int nvidia_memory_vram_used = 0; - - public int nvidia_memory_vram_total = 0; - - public int nvidia_memory_percentage = 0; - - public int nvidia_fb_percentage = 0; - - public int nvidia_percentage = 0; - - public char * nvidia_used = ""; - - public bool nvidia_resources_temperature; - - public bool nvidia_resources_vram_used; - - public bool nvidia_resources_vram_total; - - public bool nvidia_resources_used; - - public X.Display nvidia_display; + private MonitorNvmlGpu? nvml_gpu; + private MonitorNvmlSample sample_cache; public GPUNvidia (Pci.Access pci_access, Pci.Dev pci_device) { - name = pci_parse_name (pci_access, pci_device); - name = "nVidia® " + name; - + name = "NVIDIA " + pci_parse_name (pci_access, pci_device); sysfs_path = pci_parse_sysfs_path (pci_access, pci_device); driver_name = pci_device.get_string_property (Pci.FILL_DRIVER); - } + hwmon_module_name = driver_name; + hwmon_temperatures = new Gee.HashMap (); - construct { - nvidia_display = new X.Display (); - } - - private void update_nv_resources () { #if NVIDIA_SUPPORT - nvidia_resources_temperature = NVCtrl.XNVCTRLQueryAttribute ( - nvidia_display, - 0, - 0, - NV_CTRL_GPU_CORE_TEMPERATURE, - &nvidia_temperature - ); - - if (!nvidia_resources_temperature) { - debug ("Could not query NV_CTRL_GPU_CORE_TEMPERATURE attribute!\n"); - return; + string pci_bus_id = "%04x:%02x:%02x.%u".printf ( + pci_device.domain_16, + pci_device.bus, + pci_device.dev, + pci_device.func + ); + + nvml_gpu = monitor_nvml_gpu_new_from_pci_bus_id (pci_bus_id); + if (nvml_gpu == null) { + warning ("Failed to initialize NVML handle for %s (%s)", name, pci_bus_id); } +#endif + } - nvidia_resources_vram_used = NVCtrl.XNVCTRLQueryTargetAttribute ( - nvidia_display, - NV_CTRL_TARGET_TYPE_GPU, - 0, - 0, - NV_CTRL_USED_DEDICATED_GPU_MEMORY, - &nvidia_memory_vram_used - ); - - if (!nvidia_resources_vram_used) { - debug ("Could not query NV_CTRL_USED_DEDICATED_GPU_MEMORY attribute!\n"); - return; + private bool update_nv_resources () { +#if NVIDIA_SUPPORT + if (nvml_gpu == null) { + return false; } - nvidia_resources_vram_total = NVCtrl.XNVCTRLQueryTargetAttribute ( - nvidia_display, - NV_CTRL_TARGET_TYPE_GPU, - 0, - 0, - NV_CTRL_TOTAL_DEDICATED_GPU_MEMORY, - &nvidia_memory_vram_total - ); - - if (!nvidia_resources_vram_total) { - debug ("Could not query NV_CTRL_TOTAL_DEDICATED_GPU_MEMORY attribute!\n"); - return; + if (!monitor_nvml_gpu_sample (nvml_gpu, out sample_cache)) { + return false; } - nvidia_resources_used = NVCtrl.XNVCTRLQueryTargetStringAttribute ( - nvidia_display, - NV_CTRL_TARGET_TYPE_GPU, - 0, - 0, - NV_CTRL_STRING_GPU_UTILIZATION, - &nvidia_used - ); - - // var str_used = (string)nvidia_used; - nvidia_percentage = int.parse (((string) nvidia_used).split_set ("=,")[1]); - nvidia_fb_percentage = int.parse (((string) nvidia_used).split_set ("=,")[3]); - debug ("USED_GRAPHICS: %d%\n", nvidia_percentage); - debug ("USED_FB_MEMORY: %d%\n", nvidia_fb_percentage); + return true; +#else + return false; #endif - - if (!nvidia_resources_used) { - debug ("Could not query NV_CTRL_STRING_GPU_UTILIZATION attribute!\n"); - return; - } - - } - - private void update_temperature () { - temperature = nvidia_temperature; } - private void update_memory_vram_used () { - memory_vram_used = (double) nvidia_memory_vram_used * 1000000.0; + public void update_temperature () { +#if NVIDIA_SUPPORT + if (sample_cache.ok_temp) { + temperature = (double) sample_cache.temperature_c; + } else { + temperature = 0; + } +#else + temperature = 0; +#endif } - private void update_memory_vram_total () { - memory_vram_total = (double) nvidia_memory_vram_total * 1000000.0; + public void update_memory_vram_used () { +#if NVIDIA_SUPPORT + if (sample_cache.ok_mem) { + memory_vram_used = (double) sample_cache.mem_used; + } else { + memory_vram_used = 0; + } +#else + memory_vram_used = 0; +#endif } - private void update_memory_percentage () { - memory_percentage = (int) (Math.round ((memory_vram_used / memory_vram_total) * 100)); + public void update_memory_vram_total () { +#if NVIDIA_SUPPORT + if (sample_cache.ok_mem) { + memory_vram_total = (double) sample_cache.mem_total; + } else { + memory_vram_total = 0; + } +#else + memory_vram_total = 0; +#endif } - private void update_fb_percentage () { - fb_percentage = nvidia_fb_percentage; + public void update_memory_percentage () { + if (memory_vram_total > 0) { + memory_percentage = (int) Math.round ((memory_vram_used / memory_vram_total) * 100.0); + fb_percentage = memory_percentage; + } else { + memory_percentage = 0; + fb_percentage = 0; + } } - private void update_percentage () { - percentage = nvidia_percentage; + public void update_percentage () { +#if NVIDIA_SUPPORT + if (sample_cache.ok_util) { + percentage = (int) sample_cache.gpu_percent; + } else { + percentage = 0; + } +#else + percentage = 0; +#endif } public void update () { - update_nv_resources (); + bool ok = update_nv_resources (); + + if (!ok) { + percentage = 0; + memory_percentage = 0; + fb_percentage = 0; + memory_vram_used = 0; + memory_vram_total = 0; + temperature = 0; + return; + } + update_temperature (); update_memory_vram_used (); update_memory_vram_total (); update_memory_percentage (); update_percentage (); } - -} +} \ No newline at end of file diff --git a/src/Resources/GPU/nvml-bridge.c b/src/Resources/GPU/nvml-bridge.c new file mode 100644 index 00000000..57eaf89d --- /dev/null +++ b/src/Resources/GPU/nvml-bridge.c @@ -0,0 +1,95 @@ +#include "nvml-bridge.h" + +#include +#include +#include +#include +#include +#include + +struct MonitorNvmlGpu { + nvmlDevice_t handle; +}; + +static bool monitor_nvml_initialized = false; + +static bool +monitor_nvml_init_once(void) { + if (monitor_nvml_initialized) { + return true; + } + + nvmlReturn_t ret = nvmlInit_v2(); + if (ret != NVML_SUCCESS) { + fprintf(stderr, "NVML init failed: %s\n", nvmlErrorString(ret)); + return false; + } + + monitor_nvml_initialized = true; + return true; +} + +MonitorNvmlGpu* +monitor_nvml_gpu_new_from_pci_bus_id(const char* pci_bus_id) { + if (pci_bus_id == NULL || pci_bus_id[0] == '\0') { + return NULL; + } + + if (!monitor_nvml_init_once()) { + return NULL; + } + + MonitorNvmlGpu* gpu = calloc(1, sizeof(*gpu)); + if (gpu == NULL) { + return NULL; + } + + nvmlReturn_t ret = nvmlDeviceGetHandleByPciBusId_v2(pci_bus_id, &gpu->handle); + if (ret != NVML_SUCCESS) { + fprintf(stderr, "NVML PCI lookup failed for %s: %s\n", pci_bus_id, nvmlErrorString(ret)); + free(gpu); + return NULL; + } + + return gpu; +} + +void +monitor_nvml_gpu_destroy(MonitorNvmlGpu* gpu) { + free(gpu); +} + +bool +monitor_nvml_gpu_sample(MonitorNvmlGpu* gpu, MonitorNvmlSample* out) { + bool any_success = false; + + if (gpu == NULL || out == NULL) { + return false; + } + + memset(out, 0, sizeof(*out)); + + nvmlUtilization_t util = {0}; + if (nvmlDeviceGetUtilizationRates(gpu->handle, &util) == NVML_SUCCESS) { + out->gpu_percent = util.gpu; + out->ok_util = true; + any_success = true; + } + + nvmlMemory_t memory = {0}; + if (nvmlDeviceGetMemoryInfo(gpu->handle, &memory) == NVML_SUCCESS) { + out->mem_used = memory.used; + out->mem_total = memory.total; + out->ok_mem = true; + any_success = true; + } + + unsigned int temp = 0; + if (nvmlDeviceGetTemperature(gpu->handle, NVML_TEMPERATURE_GPU, &temp) == NVML_SUCCESS) { + out->temperature_c = temp; + out->ok_temp = true; + any_success = true; + } + + return any_success; +} diff --git a/src/Resources/GPU/nvml-bridge.h b/src/Resources/GPU/nvml-bridge.h new file mode 100644 index 00000000..08845b4d --- /dev/null +++ b/src/Resources/GPU/nvml-bridge.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +typedef struct MonitorNvmlGpu MonitorNvmlGpu; + +typedef struct { + unsigned int gpu_percent; + uint64_t mem_used; + uint64_t mem_total; + unsigned int temperature_c; + bool ok_util; + bool ok_mem; + bool ok_temp; +} MonitorNvmlSample; + +MonitorNvmlGpu* monitor_nvml_gpu_new_from_pci_bus_id(const char* pci_bus_id); +void monitor_nvml_gpu_destroy(MonitorNvmlGpu* gpu); +bool monitor_nvml_gpu_sample(MonitorNvmlGpu* gpu, MonitorNvmlSample* out); diff --git a/src/Views/SystemView/SystemView.vala b/src/Views/SystemView/SystemView.vala index 43906b3c..0e002a89 100644 --- a/src/Views/SystemView/SystemView.vala +++ b/src/Views/SystemView/SystemView.vala @@ -40,7 +40,7 @@ public class Monitor.SystemView : Gtk.Box { wrapper.append (storage_view); foreach (IGPU gpu in resources.gpu_list) { - if (gpu is GPUIntel || gpu is GPUNvidia) { + if (gpu is GPUIntel) { wrapper.append (build_no_support_label (gpu.name)); } else { var gpu_view = new SystemGPUView (gpu); diff --git a/src/meson.build b/src/meson.build index b6464f8d..46881707 100644 --- a/src/meson.build +++ b/src/meson.build @@ -67,7 +67,7 @@ source_app_files = [ 'Resources/GPU/IGPU.vala', 'Resources/GPU/GPUAmd.vala', 'Resources/GPU/GPUNvidia.vala', - 'Resources/GPU/GPUIntel.vala', + 'Resources/GPU/GPUIntel.vala', 'Resources/GPU/nvml-bridge.c', 'Resources/Hwmon/HwmonPathsParser.vala', 'Resources/Hwmon/IHwmonPathsParserInterface.vala',