Merge branch 'develop' into issue993

This commit is contained in:
nicolargo 2024-04-07 10:21:34 +02:00
commit 71e8fe5bfd
4 changed files with 206 additions and 167 deletions

View File

@ -3,34 +3,24 @@
# This file is part of Glances. # This file is part of Glances.
# #
# Copyright (C) 2020 Kirby Banman <kirby.banman@gmail.com> # Copyright (C) 2020 Kirby Banman <kirby.banman@gmail.com>
# Copyright (C) 2024 Nicolas Hennion <nicolashennion@gmail.com>
# #
# SPDX-License-Identifier: LGPL-3.0-only # SPDX-License-Identifier: LGPL-3.0-only
# #
"""GPU plugin (limited to NVIDIA chipsets).""" """GPU plugin for Glances.
Currently supported:
- NVIDIA GPU (need pynvml lib)
- AMD GPU (no lib needed)
"""
from glances.globals import nativestr, to_fahrenheit
from glances.logger import logger from glances.logger import logger
from glances.globals import to_fahrenheit
from glances.plugins.gpu.cards.nvidia import NvidiaGPU
from glances.plugins.gpu.cards.amd import AmdGPU
from glances.plugins.plugin.model import GlancesPluginModel from glances.plugins.plugin.model import GlancesPluginModel
try:
import pynvml
except Exception as e:
import_error_tag = True
# Display debug message if import KeyError
logger.warning("Missing Python Lib ({}), Nvidia GPU plugin is disabled".format(e))
else:
import_error_tag = False
# {
# "key": "gpu_id",
# "gpu_id": 0,
# "name": "Fake GeForce GTX",
# "mem": 5.792331695556641,
# "proc": 4,
# "temperature": 26,
# "fan_speed": 30
# }
# Fields description # Fields description
# description: human readable description # description: human readable description
# short_name: shortname to use un UI # short_name: shortname to use un UI
@ -71,7 +61,7 @@ items_history_list = [
class PluginModel(GlancesPluginModel): class PluginModel(GlancesPluginModel):
"""Glances GPU plugin (limited to NVIDIA chipsets). """Glances GPU plugin.
stats is a list of dictionaries with one entry per GPU stats is a list of dictionaries with one entry per GPU
""" """
@ -84,27 +74,20 @@ class PluginModel(GlancesPluginModel):
stats_init_value=[], stats_init_value=[],
fields_description=fields_description fields_description=fields_description
) )
# Init the GPU API
# Init the Nvidia API self.nvidia = NvidiaGPU()
self.init_nvidia() self.amd = AmdGPU()
# We want to display the stat in the curse interface # We want to display the stat in the curse interface
self.display_curse = True self.display_curse = True
def init_nvidia(self): def exit(self):
"""Init the NVIDIA API.""" """Overwrite the exit method to close the GPU API."""
if import_error_tag: self.nvidia.exit()
self.nvml_ready = False self.amd.exit()
try: # Call the father exit method
pynvml.nvmlInit() super(PluginModel, self).exit()
self.device_handles = get_device_handles()
self.nvml_ready = True
except Exception:
logger.debug("pynvml could not be initialized.")
self.nvml_ready = False
return self.nvml_ready
def get_key(self): def get_key(self):
"""Return the key of the list.""" """Return the key of the list."""
@ -117,14 +100,17 @@ class PluginModel(GlancesPluginModel):
# Init new stats # Init new stats
stats = self.get_init_value() stats = self.get_init_value()
if not self.nvml_ready: # Get the stats
stats.extend(self.nvidia.get_device_stats())
stats.extend(self.amd.get_device_stats())
# !!! # !!!
# Uncomment to test on computer without GPU # Uncomment to test on computer without GPU
# One GPU sample: # One GPU sample:
# self.stats = [ # stats = [
# { # {
# "key": "gpu_id", # "key": "gpu_id",
# "gpu_id": 0, # "gpu_id": "nvidia0",
# "name": "Fake GeForce GTX", # "name": "Fake GeForce GTX",
# "mem": 5.792331695556641, # "mem": 5.792331695556641,
# "proc": 4, # "proc": 4,
@ -133,10 +119,10 @@ class PluginModel(GlancesPluginModel):
# } # }
# ] # ]
# Two GPU sample: # Two GPU sample:
# self.stats = [ # stats = [
# { # {
# "key": "gpu_id", # "key": "gpu_id",
# "gpu_id": 0, # "gpu_id": "nvidia0",
# "name": "Fake GeForce GTX1", # "name": "Fake GeForce GTX1",
# "mem": 5.792331695556641, # "mem": 5.792331695556641,
# "proc": 4, # "proc": 4,
@ -145,7 +131,7 @@ class PluginModel(GlancesPluginModel):
# }, # },
# { # {
# "key": "gpu_id", # "key": "gpu_id",
# "gpu_id": 1, # "gpu_id": "nvidia1",
# "name": "Fake GeForce GTX2", # "name": "Fake GeForce GTX2",
# "mem": 15, # "mem": 15,
# "proc": 8, # "proc": 8,
@ -153,13 +139,6 @@ class PluginModel(GlancesPluginModel):
# "fan_speed": 75 # "fan_speed": 75
# } # }
# ] # ]
return self.stats
if self.input_method == 'local':
stats = self.get_device_stats()
elif self.input_method == 'snmp':
# not available
pass
# Update the stats # Update the stats
self.stats = stats self.stats = stats
@ -209,11 +188,10 @@ class PluginModel(GlancesPluginModel):
# Header # Header
header = '' header = ''
if len(self.stats) > 1: if len(self.stats) > 1:
header += '{} '.format(len(self.stats)) header += '{} {}'.format(len(self.stats),
'GPUs' if len(self.stats) > 1 else 'GPU')
if same_name: if same_name:
header += '{} {}'.format('GPU', gpu_stats['name']) header += ' {}'.format(gpu_stats['name'])
else:
header += '{}'.format('GPU')
msg = header[:17] msg = header[:17]
ret.append(self.curse_add_line(msg, "TITLE")) ret.append(self.curse_add_line(msg, "TITLE"))
@ -302,87 +280,3 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_add_line(msg)) ret.append(self.curse_add_line(msg))
return ret return ret
def get_device_stats(self):
"""Get GPU stats."""
stats = []
for index, device_handle in enumerate(self.device_handles):
device_stats = dict()
# Dictionary key is the GPU_ID
device_stats['key'] = self.get_key()
# GPU id (for multiple GPU, start at 0)
device_stats['gpu_id'] = index
# GPU name
device_stats['name'] = get_device_name(device_handle)
# Memory consumption in % (not available on all GPU)
device_stats['mem'] = get_mem(device_handle)
# Processor consumption in %
device_stats['proc'] = get_proc(device_handle)
# Processor temperature in °C
device_stats['temperature'] = get_temperature(device_handle)
# Fan speed in %
device_stats['fan_speed'] = get_fan_speed(device_handle)
stats.append(device_stats)
return stats
def exit(self):
"""Overwrite the exit method to close the GPU API."""
if self.nvml_ready:
try:
pynvml.nvmlShutdown()
except Exception as e:
logger.debug("pynvml failed to shutdown correctly ({})".format(e))
# Call the father exit method
super(PluginModel, self).exit()
def get_device_handles():
"""Get a list of NVML device handles, one per device.
Can throw NVMLError.
"""
return [pynvml.nvmlDeviceGetHandleByIndex(i) for i in range(pynvml.nvmlDeviceGetCount())]
def get_device_name(device_handle):
"""Get GPU device name."""
try:
return nativestr(pynvml.nvmlDeviceGetName(device_handle))
except pynvml.NVMLError:
return "NVIDIA"
def get_mem(device_handle):
"""Get GPU device memory consumption in percent."""
try:
memory_info = pynvml.nvmlDeviceGetMemoryInfo(device_handle)
return memory_info.used * 100.0 / memory_info.total
except pynvml.NVMLError:
return None
def get_proc(device_handle):
"""Get GPU device CPU consumption in percent."""
try:
return pynvml.nvmlDeviceGetUtilizationRates(device_handle).gpu
except pynvml.NVMLError:
return None
def get_temperature(device_handle):
"""Get GPU device CPU temperature in Celsius."""
try:
return pynvml.nvmlDeviceGetTemperature(device_handle, pynvml.NVML_TEMPERATURE_GPU)
except pynvml.NVMLError:
return None
def get_fan_speed(device_handle):
"""Get GPU device fan speed in percent."""
try:
return pynvml.nvmlDeviceGetFanSpeed(device_handle)
except pynvml.NVMLError:
return None

