mirror of https://github.com/nicolargo/glances.git
Merge branch 'develop' of https://github.com/nicolargo/glances into develop
This commit is contained in:
commit
507079491a
|
|
@ -37,7 +37,9 @@ jobs:
|
|||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
# Python EOL version are note tested
|
||||
# Multiple Python version only tested for Linux
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
|
||||
steps:
|
||||
|
||||
|
|
|
|||
|
|
@ -299,12 +299,12 @@ Following shells are supported: bash, zsh and tcsh.
|
|||
Requirements 🧩
|
||||
===============
|
||||
|
||||
Glances is developed in Python. A minimal Python version 3.9 or higher
|
||||
Glances is developed in Python. A minimal Python version 3.10 or higher
|
||||
should be installed on your system.
|
||||
|
||||
*Note for Python 2 users*
|
||||
|
||||
Glances version 4 or higher do not support Python 2 (and Python 3 < 3.9).
|
||||
Glances version 4 or higher do not support Python 2 (and Python 3 < 3.10).
|
||||
Please uses Glances version 3.4.x if you need Python 2 support.
|
||||
|
||||
Dependencies:
|
||||
|
|
|
|||
|
|
@ -540,12 +540,12 @@ Following shells are supported: bash, zsh and tcsh.
|
|||
Requirements 🧩
|
||||
===============
|
||||
|
||||
Glances is developed in Python. A minimal Python version 3.9 or higher
|
||||
Glances is developed in Python. A minimal Python version 3.10 or higher
|
||||
should be installed on your system.
|
||||
|
||||
*Note for Python 2 users*
|
||||
|
||||
Glances version 4 or higher do not support Python 2 (and Python 3 < 3.9).
|
||||
Glances version 4 or higher do not support Python 2 (and Python 3 < 3.10).
|
||||
Please uses Glances version 3.4.x if you need Python 2 support.
|
||||
|
||||
Dependencies:
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ elasticsearch==9.2.0 ; python_full_version >= '3.10'
|
|||
# via glances
|
||||
exceptiongroup==1.2.2 ; python_full_version < '3.11'
|
||||
# via anyio
|
||||
fastapi==0.124.0
|
||||
fastapi==0.125.0
|
||||
# via glances
|
||||
geomet==1.1.0
|
||||
# via cassandra-driver
|
||||
|
|
@ -100,7 +100,7 @@ msgpack==1.1.2
|
|||
# via influxdb
|
||||
netifaces2==0.0.22
|
||||
# via glances
|
||||
nvidia-ml-py==13.580.82
|
||||
nvidia-ml-py==13.590.44
|
||||
# via glances
|
||||
packaging==25.0
|
||||
# via glances
|
||||
|
|
@ -126,11 +126,11 @@ psutil==7.1.3
|
|||
# via glances
|
||||
psycopg==3.2.13 ; python_full_version < '3.10'
|
||||
# via glances
|
||||
psycopg==3.3.1 ; python_full_version >= '3.10'
|
||||
psycopg==3.3.2 ; python_full_version >= '3.10'
|
||||
# via glances
|
||||
psycopg-binary==3.2.13 ; python_full_version < '3.10' and implementation_name != 'pypy'
|
||||
# via psycopg
|
||||
psycopg-binary==3.3.1 ; python_full_version >= '3.10' and implementation_name != 'pypy'
|
||||
psycopg-binary==3.3.2 ; python_full_version >= '3.10' and implementation_name != 'pypy'
|
||||
# via psycopg
|
||||
pyarrow==21.0.0 ; python_full_version < '3.10'
|
||||
# via influxdb3-python
|
||||
|
|
@ -144,7 +144,7 @@ pydantic==2.12.5
|
|||
# via fastapi
|
||||
pydantic-core==2.41.5
|
||||
# via pydantic
|
||||
pygal==3.0.5
|
||||
pygal==3.1.0
|
||||
# via glances
|
||||
pyjwt==2.10.1
|
||||
# via
|
||||
|
|
@ -236,9 +236,9 @@ typing-extensions==4.15.0
|
|||
# uvicorn
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
tzdata==2025.2 ; sys_platform == 'win32'
|
||||
tzdata==2025.3 ; sys_platform == 'win32'
|
||||
# via psycopg
|
||||
urllib3==2.6.0
|
||||
urllib3==2.6.2
|
||||
# via
|
||||
# docker
|
||||
# elastic-transport
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ certifi==2025.11.12
|
|||
# httpx
|
||||
# requests
|
||||
# selenium
|
||||
cffi==2.0.0 ; implementation_name != 'pypy' and os_name == 'nt'
|
||||
# via trio
|
||||
cffi==2.0.0 ; (python_full_version >= '3.10' and implementation_name == 'pypy' and platform_python_implementation != 'PyPy') or (python_full_version >= '3.10' and os_name != 'nt' and platform_python_implementation != 'PyPy') or (implementation_name != 'pypy' and os_name == 'nt')
|
||||
# via
|
||||
# cryptography
|
||||
# trio
|
||||
cfgv==3.4.0 ; python_full_version < '3.10'
|
||||
# via pre-commit
|
||||
cfgv==3.5.0 ; python_full_version >= '3.10'
|
||||
|
|
@ -76,6 +78,8 @@ contourpy==1.3.2 ; python_full_version == '3.10.*'
|
|||
# via matplotlib
|
||||
contourpy==1.3.3 ; python_full_version >= '3.11'
|
||||
# via matplotlib
|
||||
cryptography==46.0.3 ; python_full_version >= '3.10'
|
||||
# via pyjwt
|
||||
cycler==0.12.1
|
||||
# via matplotlib
|
||||
defusedxml==0.7.1 ; python_full_version < '3.10'
|
||||
|
|
@ -102,11 +106,11 @@ face==24.0.0
|
|||
# via glom
|
||||
filelock==3.19.1 ; python_full_version < '3.10'
|
||||
# via virtualenv
|
||||
filelock==3.20.0 ; python_full_version >= '3.10'
|
||||
filelock==3.20.1 ; python_full_version >= '3.10'
|
||||
# via virtualenv
|
||||
fonttools==4.60.1 ; python_full_version < '3.10'
|
||||
fonttools==4.60.2 ; python_full_version < '3.10'
|
||||
# via matplotlib
|
||||
fonttools==4.61.0 ; python_full_version >= '3.10'
|
||||
fonttools==4.61.1 ; python_full_version >= '3.10'
|
||||
# via matplotlib
|
||||
glom==22.1.0
|
||||
# via semgrep
|
||||
|
|
@ -169,8 +173,8 @@ markdown-it-py==4.0.0 ; python_full_version >= '3.10'
|
|||
markupsafe==3.0.3
|
||||
# via jinja2
|
||||
matplotlib==3.9.4 ; python_full_version < '3.10'
|
||||
matplotlib==3.10.7 ; python_full_version >= '3.10'
|
||||
mcp==1.16.0 ; python_full_version >= '3.10'
|
||||
matplotlib==3.10.8 ; python_full_version >= '3.10'
|
||||
mcp==1.23.3 ; python_full_version >= '3.10'
|
||||
# via semgrep
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
|
|
@ -278,7 +282,7 @@ platformdirs==4.5.1 ; python_full_version >= '3.10'
|
|||
pluggy==1.6.0
|
||||
# via pytest
|
||||
pre-commit==4.3.0 ; python_full_version < '3.10'
|
||||
pre-commit==4.5.0 ; python_full_version >= '3.10'
|
||||
pre-commit==4.5.1 ; python_full_version >= '3.10'
|
||||
# via
|
||||
# googleapis-common-protos
|
||||
# opentelemetry-proto
|
||||
|
|
@ -290,7 +294,7 @@ protobuf==4.25.8 ; python_full_version < '3.10'
|
|||
psutil==7.1.3
|
||||
# via memory-profiler
|
||||
py-spy==0.4.1
|
||||
pycparser==2.23 ; implementation_name != 'PyPy' and implementation_name != 'pypy' and os_name == 'nt'
|
||||
pycparser==2.23 ; (python_full_version >= '3.10' and implementation_name != 'PyPy' and os_name != 'nt' and platform_python_implementation != 'PyPy') or (python_full_version >= '3.10' and implementation_name == 'pypy' and os_name == 'nt' and platform_python_implementation != 'PyPy') or (implementation_name != 'PyPy' and implementation_name != 'pypy' and os_name == 'nt')
|
||||
# via cffi
|
||||
pydantic==2.12.5
|
||||
# via
|
||||
|
|
@ -307,13 +311,15 @@ pygments==2.19.2
|
|||
# rich
|
||||
# sphinx
|
||||
pyinstrument==5.1.1
|
||||
pyjwt==2.10.1 ; python_full_version >= '3.10'
|
||||
# via mcp
|
||||
pyparsing==3.2.5
|
||||
# via matplotlib
|
||||
pyright==1.1.407
|
||||
pysocks==1.7.1
|
||||
# via urllib3
|
||||
pytest==8.4.2 ; python_full_version < '3.10'
|
||||
pytest==9.0.1 ; python_full_version >= '3.10'
|
||||
pytest==9.0.2 ; python_full_version >= '3.10'
|
||||
python-dateutil==2.9.0.post0
|
||||
# via matplotlib
|
||||
python-debian==1.0.1
|
||||
|
|
@ -324,7 +330,7 @@ python-dotenv==1.2.1
|
|||
# webdriver-manager
|
||||
python-magic==0.4.27 ; python_full_version >= '3.10'
|
||||
# via reuse
|
||||
python-multipart==0.0.20 ; python_full_version >= '3.10'
|
||||
python-multipart==0.0.21 ; python_full_version >= '3.10'
|
||||
# via mcp
|
||||
pywin32==311 ; python_full_version >= '3.10' and sys_platform == 'win32'
|
||||
# via
|
||||
|
|
@ -353,7 +359,9 @@ rich==13.5.3
|
|||
# via
|
||||
# semgrep
|
||||
# typer
|
||||
roman-numerals-py==3.1.0 ; python_full_version >= '3.11'
|
||||
roman-numerals==4.1.0 ; python_full_version >= '3.11'
|
||||
# via roman-numerals-py
|
||||
roman-numerals-py==4.1.0 ; python_full_version >= '3.11'
|
||||
# via sphinx
|
||||
rpds-py==0.27.1 ; python_full_version < '3.10'
|
||||
# via
|
||||
|
|
@ -372,11 +380,11 @@ ruamel-yaml-clib==0.2.14 ; python_full_version >= '3.10' or platform_python_impl
|
|||
# via
|
||||
# ruamel-yaml
|
||||
# semgrep
|
||||
ruff==0.14.8
|
||||
ruff==0.14.10
|
||||
selenium==4.36.0 ; python_full_version < '3.10'
|
||||
selenium==4.38.0 ; python_full_version >= '3.10'
|
||||
selenium==4.39.0 ; python_full_version >= '3.10'
|
||||
semgrep==1.136.0 ; python_full_version < '3.10'
|
||||
semgrep==1.145.0 ; python_full_version >= '3.10'
|
||||
semgrep==1.146.0 ; python_full_version >= '3.10'
|
||||
setuptools==80.9.0
|
||||
# via opentelemetry-instrumentation
|
||||
shellingham==1.5.4
|
||||
|
|
@ -416,10 +424,12 @@ sphinxcontrib-qthelp==2.0.0
|
|||
# via sphinx
|
||||
sphinxcontrib-serializinghtml==2.0.0
|
||||
# via sphinx
|
||||
sse-starlette==3.0.3 ; python_full_version >= '3.10'
|
||||
sse-starlette==3.0.4 ; python_full_version >= '3.10'
|
||||
# via mcp
|
||||
starlette==0.50.0 ; python_full_version >= '3.10'
|
||||
# via mcp
|
||||
# via
|
||||
# mcp
|
||||
# sse-starlette
|
||||
tomli==2.0.2
|
||||
# via
|
||||
# pytest
|
||||
|
|
@ -437,11 +447,13 @@ trio==0.32.0 ; python_full_version >= '3.10'
|
|||
# trio-websocket
|
||||
trio-websocket==0.12.2
|
||||
# via selenium
|
||||
typer==0.20.0
|
||||
typer==0.20.1
|
||||
# via rstcheck
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# anyio
|
||||
# cryptography
|
||||
# mcp
|
||||
# opentelemetry-api
|
||||
# opentelemetry-exporter-otlp-proto-http
|
||||
# opentelemetry-sdk
|
||||
|
|
@ -459,9 +471,10 @@ typing-extensions==4.15.0
|
|||
# virtualenv
|
||||
typing-inspection==0.4.2
|
||||
# via
|
||||
# mcp
|
||||
# pydantic
|
||||
# pydantic-settings
|
||||
urllib3==2.6.0
|
||||
urllib3==2.6.2
|
||||
# via
|
||||
# requests
|
||||
# selenium
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ docker==7.1.0
|
|||
# via glances
|
||||
exceptiongroup==1.2.2 ; python_full_version < '3.11'
|
||||
# via anyio
|
||||
fastapi==0.124.0
|
||||
fastapi==0.125.0
|
||||
# via glances
|
||||
h11==0.16.0
|
||||
# via uvicorn
|
||||
|
|
@ -74,7 +74,7 @@ typing-extensions==4.15.0
|
|||
# uvicorn
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
urllib3==2.6.0
|
||||
urllib3==2.6.2
|
||||
# via
|
||||
# docker
|
||||
# podman
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class Amp(GlancesAmp):
|
|||
"""Update the AMP"""
|
||||
# Get the Nginx status
|
||||
logger.debug('{}: Update stats using status URL {}'.format(self.NAME, self.get('status_url')))
|
||||
res = requests.get(self.get('status_url'))
|
||||
res = requests.get(self.get('status_url'), timeout=15)
|
||||
if res.ok:
|
||||
# u'Active connections: 1 \nserver accepts handled requests\n 1 1 1 \nReading: 0 Writing: 1 Waiting: 0 \n'
|
||||
self.set_result(res.text.rstrip())
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"""CPU percent stats shared between CPU and Quicklook plugins."""
|
||||
|
||||
import platform
|
||||
from typing import Optional, TypedDict
|
||||
from typing import TypedDict
|
||||
|
||||
import psutil
|
||||
|
||||
|
|
@ -21,8 +21,8 @@ __all__ = ["cpu_percent"]
|
|||
|
||||
class CpuInfo(TypedDict):
|
||||
cpu_name: str
|
||||
cpu_hz: Optional[float]
|
||||
cpu_hz_current: Optional[float]
|
||||
cpu_hz: float | None
|
||||
cpu_hz_current: float | None
|
||||
|
||||
|
||||
class PerCpuPercentInfo(TypedDict):
|
||||
|
|
@ -32,15 +32,15 @@ class PerCpuPercentInfo(TypedDict):
|
|||
user: float
|
||||
system: float
|
||||
idle: float
|
||||
nice: Optional[float]
|
||||
iowait: Optional[float]
|
||||
irq: Optional[float]
|
||||
softirq: Optional[float]
|
||||
steal: Optional[float]
|
||||
guest: Optional[float]
|
||||
guest_nice: Optional[float]
|
||||
dpc: Optional[float]
|
||||
interrupt: Optional[float]
|
||||
nice: float | None
|
||||
iowait: float | None
|
||||
irq: float | None
|
||||
softirq: float | None
|
||||
steal: float | None
|
||||
guest: float | None
|
||||
guest_nice: float | None
|
||||
dpc: float | None
|
||||
interrupt: float | None
|
||||
|
||||
|
||||
class CpuPercent:
|
||||
|
|
@ -84,7 +84,7 @@ class CpuPercent:
|
|||
self.cpu_info['cpu_hz_current'] = cpu_freq.current
|
||||
else:
|
||||
self.cpu_info['cpu_hz_current'] = None
|
||||
if hasattr(cpu_freq, 'max'):
|
||||
if hasattr(cpu_freq, 'max') and cpu_freq.max != 0.0:
|
||||
self.cpu_info['cpu_hz'] = cpu_freq.max
|
||||
else:
|
||||
self.cpu_info['cpu_hz'] = None
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class Export(GlancesExport):
|
|||
# One complete loop have been done
|
||||
logger.debug(f"Export stats ({listkeys(self.buffer)}) to RESTful endpoint ({self.client})")
|
||||
# Export stats
|
||||
post(self.client, json=self.buffer, allow_redirects=True)
|
||||
post(self.client, json=self.buffer, allow_redirects=True, timeout=15)
|
||||
# Reset buffer
|
||||
self.buffer = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -364,7 +364,7 @@ def json_dumps(data) -> bytes:
|
|||
return b(res)
|
||||
|
||||
|
||||
def json_loads(data: Union[str, bytes, bytearray]) -> Union[dict, list]:
|
||||
def json_loads(data: str | bytes | bytearray) -> dict | list:
|
||||
"""Load a JSON buffer into memory as a Python object"""
|
||||
return json.loads(data)
|
||||
|
||||
|
|
@ -401,7 +401,7 @@ def dictlist_json_dumps(data, item):
|
|||
return json_dumps(dl)
|
||||
|
||||
|
||||
def dictlist_first_key_value(data: list[dict], key, value) -> Optional[dict]:
|
||||
def dictlist_first_key_value(data: list[dict], key, value) -> dict | None:
|
||||
"""In a list of dict, return first item where key=value or none if not found."""
|
||||
try:
|
||||
ret = next(item for item in data if key in item and item[key] == value)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import os
|
|||
import socket
|
||||
import sys
|
||||
import webbrowser
|
||||
from typing import Annotated, Any, Union
|
||||
from typing import Annotated, Any
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from glances import __apiversion__, __version__
|
||||
|
|
@ -815,7 +815,7 @@ class GlancesRestfulApi:
|
|||
else:
|
||||
return GlancesJSONResponse(ret)
|
||||
|
||||
def _api_value(self, plugin: str, item: str, value: Union[str, int, float]):
|
||||
def _api_value(self, plugin: str, item: str, value: str | int | float):
|
||||
"""Glances API RESTful implementation.
|
||||
|
||||
Return the process stats (dict) for the given item=value
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class GlancesStdoutFetch:
|
|||
fetch_template = f.read()
|
||||
|
||||
# Create a Jinja2 environment
|
||||
jinja_env = jinja2.Environment(loader=jinja2.BaseLoader())
|
||||
jinja_env = jinja2.Environment(loader=jinja2.BaseLoader(), autoescape=True)
|
||||
template = jinja_env.from_string(fetch_template)
|
||||
output = template.render(gl=self.gl)
|
||||
print(output)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,34 +1,34 @@
|
|||
{
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.5",
|
||||
"bootstrap": "^5.3.8",
|
||||
"favico.js": "^0.3.10",
|
||||
"hotkeys-js": "^3.13.9",
|
||||
"hotkeys-js": "^3.13.15",
|
||||
"lodash": "^4.17.21",
|
||||
"sanitize-html": "^2.16.0",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/compiler-sfc": "^3.5.13",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"copy-webpack-plugin": "^13.0.1",
|
||||
"css-loader": "^7.1.2",
|
||||
"del": "^8.0.0",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-vue": "^10.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"less": "^4.3.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"sass": "^1.86.3",
|
||||
"sass-loader": "^16.0.5",
|
||||
"del": "^8.0.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"globals": "^16.5.0",
|
||||
"html-webpack-plugin": "^5.6.5",
|
||||
"less": "^4.5.1",
|
||||
"less-loader": "^12.3.0",
|
||||
"sass": "^1.97.1",
|
||||
"sass-loader": "^16.0.6",
|
||||
"style-loader": "^4.0.0",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"typescript-eslint": "^8.50.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^17.4.2",
|
||||
"webpack": "^5.99.6",
|
||||
"webpack": "^5.104.1",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.1"
|
||||
"webpack-dev-server": "^5.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --mode=production",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -11,7 +11,7 @@
|
|||
from copy import deepcopy
|
||||
from functools import partial, reduce
|
||||
from itertools import chain
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from glances.globals import nativestr
|
||||
from glances.logger import logger
|
||||
|
|
@ -514,7 +514,7 @@ class ContainersPlugin(GlancesPluginModel):
|
|||
|
||||
return ret
|
||||
|
||||
def msg_curse(self, args=None, max_width: Optional[int] = None) -> list[str]:
|
||||
def msg_curse(self, args=None, max_width: int | None = None) -> list[str]:
|
||||
"""Return the dict to display in the curse interface."""
|
||||
# Init the return message
|
||||
init = []
|
||||
|
|
@ -594,7 +594,6 @@ def sort_docker_stats(stats: list[dict[str, Any]]) -> tuple[str, list[dict[str,
|
|||
|
||||
# Return the main sort key and the sorted stats
|
||||
return sort_by, stats
|
||||
# Return the main sort key and the sorted stats
|
||||
return sort_by, stats
|
||||
# Return the main sort key and the sorted stats
|
||||
return sort_by, stats
|
||||
|
||||
|
||||
# End of file
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"""Docker Extension unit for Glances' Containers plugin."""
|
||||
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from glances.globals import nativestr, pretty_date, replace_special_chars
|
||||
from glances.logger import logger
|
||||
|
|
@ -83,7 +83,7 @@ class DockerStatsFetcher:
|
|||
# In case no update, default to 1
|
||||
return max(1, self._streamer.last_update_time - self._last_stats_computed_time)
|
||||
|
||||
def _get_cpu_stats(self) -> Optional[dict[str, float]]:
|
||||
def _get_cpu_stats(self) -> dict[str, float] | None:
|
||||
"""Return the container CPU usage.
|
||||
|
||||
Output: a dict {'total': 1.49}
|
||||
|
|
@ -117,7 +117,7 @@ class DockerStatsFetcher:
|
|||
# Return the stats
|
||||
return stats
|
||||
|
||||
def _get_memory_stats(self) -> Optional[dict[str, float]]:
|
||||
def _get_memory_stats(self) -> dict[str, float] | None:
|
||||
"""Return the container MEMORY.
|
||||
|
||||
Output: a dict {'usage': ..., 'limit': ..., 'inactive_file': ...}
|
||||
|
|
@ -140,7 +140,7 @@ class DockerStatsFetcher:
|
|||
# Return the stats
|
||||
return stats
|
||||
|
||||
def _get_network_stats(self) -> Optional[dict[str, float]]:
|
||||
def _get_network_stats(self) -> dict[str, float] | None:
|
||||
"""Return the container network usage using the Docker API (v1.0 or higher).
|
||||
|
||||
Output: a dict {'time_since_update': 3000, 'rx': 10, 'tx': 65}.
|
||||
|
|
@ -169,7 +169,7 @@ class DockerStatsFetcher:
|
|||
# Return the stats
|
||||
return stats
|
||||
|
||||
def _get_io_stats(self) -> Optional[dict[str, float]]:
|
||||
def _get_io_stats(self) -> dict[str, float] | None:
|
||||
"""Return the container IO usage using the Docker API (v1.0 or higher).
|
||||
|
||||
Output: a dict {'time_since_update': 3000, 'ior': 10, 'iow': 65}.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from glances.globals import nativestr, pretty_date, replace_special_chars, string_value_to_float
|
||||
from glances.logger import logger
|
||||
|
|
@ -164,7 +164,7 @@ class PodmanPodStatsFetcher:
|
|||
|
||||
return result_stats
|
||||
|
||||
def _get_cpu_stats(self, stats: dict) -> Optional[dict]:
|
||||
def _get_cpu_stats(self, stats: dict) -> dict | None:
|
||||
"""Return the container CPU usage.
|
||||
|
||||
Output: a dict {'total': 1.49}
|
||||
|
|
@ -176,7 +176,7 @@ class PodmanPodStatsFetcher:
|
|||
cpu_usage = string_value_to_float(stats["CPU"].rstrip("%"))
|
||||
return {"total": cpu_usage}
|
||||
|
||||
def _get_memory_stats(self, stats) -> Optional[dict]:
|
||||
def _get_memory_stats(self, stats) -> dict | None:
|
||||
"""Return the container MEMORY.
|
||||
|
||||
Output: a dict {'usage': ..., 'limit': ...}
|
||||
|
|
@ -197,7 +197,7 @@ class PodmanPodStatsFetcher:
|
|||
|
||||
return {'usage': usage, 'limit': limit, 'inactive_file': 0}
|
||||
|
||||
def _get_network_stats(self, stats) -> Optional[dict]:
|
||||
def _get_network_stats(self, stats) -> dict | None:
|
||||
"""Return the container network usage using the Docker API (v1.0 or higher).
|
||||
|
||||
Output: a dict {'time_since_update': 3000, 'rx': 10, 'tx': 65}.
|
||||
|
|
@ -223,7 +223,7 @@ class PodmanPodStatsFetcher:
|
|||
# Hardcode `time_since_update` to 1 as podman docs don't specify the rate calculation procedure
|
||||
return {"rx": rx, "tx": tx, "time_since_update": 1}
|
||||
|
||||
def _get_io_stats(self, stats) -> Optional[dict]:
|
||||
def _get_io_stats(self, stats) -> dict | None:
|
||||
"""Return the container IO usage using the Docker API (v1.0 or higher).
|
||||
|
||||
Output: a dict {'time_since_update': 3000, 'ior': 10, 'iow': 65}.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Currently supported:
|
|||
"""
|
||||
|
||||
from glances.globals import to_fahrenheit
|
||||
from glances.logger import logger
|
||||
from glances.plugins.gpu.cards.amd import AmdGPU
|
||||
from glances.plugins.gpu.cards.nvidia import NvidiaGPU
|
||||
from glances.plugins.plugin.model import GlancesPluginModel
|
||||
|
|
@ -73,11 +74,21 @@ class GpuPlugin(GlancesPluginModel):
|
|||
stats_init_value=[],
|
||||
fields_description=fields_description,
|
||||
)
|
||||
# Init the GPU API
|
||||
# Init the Nvidia GPU API
|
||||
try:
|
||||
self.nvidia = NvidiaGPU()
|
||||
self.amd = AmdGPU()
|
||||
except Exception as e:
|
||||
logger.debug(f'Nvidia GPU initialization error: {e}')
|
||||
self.nvidia = None
|
||||
|
||||
# Init the AMD GPU API
|
||||
# Just for test purpose (uncomment to test on computer without AMD GPU)
|
||||
# self.amd = AmdGPU(drm_root_folder='./tests-data/plugins/gpu/amd/sys/class/drm')
|
||||
try:
|
||||
self.amd = AmdGPU()
|
||||
except Exception as e:
|
||||
logger.debug(f'AMD GPU initialization error: {e}')
|
||||
self.amd = None
|
||||
|
||||
# We want to display the stat in the curse interface
|
||||
self.display_curse = True
|
||||
|
|
@ -102,11 +113,13 @@ class GpuPlugin(GlancesPluginModel):
|
|||
stats = self.get_init_value()
|
||||
|
||||
# Get the stats
|
||||
if self.nvidia:
|
||||
stats.extend(self.nvidia.get_device_stats())
|
||||
if self.amd:
|
||||
stats.extend(self.amd.get_device_stats())
|
||||
|
||||
# !!!
|
||||
# Uncomment to test on computer without GPU
|
||||
# Uncomment to test on computer without Nvidia GPU
|
||||
# One GPU sample:
|
||||
# stats = [
|
||||
# {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ See: https://wiki.archlinux.org/title/AMDGPU#Manually
|
|||
# │ ├── gpu_busy_percent
|
||||
# │ ├── hwmon
|
||||
# │ │ └── hwmon0
|
||||
# │ │ ├── in1_input
|
||||
# │ │ └── temp1_input
|
||||
# │ ├── mem_info_gtt_total
|
||||
# │ ├── mem_info_gtt_used
|
||||
# │ ├── mem_info_vram_total
|
||||
# │ ├── mem_info_vram_used
|
||||
# │ ├── pp_dpm_mclk
|
||||
|
|
@ -37,21 +40,22 @@ See: https://wiki.archlinux.org/title/AMDGPU#Manually
|
|||
# └── amdgpu_pm_info
|
||||
|
||||
import functools
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
DRM_ROOT_FOLDER: str = '/sys/class/drm'
|
||||
CARD_REGEX: str = r"^card\d$"
|
||||
DEVICE_FOLDER: str = 'device'
|
||||
DEVICE_FOLDER_PATTERN: str = 'card[0-9]/device'
|
||||
AMDGPU_IDS_FILE: str = '/usr/share/libdrm/amdgpu.ids'
|
||||
PCI_DEVICE_ID: str = 'device'
|
||||
PCI_REVISION_ID: str = 'revision'
|
||||
GPU_PROC_PERCENT: str = 'gpu_busy_percent'
|
||||
GPU_MEM_TOTAL: str = 'mem_info_vram_total'
|
||||
GPU_MEM_USED: str = 'mem_info_vram_used'
|
||||
HWMON_REGEXP: str = r"^hwmon\d$"
|
||||
GPU_TEMPERATURE_REGEXP: str = r"^temp\d_input"
|
||||
GTT_MEM_TOTAL: str = 'mem_info_gtt_total'
|
||||
GTT_MEM_USED: str = 'mem_info_gtt_used'
|
||||
HWMON_NORTHBRIDGE_VOLTAGE_PATTERN: str = 'hwmon/hwmon[0-9]/in1_input'
|
||||
HWMON_TEMPERATURE_PATTERN = 'hwmon/hwmon[0-9]/temp[0-9]_input'
|
||||
|
||||
|
||||
class AmdGPU:
|
||||
|
|
@ -90,22 +94,17 @@ class AmdGPU:
|
|||
return stats
|
||||
|
||||
|
||||
def get_device_list(drm_root_folder: str) -> list:
|
||||
def get_device_list(drm_root_folder: str) -> list[str]:
|
||||
"""Return a list of path to the device stats."""
|
||||
ret = []
|
||||
for root, dirs, _ in os.walk(drm_root_folder):
|
||||
for d in dirs:
|
||||
if (
|
||||
re.match(CARD_REGEX, d)
|
||||
and DEVICE_FOLDER in os.listdir(os.path.join(root, d))
|
||||
and os.path.isfile(os.path.join(root, d, DEVICE_FOLDER, GPU_PROC_PERCENT))
|
||||
):
|
||||
for device_folder in glob.glob(DEVICE_FOLDER_PATTERN, root_dir=drm_root_folder):
|
||||
if os.path.isfile(os.path.join(drm_root_folder, device_folder, GPU_PROC_PERCENT)):
|
||||
# If the GPU busy file is present then take the card into account
|
||||
ret.append(os.path.join(root, d, DEVICE_FOLDER))
|
||||
ret.append(os.path.join(drm_root_folder, device_folder))
|
||||
return ret
|
||||
|
||||
|
||||
def read_file(*path_segments: str) -> Optional[str]:
|
||||
def read_file(*path_segments: str) -> str | None:
|
||||
"""Return content of file."""
|
||||
path = os.path.join(*path_segments)
|
||||
if os.path.isfile(path):
|
||||
|
|
@ -139,35 +138,39 @@ def get_device_name(device_folder: str) -> str:
|
|||
return 'AMD GPU'
|
||||
|
||||
|
||||
def get_mem(device_folder: str) -> Optional[int]:
|
||||
def get_mem(device_folder: str) -> int | None:
|
||||
"""Return the memory consumption in %."""
|
||||
mem_info_vram_total = read_file(device_folder, GPU_MEM_TOTAL)
|
||||
mem_info_vram_used = read_file(device_folder, GPU_MEM_USED)
|
||||
if mem_info_vram_total and mem_info_vram_used:
|
||||
mem_info_vram_total = int(mem_info_vram_total)
|
||||
mem_info_vram_used = int(mem_info_vram_used)
|
||||
if mem_info_vram_total > 0:
|
||||
return round(mem_info_vram_used / mem_info_vram_total * 100)
|
||||
mem_info_total = read_file(device_folder, GPU_MEM_TOTAL)
|
||||
mem_info_used = read_file(device_folder, GPU_MEM_USED)
|
||||
if mem_info_total and mem_info_used:
|
||||
mem_info_total = int(mem_info_total)
|
||||
mem_info_used = int(mem_info_used)
|
||||
# Detect integrated GPU by looking for APU-only Northbridge voltage.
|
||||
# See https://docs.kernel.org/gpu/amdgpu/thermal.html
|
||||
if glob.glob(HWMON_NORTHBRIDGE_VOLTAGE_PATTERN, root_dir=device_folder):
|
||||
mem_info_gtt_total = read_file(device_folder, GTT_MEM_TOTAL)
|
||||
mem_info_gtt_used = read_file(device_folder, GTT_MEM_USED)
|
||||
if mem_info_gtt_total and mem_info_gtt_used:
|
||||
# Integrated GPU allocates static VRAM and dynamic GTT from the same system memory.
|
||||
mem_info_total += int(mem_info_gtt_total)
|
||||
mem_info_used += int(mem_info_gtt_used)
|
||||
if mem_info_total > 0:
|
||||
return round(mem_info_used / mem_info_total * 100)
|
||||
return None
|
||||
|
||||
|
||||
def get_proc(device_folder: str) -> Optional[int]:
|
||||
def get_proc(device_folder: str) -> int | None:
|
||||
"""Return the processor consumption in %."""
|
||||
if gpu_busy_percent := read_file(device_folder, GPU_PROC_PERCENT):
|
||||
return int(gpu_busy_percent)
|
||||
return None
|
||||
|
||||
|
||||
def get_temperature(device_folder: str) -> Optional[int]:
|
||||
def get_temperature(device_folder: str) -> int | None:
|
||||
"""Return the processor temperature in °C (mean of all HWMON)"""
|
||||
temp_input = []
|
||||
for root, dirs, _ in os.walk(device_folder):
|
||||
for d in dirs:
|
||||
if re.match(HWMON_REGEXP, d):
|
||||
for _, _, files in os.walk(os.path.join(root, d)):
|
||||
for f in files:
|
||||
if re.match(GPU_TEMPERATURE_REGEXP, f):
|
||||
if a_temp_input := read_file(root, d, f):
|
||||
for temp_file in glob.glob(HWMON_TEMPERATURE_PATTERN, root_dir=device_folder):
|
||||
if a_temp_input := read_file(device_folder, temp_file):
|
||||
temp_input.append(int(a_temp_input))
|
||||
else:
|
||||
return None
|
||||
|
|
@ -176,6 +179,6 @@ def get_temperature(device_folder: str) -> Optional[int]:
|
|||
return None
|
||||
|
||||
|
||||
def get_fan_speed(device_folder: str) -> Optional[int]:
|
||||
def get_fan_speed(device_folder: str) -> int | None:
|
||||
"""Return the fan speed in %."""
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -98,11 +98,11 @@ class LoadPlugin(GlancesPluginModel):
|
|||
# Update stats using the standard system lib
|
||||
|
||||
# Get the load using the os standard lib
|
||||
load = get_load_average()
|
||||
load = load_average()
|
||||
if load is None:
|
||||
stats = self.get_init_value()
|
||||
else:
|
||||
stats = {'min1': load[0], 'min5': load[1], 'min15': load[2], 'cpucore': get_nb_log_core()}
|
||||
stats = {'min1': load[0], 'min5': load[1], 'min15': load[2], 'cpucore': log_core()}
|
||||
|
||||
elif self.input_method == 'snmp':
|
||||
# Update stats using SNMP
|
||||
|
|
@ -116,7 +116,7 @@ class LoadPlugin(GlancesPluginModel):
|
|||
for k, v in stats.items():
|
||||
stats[k] = float(v)
|
||||
|
||||
stats['cpucore'] = get_nb_log_core()
|
||||
stats['cpucore'] = log_core()
|
||||
|
||||
# Update the stats
|
||||
self.stats = stats
|
||||
|
|
@ -164,9 +164,9 @@ class LoadPlugin(GlancesPluginModel):
|
|||
ret.append(self.curse_new_line())
|
||||
msg = '{:7}'.format(f'{load_time} min')
|
||||
ret.append(self.curse_add_line(msg))
|
||||
if args.disable_irix and get_nb_log_core() != 0:
|
||||
if args.disable_irix and log_core() != 0:
|
||||
# Enable Irix mode for load (see issue #1554)
|
||||
load_stat = self.stats[f'min{load_time}'] / get_nb_log_core() * 100
|
||||
load_stat = self.stats[f'min{load_time}'] / log_core() * 100
|
||||
msg = f'{load_stat:>5.1f}%'
|
||||
else:
|
||||
# Default mode for load
|
||||
|
|
@ -177,30 +177,30 @@ class LoadPlugin(GlancesPluginModel):
|
|||
return ret
|
||||
|
||||
|
||||
def get_nb_log_core():
|
||||
def log_core():
|
||||
"""Get the number of logical CPU core."""
|
||||
return nb_log_core
|
||||
|
||||
|
||||
def get_nb_phys_core():
|
||||
def phys_core():
|
||||
"""Get the number of physical CPU core."""
|
||||
return nb_phys_core
|
||||
|
||||
|
||||
def get_load_average(percent: bool = False):
|
||||
def load_average(percent: bool = False):
|
||||
"""Get load average. On both Linux and Windows thanks to PsUtil
|
||||
|
||||
if percent is True, return the load average in percent
|
||||
Ex: if you only have one CPU core and the load average is 1.0, then return 100%"""
|
||||
load_average = None
|
||||
ret = None
|
||||
try:
|
||||
load_average = psutil.getloadavg()
|
||||
ret = psutil.getloadavg()
|
||||
except (AttributeError, OSError):
|
||||
try:
|
||||
load_average = os.getloadavg()
|
||||
ret = os.getloadavg()
|
||||
except (AttributeError, OSError):
|
||||
pass
|
||||
|
||||
if load_average and percent:
|
||||
return tuple([round(i / get_nb_log_core() * 100, 1) for i in load_average])
|
||||
return load_average
|
||||
if ret and percent:
|
||||
return tuple([round(i / log_core() * 100, 1) for i in ret])
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from glances.logger import logger
|
|||
from glances.outputs.glances_bars import Bar
|
||||
from glances.outputs.glances_sparklines import Sparkline
|
||||
from glances.plugins.fs.zfs import zfs_enable, zfs_stats
|
||||
from glances.plugins.load import get_load_average, get_nb_log_core, get_nb_phys_core
|
||||
from glances.plugins.load import load_average, log_core, phys_core
|
||||
from glances.plugins.plugin.model import GlancesPluginModel
|
||||
|
||||
# Fields description
|
||||
|
|
@ -144,12 +144,12 @@ class QuicklookPlugin(GlancesPluginModel):
|
|||
stats['swap'] = None
|
||||
|
||||
# Get load
|
||||
stats['cpu_log_core'] = get_nb_log_core()
|
||||
stats['cpu_phys_core'] = get_nb_phys_core()
|
||||
stats['cpu_log_core'] = log_core()
|
||||
stats['cpu_phys_core'] = phys_core()
|
||||
try:
|
||||
# Load average is a tuple (1 min, 5 min, 15 min)
|
||||
# Process only the 15 min value (index 2)
|
||||
stats['load'] = get_load_average(percent=True)[2]
|
||||
stats['load'] = load_average(percent=True)[2]
|
||||
except (TypeError, IndexError):
|
||||
stats['load'] = None
|
||||
|
||||
|
|
@ -202,15 +202,13 @@ class QuicklookPlugin(GlancesPluginModel):
|
|||
##########################
|
||||
|
||||
# System information
|
||||
if (
|
||||
'cpu_hz_current' in self.stats
|
||||
and self.stats['cpu_hz_current'] is not None
|
||||
and 'cpu_hz' in self.stats
|
||||
and self.stats['cpu_hz'] is not None
|
||||
):
|
||||
if 'cpu_hz_current' in self.stats and self.stats['cpu_hz_current'] is not None:
|
||||
if 'cpu_hz' in self.stats and self.stats['cpu_hz'] is not None:
|
||||
msg_freq = ' {:.2f}/{:.2f}GHz'.format(
|
||||
self._hz_to_ghz(self.stats['cpu_hz_current']), self._hz_to_ghz(self.stats['cpu_hz'])
|
||||
)
|
||||
else:
|
||||
msg_freq = ' {:.2f}GHz'.format(self._hz_to_ghz(self.stats['cpu_hz_current']))
|
||||
else:
|
||||
msg_freq = ''
|
||||
|
||||
|
|
@ -320,3 +318,6 @@ class QuicklookPlugin(GlancesPluginModel):
|
|||
def _mhz_to_hz(self, hz):
|
||||
"""Convert Mhz to Hz."""
|
||||
return hz * 1000000.0
|
||||
|
||||
|
||||
# End of file
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"""Vms plugin."""
|
||||
|
||||
from copy import deepcopy
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from glances.logger import logger
|
||||
from glances.plugins.plugin.model import GlancesPluginModel
|
||||
|
|
@ -201,7 +201,7 @@ class VmsPlugin(GlancesPluginModel):
|
|||
|
||||
return True
|
||||
|
||||
def msg_curse(self, args=None, max_width: Optional[int] = None) -> list[str]:
|
||||
def msg_curse(self, args=None, max_width: int | None = None) -> list[str]:
|
||||
"""Return the dict to display in the curse interface."""
|
||||
# Init the return message
|
||||
ret = []
|
||||
|
|
@ -345,3 +345,6 @@ def sort_vm_stats(stats: list[dict[str, Any]]) -> tuple[str, list[dict[str, Any]
|
|||
|
||||
# Return the main sort key and the sorted stats
|
||||
return sort_by, stats
|
||||
|
||||
|
||||
# End of file
|
||||
|
|
|
|||
|
|
@ -8,10 +8,8 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"Intended Audience :: System Administrators",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
|
|
@ -30,10 +28,10 @@ dependencies = [
|
|||
description = "A cross-platform curses-based monitoring tool"
|
||||
dynamic = ["version"]
|
||||
keywords = ["cli", "curses", "monitoring", "system"]
|
||||
license = {text = "LGPLv3"}
|
||||
license = "LGPL-3.0-only"
|
||||
name = "Glances"
|
||||
readme = "README-pypi.rst"
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
urls.Homepage = "https://github.com/nicolargo/glances"
|
||||
|
||||
[dependency-groups]
|
||||
|
|
@ -136,7 +134,7 @@ include = ["glances*"]
|
|||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "preserve"
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0
|
||||
|
|
@ -0,0 +1 @@
|
|||
17179869184
|
||||
|
|
@ -0,0 +1 @@
|
|||
79949824
|
||||
Loading…
Reference in New Issue