Glances 4.0.8

This commit is contained in:
nicolargo 2024-06-08 10:25:45 +02:00
commit bf3bb6a7e8
38 changed files with 508 additions and 263 deletions

View File

@ -123,19 +123,12 @@ jobs:
- name: Retrieve Repository Docker metadata - name: Retrieve Repository Docker metadata
id: docker_meta id: docker_meta
uses: crazy-max/ghaction-docker-meta@v5.0.0 uses: docker/metadata-action@v5
with: with:
images: ${{ env.DEFAULT_DOCKER_IMAGE }} images: ${{ env.DEFAULT_DOCKER_IMAGE }}
labels: | labels: |
org.opencontainers.image.url=https://nicolargo.github.io/glances/ org.opencontainers.image.url=https://nicolargo.github.io/glances/
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ env.NODE_ENV }}-${{ matrix.os }}-${{ matrix.tag.tag }}
restore-keys: ${{ runner.os }}-buildx-${{ env.NODE_ENV }}-${{ matrix.os }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
with: with:
@ -166,5 +159,7 @@ jobs:
platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }} platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }}
target: ${{ matrix.tag.target }} target: ${{ matrix.tag.target }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.docker_meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache # GHA default behaviour overwrites last build cache. Causes alpine and ubuntu cache to overwrite each other.
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max # Use `scope` with the os name to prevent that
cache-from: 'type=gha,scope=${{ matrix.os }}'
cache-to: 'type=gha,mode=max,scope=${{ matrix.os }}'

View File

@ -48,6 +48,62 @@ Version 4.0.2
Thanks to RazCrimson for the sensors patch ! Thanks to RazCrimson for the sensors patch !
===============
Version 4.0.8
===============
* Make CORS option configurable security webui #2812
* When Glances is installed via venv, default configuration file is not used documentation packaging #2803
* GET /1272f6e9e8f9d6bfd6de.png results in 404 bug webui #2781 by Emporea was closed May 25, 2024
* Screen frequently flickers when outputting to local display bug needs test #2490
* Retire ujson for being in maintenance mode dependencies enhancement #2791
===============
Version 4.0.7
===============
* cpu_hz_current not available on NetBSD #2792
* SensorType change in REST API breaks compatibility in 4.0.4 #2788
===============
Version 4.0.6
===============
* No GPU info on Web View #2796
===============
Version 4.0.5
===============
* SensorType change in REST API breaks compatibility in 4.0.4 #2788
* Please make pydantic optional dependency, not required one #2777
* Update the Grafana dashboard #2780
* 4.0.4 - On Glances startup "ERROR -- Can not init battery class #2776
* In codeSpace (with Python 3.8), an error occurs in ./unittest-restful.py #2773
Use Ruff as default Linter.
===============
Version 4.0.4
===============
Hostfix release for support sensors plugin on python 3.8
===============
Version 4.0.3
===============
Additional fixes for Sensor plugin
===============
Version 4.0.2
===============
* hotfix: plugin(sensors) - race conditions btw fan_speed & temperature… #2766
* fix: include requirements.txt and SECURITY.md for pypi dist #2761
Thanks to RazCrimson for the sensors patch !
=============== ===============
Version 4.0.1 Version 4.0.1
=============== ===============

View File

@ -86,7 +86,7 @@ Requirements
- ``psutil`` (better with latest version) - ``psutil`` (better with latest version)
- ``defusedxml`` (in order to monkey patch xmlrpc) - ``defusedxml`` (in order to monkey patch xmlrpc)
- ``packaging`` (for the version comparison) - ``packaging`` (for the version comparison)
- ``ujson`` (an optimized alternative to the standard json module) - ``orjson`` (an optimized alternative to the standard json module)
*Note for Python 2 users* *Note for Python 2 users*
@ -115,7 +115,6 @@ Optional dependencies:
- ``podman`` (for the Containers Podman monitoring support) - ``podman`` (for the Containers Podman monitoring support)
- ``potsdb`` (for the OpenTSDB export module) - ``potsdb`` (for the OpenTSDB export module)
- ``prometheus_client`` (for the Prometheus export module) - ``prometheus_client`` (for the Prometheus export module)
- ``py-cpuinfo`` (for the Quicklook CPU info module)
- ``pygal`` (for the graph export module) - ``pygal`` (for the graph export module)
- ``pymdstat`` (for RAID support) [Linux-only] - ``pymdstat`` (for RAID support) [Linux-only]
- ``pymongo`` (for the MongoDB export module) - ``pymongo`` (for the MongoDB export module)

View File

@ -23,12 +23,16 @@ history_size=1200
############################################################################## ##############################################################################
[outputs] [outputs]
# Options for all UIs
#--------------------
# Separator in the Curses and WebUI interface (between top and others plugins) # Separator in the Curses and WebUI interface (between top and others plugins)
separator=True separator=True
# Set the the Curses and WebUI interface left menu plugin list (comma-separated) # Set the the Curses and WebUI interface left menu plugin list (comma-separated)
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
# Limit the number of processes to display (in the WebUI) # Limit the number of processes to display (in the WebUI)
max_processes_display=25 max_processes_display=25
# Options for WebUI
#------------------
# Set URL prefix for the WebUI and the API # Set URL prefix for the WebUI and the API
# Example: url_prefix=/glances/ => http://localhost/glances/ # Example: url_prefix=/glances/ => http://localhost/glances/
# Note: The final / is mandatory # Note: The final / is mandatory
@ -41,9 +45,22 @@ max_processes_display=25
# then configure this folder with the webui_root_path key # then configure this folder with the webui_root_path key
# Default is folder where glances_restfull_api.py is hosted # Default is folder where glances_restfull_api.py is hosted
#webui_root_path= #webui_root_path=
# CORS options
# Comma separated list of origins that should be permitted to make cross-origin requests.
# Default is *
#cors_origins=*
# Indicate that cookies should be supported for cross-origin requests.
# Default is True
#cors_credentials=True
# Comma separated list of HTTP methods that should be allowed for cross-origin requests.
# Default is *
#cors_methods=*
# Comma separated list of HTTP request headers that should be supported for cross-origin requests.
# Default is *
#cors_headers=*
############################################################################## ##############################################################################
# plugins # Plugins
############################################################################## ##############################################################################
[quicklook] [quicklook]
@ -199,6 +216,10 @@ tx_critical=90
hide=docker.*,lo hide=docker.*,lo
# Define the list of wireless network interfaces to be show (comma-separated) # Define the list of wireless network interfaces to be show (comma-separated)
#show=docker.* #show=docker.*
# Automatically hide interface not up (default is False)
#hide_no_up=True
# Automatically hide interface with no IP address (default is False)
#hide_no_ip=True
# It is possible to overwrite the bitrate thresholds per interface # It is possible to overwrite the bitrate thresholds per interface
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
#wlan0_rx_careful=4000000 #wlan0_rx_careful=4000000
@ -765,13 +786,13 @@ refresh=3
countmax=20 countmax=20
[amp_conntrack] [amp_conntrack]
# Use comma separated for multiple commands (no space around the comma) # Use && separator for multiple commands
# If the regex key is not defined, the AMP will be executed every refresh second # If the regex key is not defined, the AMP will be executed every refresh second
# and the process count will not be displayed (countmin and countmax will be ignore) # and the process count will not be displayed (countmin and countmax will be ignore)
enable=false enable=false
refresh=30 refresh=30
one_line=false one_line=false
command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max
[amp_nginx] [amp_nginx]
# Use the NGinx AMP # Use the NGinx AMP

View File

@ -1,5 +1,5 @@
orjson
reuse reuse
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
sphinx sphinx
sphinx_rtd_theme sphinx_rtd_theme
ujson

View File

@ -23,12 +23,16 @@ history_size=1200
############################################################################## ##############################################################################
[outputs] [outputs]
# Options for all UIs
#--------------------
# Separator in the Curses and WebUI interface (between top and others plugins) # Separator in the Curses and WebUI interface (between top and others plugins)
separator=True separator=True
# Set the the Curses and WebUI interface left menu plugin list (comma-separated) # Set the the Curses and WebUI interface left menu plugin list (comma-separated)
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
# Limit the number of processes to display (in the WebUI) # Limit the number of processes to display (in the WebUI)
max_processes_display=25 max_processes_display=25
# Options for WebUI
#------------------
# Set URL prefix for the WebUI and the API # Set URL prefix for the WebUI and the API
# Example: url_prefix=/glances/ => http://localhost/glances/ # Example: url_prefix=/glances/ => http://localhost/glances/
# Note: The final / is mandatory # Note: The final / is mandatory
@ -41,6 +45,19 @@ max_processes_display=25
# then configure this folder with the webui_root_path key # then configure this folder with the webui_root_path key
# Default is folder where glances_restfull_api.py is hosted # Default is folder where glances_restfull_api.py is hosted
#webui_root_path= #webui_root_path=
# CORS options
# Comma separated list of origins that should be permitted to make cross-origin requests.
# Default is *
#cors_origins=*
# Indicate that cookies should be supported for cross-origin requests.
# Default is True
#cors_credentials=True
# Comma separated list of HTTP methods that should be allowed for cross-origin requests.
# Default is *
#cors_methods=*
# Comma separated list of HTTP request headers that should be supported for cross-origin requests.
# Default is *
#cors_headers=*
############################################################################## ##############################################################################
# plugins # plugins
@ -199,6 +216,10 @@ tx_critical=90
#hide=docker.*,lo #hide=docker.*,lo
# Define the list of wireless network interfaces to be show (comma-separated) # Define the list of wireless network interfaces to be show (comma-separated)
#show=docker.* #show=docker.*
# Automatically hide interface not up (default is False)
hide_no_up=True
# Automatically hide interface with no IP address (default is False)
hide_no_ip=True
# It is possible to overwrite the bitrate thresholds per interface # It is possible to overwrite the bitrate thresholds per interface
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate # WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
#wlan0_rx_careful=4000000 #wlan0_rx_careful=4000000
@ -765,13 +786,13 @@ refresh=3
countmax=20 countmax=20
[amp_conntrack] [amp_conntrack]
# Use comma separated for multiple commands (no space around the comma) # Use && separator for multiple commands
# If the regex key is not defined, the AMP will be executed every refresh second # If the regex key is not defined, the AMP will be executed every refresh second
# and the process count will not be displayed (countmin and countmax will be ignore) # and the process count will not be displayed (countmin and countmax will be ignore)
enable=false enable=false
refresh=30 refresh=30
one_line=false one_line=false
command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max
[amp_nginx] [amp_nginx]
# Use the NGinx AMP # Use the NGinx AMP

View File

@ -1,9 +1,8 @@
# install with base requirements file # install with base requirements file
-r requirements.txt -r requirements.txt
docker>=6.1.1; python_version >= "3.7" docker>=6.1.1
packaging; python_version >= "3.7" podman
podman; python_version >= "3.6"
python-dateutil python-dateutil
requests requests
six six

View File

@ -61,9 +61,11 @@ For example:
enable=false enable=false
refresh=30 refresh=30
one_line=false one_line=false
command=sysctl net.netfilter.nf_conntrack_count;sysctl net.netfilter.nf_conntrack_max command=sysctl net.netfilter.nf_conntrack_count && sysctl net.netfilter.nf_conntrack_max
For security reason, pipe is not directly allowed in a AMP command but you create a sheel Note: for multiple command, please use the '&&'' separator.
For security reason, pipe is not directly allowed in a AMP command but you create a shell
script with your command: script with your command:
.. code-block:: ini .. code-block:: ini

View File

@ -17,6 +17,8 @@ In this case thresholds values are define in bps.
Additionally, you can define: Additionally, you can define:
- a list of network interfaces to hide - a list of network interfaces to hide
- automatically hide interfaces not up
- automatically hide interfaces without IP address
- per-interface limit values - per-interface limit values
- aliases for interface name - aliases for interface name
@ -41,6 +43,10 @@ virtual docker interface (docker0, docker1, ...):
hide=docker.*,lo hide=docker.*,lo
# Define the list of network interfaces to show (comma-separated regexp) # Define the list of network interfaces to show (comma-separated regexp)
#show=eth0,eth1 #show=eth0,eth1
# Automatically hide interface not up (default is False)
hide_no_up=True
# Automatically hide interface with no IP address (default is False)
hide_no_ip=True
# WLAN 0 alias # WLAN 0 alias
wlan0_alias=Wireless IF wlan0_alias=Wireless IF
# It is possible to overwrite the bitrate thresholds per interface # It is possible to overwrite the bitrate thresholds per interface

View File

@ -141,7 +141,7 @@ Get plugin stats::
"refresh": 3.0, "refresh": 3.0,
"regex": True, "regex": True,
"result": None, "result": None,
"timer": 0.17942380905151367}, "timer": 0.24439311027526855},
{"count": 0, {"count": 0,
"countmax": 20.0, "countmax": 20.0,
"countmin": None, "countmin": None,
@ -150,7 +150,7 @@ Get plugin stats::
"refresh": 3.0, "refresh": 3.0,
"regex": True, "regex": True,
"result": None, "result": None,
"timer": 0.17932724952697754}] "timer": 0.2443389892578125}]
Fields descriptions: Fields descriptions:
@ -178,7 +178,7 @@ Get a specific item when field matches the given value::
"refresh": 3.0, "refresh": 3.0,
"regex": True, "regex": True,
"result": None, "result": None,
"timer": 0.17942380905151367}]} "timer": 0.24439311027526855}]}
GET cloud GET cloud
--------- ---------
@ -219,7 +219,21 @@ GET containers
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/containers # curl http://localhost:61208/api/4/containers
[] [{"command": "/bin/sh -c /venv/bin/python3 -m glances $GLANCES_OPT",
"cpu": {"total": 0.0},
"cpu_percent": 0.0,
"created": "2024-05-25T13:52:22.535373707Z",
"engine": "docker",
"id": "bb99d31288db8904ed4cd43db8255a926830936189bc180d77c3459cbaa7f490",
"image": ["nicolargo/glances:latest"],
"io": {},
"key": "name",
"memory": {},
"memory_usage": None,
"name": "wizardly_nightingale",
"network": {},
"status": "running",
"uptime": "14 mins"}]
Fields descriptions: Fields descriptions:
@ -240,6 +254,31 @@ Fields descriptions:
* **pod_name**: Pod name (only with Podman) (unit is *None*) * **pod_name**: Pod name (only with Podman) (unit is *None*)
* **pod_id**: Pod ID (only with Podman) (unit is *None*) * **pod_id**: Pod ID (only with Podman) (unit is *None*)
Get a specific field::
# curl http://localhost:61208/api/4/containers/name
{"name": ["wizardly_nightingale"]}
Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/containers/name/wizardly_nightingale
{"wizardly_nightingale": [{"command": "/bin/sh -c /venv/bin/python3 -m glances "
"$GLANCES_OPT",
"cpu": {"total": 0.0},
"cpu_percent": 0.0,
"created": "2024-05-25T13:52:22.535373707Z",
"engine": "docker",
"id": "bb99d31288db8904ed4cd43db8255a926830936189bc180d77c3459cbaa7f490",
"image": ["nicolargo/glances:latest"],
"io": {},
"key": "name",
"memory": {},
"memory_usage": None,
"name": "wizardly_nightingale",
"network": {},
"status": "running",
"uptime": "14 mins"}]}
GET core GET core
-------- --------
@ -265,18 +304,18 @@ Get plugin stats::
# curl http://localhost:61208/api/4/cpu # curl http://localhost:61208/api/4/cpu
{"cpucore": 16, {"cpucore": 16,
"ctx_switches": 247960513, "ctx_switches": 7772781,
"guest": 0.0, "guest": 0.0,
"idle": 0.0, "idle": 3.0,
"interrupts": 235154276, "interrupts": 6643340,
"iowait": 0.0, "iowait": 0.0,
"irq": 0.0, "irq": 0.0,
"nice": 0.0, "nice": 0.0,
"soft_interrupts": 79848589, "soft_interrupts": 1761276,
"steal": 0.0, "steal": 0.0,
"syscalls": 0, "syscalls": 0,
"system": 0.0, "system": 0.0,
"total": 0.0, "total": 16.7,
"user": 1.0} "user": 1.0}
Fields descriptions: Fields descriptions:
@ -310,7 +349,7 @@ Fields descriptions:
Get a specific field:: Get a specific field::
# curl http://localhost:61208/api/4/cpu/total # curl http://localhost:61208/api/4/cpu/total
{"total": 0.0} {"total": 16.7}
GET diskio GET diskio
---------- ----------
@ -320,14 +359,14 @@ Get plugin stats::
# curl http://localhost:61208/api/4/diskio # curl http://localhost:61208/api/4/diskio
[{"disk_name": "nvme0n1", [{"disk_name": "nvme0n1",
"key": "disk_name", "key": "disk_name",
"read_bytes": 4854106624, "read_bytes": 3983976960,
"read_count": 207109, "read_count": 115648,
"write_bytes": 15446680576, "write_bytes": 3073111040,
"write_count": 891078}, "write_count": 91409},
{"disk_name": "nvme0n1p1", {"disk_name": "nvme0n1p1",
"key": "disk_name", "key": "disk_name",
"read_bytes": 7484416, "read_bytes": 7476224,
"read_count": 592, "read_count": 576,
"write_bytes": 1024, "write_bytes": 1024,
"write_count": 2}] "write_count": 2}]
@ -363,10 +402,10 @@ Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/diskio/disk_name/nvme0n1 # curl http://localhost:61208/api/4/diskio/disk_name/nvme0n1
{"nvme0n1": [{"disk_name": "nvme0n1", {"nvme0n1": [{"disk_name": "nvme0n1",
"key": "disk_name", "key": "disk_name",
"read_bytes": 4854106624, "read_bytes": 3983976960,
"read_count": 207109, "read_count": 115648,
"write_bytes": 15446680576, "write_bytes": 3073111040,
"write_count": 891078}]} "write_count": 91409}]}
GET folders GET folders
----------- -----------
@ -393,13 +432,13 @@ Get plugin stats::
# curl http://localhost:61208/api/4/fs # curl http://localhost:61208/api/4/fs
[{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
"free": 904231424000, "free": 902284546048,
"fs_type": "ext4", "fs_type": "ext4",
"key": "mnt_point", "key": "mnt_point",
"mnt_point": "/", "mnt_point": "/",
"percent": 5.1, "percent": 5.3,
"size": 1003736440832, "size": 1003736440832,
"used": 48442511360}] "used": 50389389312}]
Fields descriptions: Fields descriptions:
@ -420,13 +459,13 @@ Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/fs/mnt_point// # curl http://localhost:61208/api/4/fs/mnt_point//
{"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv", {"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
"free": 904231424000, "free": 902284546048,
"fs_type": "ext4", "fs_type": "ext4",
"key": "mnt_point", "key": "mnt_point",
"mnt_point": "/", "mnt_point": "/",
"percent": 5.1, "percent": 5.3,
"size": 1003736440832, "size": 1003736440832,
"used": 48442511360}]} "used": 50389389312}]}
GET gpu GET gpu
------- -------
@ -500,9 +539,9 @@ Get plugin stats::
# curl http://localhost:61208/api/4/load # curl http://localhost:61208/api/4/load
{"cpucore": 16, {"cpucore": 16,
"min1": 0.2802734375, "min1": 0.560546875,
"min15": 0.6240234375, "min15": 0.54833984375,
"min5": 0.61376953125} "min5": 0.70166015625}
Fields descriptions: Fields descriptions:
@ -514,7 +553,7 @@ Fields descriptions:
Get a specific field:: Get a specific field::
# curl http://localhost:61208/api/4/load/min1 # curl http://localhost:61208/api/4/load/min1
{"min1": 0.2802734375} {"min1": 0.560546875}
GET mem GET mem
------- -------
@ -522,16 +561,16 @@ GET mem
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/mem # curl http://localhost:61208/api/4/mem
{"active": 8875126784, {"active": 5540470784,
"available": 7975804928, "available": 11137699840,
"buffers": 463708160, "buffers": 226918400,
"cached": 7862255616, "cached": 6333566976,
"free": 7975804928, "free": 11137699840,
"inactive": 4694634496, "inactive": 3872489472,
"percent": 51.4, "percent": 32.2,
"shared": 849006592, "shared": 656637952,
"total": 16422486016, "total": 16422486016,
"used": 8446681088} "used": 5284786176}
Fields descriptions: Fields descriptions:
@ -558,13 +597,13 @@ GET memswap
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/memswap # curl http://localhost:61208/api/4/memswap
{"free": 4293914624, {"free": 4294963200,
"percent": 0.0, "percent": 0.0,
"sin": 0, "sin": 0,
"sout": 114688, "sout": 0,
"time_since_update": 1, "time_since_update": 1,
"total": 4294963200, "total": 4294963200,
"used": 1048576} "used": 0}
Fields descriptions: Fields descriptions:
@ -589,15 +628,26 @@ Get plugin stats::
# curl http://localhost:61208/api/4/network # curl http://localhost:61208/api/4/network
[{"alias": None, [{"alias": None,
"bytes_all": 0, "bytes_all": 0,
"bytes_all_gauge": 1608088786, "bytes_all_gauge": 422462144,
"bytes_recv": 0, "bytes_recv": 0,
"bytes_recv_gauge": 1294928139, "bytes_recv_gauge": 413272561,
"bytes_sent": 0, "bytes_sent": 0,
"bytes_sent_gauge": 313160647, "bytes_sent_gauge": 9189583,
"interface_name": "wlp0s20f3", "interface_name": "wlp0s20f3",
"key": "interface_name", "key": "interface_name",
"speed": 0, "speed": 0,
"time_since_update": 0.18457698822021484}] "time_since_update": 0.24814295768737793},
{"alias": None,
"bytes_all": 0,
"bytes_all_gauge": 18987,
"bytes_recv": 0,
"bytes_recv_gauge": 528,
"bytes_sent": 0,
"bytes_sent_gauge": 18459,
"interface_name": "vethfc47299",
"key": "interface_name",
"speed": 10485760000,
"time_since_update": 0.24814295768737793}]
Fields descriptions: Fields descriptions:
@ -619,22 +669,22 @@ Fields descriptions:
Get a specific field:: Get a specific field::
# curl http://localhost:61208/api/4/network/interface_name # curl http://localhost:61208/api/4/network/interface_name
{"interface_name": ["wlp0s20f3"]} {"interface_name": ["wlp0s20f3", "vethfc47299"]}
Get a specific item when field matches the given value:: Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/network/interface_name/wlp0s20f3 # curl http://localhost:61208/api/4/network/interface_name/wlp0s20f3
{"wlp0s20f3": [{"alias": None, {"wlp0s20f3": [{"alias": None,
"bytes_all": 0, "bytes_all": 0,
"bytes_all_gauge": 1608088786, "bytes_all_gauge": 422462144,
"bytes_recv": 0, "bytes_recv": 0,
"bytes_recv_gauge": 1294928139, "bytes_recv_gauge": 413272561,
"bytes_sent": 0, "bytes_sent": 0,
"bytes_sent_gauge": 313160647, "bytes_sent_gauge": 9189583,
"interface_name": "wlp0s20f3", "interface_name": "wlp0s20f3",
"key": "interface_name", "key": "interface_name",
"speed": 0, "speed": 0,
"time_since_update": 0.18457698822021484}]} "time_since_update": 0.24814295768737793}]}
GET now GET now
------- -------
@ -642,7 +692,7 @@ GET now
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/now # curl http://localhost:61208/api/4/now
{"custom": "2024-05-25 16:10:02 CEST", "iso": "2024-05-25T16:10:02+02:00"} {"custom": "2024-06-08 10:22:29 CEST", "iso": "2024-06-08T10:22:29+02:00"}
Fields descriptions: Fields descriptions:
@ -652,7 +702,7 @@ Fields descriptions:
Get a specific field:: Get a specific field::
# curl http://localhost:61208/api/4/now/iso # curl http://localhost:61208/api/4/now/iso
{"iso": "2024-05-25T16:10:02+02:00"} {"iso": "2024-06-08T10:22:29+02:00"}
GET percpu GET percpu
---------- ----------
@ -719,7 +769,7 @@ Get plugin stats::
"port": 0, "port": 0,
"refresh": 30, "refresh": 30,
"rtt_warning": None, "rtt_warning": None,
"status": 0.005044, "status": 0.005593,
"timeout": 3}] "timeout": 3}]
Fields descriptions: Fields descriptions:
@ -747,7 +797,7 @@ Get a specific item when field matches the given value::
"port": 0, "port": 0,
"refresh": 30, "refresh": 30,
"rtt_warning": None, "rtt_warning": None,
"status": 0.005044, "status": 0.005593,
"timeout": 3}]} "timeout": 3}]}
GET processcount GET processcount
@ -756,7 +806,7 @@ GET processcount
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/processcount # curl http://localhost:61208/api/4/processcount
{"pid_max": 0, "running": 0, "sleeping": 292, "thread": 1710, "total": 432} {"pid_max": 0, "running": 0, "sleeping": 279, "thread": 1568, "total": 410}
Fields descriptions: Fields descriptions:
@ -769,7 +819,7 @@ Fields descriptions:
Get a specific field:: Get a specific field::
# curl http://localhost:61208/api/4/processcount/total # curl http://localhost:61208/api/4/processcount/total
{"total": 432} {"total": 410}
GET processlist GET processlist
--------------- ---------------
@ -809,14 +859,14 @@ GET quicklook
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/quicklook # curl http://localhost:61208/api/4/quicklook
{"cpu": 0.0, {"cpu": 16.7,
"cpu_hz": 4475000000.0, "cpu_hz": 4475000000.0,
"cpu_hz_current": 1610092062.5, "cpu_hz_current": 1230624312.5,
"cpu_log_core": 16, "cpu_log_core": 16,
"cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H", "cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H",
"cpu_phys_core": 10, "cpu_phys_core": 10,
"load": 3.9, "load": 3.4,
"mem": 51.5, "mem": 32.1,
"percpu": [{"cpu_number": 0, "percpu": [{"cpu_number": 0,
"guest": 0.0, "guest": 0.0,
"guest_nice": 0.0, "guest_nice": 0.0,
@ -924,7 +974,7 @@ Get plugin stats::
{"cpu_number": 8, {"cpu_number": 8,
"guest": 0.0, "guest": 0.0,
"guest_nice": 0.0, "guest_nice": 0.0,
"idle": 0.0, "idle": 1.0,
"iowait": 0.0, "iowait": 0.0,
"irq": 0.0, "irq": 0.0,
"key": "cpu_number", "key": "cpu_number",
@ -932,7 +982,7 @@ Get plugin stats::
"softirq": 0.0, "softirq": 0.0,
"steal": 0.0, "steal": 0.0,
"system": 0.0, "system": 0.0,
"total": 100.0, "total": 99.0,
"user": 0.0}, "user": 0.0},
{"cpu_number": 9, {"cpu_number": 9,
"guest": 0.0, "guest": 0.0,
@ -1063,14 +1113,14 @@ Get plugin stats::
"label": "Ambient", "label": "Ambient",
"type": "temperature_core", "type": "temperature_core",
"unit": "C", "unit": "C",
"value": 38, "value": 36,
"warning": 0}, "warning": 0},
{"critical": None, {"critical": None,
"key": "label", "key": "label",
"label": "Ambient 3", "label": "Ambient 3",
"type": "temperature_core", "type": "temperature_core",
"unit": "C", "unit": "C",
"value": 35, "value": 29,
"warning": 0}] "warning": 0}]
Fields descriptions: Fields descriptions:
@ -1131,7 +1181,7 @@ Get a specific item when field matches the given value::
"label": "Ambient", "label": "Ambient",
"type": "temperature_core", "type": "temperature_core",
"unit": "C", "unit": "C",
"value": 38, "value": 36,
"warning": 0}]} "warning": 0}]}
GET smart GET smart
@ -1175,7 +1225,7 @@ GET uptime
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/uptime # curl http://localhost:61208/api/4/uptime
"11 days, 17:02:42" "0:14:44"
GET version GET version
----------- -----------
@ -1183,7 +1233,7 @@ GET version
Get plugin stats:: Get plugin stats::
# curl http://localhost:61208/api/4/version # curl http://localhost:61208/api/4/version
"4.0.7" "4.0.8"
GET wifi GET wifi
-------- --------
@ -1192,8 +1242,8 @@ Get plugin stats::
# curl http://localhost:61208/api/4/wifi # curl http://localhost:61208/api/4/wifi
[{"key": "ssid", [{"key": "ssid",
"quality_level": -62.0, "quality_level": -66.0,
"quality_link": 48.0, "quality_link": 44.0,
"ssid": "wlp0s20f3"}] "ssid": "wlp0s20f3"}]
Get a specific field:: Get a specific field::
@ -1205,8 +1255,8 @@ Get a specific item when field matches the given value::
# curl http://localhost:61208/api/4/wifi/ssid/wlp0s20f3 # curl http://localhost:61208/api/4/wifi/ssid/wlp0s20f3
{"wlp0s20f3": [{"key": "ssid", {"wlp0s20f3": [{"key": "ssid",
"quality_level": -62.0, "quality_level": -66.0,
"quality_link": 48.0, "quality_link": 44.0,
"ssid": "wlp0s20f3"}]} "ssid": "wlp0s20f3"}]}
GET all stats GET all stats
@ -1251,34 +1301,34 @@ GET stats history
History of a plugin:: History of a plugin::
# curl http://localhost:61208/api/4/cpu/history # curl http://localhost:61208/api/4/cpu/history
{"system": [["2024-05-25T16:10:03.933630", 0.0], {"system": [["2024-06-08T10:22:30.631056", 0.0],
["2024-05-25T16:10:04.963537", 0.0], ["2024-06-08T10:22:31.667772", 1.0],
["2024-05-25T16:10:06.018120", 0.0]], ["2024-06-08T10:22:32.723737", 1.0]],
"user": [["2024-05-25T16:10:03.933619", 1.0], "user": [["2024-06-08T10:22:30.631046", 1.0],
["2024-05-25T16:10:04.963532", 1.0], ["2024-06-08T10:22:31.667765", 1.0],
["2024-05-25T16:10:06.018110", 1.0]]} ["2024-06-08T10:22:32.723728", 1.0]]}
Limit history to last 2 values:: Limit history to last 2 values::
# curl http://localhost:61208/api/4/cpu/history/2 # curl http://localhost:61208/api/4/cpu/history/2
{"system": [["2024-05-25T16:10:04.963537", 0.0], {"system": [["2024-06-08T10:22:31.667772", 1.0],
["2024-05-25T16:10:06.018120", 0.0]], ["2024-06-08T10:22:32.723737", 1.0]],
"user": [["2024-05-25T16:10:04.963532", 1.0], "user": [["2024-06-08T10:22:31.667765", 1.0],
["2024-05-25T16:10:06.018110", 1.0]]} ["2024-06-08T10:22:32.723728", 1.0]]}
History for a specific field:: History for a specific field::
# curl http://localhost:61208/api/4/cpu/system/history # curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-05-25T16:10:02.825532", 0.0], {"system": [["2024-06-08T10:22:29.515717", 0.0],
["2024-05-25T16:10:03.933630", 0.0], ["2024-06-08T10:22:30.631056", 0.0],
["2024-05-25T16:10:04.963537", 0.0], ["2024-06-08T10:22:31.667772", 1.0],
["2024-05-25T16:10:06.018120", 0.0]]} ["2024-06-08T10:22:32.723737", 1.0]]}
Limit history for a specific field to last 2 values:: Limit history for a specific field to last 2 values::
# curl http://localhost:61208/api/4/cpu/system/history # curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-05-25T16:10:04.963537", 0.0], {"system": [["2024-06-08T10:22:31.667772", 1.0],
["2024-05-25T16:10:06.018120", 0.0]]} ["2024-06-08T10:22:32.723737", 1.0]]}
GET limits (used for thresholds) GET limits (used for thresholds)
-------------------------------- --------------------------------