View File

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# SPDX-FileCopyrightText: 2024 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
"""AMD Extension unit for Glances' GPU plugin."""
class AmdGPU:
"""GPU card class."""
def __init__(self):
"""Init AMD GPU card class."""
pass
def exit(self):
"""Close AMD GPU class."""
pass
def get_device_stats(self):
"""Get AMD GPU stats."""
stats = []
return stats

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# SPDX-FileCopyrightText: 2024 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
"""NVidia Extension unit for Glances' GPU plugin."""
from glances.logger import logger
from glances.globals import nativestr
try:
import pynvml
except Exception as e:
import_nvidia_error_tag = True
# Display debug message if import KeyError
logger.warning("Missing Python Lib ({}), Nvidia GPU plugin is disabled".format(e))
else:
import_nvidia_error_tag = False
class NvidiaGPU:
"""GPU card class."""
def __init__(self):
"""Init Nvidia GPU card class."""
if import_nvidia_error_tag:
self.device_handles = []
else:
try:
pynvml.nvmlInit()
self.device_handles = get_device_handles()
except Exception:
logger.debug("pynvml could not be initialized.")
self.device_handles = []
def exit(self):
"""Close NVidia GPU class."""
if self.device_handles != []:
try:
pynvml.nvmlShutdown()
except Exception as e:
logger.debug("pynvml failed to shutdown correctly ({})".format(e))
def get_device_stats(self):
"""Get Nvidia GPU stats."""
stats = []
for index, device_handle in enumerate(self.device_handles):
device_stats = dict()
# Dictionary key is the GPU_ID
device_stats['key'] = 'gpu_id'
# GPU id (for multiple GPU, start at 0)
device_stats['gpu_id'] = f'nvidia{index}'
# GPU name
device_stats['name'] = get_device_name(device_handle)
# Memory consumption in % (not available on all GPU)
device_stats['mem'] = get_mem(device_handle)
# Processor consumption in %
device_stats['proc'] = get_proc(device_handle)
# Processor temperature in °C
device_stats['temperature'] = get_temperature(device_handle)
# Fan speed in %
device_stats['fan_speed'] = get_fan_speed(device_handle)
stats.append(device_stats)
return stats
def get_device_handles():
"""Get a list of NVML device handles, one per device.
Can throw NVMLError.
"""
return [pynvml.nvmlDeviceGetHandleByIndex(i) for i in range(pynvml.nvmlDeviceGetCount())]
def get_device_name(device_handle):
"""Get GPU device name."""
try:
return nativestr(pynvml.nvmlDeviceGetName(device_handle))
except pynvml.NVMLError:
return "NVIDIA"
def get_mem(device_handle):
"""Get GPU device memory consumption in percent."""
try:
memory_info = pynvml.nvmlDeviceGetMemoryInfo(device_handle)
return memory_info.used * 100.0 / memory_info.total
except pynvml.NVMLError:
return None
def get_proc(device_handle):
"""Get GPU device CPU consumption in percent."""
try:
return pynvml.nvmlDeviceGetUtilizationRates(device_handle).gpu
except pynvml.NVMLError:
return None
def get_temperature(device_handle):
"""Get GPU device CPU temperature in Celsius."""
try:
return pynvml.nvmlDeviceGetTemperature(device_handle, pynvml.NVML_TEMPERATURE_GPU)
except pynvml.NVMLError:
return None
def get_fan_speed(device_handle):
"""Get GPU device fan speed in percent."""
try:
return pynvml.nvmlDeviceGetFanSpeed(device_handle)
except pynvml.NVMLError:
return None