mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-28 05:42:57 +03:00
Merge branch 'develop' into issue993
This commit is contained in:
commit
71e8fe5bfd
@ -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
|
|
||||||
|
0
glances/plugins/gpu/cards/__init__.py
Normal file
0
glances/plugins/gpu/cards/__init__.py
Normal file
27
glances/plugins/gpu/cards/amd.py
Normal file
27
glances/plugins/gpu/cards/amd.py
Normal 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
|
118
glances/plugins/gpu/cards/nvidia.py
Normal file
118
glances/plugins/gpu/cards/nvidia.py
Normal 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
|
Loading…
Reference in New Issue
Block a user