View File

@ -21,6 +21,7 @@ You can place your ``glances.conf`` file in the following locations:
``*BSD`` ~/.config/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ ``*BSD`` ~/.config/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/
``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/ ``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/
``Windows`` %APPDATA%\\glances\\glances.conf ``Windows`` %APPDATA%\\glances\\glances.conf
``All`` + <venv_root_folder>/share/doc/glances/
==================== ============================================================= ==================== =============================================================
- On Windows XP, ``%APPDATA%`` is: ``C:\Documents and Settings\<USERNAME>\Application Data``. - On Windows XP, ``%APPDATA%`` is: ``C:\Documents and Settings\<USERNAME>\Application Data``.
@ -59,17 +60,41 @@ than a second one concerning the user interface:
.. code-block:: ini .. code-block:: ini
[outputs] [outputs]
# Options for all UIs
#--------------------
# Separator in the Curses and WebUI interface (between top and others plugins) # Separator in the Curses and WebUI interface (between top and others plugins)
separator=True separator=True
# Set the the Curses and WebUI interface left menu plugin list (comma-separated) # Set the the Curses and WebUI interface left menu plugin list (comma-separated)
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
# Limit the number of processes to display (for the WebUI) # Limit the number of processes to display (in the WebUI)
max_processes_display=25 max_processes_display=25
# Set the URL prefix (for the WebUI and the API) # Options for WebUI
#------------------
# Set URL prefix for the WebUI and the API
# Example: url_prefix=/glances/ => http://localhost/glances/ # Example: url_prefix=/glances/ => http://localhost/glances/
# The final / is mandatory # Note: The final / is mandatory
# Default is no prefix (/) # Default is no prefix (/)
#url_prefix=/glances/ #url_prefix=/glances/
# Set root path for WebUI statics files
# Why ? On Debian system, WebUI statics files are not provided.
# You can download it in a specific folder
# thanks to https://github.com/nicolargo/glances/issues/2021
# then configure this folder with the webui_root_path key
# Default is folder where glances_restfull_api.py is hosted
#webui_root_path=
# CORS options
# Comma separated list of origins that should be permitted to make cross-origin requests.
# Default is *
#cors_origins=*
# Indicate that cookies should be supported for cross-origin requests.
# Default is True
#cors_credentials=True
# Comma separated list of HTTP methods that should be allowed for cross-origin requests.
# Default is *
#cors_methods=*
# Comma separated list of HTTP request headers that should be supported for cross-origin requests.
# Default is *
#cors_headers=*
Each plugin, export module, and application monitoring process (AMP) can Each plugin, export module, and application monitoring process (AMP) can
have a section. Below is an example for the CPU plugin: have a section. Below is an example for the CPU plugin:

