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
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v5.0.0
uses: docker/metadata-action@v5
with:
images: ${{ env.DEFAULT_DOCKER_IMAGE }}
labels: |
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
uses: docker/setup-qemu-action@v3
with:
@ -166,5 +159,7 @@ jobs:
platforms: ${{ matrix.os != 'ubuntu' && env.DOCKER_PLATFORMS || env.DOCKER_PLATFORMS_UBUNTU }}
target: ${{ matrix.tag.target }}
labels: ${{ steps.docker_meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
# GHA default behaviour overwrites last build cache. Causes alpine and ubuntu cache to overwrite each other.
# 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 !
===============
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
===============

View File

@ -86,7 +86,7 @@ Requirements
- ``psutil`` (better with latest version)
- ``defusedxml`` (in order to monkey patch xmlrpc)
- ``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*
@ -115,7 +115,6 @@ Optional dependencies:
- ``podman`` (for the Containers Podman monitoring support)
- ``potsdb`` (for the OpenTSDB export module)
- ``prometheus_client`` (for the Prometheus export module)
- ``py-cpuinfo`` (for the Quicklook CPU info module)
- ``pygal`` (for the graph export module)
- ``pymdstat`` (for RAID support) [Linux-only]
- ``pymongo`` (for the MongoDB export module)

View File

@ -23,12 +23,16 @@ history_size=1200
##############################################################################
[outputs]
# Options for all UIs
#--------------------
# Separator in the Curses and WebUI interface (between top and others plugins)
separator=True
# 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
# Limit the number of processes to display (in the WebUI)
max_processes_display=25
# Options for WebUI
#------------------
# Set URL prefix for the WebUI and the API
# Example: url_prefix=/glances/ => http://localhost/glances/
# Note: The final / is mandatory
@ -41,9 +45,22 @@ max_processes_display=25
# 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=*
##############################################################################
# plugins
# Plugins
##############################################################################
[quicklook]
@ -199,6 +216,10 @@ tx_critical=90
hide=docker.*,lo
# Define the list of wireless network interfaces to be show (comma-separated)
#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
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
#wlan0_rx_careful=4000000
@ -765,13 +786,13 @@ refresh=3
countmax=20
[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
# and the process count will not be displayed (countmin and countmax will be ignore)
enable=false
refresh=30
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]
# Use the NGinx AMP

View File

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

View File

@ -23,12 +23,16 @@ history_size=1200
##############################################################################
[outputs]
# Options for all UIs
#--------------------
# Separator in the Curses and WebUI interface (between top and others plugins)
separator=True
# 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
# Limit the number of processes to display (in the WebUI)
max_processes_display=25
# Options for WebUI
#------------------
# Set URL prefix for the WebUI and the API
# Example: url_prefix=/glances/ => http://localhost/glances/
# Note: The final / is mandatory
@ -41,6 +45,19 @@ max_processes_display=25
# 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=*
##############################################################################
# plugins
@ -199,6 +216,10 @@ tx_critical=90
#hide=docker.*,lo
# Define the list of wireless network interfaces to be show (comma-separated)
#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
# WLAN 0 Default limits (in bits per second aka bps) for interface bitrate
#wlan0_rx_careful=4000000
@ -765,13 +786,13 @@ refresh=3
countmax=20
[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
# and the process count will not be displayed (countmin and countmax will be ignore)
enable=false
refresh=30
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]
# Use the NGinx AMP

View File

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

View File

@ -61,9 +61,11 @@ For example:
enable=false
refresh=30
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:
.. code-block:: ini

View File

@ -17,6 +17,8 @@ In this case thresholds values are define in bps.
Additionally, you can define:
- a list of network interfaces to hide
- automatically hide interfaces not up
- automatically hide interfaces without IP address
- per-interface limit values
- aliases for interface name
@ -41,6 +43,10 @@ virtual docker interface (docker0, docker1, ...):
hide=docker.*,lo
# Define the list of network interfaces to show (comma-separated regexp)
#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
wlan0_alias=Wireless IF
# It is possible to overwrite the bitrate thresholds per interface

View File

@ -141,7 +141,7 @@ Get plugin stats::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.17942380905151367},
"timer": 0.24439311027526855},
{"count": 0,
"countmax": 20.0,
"countmin": None,
@ -150,7 +150,7 @@ Get plugin stats::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.17932724952697754}]
"timer": 0.2443389892578125}]
Fields descriptions:
@ -178,7 +178,7 @@ Get a specific item when field matches the given value::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.17942380905151367}]}
"timer": 0.24439311027526855}]}
GET cloud
---------
@ -219,7 +219,21 @@ GET containers
Get plugin stats::
# 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:
@ -240,6 +254,31 @@ Fields descriptions:
* **pod_name**: Pod name (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
--------
@ -265,18 +304,18 @@ Get plugin stats::
# curl http://localhost:61208/api/4/cpu
{"cpucore": 16,
"ctx_switches": 247960513,
"ctx_switches": 7772781,
"guest": 0.0,
"idle": 0.0,
"interrupts": 235154276,
"idle": 3.0,
"interrupts": 6643340,
"iowait": 0.0,
"irq": 0.0,
"nice": 0.0,
"soft_interrupts": 79848589,
"soft_interrupts": 1761276,
"steal": 0.0,
"syscalls": 0,
"system": 0.0,
"total": 0.0,
"total": 16.7,
"user": 1.0}
Fields descriptions:
@ -310,7 +349,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/cpu/total
{"total": 0.0}
{"total": 16.7}
GET diskio
----------
@ -320,14 +359,14 @@ Get plugin stats::
# curl http://localhost:61208/api/4/diskio
[{"disk_name": "nvme0n1",
"key": "disk_name",
"read_bytes": 4854106624,
"read_count": 207109,
"write_bytes": 15446680576,
"write_count": 891078},
"read_bytes": 3983976960,
"read_count": 115648,
"write_bytes": 3073111040,
"write_count": 91409},
{"disk_name": "nvme0n1p1",
"key": "disk_name",
"read_bytes": 7484416,
"read_count": 592,
"read_bytes": 7476224,
"read_count": 576,
"write_bytes": 1024,
"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
{"nvme0n1": [{"disk_name": "nvme0n1",
"key": "disk_name",
"read_bytes": 4854106624,
"read_count": 207109,
"write_bytes": 15446680576,
"write_count": 891078}]}
"read_bytes": 3983976960,
"read_count": 115648,
"write_bytes": 3073111040,
"write_count": 91409}]}
GET folders
-----------
@ -393,13 +432,13 @@ Get plugin stats::
# curl http://localhost:61208/api/4/fs
[{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
"free": 904231424000,
"free": 902284546048,
"fs_type": "ext4",
"key": "mnt_point",
"mnt_point": "/",
"percent": 5.1,
"percent": 5.3,
"size": 1003736440832,
"used": 48442511360}]
"used": 50389389312}]
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//
{"/": [{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
"free": 904231424000,
"free": 902284546048,
"fs_type": "ext4",
"key": "mnt_point",
"mnt_point": "/",
"percent": 5.1,
"percent": 5.3,
"size": 1003736440832,
"used": 48442511360}]}
"used": 50389389312}]}
GET gpu
-------
@ -500,9 +539,9 @@ Get plugin stats::
# curl http://localhost:61208/api/4/load
{"cpucore": 16,
"min1": 0.2802734375,
"min15": 0.6240234375,
"min5": 0.61376953125}
"min1": 0.560546875,
"min15": 0.54833984375,
"min5": 0.70166015625}
Fields descriptions:
@ -514,7 +553,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/load/min1
{"min1": 0.2802734375}
{"min1": 0.560546875}
GET mem
-------
@ -522,16 +561,16 @@ GET mem
Get plugin stats::
# curl http://localhost:61208/api/4/mem
{"active": 8875126784,
"available": 7975804928,
"buffers": 463708160,
"cached": 7862255616,
"free": 7975804928,
"inactive": 4694634496,
"percent": 51.4,
"shared": 849006592,
{"active": 5540470784,
"available": 11137699840,
"buffers": 226918400,
"cached": 6333566976,
"free": 11137699840,
"inactive": 3872489472,
"percent": 32.2,
"shared": 656637952,
"total": 16422486016,
"used": 8446681088}
"used": 5284786176}
Fields descriptions:
@ -558,13 +597,13 @@ GET memswap
Get plugin stats::
# curl http://localhost:61208/api/4/memswap
{"free": 4293914624,
{"free": 4294963200,
"percent": 0.0,
"sin": 0,
"sout": 114688,
"sout": 0,
"time_since_update": 1,
"total": 4294963200,
"used": 1048576}
"used": 0}
Fields descriptions:
@ -589,15 +628,26 @@ Get plugin stats::
# curl http://localhost:61208/api/4/network
[{"alias": None,
"bytes_all": 0,
"bytes_all_gauge": 1608088786,
"bytes_all_gauge": 422462144,
"bytes_recv": 0,
"bytes_recv_gauge": 1294928139,
"bytes_recv_gauge": 413272561,
"bytes_sent": 0,
"bytes_sent_gauge": 313160647,
"bytes_sent_gauge": 9189583,
"interface_name": "wlp0s20f3",
"key": "interface_name",
"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:
@ -619,22 +669,22 @@ Fields descriptions:
Get a specific field::
# 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::
# curl http://localhost:61208/api/4/network/interface_name/wlp0s20f3
{"wlp0s20f3": [{"alias": None,
"bytes_all": 0,
"bytes_all_gauge": 1608088786,
"bytes_all_gauge": 422462144,
"bytes_recv": 0,
"bytes_recv_gauge": 1294928139,
"bytes_recv_gauge": 413272561,
"bytes_sent": 0,
"bytes_sent_gauge": 313160647,
"bytes_sent_gauge": 9189583,
"interface_name": "wlp0s20f3",
"key": "interface_name",
"speed": 0,
"time_since_update": 0.18457698822021484}]}
"time_since_update": 0.24814295768737793}]}
GET now
-------
@ -642,7 +692,7 @@ GET now
Get plugin stats::
# 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:
@ -652,7 +702,7 @@ Fields descriptions:
Get a specific field::
# 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
----------
@ -719,7 +769,7 @@ Get plugin stats::
"port": 0,
"refresh": 30,
"rtt_warning": None,
"status": 0.005044,
"status": 0.005593,
"timeout": 3}]
Fields descriptions:
@ -747,7 +797,7 @@ Get a specific item when field matches the given value::
"port": 0,
"refresh": 30,
"rtt_warning": None,
"status": 0.005044,
"status": 0.005593,
"timeout": 3}]}
GET processcount
@ -756,7 +806,7 @@ GET processcount
Get plugin stats::
# 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:
@ -769,7 +819,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/processcount/total
{"total": 432}
{"total": 410}
GET processlist
---------------
@ -809,14 +859,14 @@ GET quicklook
Get plugin stats::
# curl http://localhost:61208/api/4/quicklook
{"cpu": 0.0,
{"cpu": 16.7,
"cpu_hz": 4475000000.0,
"cpu_hz_current": 1610092062.5,
"cpu_hz_current": 1230624312.5,
"cpu_log_core": 16,
"cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H",
"cpu_phys_core": 10,
"load": 3.9,
"mem": 51.5,
"load": 3.4,
"mem": 32.1,
"percpu": [{"cpu_number": 0,
"guest": 0.0,
"guest_nice": 0.0,
@ -924,7 +974,7 @@ Get plugin stats::
{"cpu_number": 8,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 1.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -932,7 +982,7 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 99.0,
"user": 0.0},
{"cpu_number": 9,
"guest": 0.0,
@ -1063,14 +1113,14 @@ Get plugin stats::
"label": "Ambient",
"type": "temperature_core",
"unit": "C",
"value": 38,
"value": 36,
"warning": 0},
{"critical": None,
"key": "label",
"label": "Ambient 3",
"type": "temperature_core",
"unit": "C",
"value": 35,
"value": 29,
"warning": 0}]
Fields descriptions:
@ -1131,7 +1181,7 @@ Get a specific item when field matches the given value::
"label": "Ambient",
"type": "temperature_core",
"unit": "C",
"value": 38,
"value": 36,
"warning": 0}]}
GET smart
@ -1175,7 +1225,7 @@ GET uptime
Get plugin stats::
# curl http://localhost:61208/api/4/uptime
"11 days, 17:02:42"
"0:14:44"
GET version
-----------
@ -1183,7 +1233,7 @@ GET version
Get plugin stats::
# curl http://localhost:61208/api/4/version
"4.0.7"
"4.0.8"
GET wifi
--------
@ -1192,8 +1242,8 @@ Get plugin stats::
# curl http://localhost:61208/api/4/wifi
[{"key": "ssid",
"quality_level": -62.0,
"quality_link": 48.0,
"quality_level": -66.0,
"quality_link": 44.0,
"ssid": "wlp0s20f3"}]
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
{"wlp0s20f3": [{"key": "ssid",
"quality_level": -62.0,
"quality_link": 48.0,
"quality_level": -66.0,
"quality_link": 44.0,
"ssid": "wlp0s20f3"}]}
GET all stats
@ -1251,34 +1301,34 @@ GET stats history
History of a plugin::
# curl http://localhost:61208/api/4/cpu/history
{"system": [["2024-05-25T16:10:03.933630", 0.0],
["2024-05-25T16:10:04.963537", 0.0],
["2024-05-25T16:10:06.018120", 0.0]],
"user": [["2024-05-25T16:10:03.933619", 1.0],
["2024-05-25T16:10:04.963532", 1.0],
["2024-05-25T16:10:06.018110", 1.0]]}
{"system": [["2024-06-08T10:22:30.631056", 0.0],
["2024-06-08T10:22:31.667772", 1.0],
["2024-06-08T10:22:32.723737", 1.0]],
"user": [["2024-06-08T10:22:30.631046", 1.0],
["2024-06-08T10:22:31.667765", 1.0],
["2024-06-08T10:22:32.723728", 1.0]]}
Limit history to last 2 values::
# curl http://localhost:61208/api/4/cpu/history/2
{"system": [["2024-05-25T16:10:04.963537", 0.0],
["2024-05-25T16:10:06.018120", 0.0]],
"user": [["2024-05-25T16:10:04.963532", 1.0],
["2024-05-25T16:10:06.018110", 1.0]]}
{"system": [["2024-06-08T10:22:31.667772", 1.0],
["2024-06-08T10:22:32.723737", 1.0]],
"user": [["2024-06-08T10:22:31.667765", 1.0],
["2024-06-08T10:22:32.723728", 1.0]]}
History for a specific field::
# curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-05-25T16:10:02.825532", 0.0],
["2024-05-25T16:10:03.933630", 0.0],
["2024-05-25T16:10:04.963537", 0.0],
["2024-05-25T16:10:06.018120", 0.0]]}
{"system": [["2024-06-08T10:22:29.515717", 0.0],
["2024-06-08T10:22:30.631056", 0.0],
["2024-06-08T10:22:31.667772", 1.0],
["2024-06-08T10:22:32.723737", 1.0]]}
Limit history for a specific field to last 2 values::
# curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-05-25T16:10:04.963537", 0.0],
["2024-05-25T16:10:06.018120", 0.0]]}
{"system": [["2024-06-08T10:22:31.667772", 1.0],
["2024-06-08T10:22:32.723737", 1.0]]}
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/
``macOS`` ~/.config/glances/, ~/Library/Application Support/glances/, /usr/local/etc/glances/, /usr/share/docs/glances/
``Windows`` %APPDATA%\\glances\\glances.conf
``All`` + <venv_root_folder>/share/doc/glances/
==================== =============================================================
- 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
[outputs]
# Options for all UIs
#--------------------
# Separator in the Curses and WebUI interface (between top and others plugins)
separator=True
# 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
# Limit the number of processes to display (for the WebUI)
# Limit the number of processes to display (in the WebUI)
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/
# The final / is mandatory
# Note: The final / is mandatory
# Default is no prefix (/)
#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
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]]
.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
glances \- An eye on your system
.SH SYNOPSIS
@ -585,6 +585,15 @@ T} T{
%APPDATA%\eglances\eglances.conf
T}
_
T{
\fBAll\fP
T} T{
.INDENT 0.0
.IP \(bu 2
<venv_root_folder>/share/doc/glances/
.UNINDENT
T}
_
.TE
.INDENT 0.0
.IP \(bu 2
@ -632,17 +641,41 @@ than a second one concerning the user interface:
.nf
.ft C
[outputs]
# Options for all UIs
#\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
# Separator in the Curses and WebUI interface (between top and others plugins)
separator=True
# 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
# Limit the number of processes to display (for the WebUI)
# Limit the number of processes to display (in the WebUI)
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/
# The final / is mandatory
# Note: The final / is mandatory
# Default is no prefix (/)
#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
.fi
.UNINDENT

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@
import sys
import time
import ujson
import orjson
from glances import __version__
from glances.globals import Fault, ProtocolError, ServerProxy, Transport
@ -118,7 +118,7 @@ class GlancesClient:
if __version__.split('.')[0] == client_version.split('.')[0]:
# Init stats
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}")
else:
self.log_and_exit(
@ -195,7 +195,7 @@ class GlancesClient:
"""
# Update the stats
try:
server_stats = ujson.loads(self.client.getAll())
server_stats = orjson.loads(self.client.getAll())
except OSError:
# Client cannot get server stats
return "Disconnected"

View File

@ -10,7 +10,7 @@
import threading
import ujson
import orjson
from glances.autodiscover import GlancesAutoDiscoverServer
from glances.client import GlancesClient, GlancesClientTransport
@ -95,12 +95,12 @@ class GlancesClientBrowser:
# Mandatory stats
try:
# 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}'
# MEM%
server['mem_percent'] = ujson.loads(s.getMem())['percent']
server['mem_percent'] = orjson.loads(s.getPlugin('mem'))['percent']
# 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:
logger.debug(f"Error while grabbing stats form server ({e})")
server['status'] = 'OFFLINE'
@ -120,7 +120,7 @@ class GlancesClientBrowser:
# Optional stats (load is not available on Windows OS)
try:
# LOAD
load_min5 = ujson.loads(s.getLoad())['min5']
load_min5 = orjson.loads(s.getPlugin('load'))['min5']
server['load_min5'] = f'{load_min5:.2f}'
except Exception as 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)
- Windows: %APPDATA%\glances
"""
if LINUX or SUNOS or BSD or MACOS:
path = '/usr/share/doc'
else:
path = os.environ.get('APPDATA')
if path is None:
path = ''
else:
path = os.path.join(path, 'glances')
path = []
# Add venv path (solve issue #2803)
if in_virtualenv():
path.append(os.path.join(sys.prefix, 'share', 'doc', '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:
@ -104,6 +117,7 @@ class Config:
self.config_dir = config_dir
self.config_filename = 'glances.conf'
self._loaded_config_file = None
self._config_file_paths = self.config_file_paths()
# Re pattern for optimize research of `foo`
self.re_pattern = re.compile(r'(\`.+?\`)')
@ -151,7 +165,7 @@ class Config:
def read(self):
"""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}')
if os.path.exists(config_file):
try:

View File

@ -23,7 +23,7 @@ class CpuPercent:
self.percpu_percent = []
# 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
# since last update is passed (will retrieve old cached info instead)
@ -71,12 +71,18 @@ class CpuPercent:
def __get_cpu_name(self):
# Get the CPU name once from the /proc/cpuinfo file
# TODO: Multisystem...
# Read the first line with the "model name"
ret = None
try:
self.cpu_info['cpu_name'] = open('/proc/cpuinfo').readlines()[4].split(':')[1].strip()
except (FileNotFoundError, PermissionError, IndexError, KeyError, AttributeError):
self.cpu_info['cpu_name'] = 'CPU'
return self.cpu_info['cpu_name']
cpuinfo_file = open('/proc/cpuinfo').readlines()
except (FileNotFoundError, PermissionError):
pass
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):
"""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.server import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
import ujson
import orjson
# Correct issue #1025 by monkey path the xmlrpc lib
from defusedxml.xmlrpc import monkey_patch
@ -309,9 +309,9 @@ def json_dumps(data):
Manage the issue #815 for Windows OS with UnicodeDecodeError catching.
"""
try:
return ujson.dumps(data)
return orjson.dumps(data)
except UnicodeDecodeError:
return ujson.dumps(data, ensure_ascii=False)
return orjson.dumps(data, ensure_ascii=False)
def dictlist(data, item):

View File

@ -249,15 +249,19 @@ class GlancesCursesBrowser(_GlancesCurses):
screen_x = self.screen.getmaxyx()[1]
screen_y = self.screen.getmaxyx()[0]
stats_max = screen_y - 3
stats_len = len(stats)
self._page_max_lines = stats_max
self._page_max = int(math.ceil(stats_len / stats_max))
# Init position
x = 0
y = 0
self._page_max = int(math.ceil(len(stats) / stats_max))
# 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 self.first_scan and not self.args.disable_autodiscover:
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}'
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
# Display the Glances server list
# ================================
stats_max = screen_y - 3
# Table of table
# Item description: [stats_id, column name, column size]

View File

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

View File

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

View File

@ -1,7 +1,6 @@
<template>
<div v-if="!dataLoaded" class="container-fluid" id="loading-page">
<div class="glances-logo"></div>
<div class="loader">Loading...</div>
<div class="loader">Glances is loading...</div>
</div>
<glances-help v-else-if="args.help_tag"></glances-help>
<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
from ujson import loads
from orjson import loads
from glances.globals import queue, urlopen_auth
from glances.logger import logger

View File

@ -88,6 +88,11 @@ class PluginModel(GlancesPluginModel):
self.hide_zero = False
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
self.update()
self.refresh_timer.set(0)
@ -140,10 +145,21 @@ class PluginModel(GlancesPluginModel):
net_status = {}
try:
net_status = psutil.net_if_stats()
net_addrs = psutil.net_if_addrs()
except OSError as e:
# see psutil #797/glances #1106
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():
# Do not take hidden interface into account
# or KeyError: 'eth0' when interface is not connected #1348

View File

@ -95,7 +95,7 @@ class GlancesPluginModel:
# Init the limits (configuration keys) dictionary
self._limits = {}
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)
# Init the alias (dictionnary)

View File

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

View File

@ -8,13 +8,14 @@
"""Manage the Glances server."""
import json
import socket
import sys
from base64 import b64decode
from glances import __version__
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.stats_server import GlancesStatsServer
from glances.timer import Timer
@ -140,39 +141,28 @@ class GlancesInstance:
def getAll(self):
# Update and return all the stats
self.__update__()
return json_dumps(self.stats.getAll())
return json.dumps(self.stats.getAll())
def getAllPlugins(self):
# Return the plugins list
return json_dumps(self.stats.getPluginsList())
return json.dumps(self.stats.getPluginsList())
def getAllLimits(self):
# Return all the plugins limits
return json_dumps(self.stats.getAllLimitsAsDict())
return json.dumps(self.stats.getAllLimitsAsDict())
def getAllViews(self):
# Return all the plugins views
return json_dumps(self.stats.getAllViewsAsDict())
return json.dumps(self.stats.getAllViewsAsDict())
def __getattr__(self, item):
"""Overwrite the getattr method in case of attribute is not found.
def getPlugin(self, plugin):
# 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.
"""
header = 'get'
# 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)
def getPluginView(self, plugin):
# Update and return the plugin view
return json.dumps(self.stats.get_plugin(plugin).get_views())
class GlancesServer:

View File

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

View File

@ -7,35 +7,32 @@ cassandra-driver
chevron
docker>=6.1.1
elasticsearch
fastapi; python_version >= "3.8"
fastapi
graphitesender
hddtemp
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
kafka-python
netifaces
nvidia-ml-py; python_version >= "3.5"
packaging; python_version >= "3.7"
nvidia-ml-py
paho-mqtt
pika
podman; python_version >= "3.6"
podman
potsdb
prometheus_client
pycouchdb
pydantic
pygal
pymdstat
pymongo; python_version >= "3.7"
pymongo
pySMART.smartx
pysnmp-lextudio; python_version >= "3.7"
pysnmp-lextudio
python-dateutil
pyzmq
requests
scandir; python_version < "3.5"
six
sparklines
statsd
uvicorn; python_version >= "3.8"
wifi
uvicorn
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]
line-length = 120
target-version = "py38"

View File

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

View File

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

View File

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

View File

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