View File

@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
.. ..
.TH "GLANCES" "1" "May 25, 2024" "4.0.7" "Glances" .TH "GLANCES" "1" "Jun 08, 2024" "4.0.8" "Glances"
.SH NAME .SH NAME
glances \- An eye on your system glances \- An eye on your system
.SH SYNOPSIS .SH SYNOPSIS
@ -585,6 +585,15 @@ T} T{
%APPDATA%\eglances\eglances.conf %APPDATA%\eglances\eglances.conf
T} T}
_ _
T{
\fBAll\fP
T} T{
.INDENT 0.0
.IP \(bu 2
<venv_root_folder>/share/doc/glances/
.UNINDENT
T}
_
.TE .TE
.INDENT 0.0 .INDENT 0.0
.IP \(bu 2 .IP \(bu 2
@ -632,17 +641,41 @@ than a second one concerning the user interface:
.nf .nf
.ft C .ft C
[outputs] [outputs]
# Options for all UIs
#\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
# Separator in the Curses and WebUI interface (between top and others plugins) # Separator in the Curses and WebUI interface (between top and others plugins)
separator=True separator=True
# Set the the Curses and WebUI interface left menu plugin list (comma\-separated) # Set the the Curses and WebUI interface left menu plugin list (comma\-separated)
#left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now #left_menu=network,wifi,connections,ports,diskio,fs,irq,folders,raid,smart,sensors,now
# Limit the number of processes to display (for the WebUI) # Limit the number of processes to display (in the WebUI)
max_processes_display=25 max_processes_display=25
# Set the URL prefix (for the WebUI and the API) # Options for WebUI
#\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
# Set URL prefix for the WebUI and the API
# Example: url_prefix=/glances/ => http://localhost/glances/ # Example: url_prefix=/glances/ => http://localhost/glances/
# The final / is mandatory # Note: The final / is mandatory
# Default is no prefix (/) # Default is no prefix (/)
#url_prefix=/glances/ #url_prefix=/glances/
# Set root path for WebUI statics files
# Why ? On Debian system, WebUI statics files are not provided.
# You can download it in a specific folder
# thanks to https://github.com/nicolargo/glances/issues/2021
# then configure this folder with the webui_root_path key
# Default is folder where glances_restfull_api.py is hosted
#webui_root_path=
# CORS options
# Comma separated list of origins that should be permitted to make cross\-origin requests.
# Default is *
#cors_origins=*
# Indicate that cookies should be supported for cross\-origin requests.
# Default is True
#cors_credentials=True
# Comma separated list of HTTP methods that should be allowed for cross\-origin requests.
# Default is *
#cors_methods=*
# Comma separated list of HTTP request headers that should be supported for cross\-origin requests.
# Default is *
#cors_headers=*
.ft P .ft P
.fi .fi
.UNINDENT .UNINDENT

View File

@ -19,7 +19,7 @@ import tracemalloc
# Global name # Global name
# Version should start and end with a numerical char # Version should start and end with a numerical char
# See https://packaging.python.org/specifications/core-metadata/#version # See https://packaging.python.org/specifications/core-metadata/#version
__version__ = '4.0.7' __version__ = '4.0.8'
__apiversion__ = '4' __apiversion__ = '4'
__author__ = 'Nicolas Hennion <nicolas@nicolargo.com>' __author__ = 'Nicolas Hennion <nicolas@nicolargo.com>'
__license__ = 'LGPLv3' __license__ = 'LGPLv3'

View File

@ -24,11 +24,9 @@ one_line=false
command=foo status command=foo status
""" """
from subprocess import STDOUT, CalledProcessError, check_output
from glances.amps.amp import GlancesAmp from glances.amps.amp import GlancesAmp
from glances.globals import to_ascii, u
from glances.logger import logger from glances.logger import logger
from glances.secure import secure_popen
class Amp(GlancesAmp): class Amp(GlancesAmp):
@ -68,10 +66,7 @@ class Amp(GlancesAmp):
# Run command(s) # Run command(s)
# Comma separated commands can be executed # Comma separated commands can be executed
try: try:
msg = '' self.set_result(secure_popen(res).rstrip())
for cmd in res.split(';'): except Exception as e:
msg += u(check_output(cmd.split(), stderr=STDOUT))
self.set_result(to_ascii(msg.rstrip()))
except CalledProcessError as e:
self.set_result(e.output) self.set_result(e.output)
return self.result() return self.result()

View File

@ -33,11 +33,10 @@ one_line=true
service_cmd=/usr/bin/service --status-all service_cmd=/usr/bin/service --status-all
""" """
from subprocess import STDOUT, check_output
from glances.amps.amp import GlancesAmp from glances.amps.amp import GlancesAmp
from glances.globals import iteritems from glances.globals import iteritems
from glances.logger import logger from glances.logger import logger
from glances.secure import secure_popen
class Amp(GlancesAmp): class Amp(GlancesAmp):
@ -58,8 +57,9 @@ class Amp(GlancesAmp):
# Get the systemctl status # Get the systemctl status
logger.debug('{}: Update stats using service {}'.format(self.NAME, self.get('service_cmd'))) logger.debug('{}: Update stats using service {}'.format(self.NAME, self.get('service_cmd')))
try: try:
res = check_output(self.get('service_cmd').split(), stderr=STDOUT).decode('utf-8') # res = check_output(self.get('service_cmd').split(), stderr=STDOUT).decode('utf-8')
except OSError as e: res = secure_popen(self.get('service_cmd'))
except Exception as e:
logger.debug(f'{self.NAME}: Error while executing service ({e})') logger.debug(f'{self.NAME}: Error while executing service ({e})')
else: else:
status = {'running': 0, 'stopped': 0, 'upstart': 0} status = {'running': 0, 'stopped': 0, 'upstart': 0}

View File

@ -11,7 +11,7 @@
import sys import sys
import time import time
import ujson import orjson
from glances import __version__ from glances import __version__
from glances.globals import Fault, ProtocolError, ServerProxy, Transport from glances.globals import Fault, ProtocolError, ServerProxy, Transport
@ -118,7 +118,7 @@ class GlancesClient:
if __version__.split('.')[0] == client_version.split('.')[0]: if __version__.split('.')[0] == client_version.split('.')[0]:
# Init stats # Init stats
self.stats = GlancesStatsClient(config=self.config, args=self.args) self.stats = GlancesStatsClient(config=self.config, args=self.args)
self.stats.set_plugins(ujson.loads(self.client.getAllPlugins())) self.stats.set_plugins(orjson.loads(self.client.getAllPlugins()))
logger.debug(f"Client version: {__version__} / Server version: {client_version}") logger.debug(f"Client version: {__version__} / Server version: {client_version}")
else: else:
self.log_and_exit( self.log_and_exit(
@ -195,7 +195,7 @@ class GlancesClient:
""" """
# Update the stats # Update the stats
try: try:
server_stats = ujson.loads(self.client.getAll()) server_stats = orjson.loads(self.client.getAll())
except OSError: except OSError:
# Client cannot get server stats # Client cannot get server stats
return "Disconnected" return "Disconnected"

View File

@ -10,7 +10,7 @@
import threading import threading
import ujson import orjson
from glances.autodiscover import GlancesAutoDiscoverServer from glances.autodiscover import GlancesAutoDiscoverServer
from glances.client import GlancesClient, GlancesClientTransport from glances.client import GlancesClient, GlancesClientTransport
@ -95,12 +95,12 @@ class GlancesClientBrowser:
# Mandatory stats # Mandatory stats
try: try:
# CPU% # CPU%
cpu_percent = 100 - ujson.loads(s.getCpu())['idle'] cpu_percent = 100 - orjson.loads(s.getPlugin('cpu'))['idle']
server['cpu_percent'] = f'{cpu_percent:.1f}' server['cpu_percent'] = f'{cpu_percent:.1f}'
# MEM% # MEM%
server['mem_percent'] = ujson.loads(s.getMem())['percent'] server['mem_percent'] = orjson.loads(s.getPlugin('mem'))['percent']
# OS (Human Readable name) # OS (Human Readable name)
server['hr_name'] = ujson.loads(s.getSystem())['hr_name'] server['hr_name'] = orjson.loads(s.getPlugin('system'))['hr_name']
except (OSError, Fault, KeyError) as e: except (OSError, Fault, KeyError) as e:
logger.debug(f"Error while grabbing stats form server ({e})") logger.debug(f"Error while grabbing stats form server ({e})")
server['status'] = 'OFFLINE' server['status'] = 'OFFLINE'
@ -120,7 +120,7 @@ class GlancesClientBrowser:
# Optional stats (load is not available on Windows OS) # Optional stats (load is not available on Windows OS)
try: try:
# LOAD # LOAD
load_min5 = ujson.loads(s.getLoad())['min5'] load_min5 = orjson.loads(s.getPlugin('load'))['min5']
server['load_min5'] = f'{load_min5:.2f}' server['load_min5'] = f'{load_min5:.2f}'
except Exception as e: except Exception as e:
logger.warning(f"Error while grabbing stats form server ({e})") logger.warning(f"Error while grabbing stats form server ({e})")

View File

@ -81,16 +81,29 @@ def default_config_dir():
- Linux, SunOS, *BSD, macOS: /usr/share/doc (as defined in the setup.py files) - Linux, SunOS, *BSD, macOS: /usr/share/doc (as defined in the setup.py files)
- Windows: %APPDATA%\glances - Windows: %APPDATA%\glances
""" """
if LINUX or SUNOS or BSD or MACOS: path = []
path = '/usr/share/doc' # Add venv path (solve issue #2803)
else: if in_virtualenv():
path = os.environ.get('APPDATA') path.append(os.path.join(sys.prefix, 'share', 'doc', 'glances'))
if path is None:
path = ''
else:
path = os.path.join(path, 'glances')
return [path] # Add others system path
if LINUX or SUNOS or BSD or MACOS:
path.append('/usr/share/doc')
else:
path.append(os.environ.get('APPDATA'))
return path
def in_virtualenv():
# Source: https://stackoverflow.com/questions/1871549/how-to-determine-if-python-is-running-inside-a-virtualenv/1883251#1883251
return sys.prefix != get_base_prefix_compat()
def get_base_prefix_compat():
"""Get base/real prefix, or sys.prefix if there is none."""
# Source: https://stackoverflow.com/questions/1871549/how-to-determine-if-python-is-running-inside-a-virtualenv/1883251#1883251
return getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix
class Config: class Config:
@ -104,6 +117,7 @@ class Config:
self.config_dir = config_dir self.config_dir = config_dir
self.config_filename = 'glances.conf' self.config_filename = 'glances.conf'
self._loaded_config_file = None self._loaded_config_file = None
self._config_file_paths = self.config_file_paths()
# Re pattern for optimize research of `foo` # Re pattern for optimize research of `foo`
self.re_pattern = re.compile(r'(\`.+?\`)') self.re_pattern = re.compile(r'(\`.+?\`)')
@ -151,7 +165,7 @@ class Config:
def read(self): def read(self):
"""Read the config file, if it exists. Using defaults otherwise.""" """Read the config file, if it exists. Using defaults otherwise."""
for config_file in self.config_file_paths(): for config_file in self._config_file_paths:
logger.debug(f'Search glances.conf file in {config_file}') logger.debug(f'Search glances.conf file in {config_file}')
if os.path.exists(config_file): if os.path.exists(config_file):
try: try:

View File

@ -23,7 +23,7 @@ class CpuPercent:
self.percpu_percent = [] self.percpu_percent = []
# Get CPU name # Get CPU name
self.__get_cpu_name() self.cpu_info['cpu_name'] = self.__get_cpu_name()
# cached_timer_cpu is the minimum time interval between stats updates # cached_timer_cpu is the minimum time interval between stats updates
# since last update is passed (will retrieve old cached info instead) # since last update is passed (will retrieve old cached info instead)
@ -71,12 +71,18 @@ class CpuPercent:
def __get_cpu_name(self): def __get_cpu_name(self):
# Get the CPU name once from the /proc/cpuinfo file # Get the CPU name once from the /proc/cpuinfo file
# TODO: Multisystem... # Read the first line with the "model name"
ret = None
try: try:
self.cpu_info['cpu_name'] = open('/proc/cpuinfo').readlines()[4].split(':')[1].strip() cpuinfo_file = open('/proc/cpuinfo').readlines()
except (FileNotFoundError, PermissionError, IndexError, KeyError, AttributeError): except (FileNotFoundError, PermissionError):
self.cpu_info['cpu_name'] = 'CPU' pass
return self.cpu_info['cpu_name'] else:
for line in cpuinfo_file:
if line.startswith('model name'):
ret = line.split(':')[1].strip()
break
return ret if ret else 'CPU'
def __get_cpu(self): def __get_cpu(self):
"""Update and/or return the CPU using the psutil library.""" """Update and/or return the CPU using the psutil library."""

View File

@ -33,7 +33,7 @@ from urllib.request import Request, urlopen
from xmlrpc.client import Fault, ProtocolError, Server, ServerProxy, Transport from xmlrpc.client import Fault, ProtocolError, Server, ServerProxy, Transport
from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer from xmlrpc.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
import ujson import orjson
# Correct issue #1025 by monkey path the xmlrpc lib # Correct issue #1025 by monkey path the xmlrpc lib
from defusedxml.xmlrpc import monkey_patch from defusedxml.xmlrpc import monkey_patch
@ -309,9 +309,9 @@ def json_dumps(data):
Manage the issue #815 for Windows OS with UnicodeDecodeError catching. Manage the issue #815 for Windows OS with UnicodeDecodeError catching.
""" """
try: try:
return ujson.dumps(data) return orjson.dumps(data)
except UnicodeDecodeError: except UnicodeDecodeError:
return ujson.dumps(data, ensure_ascii=False) return orjson.dumps(data, ensure_ascii=False)
def dictlist(data, item): def dictlist(data, item):

View File

@ -249,15 +249,19 @@ class GlancesCursesBrowser(_GlancesCurses):
screen_x = self.screen.getmaxyx()[1] screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0] screen_y = self.screen.getmaxyx()[0]
stats_max = screen_y - 3 stats_max = screen_y - 3
stats_len = len(stats)
self._page_max_lines = stats_max self._page_max_lines = stats_max
self._page_max = int(math.ceil(stats_len / stats_max)) self._page_max = int(math.ceil(len(stats) / stats_max))
# Init position
x = 0
y = 0
# Display top header # Display header
x, y = self.__display_header(stats, 0, 0, screen_x, screen_y)
# Display Glances server list
# ================================
return self.__display_server_list(stats, x, y, screen_x, screen_y)
def __display_header(self, stats, x, y, screen_x, screen_y):
stats_len = len(stats)
stats_max = screen_y - 3
if stats_len == 0: if stats_len == 0:
if self.first_scan and not self.args.disable_autodiscover: if self.first_scan and not self.args.disable_autodiscover:
msg = 'Glances is scanning your network. Please wait...' msg = 'Glances is scanning your network. Please wait...'
@ -282,11 +286,14 @@ class GlancesCursesBrowser(_GlancesCurses):
msg = f'{page_lines} servers displayed.({self._current_page + 1}/{self._page_max}) {status_count}' msg = f'{page_lines} servers displayed.({self._current_page + 1}/{self._page_max}) {status_count}'
self.term_window.addnstr(y + 1, x, msg, screen_x - x) self.term_window.addnstr(y + 1, x, msg, screen_x - x)
if stats_len == 0: return x, y
def __display_server_list(self, stats, x, y, screen_x, screen_y):
if len(stats) == 0:
# No server to display
return False return False
# Display the Glances server list stats_max = screen_y - 3
# ================================
# Table of table # Table of table
# Item description: [stats_id, column name, column size] # Item description: [stats_id, column name, column size]

View File

@ -130,11 +130,11 @@ class GlancesRestfulApi:
# https://fastapi.tiangolo.com/tutorial/cors/ # https://fastapi.tiangolo.com/tutorial/cors/
self._app.add_middleware( self._app.add_middleware(
CORSMiddleware, CORSMiddleware,
# allow_origins=["*"], # Related to https://github.com/nicolargo/glances/issues/2812
allow_origins=[self.bind_url], allow_origins=config.get_list_value('outputs', 'cors_origins', default=["*"]),
allow_credentials=True, allow_credentials=config.get_bool_value('outputs', 'cors_credentials', default=True),
allow_methods=["*"], allow_methods=config.get_list_value('outputs', 'cors_methods', default=["*"]),
allow_headers=["*"], allow_headers=config.get_list_value('outputs', 'cors_headers', default=["*"]),
) )
# FastAPI Enable GZIP compression # FastAPI Enable GZIP compression

View File

@ -218,23 +218,23 @@ body {
padding-left: 10px; padding-left: 10px;
} }
/* Loading page */ // /* Loading page */
#loading-page .glances-logo { // #loading-page .glances-logo {
background: url('../images/glances.png') no-repeat center center; // background: url('../images/glances.png') no-repeat center center;
background-size: contain; // background-size: contain;
} // }
@media (max-width: 750px) { // @media (max-width: 750px) {
#loading-page .glances-logo { // #loading-page .glances-logo {
height: 400px; // height: 400px;
} // }
} // }
@media (min-width: 750px) { // @media (min-width: 750px) {
#loading-page .glances-logo { // #loading-page .glances-logo {
height: 500px; // height: 500px;
} // }
} // }
/* /*

View File

@ -1,7 +1,6 @@
<template> <template>
<div v-if="!dataLoaded" class="container-fluid" id="loading-page"> <div v-if="!dataLoaded" class="container-fluid" id="loading-page">
<div class="glances-logo"></div> <div class="loader">Glances is loading...</div>
<div class="loader">Loading...</div>
</div> </div>
<glances-help v-else-if="args.help_tag"></glances-help> <glances-help v-else-if="args.help_tag"></glances-help>
<main v-else> <main v-else>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@
import threading import threading
from ujson import loads from orjson import loads
from glances.globals import queue, urlopen_auth from glances.globals import queue, urlopen_auth
from glances.logger import logger from glances.logger import logger

View File

@ -88,6 +88,11 @@ class PluginModel(GlancesPluginModel):
self.hide_zero = False self.hide_zero = False
self.hide_zero_fields = ['bytes_recv', 'bytes_sent'] self.hide_zero_fields = ['bytes_recv', 'bytes_sent']
# Add support for automatically hiding network interfaces that are down
# or that don't have any IP addresses #2799
self.hide_no_up = config.get_bool_value(self.plugin_name, 'hide_no_up', default=False)
self.hide_no_ip = config.get_bool_value(self.plugin_name, 'hide_no_ip', default=False)
# Force a first update because we need two updates to have the first stat # Force a first update because we need two updates to have the first stat
self.update() self.update()
self.refresh_timer.set(0) self.refresh_timer.set(0)
@ -140,10 +145,21 @@ class PluginModel(GlancesPluginModel):
net_status = {} net_status = {}
try: try:
net_status = psutil.net_if_stats() net_status = psutil.net_if_stats()
net_addrs = psutil.net_if_addrs()
except OSError as e: except OSError as e:
# see psutil #797/glances #1106 # see psutil #797/glances #1106
logger.debug(f'Can not get network interface status ({e})') logger.debug(f'Can not get network interface status ({e})')
# Filter interfaces (related to #2799)
if self.hide_no_up:
net_status = {k: v for k, v in net_status.items() if v.isup}
if self.hide_no_ip:
net_status = {
k: v
for k, v in net_status.items()
if k in net_addrs and any(a.family != psutil.AF_LINK for a in net_addrs[k])
}
for interface_name, interface_stat in net_io_counters.items(): for interface_name, interface_stat in net_io_counters.items():
# Do not take hidden interface into account # Do not take hidden interface into account
# or KeyError: 'eth0' when interface is not connected #1348 # or KeyError: 'eth0' when interface is not connected #1348

View File

@ -95,7 +95,7 @@ class GlancesPluginModel:
# Init the limits (configuration keys) dictionary # Init the limits (configuration keys) dictionary
self._limits = {} self._limits = {}
if config is not None: if config is not None:
logger.debug(f'Load section {self.plugin_name} in {config.config_file_paths()}') logger.debug(f'Load section {self.plugin_name} in Glances configuration file')
self.load_limits(config=config) self.load_limits(config=config)
# Init the alias (dictionnary) # Init the alias (dictionnary)

View File

@ -23,7 +23,7 @@ def secure_popen(cmd):
""" """
ret = '' ret = ''
# Split by multiple commands '&&' # Split by multiple commands (only '&&' separator is supported)
for c in cmd.split('&&'): for c in cmd.split('&&'):
ret += __secure_popen(c) ret += __secure_popen(c)

View File

@ -8,13 +8,14 @@
"""Manage the Glances server.""" """Manage the Glances server."""
import json
import socket import socket
import sys import sys
from base64 import b64decode from base64 import b64decode
from glances import __version__ from glances import __version__
from glances.autodiscover import GlancesAutoDiscoverClient from glances.autodiscover import GlancesAutoDiscoverClient
from glances.globals import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer, json_dumps from glances.globals import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
from glances.logger import logger from glances.logger import logger
from glances.stats_server import GlancesStatsServer from glances.stats_server import GlancesStatsServer
from glances.timer import Timer from glances.timer import Timer
@ -140,39 +141,28 @@ class GlancesInstance:
def getAll(self): def getAll(self):
# Update and return all the stats # Update and return all the stats
self.__update__() self.__update__()
return json_dumps(self.stats.getAll()) return json.dumps(self.stats.getAll())
def getAllPlugins(self): def getAllPlugins(self):
# Return the plugins list # Return the plugins list
return json_dumps(self.stats.getPluginsList()) return json.dumps(self.stats.getPluginsList())
def getAllLimits(self): def getAllLimits(self):
# Return all the plugins limits # Return all the plugins limits
return json_dumps(self.stats.getAllLimitsAsDict()) return json.dumps(self.stats.getAllLimitsAsDict())
def getAllViews(self): def getAllViews(self):
# Return all the plugins views # Return all the plugins views
return json_dumps(self.stats.getAllViewsAsDict()) return json.dumps(self.stats.getAllViewsAsDict())
def __getattr__(self, item): def getPlugin(self, plugin):
"""Overwrite the getattr method in case of attribute is not found. # Update and return the plugin stat
self.__update__()
return json.dumps(self.stats.get_plugin(plugin).get_raw())
The goal is to dynamically generate the API get'Stats'() methods. def getPluginView(self, plugin):
""" # Update and return the plugin view
header = 'get' return json.dumps(self.stats.get_plugin(plugin).get_views())
# Check if the attribute starts with 'get'
if item.startswith(header):
try:
# Update the stat
self.__update__()
# Return the attribute
return getattr(self.stats, item)
except Exception:
# The method is not found for the plugin
raise AttributeError(item)
else:
# Default behavior
raise AttributeError(item)
class GlancesServer: class GlancesServer:

View File

@ -52,7 +52,7 @@ class GlancesStats:
# Get the plugin instance # Get the plugin instance
plugin = self._plugins[plugname] plugin = self._plugins[plugname]
if hasattr(plugin, 'get_json_views'): if hasattr(plugin, 'get_json_views'):
# The method get_views exist, return it # The method get_json_views exist, return it
return getattr(plugin, 'get_json_views') return getattr(plugin, 'get_json_views')
# The method get_views is not found for the plugin # The method get_views is not found for the plugin
raise AttributeError(item) raise AttributeError(item)
@ -61,9 +61,9 @@ class GlancesStats:
plugname = item[len('get') :].lower() plugname = item[len('get') :].lower()
# Get the plugin instance # Get the plugin instance
plugin = self._plugins[plugname] plugin = self._plugins[plugname]
if hasattr(plugin, 'get_stats'): if hasattr(plugin, 'get_json'):
# The method get_stats exist, return it # The method get_json exist, return it
return getattr(plugin, 'get_stats') return getattr(plugin, 'get_json')
# The method get_stats is not found for the plugin # The method get_stats is not found for the plugin
raise AttributeError(item) raise AttributeError(item)
# Default behavior # Default behavior
@ -358,11 +358,17 @@ class GlancesStats:
return self._plugins return self._plugins
def get_plugin(self, plugin_name): def get_plugin(self, plugin_name):
"""Return the plugin name.""" """Return the plugin stats."""
if plugin_name in self._plugins: if plugin_name in self._plugins:
return self._plugins[plugin_name] return self._plugins[plugin_name]
return None return None
def get_plugin_view(self, plugin_name):
"""Return the plugin views."""
if plugin_name in self._plugins:
return self._plugins[plugin_name].get_views()
return None
def end(self): def end(self):
"""End of the Glances stats.""" """End of the Glances stats."""
# Close export modules # Close export modules

View File

@ -7,35 +7,32 @@ cassandra-driver
chevron chevron
docker>=6.1.1 docker>=6.1.1
elasticsearch elasticsearch
fastapi; python_version >= "3.8" fastapi
graphitesender graphitesender
hddtemp hddtemp
influxdb>=1.0.0 # For InfluxDB < 1.8 influxdb>=1.0.0 # For InfluxDB < 1.8
influxdb-client; python_version >= "3.7" # For InfluxDB >= 1.8 influxdb-client # For InfluxDB >= 1.8
jinja2 jinja2
kafka-python kafka-python
netifaces netifaces
nvidia-ml-py; python_version >= "3.5" nvidia-ml-py
packaging; python_version >= "3.7"
paho-mqtt paho-mqtt
pika pika
podman; python_version >= "3.6" podman
potsdb potsdb
prometheus_client prometheus_client
pycouchdb pycouchdb
pydantic pydantic
pygal pygal
pymdstat pymdstat
pymongo; python_version >= "3.7" pymongo
pySMART.smartx pySMART.smartx
pysnmp-lextudio; python_version >= "3.7" pysnmp-lextudio
python-dateutil python-dateutil
pyzmq pyzmq
requests requests
scandir; python_version < "3.5"
six six
sparklines sparklines
statsd statsd
uvicorn; python_version >= "3.8" uvicorn
wifi
zeroconf==0.131.0 # Waiting correction for ARMv7: https://github.com/python-zeroconf/python-zeroconf/issues/1372 zeroconf==0.131.0 # Waiting correction for ARMv7: https://github.com/python-zeroconf/python-zeroconf/issues/1372

View File

@ -1,8 +1,3 @@
[tool.black]
line-length = 120
skip-string-normalization = true
exclude = '\./glances/outputs/static/*'
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
target-version = "py38" target-version = "py38"

View File

@ -1,4 +1,4 @@
defusedxml defusedxml
orjson
packaging packaging
psutil>=5.6.7 psutil>=5.6.7
ujson>=5.4.0

View File

@ -1,5 +1,5 @@
name: glances name: glances
version: '4.0.7' version: '4.0.8'
summary: Glances an Eye on your system. A top/htop alternative. summary: Glances an Eye on your system. A top/htop alternative.
description: | description: |
@ -9,7 +9,7 @@ description: |
depending on the user interface size. depending on the user interface size.
base: core22 base: core22
grade: stable grade: stable # devel
confinement: strict confinement: strict
apps: apps:
@ -49,7 +49,7 @@ parts:
glances: glances:
plugin: python plugin: python
source: https://github.com/nicolargo/glances.git source: https://github.com/nicolargo/glances.git
source-branch: master source-branch: master # develop
python-requirements: python-requirements:
- requirements.txt - requirements.txt
- webui-requirements.txt - webui-requirements.txt

View File

@ -18,7 +18,7 @@ deps =
psutil psutil
defusedxml defusedxml
packaging packaging
ujson orjson
fastapi fastapi
uvicorn uvicorn
jinja2 jinja2

View File

@ -17,14 +17,24 @@ import time
import unittest import unittest
from glances import __version__ from glances import __version__
from glances.globals import ServerProxy from glances.client import GlancesClient
SERVER_HOST = 'localhost'
SERVER_PORT = 61234 SERVER_PORT = 61234
URL = f"http://localhost:{SERVER_PORT}"
pid = None pid = None
# Init the XML-RPC client # Init the XML-RPC client
client = ServerProxy(URL) class args:
client = SERVER_HOST
port = SERVER_PORT
username = ""
password = ""
time = 3
quiet = False
client = GlancesClient(args=args).client
# Unitest class # Unitest class
# ============== # ==============
@ -71,14 +81,16 @@ class TestGlances(unittest.TestCase):
print('INFO: [TEST_002] Get plugins list') print('INFO: [TEST_002] Get plugins list')
print(f"XML-RPC request: {method}") print(f"XML-RPC request: {method}")
req = json.loads(client.getAllPlugins()) req = json.loads(client.getAllPlugins())
print(req)
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
self.assertIn('cpu', req)
def test_003_system(self): def test_003_system(self):
"""System.""" """System."""
method = "getSystem()" method = "getSystem()"
print(f'INFO: [TEST_003] Method: {method}') print(f'INFO: [TEST_003] Method: {method}')
req = json.loads(client.getSystem()) req = json.loads(client.getPlugin('system'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
@ -87,16 +99,16 @@ class TestGlances(unittest.TestCase):
method = "getCpu(), getPerCpu(), getLoad() and getCore()" method = "getCpu(), getPerCpu(), getLoad() and getCore()"
print(f'INFO: [TEST_004] Method: {method}') print(f'INFO: [TEST_004] Method: {method}')
req = json.loads(client.getCpu()) req = json.loads(client.getPlugin('cpu'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
req = json.loads(client.getPerCpu()) req = json.loads(client.getPlugin('percpu'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
req = json.loads(client.getLoad()) req = json.loads(client.getPlugin('load'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
req = json.loads(client.getCore()) req = json.loads(client.getPlugin('core'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
def test_005_mem(self): def test_005_mem(self):
@ -104,10 +116,10 @@ class TestGlances(unittest.TestCase):
method = "getMem() and getMemSwap()" method = "getMem() and getMemSwap()"
print(f'INFO: [TEST_005] Method: {method}') print(f'INFO: [TEST_005] Method: {method}')
req = json.loads(client.getMem()) req = json.loads(client.getPlugin('mem'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
req = json.loads(client.getMemSwap()) req = json.loads(client.getPlugin('memswap'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
def test_006_net(self): def test_006_net(self):
@ -115,7 +127,7 @@ class TestGlances(unittest.TestCase):
method = "getNetwork()" method = "getNetwork()"
print(f'INFO: [TEST_006] Method: {method}') print(f'INFO: [TEST_006] Method: {method}')
req = json.loads(client.getNetwork()) req = json.loads(client.getPlugin('network'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
def test_007_disk(self): def test_007_disk(self):
@ -123,13 +135,13 @@ class TestGlances(unittest.TestCase):
method = "getFs(), getFolders() and getDiskIO()" method = "getFs(), getFolders() and getDiskIO()"
print(f'INFO: [TEST_007] Method: {method}') print(f'INFO: [TEST_007] Method: {method}')
req = json.loads(client.getFs()) req = json.loads(client.getPlugin('fs'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
req = json.loads(client.getFolders()) req = json.loads(client.getPlugin('folders'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
req = json.loads(client.getDiskIO()) req = json.loads(client.getPlugin('diskio'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
def test_008_sensors(self): def test_008_sensors(self):
@ -137,7 +149,7 @@ class TestGlances(unittest.TestCase):
method = "getSensors()" method = "getSensors()"
print(f'INFO: [TEST_008] Method: {method}') print(f'INFO: [TEST_008] Method: {method}')
req = json.loads(client.getSensors()) req = json.loads(client.getPlugin('sensors'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
def test_009_process(self): def test_009_process(self):
@ -145,10 +157,10 @@ class TestGlances(unittest.TestCase):
method = "getProcessCount() and getProcessList()" method = "getProcessCount() and getProcessList()"
print(f'INFO: [TEST_009] Method: {method}') print(f'INFO: [TEST_009] Method: {method}')
req = json.loads(client.getProcessCount()) req = json.loads(client.getPlugin('processcount'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
req = json.loads(client.getProcessList()) req = json.loads(client.getPlugin('processlist'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
def test_010_all_limits(self): def test_010_all_limits(self):
@ -173,7 +185,7 @@ class TestGlances(unittest.TestCase):
"""IRQS""" """IRQS"""
method = "getIrqs()" method = "getIrqs()"
print(f'INFO: [TEST_012] Method: {method}') print(f'INFO: [TEST_012] Method: {method}')
req = json.loads(client.getIrq()) req = json.loads(client.getPlugin('irq'))
self.assertIsInstance(req, list) self.assertIsInstance(req, list)
def test_013_plugin_views(self): def test_013_plugin_views(self):
@ -181,12 +193,13 @@ class TestGlances(unittest.TestCase):
method = "getViewsCpu()" method = "getViewsCpu()"
print(f'INFO: [TEST_013] Method: {method}') print(f'INFO: [TEST_013] Method: {method}')
req = json.loads(client.getViewsCpu()) req = json.loads(client.getPluginView('cpu'))
self.assertIsInstance(req, dict) self.assertIsInstance(req, dict)
def test_999_stop_server(self): def test_999_stop_server(self):
"""Stop the Glances Web Server.""" """Stop the Glances Web Server."""
print('INFO: [TEST_999] Stop the Glances Server') print('INFO: [TEST_999] Stop the Glances Server')
print(client.system.listMethods())
print("Stop the Glances Server") print("Stop the Glances Server")
pid.terminate() pid.terminate()