Compare commits

...

41 Commits

Author SHA1 Message Date
renovate[bot]
03d7380456
Merge dc24e35786 into 6af2340c5c 2024-07-02 08:00:29 +00:00
renovate[bot]
dc24e35786
chore(deps): update dependency eslint-plugin-vue to v9.27.0 2024-07-02 08:00:27 +00:00
Bharath Vignesh J K
6af2340c5c chg: plugin(percpu) - show times based on host OS 2024-07-01 10:27:33 +05:30
RazCrimson
46abd4b4c9
Merge pull request #2860 from nicolargo/2859-glances-411-on-windows-attributeerror-cpupercent-object-has-no-attribute-cpu_percent
fix: cpu_percent - initialization timer bugs
2024-07-01 09:33:57 +05:30
Bharath Vignesh J K
2801add7e2 fix: cpu_percent - initialization timer bugs
fixes #2859
2024-07-01 09:33:05 +05:30
nicolargo
9b853d857b Merge branch 'develop' of github.com:nicolargo/glances into develop 2024-06-30 10:18:29 +02:00
Bharath Vignesh J K
99e65079a0 chg: plugin(docker) - add some typing 2024-06-30 03:52:09 +05:30
Bharath Vignesh J K
0e1d462c69 chg: exports (influxdb) - log warning message if export fails 2024-06-30 01:33:51 +05:30
nicolargo
f4c7ca01fe Correct codespell 2024-06-29 19:19:26 +02:00
nicolargo
60591630df Improve lisibility of OK_LOG 2024-06-29 19:01:25 +02:00
nicolargo
48103f167e Only test latest Python version on MacOS 2024-06-29 16:43:47 +02:00
nicolargo
154ae8e610 On the road (again) to Glances 4.2.0 2024-06-29 16:36:22 +02:00
nicolargo
86c2cd1d4a Glances 4.1.1 2024-06-29 16:28:50 +02:00
nicolargo
452fd6497e Sensors data is not exported using InfluxDB2 exporter #2856 2024-06-29 16:03:27 +02:00
nicolargo
ccfd8f0aa9 On the road of Glances 4.2.0 2024-06-29 10:01:07 +02:00
nicolargo
d91bfa2f8b Update docs 2024-06-29 09:55:33 +02:00
nicolargo
582ca07716 Glances 4.1.0 2024-06-29 09:49:36 +02:00
Nicolas Hennion
036eb976e9
Merge pull request #2854 from CognitiveDisson/patch-1
Remove duplicate line from the README file
2024-06-27 06:37:58 +02:00
Vadim Smal
2c5a1150bd
Remove duplicate line from the README file 2024-06-26 23:35:37 +01:00
nicolargo
f6066e5d46 PsUtil 6+ no longer check PID reused #2755 2024-06-26 18:56:31 +02:00
nicolargo
cff2e9fc4a Call process_iter.clear_cache() (PsUtil 6+) when Glances user force a refresh (F5 or CTRL-R) #2753 2024-06-26 18:36:10 +02:00
nicolargo
e5d5351d31 Add cpu model for CPU info (Raspberry PI 5) 2024-06-26 18:12:56 +02:00
nicolargo
be89ee0025 Merge remote-tracking branch 'origin/2616-raspberry-pi-cpu-info-is-not-correct' into develop 2024-06-26 18:10:00 +02:00
nicolargo
f4cd350221 Update Security messag 2024-06-23 10:50:35 +02:00
nicolargo
3928007169 Merge branch 'issue2849' into develop 2024-06-23 10:23:10 +02:00
nicolargo
eebd769c46 perCPU and CPU consumption display time to time a total of 100% #2849 2024-06-23 10:22:37 +02:00
nicolargo
2ee3c86e2b Graph export is broken if there is no graph section in Glances configuration file #2839 2024-06-22 10:37:00 +02:00
nicolargo
f5ecbf7a1f Merge branch 'clean-fs-plugin-code' into develop 2024-06-22 10:23:55 +02:00
nicolargo
267e7e2ff0 Clean FS Plugin code 2024-06-22 10:23:37 +02:00
Bharath Vignesh J K
1e2e36af23 chg: cpu_percent - support CPU Name for Raspberry Pi
fixes #2616
2024-06-22 02:32:20 +05:30
Continuous Integration
c153e71aa3 Continuous Integration Build Artifacts 2024-06-18 00:13:48 +00:00
RazCrimson
f9915d93e8
Merge pull request #2842 from nicolargo/2841-glances-api-status-check-returns-error-405-method-not-allowed
fix: RESTful API - HEAD request not supported on `{version}/status` as mentioned in docs
2024-06-18 05:38:14 +05:30
Bharath Vignesh J K
69c3a948ba fix: RESTful API - HEAD request not supported on {version}/status as mentioned in docs
fixes #2841
2024-06-18 05:36:18 +05:30
Nicolas Hennion
8c520e1d94
Update network.rst
Correct alias example
2024-06-17 13:40:11 +02:00
Nicolas Hennion
1edaed52de
Update doc-requirements.txt
Add defusedxml
2024-06-17 13:36:17 +02:00
Nicolas Hennion
752bd40a04
Update doc-requirements.txt
Add PsUutl as a requirement to generate doc.
https://readthedocs.org/projects/glances/builds/24710902/
2024-06-17 13:34:13 +02:00
nicolargo
fce3aac34e Add .redthedocs.yaml configuration file 2024-06-16 22:46:36 +02:00
nicolargo
9c482196f3 Add .redthedocs.yaml configuration file 2024-06-16 22:43:55 +02:00
nicolargo
3beb08b66e Merge branch 'issue977' into develop 2024-06-16 17:06:11 +02:00
nicolargo
0874e13f1d Enhance Glances browser color #977 2024-06-16 17:05:53 +02:00
Bharath Vignesh J K
8b4ef8c235 fix: containers (podman) - handle dead infra container with active children 2024-06-16 06:25:46 +05:30
49 changed files with 901 additions and 727 deletions

View File

@ -99,7 +99,8 @@ jobs:
runs-on: macos-14
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
# Only test the latest stable version
python-version: ["3.12"]
steps:

34
.readthedocs.yaml Normal file
View File

@ -0,0 +1,34 @@
# Read the Docs configuration file for Glances projects
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: doc-requirements.txt

View File

@ -6,7 +6,7 @@ In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
education, socioeconomic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards

View File

@ -93,7 +93,7 @@ lint: ## Lint the code.
./venv-dev/bin/python -m ruff check . --fix
codespell: ## Run codespell to fix common misspellings in text files
./venv-dev/bin/codespell -S .git,./docs/_build,./Glances.egg-info,./venv*,./glances/outputs,*.svg -L hart,bu,te,statics
./venv-dev/bin/codespell -S .git,./docs/_build,./Glances.egg-info,./venv*,./glances/outputs,*.svg -L hart,bu,te,statics -w
semgrep: ## Run semgrep to find bugs and enforce code standards
./venv-dev/bin/semgrep scan --config=auto
@ -185,7 +185,7 @@ flatpak: venv-dev-upgrade ## Generate FlatPack JSON file
rm -rf ./flatpak-builder-tools
@echo "Now follow: https://github.com/flathub/flathub/wiki/App-Submission"
# Snap package is automaticaly build on the Snapcraft.io platform
# Snap package is automatically build on the Snapcraft.io platform
# https://snapcraft.io/glances
# But you can try an offline build with the following command
snapcraft:

View File

@ -2,13 +2,56 @@
Glances ChangeLog
==============================================================================
===============
Version 4.2.0
===============
Under development, see roadmap here: https://github.com/nicolargo/glances/milestone/73
Contributors are welcome !
===============
Version 4.1.1
===============
Bug corrected:
* Sensors data is not exported using InfluxDB2 exporter #2856
===============
Version 4.1.0
===============
Under development, see roadmap here: https://github.com/nicolargo/glances/milestone/68
Enhancements:
Contributors are welcome !
* Call process_iter.clear_cache() (PsUtil 6+) when Glances user force a refresh (F5 or CTRL-R) #2753
* PsUtil 6+ no longer check PID reused #2755
* Add support for automatically hiding network interfaces that are down or that don't have any IP addresses #2799
Bug corrected:
* API: Network module is disabled but appears in endpoint "all" #2815
* API is not compatible with requests containing special/encoding char #2820
* 'j' hot key crashes Glances #2831
* Raspberry PI - CPU info is not correct #2616
* Graph export is broken if there is no graph section in Glances configuration file #2839
* Glances API status check returns Error 405 - Method Not Allowed #2841
* Rootless podman containers cause glances to fail with KeyError #2827
* --export-process-filter Filter using complete command #2824
* Exception when Glances is ran with limited plugin list #2822
* Disable separator option do not work #2823
Continuous integration and documentation:
* test test_107_fs_plugin_method fails on aarch64-linux #2819
Thanks to all contributors and bug reporters !
Special thanks to:
* Bharath Vignesh J K
* RazCrimson
* Vadim Small
===============
Version 4.0.8
@ -183,7 +226,7 @@ Many thinks to the contributors:
* Christoph Zimmermann
* RazCrimson
* Robin Candau
* Github GPG acces
* Github GPG access
* Continuous Integration
* Georgiy Timchenko
* turbocrime
@ -357,7 +400,7 @@ Documentation and CI:
* Update Makefile with comments
* Update Python minimal requirement for py3nvlm
* Update security policy (user can open private issue directly in Github)
* Add a simple run script. Entry point for IDE debuger
* Add a simple run script. Entry point for IDE debugger
Cyber security update:
@ -386,7 +429,7 @@ And also a big thanks to @RazCrimson (https://github.com/RazCrimson) for the sup
Version 3.3.0.4
===============
Refactor the Docker images factory, from now, only Alpine image wll be provided.
Refactor the Docker images factory, from now, only Alpine image will be provided.
The following Docker images (nicolargo/glances) are availables:
@ -434,9 +477,9 @@ Bug corrected:
* Correct issue with the regexp filter (use fullmatch instead of match)
* Errors when running Glances as web service #1702
* Apply alias to Duplicate sensor name #1686
* Make the hide function in sensors section compliant with lower/upercase #1590
* Make the hide function in sensors section compliant with lower/uppercase #1590
* Web UI truncates the days part of CPU time counter of the process list #2108
* Correct alignement issue with the diskio plugin (Console UI)
* Correct alignment issue with the diskio plugin (Console UI)
Documentation and CI:
@ -703,7 +746,7 @@ Bugs corrected:
* Docker containers information missing with Docker 20.10.x #1878
* Get system sensors temperatures thresholds #1864
Contibutors for this version:
Contributors for this version:
* Nicolargo
* Markus Pöschl
@ -1310,7 +1353,7 @@ Enhancements and new features:
* Add ZeroMQ exporter (issue #939)
* Add CouchDB exporter (issue #928)
* Add hotspot Wifi information (issue #937)
* Add default interface speed and automatic rate thresolds (issue #718)
* Add default interface speed and automatic rate thresholds (issue #718)
* Highlight max stats in the processes list (issue #878)
* Docker alerts and actions (issue #875)
* Glances API returns the processes PPID (issue #926)

View File

@ -2,7 +2,7 @@
Glances - An eye on your system
===============================
| |pypi| |test| |contibutors| |quality|
| |pypi| |test| |contributors| |quality|
| |starts| |docker| |pypistat|
| |sponsors| |twitter|
@ -25,9 +25,9 @@ Glances - An eye on your system
:target: https://github.com/nicolargo/glances/actions
:alt: Linux tests (GitHub Actions)
.. |contibutors| image:: https://img.shields.io/github/contributors/nicolargo/glances
.. |contributors| image:: https://img.shields.io/github/contributors/nicolargo/glances
:target: https://github.com/nicolargo/glances/issues?q=is%3Aissue+is%3Aopen+label%3A%22needs+contributor%22
:alt: Contibutors
:alt: Contributors
.. |quality| image:: https://scrutinizer-ci.com/g/nicolargo/glances/badges/quality-score.png?b=develop
:target: https://scrutinizer-ci.com/g/nicolargo/glances/?branch=develop
@ -141,7 +141,7 @@ stable version.
To install Glances, simply use the ``pip`` command line.
Warning: on modern Linux operating systems, you may have an externally-managed-environment
error message when you try to use ``pip``. In this case, go to the the PipX section bellow.
error message when you try to use ``pip``. In this case, go to the the PipX section below.
.. code-block:: console
@ -221,8 +221,6 @@ Run last version of Glances container in *console mode*:
By default, the /etc/glances/glances.conf file is used (based on docker-compose/glances.conf).
By default, the /etc/glances/glances.conf file is used (based on docker-compose/glances.conf).
Additionally, if you want to use your own glances.conf file, you can
create your own Dockerfile:

View File

@ -2,13 +2,10 @@
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Support security updates |
| ------- | ------------------------ |
| 3.x | :white_check_mark: |
| < 3.0 | :x: |
| 4.x | :white_check_mark: |
| < 4.0 | :x: |
## Reporting a Vulnerability
@ -31,4 +28,3 @@ If there are any vulnerabilities in {{cookiecutter.project_name}}, don't hesitat
4. Please do not disclose the vulnerability publicly until a fix is released!
Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it.

View File

@ -217,9 +217,9 @@ 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
hide_no_up=True
# Automatically hide interface with no IP address (default is False)
#hide_no_ip=True
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
@ -375,7 +375,7 @@ temperature_hdd_critical=60
battery_careful=80
battery_warning=90
battery_critical=95
# Fan speed threashold in RPM
# Fan speed threshold in RPM
#fan_speed_careful=100
# Sensors alias
#alias=core 0:CPU Core 0,core 1:CPU Core 1

View File

@ -1,3 +1,5 @@
psutil
defusedxml
orjson
reuse
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability

View File

@ -27,7 +27,7 @@ services:
environment:
- TZ=${TZ}
- "GLANCES_OPT=-C /glances/conf/glances.conf -w"
# Uncomment for GPU compatibilty (Nvidia) inside the container
# Uncomment for GPU compatibility (Nvidia) inside the container
# deploy:
# resources:
# reservations:

View File

@ -15,7 +15,7 @@ services:
environment:
- TZ=${TZ}
- "GLANCES_OPT=-C /glances/conf/glances.conf -w"
# Uncomment for GPU compatibilty (Nvidia) inside the container
# Uncomment for GPU compatibility (Nvidia) inside the container
# deploy:
# resources:
# reservations:

View File

@ -60,7 +60,7 @@ max_processes_display=25
#cors_headers=*
##############################################################################
# plugins
# Plugins
##############################################################################
[quicklook]
@ -375,7 +375,7 @@ temperature_hdd_critical=60
battery_careful=80
battery_warning=90
battery_critical=95
# Fan speed threashold in RPM
# Fan speed threshold in RPM
#fan_speed_careful=100
# Sensors alias
#alias=core 0:CPU Core 0,core 1:CPU Core 1

View File

@ -3,7 +3,7 @@
CLOUD
=====
This plugin diplays information about the cloud provider if your host is running on OpenStack.
This plugin displays information about the cloud provider if your host is running on OpenStack.
The plugin use the standard OpenStack `metadata`_ service to retrieve the information.

View File

@ -53,7 +53,7 @@ To switch to per-CPU stats, just hit the ``1`` key:
.. image:: ../_static/per-cpu.png
In this case, Glances will show on line per logical CPU on the system.
If you have multiple core, it is possible to define the maximun number
If you have multiple core, it is possible to define the maximum number
of CPU to display. The top 'max_cpu_display' will be display and an
extra line with the mean of all others CPU will be added.

View File

@ -57,7 +57,7 @@ Example:
**NOTE:** Setting low values for `public_refresh_interval` will result in frequent
HTTP requests to the onlive service defined in public_api. Recommended range: 120-600 seconds.
Glances uses online services in order to get the IP addresses and the additional informations.
Glances uses online services in order to get the IP addresses and the additional information.
Your IP address could be blocked if too many requests are done.

View File

@ -39,7 +39,7 @@ Trend Status
======== ==============================================================
``-`` Mean 15 lasts values equal mean 15 previous values
```` Mean 15 lasts values is lower mean 15 previous values
```` Mean 15 lasts values is higher mean 15 previous valuess
```` Mean 15 lasts values is higher mean 15 previous values
======== ==============================================================
Legend:

View File

@ -49,7 +49,7 @@ Trend Status
======== ==============================================================
``-`` Mean 15 lasts values equal mean 15 previous values
```` Mean 15 lasts values is lower mean 15 previous values
```` Mean 15 lasts values is higher mean 15 previous valuess
```` Mean 15 lasts values is higher mean 15 previous values
======== ==============================================================
Alerts are only set for used memory and used swap.

View File

@ -48,7 +48,7 @@ virtual docker interface (docker0, docker1, ...):
# Automatically hide interface with no IP address (default is False)
hide_no_ip=True
# WLAN 0 alias
wlan0_alias=Wireless IF
alias=wlan0:Wireless IF
# 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
@ -64,4 +64,4 @@ Filtering is based on regular expression. Please be sure that your regular
expression works as expected. You can use an online tool like `regex101`_ in
order to test your regular expression.
.. _regex101: https://regex101.com/
.. _regex101: https://regex101.com/

View File

@ -27,8 +27,7 @@ There is no alert on this information.
.. note 3::
If a sensors has temperature and fan speed with the same name unit,
it is possible to alias it using:
unitname_temperature_core_alias=Alias for temp
unitname_fan_speed_alias=Alias for fan speed
alias=unitname_temperature_core_alias:Alias for temp,unitname_fan_speed_alias:Alias for fan speed
.. note 4::
If a sensors has multiple identical features names (see #2280), then

View File

@ -141,7 +141,7 @@ Get plugin stats::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.34838008880615234},
"timer": 0.2482151985168457},
{"count": 0,
"countmax": 20.0,
"countmin": None,
@ -150,7 +150,7 @@ Get plugin stats::
"refresh": 3.0,
"regex": True,
"result": None,
"timer": 0.348278284072876}]
"timer": 0.24815130233764648}]
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.34838008880615234}]}
"timer": 0.2482151985168457}]}
GET cloud
---------
@ -265,19 +265,19 @@ Get plugin stats::
# curl http://localhost:61208/api/4/cpu
{"cpucore": 16,
"ctx_switches": 385434943,
"ctx_switches": 493080568,
"guest": 0.0,
"idle": 0.0,
"interrupts": 330921576,
"iowait": 0.0,
"idle": 86.5,
"interrupts": 420997918,
"iowait": 0.5,
"irq": 0.0,
"nice": 0.0,
"soft_interrupts": 117854827,
"soft_interrupts": 155707720,
"steal": 0.0,
"syscalls": 0,
"system": 0.0,
"total": 0.0,
"user": 0.0}
"system": 3.5,
"total": 12.9,
"user": 9.4}
Fields descriptions:
@ -310,7 +310,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/cpu/total
{"total": 0.0}
{"total": 12.9}
GET diskio
----------
@ -320,14 +320,14 @@ Get plugin stats::
# curl http://localhost:61208/api/4/diskio
[{"disk_name": "nvme0n1",
"key": "disk_name",
"read_bytes": 5188239872,
"read_count": 215068,
"write_bytes": 19667686400,
"write_count": 1131877},
"read_bytes": 10167291392,
"read_count": 391026,
"write_bytes": 31230641152,
"write_count": 1527146},
{"disk_name": "nvme0n1p1",
"key": "disk_name",
"read_bytes": 8869888,
"read_count": 834,
"read_bytes": 7558144,
"read_count": 605,
"write_bytes": 1024,
"write_count": 2}]
@ -363,10 +363,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": 5188239872,
"read_count": 215068,
"write_bytes": 19667686400,
"write_count": 1131877}]}
"read_bytes": 10167291392,
"read_count": 391026,
"write_bytes": 31230641152,
"write_count": 1527146}]}
GET folders
-----------
@ -393,13 +393,13 @@ Get plugin stats::
# curl http://localhost:61208/api/4/fs
[{"device_name": "/dev/mapper/ubuntu--vg-ubuntu--lv",
"free": 904171712512,
"free": 897378041856,
"fs_type": "ext4",
"key": "mnt_point",
"mnt_point": "/",
"percent": 5.1,
"percent": 5.8,
"size": 1003736440832,
"used": 48502222848}]
"used": 55295893504}]
Fields descriptions:
@ -420,13 +420,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": 904171712512,
"free": 897378041856,
"fs_type": "ext4",
"key": "mnt_point",
"mnt_point": "/",
"percent": 5.1,
"percent": 5.8,
"size": 1003736440832,
"used": 48502222848}]}
"used": 55295893504}]}
GET gpu
-------
@ -500,9 +500,9 @@ Get plugin stats::
# curl http://localhost:61208/api/4/load
{"cpucore": 16,
"min1": 1.5205078125,
"min15": 0.82275390625,
"min5": 1.09228515625}
"min1": 0.40185546875,
"min15": 0.587890625,
"min5": 0.638671875}
Fields descriptions:
@ -514,7 +514,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/load/min1
{"min1": 1.5205078125}
{"min1": 0.40185546875}
GET mem
-------
@ -522,16 +522,16 @@ GET mem
Get plugin stats::
# curl http://localhost:61208/api/4/mem
{"active": 8721498112,
"available": 7341674496,
"buffers": 416690176,
"cached": 7607521280,
"free": 7341674496,
"inactive": 4933640192,
"percent": 55.3,
"shared": 1056010240,
{"active": 5094199296,
"available": 10908983296,
"buffers": 180162560,
"cached": 5800796160,
"free": 10908983296,
"inactive": 3735175168,
"percent": 33.6,
"shared": 622718976,
"total": 16422486016,
"used": 9080811520}
"used": 5513502720}
Fields descriptions:
@ -558,13 +558,13 @@ GET memswap
Get plugin stats::
# curl http://localhost:61208/api/4/memswap
{"free": 4293390336,
"percent": 0.0,
"sin": 0,
"sout": 126976,
{"free": 3836997632,
"percent": 10.7,
"sin": 186925056,
"sout": 1518604288,
"time_since_update": 1,
"total": 4294963200,
"used": 1572864}
"used": 457965568}
Fields descriptions:
@ -589,15 +589,15 @@ Get plugin stats::
# curl http://localhost:61208/api/4/network
[{"alias": None,
"bytes_all": 0,
"bytes_all_gauge": 4462887866,
"bytes_all_gauge": 6286191015,
"bytes_recv": 0,
"bytes_recv_gauge": 4049401085,
"bytes_recv_gauge": 5977645732,
"bytes_sent": 0,
"bytes_sent_gauge": 413486781,
"bytes_sent_gauge": 308545283,
"interface_name": "wlp0s20f3",
"key": "interface_name",
"speed": 0,
"time_since_update": 0.3545956611633301}]
"time_since_update": 0.2501566410064697}]
Fields descriptions:
@ -626,15 +626,15 @@ 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": 4462887866,
"bytes_all_gauge": 6286191015,
"bytes_recv": 0,
"bytes_recv_gauge": 4049401085,
"bytes_recv_gauge": 5977645732,
"bytes_sent": 0,
"bytes_sent_gauge": 413486781,
"bytes_sent_gauge": 308545283,
"interface_name": "wlp0s20f3",
"key": "interface_name",
"speed": 0,
"time_since_update": 0.3545956611633301}]}
"time_since_update": 0.2501566410064697}]}
GET now
-------
@ -642,7 +642,7 @@ GET now
Get plugin stats::
# curl http://localhost:61208/api/4/now
{"custom": "2024-06-01 18:28:14 CEST", "iso": "2024-06-01T18:28:14+02:00"}
{"custom": "2024-06-29 19:17:41 CEST", "iso": "2024-06-29T19:17:41+02:00"}
Fields descriptions:
@ -652,7 +652,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/now/iso
{"iso": "2024-06-01T18:28:14+02:00"}
{"iso": "2024-06-29T19:17:41+02:00"}
GET percpu
----------
@ -663,7 +663,7 @@ Get plugin stats::
[{"cpu_number": 0,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 1.0,
"idle": 21.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -671,12 +671,12 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 99.0,
"user": 0.0},
"total": 79.0,
"user": 1.0},
{"cpu_number": 1,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 23.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -684,7 +684,7 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 77.0,
"user": 0.0}]
Fields descriptions:
@ -719,7 +719,7 @@ Get plugin stats::
"port": 0,
"refresh": 30,
"rtt_warning": None,
"status": 0.006269,
"status": 0.006275,
"timeout": 3}]
Fields descriptions:
@ -747,7 +747,7 @@ Get a specific item when field matches the given value::
"port": 0,
"refresh": 30,
"rtt_warning": None,
"status": 0.006269,
"status": 0.006275,
"timeout": 3}]}
GET processcount
@ -756,7 +756,7 @@ GET processcount
Get plugin stats::
# curl http://localhost:61208/api/4/processcount
{"pid_max": 0, "running": 0, "sleeping": 290, "thread": 1660, "total": 425}
{"pid_max": 0, "running": 2, "sleeping": 277, "thread": 1508, "total": 412}
Fields descriptions:
@ -769,7 +769,7 @@ Fields descriptions:
Get a specific field::
# curl http://localhost:61208/api/4/processcount/total
{"total": 425}
{"total": 412}
GET processlist
---------------
@ -777,7 +777,78 @@ GET processlist
Get plugin stats::
# curl http://localhost:61208/api/4/processlist
[]
[{"cmdline": ["/snap/firefox/4336/usr/lib/firefox/firefox"],
"cpu_percent": 0.0,
"cpu_times": {"children_system": 3.08,
"children_user": 3.33,
"iowait": 0.0,
"system": 13.75,
"user": 34.7},
"gids": {"effective": 1000, "real": 1000, "saved": 1000},
"io_counters": [393136128, 195969024, 0, 0, 0],
"key": "pid",
"memory_info": {"data": 681967616,
"dirty": 0,
"lib": 0,
"rss": 443969536,
"shared": 222199808,
"text": 987136,
"vms": 3721211904},
"memory_percent": 2.7034246554842674,
"name": "firefox",
"nice": 0,
"num_threads": 120,
"pid": 793506,
"status": "S",
"time_since_update": 1,
"username": "nicolargo"},
{"cmdline": ["/snap/firefox/4336/usr/lib/firefox/firefox",
"-contentproc",
"-childID",
"2",
"-isForBrowser",
"-prefsLen",
"28218",
"-prefMapSize",
"244440",
"-jsInitLen",
"231800",
"-parentBuildID",
"20240527194810",
"-greomni",
"/snap/firefox/4336/usr/lib/firefox/omni.ja",
"-appomni",
"/snap/firefox/4336/usr/lib/firefox/browser/omni.ja",
"-appDir",
"/snap/firefox/4336/usr/lib/firefox/browser",
"{bc853380-6b8f-46ad-afe0-9da5ba832e62}",
"793506",
"true",
"tab"],
"cpu_percent": 0.0,
"cpu_times": {"children_system": 0.0,
"children_user": 0.0,
"iowait": 0.0,
"system": 2.54,
"user": 19.25},
"gids": {"effective": 1000, "real": 1000, "saved": 1000},
"io_counters": [1827840, 0, 0, 0, 0],
"key": "pid",
"memory_info": {"data": 412688384,
"dirty": 0,
"lib": 0,
"rss": 440922112,
"shared": 111878144,
"text": 987136,
"vms": 2946224128},
"memory_percent": 2.684868244493684,
"name": "Isolated Web Co",
"nice": 0,
"num_threads": 28,
"pid": 793778,
"status": "S",
"time_since_update": 1,
"username": "nicolargo"}]
Fields descriptions:
@ -801,7 +872,7 @@ GET psutilversion
Get plugin stats::
# curl http://localhost:61208/api/4/psutilversion
"5.9.8"
"6.0.0"
GET quicklook
-------------
@ -809,18 +880,18 @@ GET quicklook
Get plugin stats::
# curl http://localhost:61208/api/4/quicklook
{"cpu": 0.0,
{"cpu": 12.9,
"cpu_hz": 4475000000.0,
"cpu_hz_current": 1456909124.9999998,
"cpu_hz_current": 1666410624.9999998,
"cpu_log_core": 16,
"cpu_name": "13th Gen Intel(R) Core(TM) i7-13620H",
"cpu_phys_core": 10,
"load": 5.1,
"mem": 55.3,
"load": 3.7,
"mem": 33.6,
"percpu": [{"cpu_number": 0,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 1.0,
"idle": 21.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -828,12 +899,12 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 99.0,
"user": 0.0},
"total": 79.0,
"user": 1.0},
{"cpu_number": 1,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 23.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -841,25 +912,25 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 77.0,
"user": 0.0},
{"cpu_number": 2,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 1.0,
"idle": 18.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 99.0,
"user": 0.0},
"system": 1.0,
"total": 82.0,
"user": 5.0},
{"cpu_number": 3,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 22.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -867,25 +938,25 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 78.0,
"user": 0.0},
{"cpu_number": 4,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 1.0,
"idle": 8.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 99.0,
"user": 0.0},
"system": 1.0,
"total": 92.0,
"user": 14.0},
{"cpu_number": 5,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 23.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -893,64 +964,64 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 77.0,
"user": 0.0},
{"cpu_number": 6,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"iowait": 0.0,
"idle": 3.0,
"iowait": 1.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"user": 0.0},
"system": 4.0,
"total": 97.0,
"user": 15.0},
{"cpu_number": 7,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 1.0,
"idle": 23.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 99.0,
"system": 1.0,
"total": 77.0,
"user": 0.0},
{"cpu_number": 8,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 19.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"user": 0.0},
"system": 1.0,
"total": 81.0,
"user": 3.0},
{"cpu_number": 9,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"iowait": 0.0,
"idle": 22.0,
"iowait": 1.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 78.0,
"user": 0.0},
{"cpu_number": 10,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 22.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -958,12 +1029,12 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 78.0,
"user": 0.0},
{"cpu_number": 11,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 23.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -971,25 +1042,25 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 77.0,
"user": 0.0},
{"cpu_number": 12,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 21.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
"nice": 0.0,
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"user": 0.0},
"system": 1.0,
"total": 79.0,
"user": 1.0},
{"cpu_number": 13,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 23.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -997,12 +1068,12 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 77.0,
"user": 0.0},
{"cpu_number": 14,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 22.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -1010,12 +1081,12 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 78.0,
"user": 0.0},
{"cpu_number": 15,
"guest": 0.0,
"guest_nice": 0.0,
"idle": 0.0,
"idle": 22.0,
"iowait": 0.0,
"irq": 0.0,
"key": "cpu_number",
@ -1023,9 +1094,9 @@ Get plugin stats::
"softirq": 0.0,
"steal": 0.0,
"system": 0.0,
"total": 100.0,
"total": 78.0,
"user": 0.0}],
"swap": 0.0}
"swap": 10.7}
Fields descriptions:
@ -1063,14 +1134,14 @@ Get plugin stats::
"label": "Ambient",
"type": "temperature_core",
"unit": "C",
"value": 35,
"value": 39,
"warning": 0},
{"critical": None,
"key": "label",
"label": "Ambient 3",
"type": "temperature_core",
"unit": "C",
"value": 30,
"value": 32,
"warning": 0}]
Fields descriptions:
@ -1131,7 +1202,7 @@ Get a specific item when field matches the given value::
"label": "Ambient",
"type": "temperature_core",
"unit": "C",
"value": 35,
"value": 39,
"warning": 0}]}
GET smart
@ -1162,7 +1233,7 @@ Fields descriptions:
* **platform**: Platform (32 or 64 bits) (unit is *None*)
* **linux_distro**: Linux distribution (unit is *None*)
* **os_version**: Operating system version (unit is *None*)
* **hr_name**: Human readable operating sytem name (unit is *None*)
* **hr_name**: Human readable operating system name (unit is *None*)
Get a specific field::
@ -1175,7 +1246,7 @@ GET uptime
Get plugin stats::
# curl http://localhost:61208/api/4/uptime
"18 days, 19:20:46"
"20 days, 2:20:27"
GET version
-----------
@ -1183,7 +1254,7 @@ GET version
Get plugin stats::
# curl http://localhost:61208/api/4/version
"4.1.0_beta01"
"4.2.0_beta01"
GET wifi
--------
@ -1251,34 +1322,34 @@ GET stats history
History of a plugin::
# curl http://localhost:61208/api/4/cpu/history
{"system": [["2024-06-01T18:28:15.830521", 0.0],
["2024-06-01T18:28:16.865265", 0.0],
["2024-06-01T18:28:17.937644", 0.0]],
"user": [["2024-06-01T18:28:15.830511", 0.0],
["2024-06-01T18:28:16.865259", 0.0],
["2024-06-01T18:28:17.937634", 0.0]]}
{"system": [["2024-06-29T19:17:42.396270", 3.5],
["2024-06-29T19:17:43.447397", 0.7],
["2024-06-29T19:17:44.455830", 0.7]],
"user": [["2024-06-29T19:17:42.396268", 9.4],
["2024-06-29T19:17:43.447396", 4.0],
["2024-06-29T19:17:44.455825", 4.0]]}
Limit history to last 2 values::
# curl http://localhost:61208/api/4/cpu/history/2
{"system": [["2024-06-01T18:28:16.865265", 0.0],
["2024-06-01T18:28:17.937644", 0.0]],
"user": [["2024-06-01T18:28:16.865259", 0.0],
["2024-06-01T18:28:17.937634", 0.0]]}
{"system": [["2024-06-29T19:17:43.447397", 0.7],
["2024-06-29T19:17:44.455830", 0.7]],
"user": [["2024-06-29T19:17:43.447396", 4.0],
["2024-06-29T19:17:44.455825", 4.0]]}
History for a specific field::
# curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-06-01T18:28:14.644798", 0.0],
["2024-06-01T18:28:15.830521", 0.0],
["2024-06-01T18:28:16.865265", 0.0],
["2024-06-01T18:28:17.937644", 0.0]]}
{"system": [["2024-06-29T19:17:41.318736", 3.5],
["2024-06-29T19:17:42.396270", 3.5],
["2024-06-29T19:17:43.447397", 0.7],
["2024-06-29T19:17:44.455830", 0.7]]}
Limit history for a specific field to last 2 values::
# curl http://localhost:61208/api/4/cpu/system/history
{"system": [["2024-06-01T18:28:16.865265", 0.0],
["2024-06-01T18:28:17.937644", 0.0]]}
{"system": [["2024-06-29T19:17:43.447397", 0.7],
["2024-06-29T19:17:44.455830", 0.7]]}
GET limits (used for thresholds)
--------------------------------
@ -1363,6 +1434,8 @@ All limits/thresholds::
"network": {"history_size": 1200.0,
"network_disable": ["False"],
"network_hide": ["docker.*", "lo"],
"network_hide_no_ip": ["True"],
"network_hide_no_up": ["True"],
"network_rx_careful": 70.0,
"network_rx_critical": 90.0,
"network_rx_warning": 80.0,

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" "Jun 01, 2024" "4.1.0_beta01" "Glances"
.TH "GLANCES" "1" "Jun 29, 2024" "4.2.0_beta01" "Glances"
.SH NAME
glances \- An eye on your system
.SH SYNOPSIS

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.1.0_beta02'
__version__ = '4.2.0_beta01'
__apiversion__ = '4'
__author__ = 'Nicolas Hennion <nicolas@nicolargo.com>'
__license__ = 'LGPLv3'
@ -55,7 +55,7 @@ if psutil_version_info < psutil_min_version:
def __signal_handler(signal, frame):
logger.debug(f"Signal {signal} catched")
logger.debug(f"Signal {signal} caught")
end()

View File

@ -95,12 +95,16 @@ class GlancesClientBrowser:
# Mandatory stats
try:
# CPU%
cpu_percent = 100 - orjson.loads(s.getPlugin('cpu'))['idle']
server['cpu_percent'] = f'{cpu_percent:.1f}'
# logger.info(f"CPU stats {s.getPlugin('cpu')}")
# logger.info(f"CPU views {s.getPluginView('cpu')}")
server['cpu_percent'] = orjson.loads(s.getPlugin('cpu'))['total']
server['cpu_percent_decoration'] = orjson.loads(s.getPluginView('cpu'))['total']['decoration']
# MEM%
server['mem_percent'] = orjson.loads(s.getPlugin('mem'))['percent']
server['mem_percent_decoration'] = orjson.loads(s.getPluginView('mem'))['percent']['decoration']
# OS (Human Readable name)
server['hr_name'] = orjson.loads(s.getPlugin('system'))['hr_name']
server['hr_name_decoration'] = 'DEFAULT'
except (OSError, Fault, KeyError) as e:
logger.debug(f"Error while grabbing stats form server ({e})")
server['status'] = 'OFFLINE'
@ -120,8 +124,8 @@ class GlancesClientBrowser:
# Optional stats (load is not available on Windows OS)
try:
# LOAD
load_min5 = orjson.loads(s.getPlugin('load'))['min5']
server['load_min5'] = f'{load_min5:.2f}'
server['load_min5'] = round(orjson.loads(s.getPlugin('load'))['min5'], 1)
server['load_min5_decoration'] = orjson.loads(s.getPluginView('load'))['min5']['decoration']
except Exception as e:
logger.warning(f"Error while grabbing stats form server ({e})")

View File

@ -8,46 +8,68 @@
"""CPU percent stats shared between CPU and Quicklook plugins."""
from typing import List, Optional, TypedDict
import psutil
from glances.logger import logger
from glances.timer import Timer
__all__ = ["cpu_percent"]
class CpuInfo(TypedDict):
cpu_name: str
cpu_hz: Optional[float]
cpu_hz_current: Optional[float]
class PerCpuPercentInfo(TypedDict):
key: str
cpu_number: int
total: float
user: float
system: float
idle: float
nice: Optional[float]
iowait: Optional[float]
irq: Optional[float]
softirq: Optional[float]
steal: Optional[float]
guest: Optional[float]
guest_nice: Optional[float]
dpc: Optional[float]
interrupt: Optional[float]
class CpuPercent:
"""Get and store the CPU percent."""
def __init__(self, cached_timer_cpu=3):
self.cpu_info = {'cpu_name': None, 'cpu_hz_current': None, 'cpu_hz': None}
self.cpu_percent = 0
self.percpu_percent = []
# Get CPU name
self.cpu_info['cpu_name'] = self.__get_cpu_name()
def __init__(self, cached_timer_cpu: int = 2):
# cached_timer_cpu is the minimum time interval between stats updates
# since last update is passed (will retrieve old cached info instead)
self.cached_timer_cpu = cached_timer_cpu
self.timer_cpu = Timer(0)
self.timer_percpu = Timer(0)
# psutil.cpu_freq() consumes lots of CPU
# So refresh the stats every refresh*2 (6 seconds)
# So refresh CPU frequency stats every refresh * 2
self.cached_timer_cpu_info = cached_timer_cpu * 2
# Get CPU name
self.timer_cpu_info = Timer(0)
self.cpu_info: CpuInfo = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None}
# Warning from PsUtil documentation
# The first time this function is called with interval = 0.0 or None
# it will return a meaningless 0.0 value which you are supposed to ignore.
self.timer_cpu = Timer(0)
self.cpu_percent = self._compute_cpu()
self.timer_percpu = Timer(0)
self.percpu_percent = self._compute_percpu()
def get_key(self):
"""Return the key of the per CPU list."""
return 'cpu_number'
def get(self, percpu=False):
"""Update and/or return the CPU using the psutil library.
If percpu, return the percpu stats"""
if percpu:
return self.__get_percpu()
return self.__get_cpu()
def get_info(self):
def get_info(self) -> CpuInfo:
"""Get additional information about the CPU"""
# Never update more than 1 time per cached_timer_cpu_info
if self.timer_cpu_info.finished() and hasattr(psutil, 'cpu_freq'):
@ -69,65 +91,69 @@ class CpuPercent:
self.timer_cpu_info.reset(duration=self.cached_timer_cpu_info)
return self.cpu_info
def __get_cpu_name(self):
@staticmethod
def __get_cpu_name() -> str:
# Get the CPU name once from the /proc/cpuinfo file
# Read the first line with the "model name"
ret = None
# Read the first line with the "model name" ("Model" for Raspberry Pi)
try:
cpuinfo_file = open('/proc/cpuinfo').readlines()
cpuinfo_lines = 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'
logger.debug("No permission to read '/proc/cpuinfo'")
return 'CPU'
def __get_cpu(self):
for line in cpuinfo_lines:
if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'):
return line.split(':')[1].strip()
return 'CPU'
def get_cpu(self) -> float:
"""Update and/or return the CPU using the psutil library."""
# Never update more than 1 time per cached_timer_cpu
if self.timer_cpu.finished():
self.cpu_percent = psutil.cpu_percent(interval=0.0)
# Reset timer for cache
self.timer_cpu.reset(duration=self.cached_timer_cpu)
# Update the stats
self.cpu_percent = self._compute_cpu()
return self.cpu_percent
def __get_percpu(self):
@staticmethod
def _compute_cpu() -> float:
return psutil.cpu_percent(interval=0.0)
def get_percpu(self) -> List[PerCpuPercentInfo]:
"""Update and/or return the per CPU list using the psutil library."""
# Never update more than 1 time per cached_timer_cpu
if self.timer_percpu.finished():
self.percpu_percent = []
for cpu_number, cputimes in enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True)):
cpu = {
'key': self.get_key(),
'cpu_number': cpu_number,
'total': round(100 - cputimes.idle, 1),
'user': cputimes.user,
'system': cputimes.system,
'idle': cputimes.idle,
}
# The following stats are for API purposes only
if hasattr(cputimes, 'nice'):
cpu['nice'] = cputimes.nice
if hasattr(cputimes, 'iowait'):
cpu['iowait'] = cputimes.iowait
if hasattr(cputimes, 'irq'):
cpu['irq'] = cputimes.irq
if hasattr(cputimes, 'softirq'):
cpu['softirq'] = cputimes.softirq
if hasattr(cputimes, 'steal'):
cpu['steal'] = cputimes.steal
if hasattr(cputimes, 'guest'):
cpu['guest'] = cputimes.guest
if hasattr(cputimes, 'guest_nice'):
cpu['guest_nice'] = cputimes.guest_nice
# Append new CPU to the list
self.percpu_percent.append(cpu)
# Reset timer for cache
self.timer_percpu.reset(duration=self.cached_timer_cpu)
# Reset timer for cache
self.timer_percpu.reset(duration=self.cached_timer_cpu)
# Update stats
self.percpu_percent = self._compute_percpu()
return self.percpu_percent
def _compute_percpu(self) -> List[PerCpuPercentInfo]:
psutil_percpu = enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True))
return [
{
'key': self.get_key(),
'cpu_number': cpu_number,
'total': round(100 - cpu_times.idle, 1),
'user': cpu_times.user,
'system': cpu_times.system,
'idle': cpu_times.idle,
'nice': cpu_times.nice if hasattr(cpu_times, 'nice') else None,
'iowait': cpu_times.iowait if hasattr(cpu_times, 'iowait') else None,
'irq': cpu_times.irq if hasattr(cpu_times, 'irq') else None,
'softirq': cpu_times.softirq if hasattr(cpu_times, 'softirq') else None,
'steal': cpu_times.steal if hasattr(cpu_times, 'steal') else None,
'guest': cpu_times.guest if hasattr(cpu_times, 'guest') else None,
'guest_nice': cpu_times.steal if hasattr(cpu_times, 'guest_nice') else None,
'dpc': cpu_times.dpc if hasattr(cpu_times, 'dpc') else None,
'interrupt': cpu_times.interrupt if hasattr(cpu_times, 'interrupt') else None,
}
for cpu_number, cpu_times in psutil_percpu
]
# CpuPercent instance shared between plugins
cpu_percent = CpuPercent()

View File

@ -34,10 +34,12 @@ class Export(GlancesExport):
# Manage options (command line arguments overwrite configuration file)
self.path = args.export_graph_path or self.path
self.generate_every = int(getattr(self, 'generate_every', 0))
self.width = int(getattr(self, 'width', 800))
self.height = int(getattr(self, 'height', 600))
self.style = getattr(pygal.style, getattr(self, 'style', 'DarkStyle'), pygal.style.DarkStyle)
self.generate_every = int(getattr(self, 'generate_every', 0) or 0)
self.width = int(getattr(self, 'width', 800) or 800)
self.height = int(getattr(self, 'height', 600) or 600)
self.style = (
getattr(pygal.style, getattr(self, 'style', 'DarkStyle'), pygal.style.DarkStyle) or pygal.style.DarkStyle
)
# Create export folder
try:

View File

@ -139,8 +139,7 @@ class Export(GlancesExport):
if k in fields:
tags[k] = str(fields[k])
# Remove it from the field list (can not be a field and a tag)
if k in fields:
fields.pop(fields[k])
fields.pop(k)
# Add the measurement to the list
ret.append({'measurement': name, 'tags': tags, 'fields': fields})
return ret
@ -157,7 +156,7 @@ class Export(GlancesExport):
try:
self.client.write_points(self._normalize(name, columns, points), time_precision="s")
except Exception as e:
# Log level set to debug instead of error (see: issue #1561)
logger.debug(f"Cannot export {name} stats to InfluxDB ({e})")
# Log level set to warning instead of error (see: issue #1561)
logger.warning(f"Cannot export {name} stats to InfluxDB ({e})")
else:
logger.debug(f"Export {name} stats to InfluxDB")

View File

@ -147,8 +147,7 @@ class Export(GlancesExport):
if k in fields:
tags[k] = str(fields[k])
# Remove it from the field list (can not be a field and a tag)
if k in fields:
fields.pop(fields[k])
fields.pop(k)
# Add the measurement to the list
ret.append({'measurement': name, 'tags': tags, 'fields': fields})
return ret
@ -165,7 +164,7 @@ class Export(GlancesExport):
try:
self.client.write(self.bucket, self.org, self._normalize(name, columns, points), time_precision="s")
except Exception as e:
# Log level set to debug instead of error (see: issue #1561)
logger.debug(f"Cannot export {name} stats to InfluxDB ({e})")
# Log level set to warning instead of error (see: issue #1561)
logger.warning(f"Cannot export {name} stats to InfluxDB ({e})")
else:
logger.debug(f"Export {name} stats to InfluxDB")

View File

@ -89,7 +89,7 @@ def printandflush(string):
def to_ascii(s):
"""Convert the bytes string to a ASCII string
Usefull to remove accent (diacritics)"""
Useful to remove accent (diacritics)"""
if isinstance(s, binary_type):
return s.decode()
return s.encode('ascii', 'ignore').decode()
@ -153,7 +153,7 @@ def subsample(data, sampling):
Data should be a list of numerical itervalues
Return a subsampled list of sampling lenght
Return a subsampled list of sampling length
"""
if len(data) <= sampling:
return data
@ -216,13 +216,13 @@ def key_exist_value_not_none(k, d):
return k in d and d[k] is not None
def key_exist_value_not_none_not_v(k, d, value='', lengh=None):
def key_exist_value_not_none_not_v(k, d, value='', length=None):
# Return True if:
# - key k exists
# - d[k] is not None
# - d[k] != value
# - if lengh is not None and len(d[k]) >= lengh
return k in d and d[k] is not None and d[k] != value and (lengh is None or len(d[k]) >= lengh)
# - if length is not None and len(d[k]) >= length
return k in d and d[k] is not None and d[k] != value and (length is None or len(d[k]) >= length)
def disable(class_name, var):
@ -425,12 +425,12 @@ def weak_lru_cache(maxsize=128, typed=False):
def namedtuple_to_dict(data):
"""Convert a namedtuple to a dict, using the _asdict() method embeded in PsUtil stats."""
"""Convert a namedtuple to a dict, using the _asdict() method embedded in PsUtil stats."""
return {k: (v._asdict() if hasattr(v, '_asdict') else v) for k, v in data.items()}
def list_of_namedtuple_to_list_of_dict(data):
"""Convert a list of namedtuples to a dict, using the _asdict() method embeded in PsUtil stats."""
"""Convert a list of namedtuples to a dict, using the _asdict() method embedded in PsUtil stats."""
return [namedtuple_to_dict(d) for d in data]

View File

@ -162,7 +162,7 @@ class _GlancesCurses:
self._init_cursor()
# Init the colors
self._init_colors()
self.colors_list = build_colors_list(args)
# Init main window
self.term_window = self.screen.subwin(0, 0)
@ -216,133 +216,6 @@ class _GlancesCurses:
curses.cbreak()
self.set_cursor(0)
def _init_colors(self):
"""Init the Curses color layout."""
# Set curses options
try:
if hasattr(curses, 'start_color'):
curses.start_color()
logger.debug(f'Curses interface compatible with {curses.COLORS} colors')
if hasattr(curses, 'use_default_colors'):
curses.use_default_colors()
except Exception as e:
logger.warning(f'Error initializing terminal color ({e})')
# Init colors
if self.args.disable_bold:
A_BOLD = 0
self.args.disable_bg = True
else:
A_BOLD = curses.A_BOLD
self.title_color = A_BOLD
self.title_underline_color = A_BOLD | curses.A_UNDERLINE
self.help_color = A_BOLD
if curses.has_colors():
# The screen is compatible with a colored design
# ex: export TERM=xterm-256color
# export TERM=xterm-color
curses.init_pair(1, -1, -1)
if self.args.disable_bg:
curses.init_pair(2, curses.COLOR_RED, -1)
curses.init_pair(3, curses.COLOR_GREEN, -1)
curses.init_pair(5, curses.COLOR_MAGENTA, -1)
else:
curses.init_pair(2, -1, curses.COLOR_RED)
curses.init_pair(3, -1, curses.COLOR_GREEN)
curses.init_pair(5, -1, curses.COLOR_MAGENTA)
curses.init_pair(4, curses.COLOR_BLUE, -1)
curses.init_pair(6, curses.COLOR_RED, -1)
curses.init_pair(7, curses.COLOR_GREEN, -1)
curses.init_pair(8, curses.COLOR_MAGENTA, -1)
# Colors text styles
self.no_color = curses.color_pair(1)
self.default_color = curses.color_pair(3) | A_BOLD
self.nice_color = curses.color_pair(8)
self.cpu_time_color = curses.color_pair(8)
self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD
self.ifWARNING_color = curses.color_pair(5) | A_BOLD
self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD
self.default_color2 = curses.color_pair(7)
self.ifCAREFUL_color2 = curses.color_pair(4)
self.ifWARNING_color2 = curses.color_pair(8) | A_BOLD
self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
self.ifINFO_color = curses.color_pair(4)
self.filter_color = A_BOLD
self.selected_color = A_BOLD
self.separator = curses.color_pair(1)
if curses.COLORS > 8:
# ex: export TERM=xterm-256color
colors_list = [curses.COLOR_CYAN, curses.COLOR_YELLOW]
for i in range(0, 3):
try:
curses.init_pair(i + 9, colors_list[i], -1)
except Exception:
curses.init_pair(i + 9, -1, -1)
self.filter_color = curses.color_pair(9) | A_BOLD
self.selected_color = curses.color_pair(10) | A_BOLD
# Define separator line style
try:
curses.init_color(11, 500, 500, 500)
curses.init_pair(11, curses.COLOR_BLACK, -1)
self.separator = curses.color_pair(11)
except Exception:
# Catch exception in TMUX
pass
else:
# The screen is NOT compatible with a colored design
# switch to B&W text styles
# ex: export TERM=xterm-mono
self.no_color = -1
self.default_color = -1
self.nice_color = A_BOLD
self.cpu_time_color = A_BOLD
self.ifCAREFUL_color = A_BOLD
self.ifWARNING_color = curses.A_UNDERLINE
self.ifCRITICAL_color = curses.A_REVERSE
self.default_color2 = -1
self.ifCAREFUL_color2 = A_BOLD
self.ifWARNING_color2 = curses.A_UNDERLINE
self.ifCRITICAL_color2 = curses.A_REVERSE
self.ifINFO_color = A_BOLD
self.filter_color = A_BOLD
self.selected_color = A_BOLD
self.separator = -1
# Define the colors list (hash table) for stats
self.colors_list = {
'DEFAULT': self.no_color,
'UNDERLINE': curses.A_UNDERLINE,
'BOLD': A_BOLD,
'SORT': curses.A_UNDERLINE | A_BOLD,
'OK': self.default_color2,
'MAX': self.default_color2 | A_BOLD,
'FILTER': self.filter_color,
'TITLE': self.title_color,
'PROCESS': self.default_color2,
'PROCESS_SELECTED': self.default_color2 | curses.A_UNDERLINE,
'STATUS': self.default_color2,
'NICE': self.nice_color,
'CPU_TIME': self.cpu_time_color,
'CAREFUL': self.ifCAREFUL_color2,
'WARNING': self.ifWARNING_color2,
'CRITICAL': self.ifCRITICAL_color2,
'OK_LOG': self.default_color,
'CAREFUL_LOG': self.ifCAREFUL_color,
'WARNING_LOG': self.ifWARNING_color,
'CRITICAL_LOG': self.ifCRITICAL_color,
'PASSWORD': curses.A_PROTECT,
'SELECTED': self.selected_color,
'INFO': self.ifINFO_color,
'ERROR': self.selected_color,
'SEPARATOR': self.separator,
}
def set_cursor(self, value):
"""Configure the curse cursor appearance.
@ -497,7 +370,7 @@ class _GlancesCurses:
logger.info(f"Stop Glances (keypressed: {self.pressedkey})")
def _handle_refresh(self):
pass
glances_processes.reset_internal_cache()
def loop_position(self):
"""Return the current sort in the loop"""
@ -1303,3 +1176,128 @@ class GlancesTextboxYesNo(Textbox):
def do_command(self, ch):
return super().do_command(ch)
def build_colors_list(args):
"""Init the Curses color layout."""
# Set curses options
try:
if hasattr(curses, 'start_color'):
curses.start_color()
logger.debug(f'Curses interface compatible with {curses.COLORS} colors')
if hasattr(curses, 'use_default_colors'):
curses.use_default_colors()
except Exception as e:
logger.warning(f'Error initializing terminal color ({e})')
# Init colors
if args.disable_bold:
A_BOLD = 0
args.disable_bg = True
else:
A_BOLD = curses.A_BOLD
title_color = A_BOLD
if curses.has_colors():
# The screen is compatible with a colored design
# ex: export TERM=xterm-256color
# export TERM=xterm-color
curses.init_pair(1, -1, -1)
if args.disable_bg:
curses.init_pair(2, curses.COLOR_RED, -1)
curses.init_pair(3, curses.COLOR_GREEN, -1)
curses.init_pair(5, curses.COLOR_MAGENTA, -1)
else:
curses.init_pair(2, -1, curses.COLOR_RED)
curses.init_pair(3, 0, curses.COLOR_GREEN)
curses.init_pair(5, -1, curses.COLOR_MAGENTA)
curses.init_pair(4, curses.COLOR_BLUE, -1)
curses.init_pair(6, curses.COLOR_RED, -1)
curses.init_pair(7, curses.COLOR_GREEN, -1)
curses.init_pair(8, curses.COLOR_MAGENTA, -1)
# Colors text styles
no_color = curses.color_pair(1)
default_color = curses.color_pair(3) | A_BOLD
nice_color = curses.color_pair(8)
cpu_time_color = curses.color_pair(8)
ifCAREFUL_color = curses.color_pair(4) | A_BOLD
ifWARNING_color = curses.color_pair(5) | A_BOLD
ifCRITICAL_color = curses.color_pair(2) | A_BOLD
default_color2 = curses.color_pair(7)
ifCAREFUL_color2 = curses.color_pair(4)
ifWARNING_color2 = curses.color_pair(8) | A_BOLD
ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
ifINFO_color = curses.color_pair(4)
filter_color = A_BOLD
selected_color = A_BOLD
separator = curses.color_pair(1)
if curses.COLORS > 8:
# ex: export TERM=xterm-256color
colors_list = [curses.COLOR_CYAN, curses.COLOR_YELLOW]
for i in range(0, 3):
try:
curses.init_pair(i + 9, colors_list[i], -1)
except Exception:
curses.init_pair(i + 9, -1, -1)
filter_color = curses.color_pair(9) | A_BOLD
selected_color = curses.color_pair(10) | A_BOLD
# Define separator line style
try:
curses.init_color(11, 500, 500, 500)
curses.init_pair(11, curses.COLOR_BLACK, -1)
separator = curses.color_pair(11)
except Exception:
# Catch exception in TMUX
pass
else:
# The screen is NOT compatible with a colored design
# switch to B&W text styles
# ex: export TERM=xterm-mono
no_color = -1
default_color = -1
nice_color = A_BOLD
cpu_time_color = A_BOLD
ifCAREFUL_color = A_BOLD
ifWARNING_color = curses.A_UNDERLINE
ifCRITICAL_color = curses.A_REVERSE
default_color2 = -1
ifCAREFUL_color2 = A_BOLD
ifWARNING_color2 = curses.A_UNDERLINE
ifCRITICAL_color2 = curses.A_REVERSE
ifINFO_color = A_BOLD
filter_color = A_BOLD
selected_color = A_BOLD
separator = -1
# Define the colors list (hash table) for stats
return {
'DEFAULT': no_color,
'UNDERLINE': curses.A_UNDERLINE,
'BOLD': A_BOLD,
'SORT': curses.A_UNDERLINE | A_BOLD,
'OK': default_color2,
'MAX': default_color2 | A_BOLD,
'FILTER': filter_color,
'TITLE': title_color,
'PROCESS': default_color2,
'PROCESS_SELECTED': default_color2 | curses.A_UNDERLINE,
'STATUS': default_color2,
'NICE': nice_color,
'CPU_TIME': cpu_time_color,
'CAREFUL': ifCAREFUL_color2,
'WARNING': ifWARNING_color2,
'CRITICAL': ifCRITICAL_color2,
'OK_LOG': default_color,
'CAREFUL_LOG': ifCAREFUL_color,
'WARNING_LOG': ifWARNING_color,
'CRITICAL_LOG': ifCRITICAL_color,
'PASSWORD': curses.A_PROTECT,
'SELECTED': selected_color,
'INFO': ifINFO_color,
'ERROR': selected_color,
'SEPARATOR': separator,
}

View File

@ -24,11 +24,11 @@ class GlancesCursesBrowser(_GlancesCurses):
super().__init__(args=args)
_colors_list = {
'UNKNOWN': self.no_color,
'SNMP': self.default_color2,
'ONLINE': self.default_color2,
'OFFLINE': self.ifCRITICAL_color2,
'PROTECTED': self.ifWARNING_color2,
'UNKNOWN': self.colors_list['DEFAULT'],
'SNMP': self.colors_list['OK'],
'ONLINE': self.colors_list['OK'],
'OFFLINE': self.colors_list['CRITICAL'],
'PROTECTED': self.colors_list['WARNING'],
}
self.colors_list.update(_colors_list)
@ -299,13 +299,11 @@ class GlancesCursesBrowser(_GlancesCurses):
# Item description: [stats_id, column name, column size]
column_def = [
['name', 'Name', 16],
['alias', None, None],
['load_min5', 'LOAD', 6],
['cpu_percent', 'CPU%', 5],
['mem_percent', 'MEM%', 5],
['status', 'STATUS', 9],
['ip', 'IP', 15],
# ['port', 'PORT', 5],
['hr_name', 'OS', 16],
]
y = 2
@ -331,24 +329,10 @@ class GlancesCursesBrowser(_GlancesCurses):
# Display table
line = 0
for v in current_page:
for server_stat in current_page:
# Limit the number of displayed server (see issue #1256)
if line >= stats_max:
continue
# Get server stats
server_stat = {}
for c in column_def:
try:
server_stat[c[0]] = v[c[0]]
except KeyError as e:
logger.debug(f"Cannot grab stats {c[0]} from server (KeyError: {e})")
server_stat[c[0]] = '?'
# Display alias instead of name
try:
if c[0] == 'alias' and v[c[0]] is not None:
server_stat['name'] = v[c[0]]
except KeyError:
pass
# Display line for server stats
cpt = 0
@ -362,9 +346,20 @@ class GlancesCursesBrowser(_GlancesCurses):
# Display the line
xc += 2
for c in column_def:
if xc < screen_x and y < screen_y and c[1] is not None:
if xc < screen_x and y < screen_y:
# Display server stats
self.term_window.addnstr(y, xc, format(server_stat[c[0]]), c[2], self.colors_list[v['status']])
value = format(server_stat.get(c[0], '?'))
if c[0] == 'name' and 'alias' in server_stat:
value = server_stat['alias']
decoration = self.colors_list.get(
server_stat[c[0] + '_decoration'].replace('_LOG', '')
if c[0] + '_decoration' in server_stat
else self.colors_list[server_stat['status']],
self.colors_list['DEFAULT'],
)
if c[0] == 'status':
decoration = self.colors_list[server_stat['status']]
self.term_window.addnstr(y, xc, value, c[2], decoration)
xc += c[2] + self.space_between_column
cpt += 1
# Next line, next server...

View File

@ -185,6 +185,7 @@ class GlancesRestfulApi:
router.add_api_route(
f'/api/{self.API_VERSION}/status',
status_code=status.HTTP_200_OK,
methods=['HEAD', 'GET'],
response_class=ORJSONResponse,
endpoint=self._api_status,
)

View File

@ -4,7 +4,6 @@
"requires": true,
"packages": {
"": {
"name": "static",
"dependencies": {
"bootstrap": "^3.4.1",
"favico.js": "^0.3.10",
@ -1975,24 +1974,25 @@
}
},
"node_modules/eslint-plugin-vue": {
"version": "9.17.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz",
"integrity": "sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==",
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz",
"integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"globals": "^13.24.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.13",
"semver": "^7.5.4",
"vue-eslint-parser": "^9.3.1",
"postcss-selector-parser": "^6.0.15",
"semver": "^7.6.0",
"vue-eslint-parser": "^9.4.3",
"xml-name-validator": "^4.0.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
},
"peerDependencies": {
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
"eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
"node_modules/eslint-scope": {
@ -2522,9 +2522,9 @@
"dev": true
},
"node_modules/globals": {
"version": "13.20.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
"integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@ -3371,18 +3371,6 @@
"tslib": "^2.0.3"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/magic-string": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
@ -4127,9 +4115,9 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
"integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
@ -4717,13 +4705,10 @@
}
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
@ -5444,9 +5429,9 @@
}
},
"node_modules/vue-eslint-parser": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz",
"integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==",
"version": "9.4.3",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
"integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
@ -5807,9 +5792,9 @@
"dev": true
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"engines": {
"node": ">=10.0.0"
@ -5836,12 +5821,6 @@
"node": ">=12"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@ -7400,17 +7379,18 @@
}
},
"eslint-plugin-vue": {
"version": "9.17.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz",
"integrity": "sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==",
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz",
"integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"globals": "^13.24.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.13",
"semver": "^7.5.4",
"vue-eslint-parser": "^9.3.1",
"postcss-selector-parser": "^6.0.15",
"semver": "^7.6.0",
"vue-eslint-parser": "^9.4.3",
"xml-name-validator": "^4.0.0"
}
},
@ -7812,9 +7792,9 @@
"dev": true
},
"globals": {
"version": "13.20.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
"integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
@ -8422,15 +8402,6 @@
"tslib": "^2.0.3"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"magic-string": {
"version": "0.30.0",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
@ -8967,9 +8938,9 @@
}
},
"postcss-selector-parser": {
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
"integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
@ -9373,13 +9344,10 @@
}
},
"semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true
},
"send": {
"version": "0.18.0",
@ -9909,9 +9877,9 @@
}
},
"vue-eslint-parser": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz",
"integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==",
"version": "9.4.3",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
"integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
"dev": true,
"requires": {
"debug": "^4.3.4",
@ -10149,9 +10117,9 @@
"dev": true
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"requires": {}
},
@ -10161,12 +10129,6 @@
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -11,7 +11,7 @@ It should implement a Class named PluginModel (inherited from GlancesPluginModel
This class should be based on the MVC model.
- model: where the stats are updated (update method)
- view: where the stats are prepare to be displayed (update_views)
- controler: where the stats are displayed (msg_curse method)
- controller: where the stats are displayed (msg_curse method)
A plugin should define the following global variables:

View File

@ -8,12 +8,14 @@
"""Containers plugin."""
import os
from copy import deepcopy
from typing import Any, Dict, List, Optional, Tuple
from glances.globals import iteritems, itervalues
from glances.logger import logger
from glances.plugins.containers.engines.docker import DockerContainersExtension, import_docker_error_tag
from glances.plugins.containers.engines.podman import PodmanContainersExtension, import_podman_error_tag
from glances.plugins.containers.engines import ContainersExtension
from glances.plugins.containers.engines.docker import DockerExtension, import_docker_error_tag
from glances.plugins.containers.engines.podman import PodmanExtension, import_podman_error_tag
from glances.plugins.plugin.model import GlancesPluginModel
from glances.processes import glances_processes
from glances.processes import sort_stats as sort_stats_processes
@ -139,14 +141,15 @@ class PluginModel(GlancesPluginModel):
# We want to display the stat in the curse interface
self.display_curse = True
self.watchers: Dict[str, ContainersExtension] = {}
# Init the Docker API
self.docker_extension = DockerContainersExtension() if not import_docker_error_tag else None
if not import_docker_error_tag:
self.watchers['docker'] = DockerExtension()
# Init the Podman API
if import_podman_error_tag:
self.podman_extension = None
else:
self.podman_extension = PodmanContainersExtension(podman_sock=self._podman_sock())
if not import_podman_error_tag:
self.watchers['podman'] = PodmanExtension(podman_sock=self._podman_sock())
# Sort key
self.sort_key = None
@ -155,7 +158,7 @@ class PluginModel(GlancesPluginModel):
self.update()
self.refresh_timer.set(0)
def _podman_sock(self):
def _podman_sock(self) -> str:
"""Return the podman sock.
Could be desfined in the [docker] section thanks to the podman_sock option.
Default value: unix:///run/user/1000/podman/podman.sock
@ -165,20 +168,19 @@ class PluginModel(GlancesPluginModel):
return "unix:///run/user/1000/podman/podman.sock"
return conf_podman_sock[0]
def exit(self):
def exit(self) -> None:
"""Overwrite the exit method to close threads."""
if self.docker_extension:
self.docker_extension.stop()
if self.podman_extension:
self.podman_extension.stop()
for watcher in itervalues(self.watchers):
watcher.stop()
# Call the father class
super().exit()
def get_key(self):
def get_key(self) -> str:
"""Return the key of the list."""
return 'name'
def get_export(self):
def get_export(self) -> List[Dict]:
"""Overwrite the default export method.
- Only exports containers
@ -197,7 +199,7 @@ class PluginModel(GlancesPluginModel):
return ret
def _all_tag(self):
def _all_tag(self) -> bool:
"""Return the all tag of the Glances/Docker configuration file.
# By default, Glances only display running containers
@ -211,52 +213,35 @@ class PluginModel(GlancesPluginModel):
@GlancesPluginModel._check_decorator
@GlancesPluginModel._log_result_decorator
def update(self):
def update(self) -> List[Dict]:
"""Update Docker and podman stats using the input method."""
# Connection should be ok
if self.docker_extension is None and self.podman_extension is None:
if not self.watchers:
return self.get_init_value()
if self.input_method == 'local':
# Update stats
stats_docker = self.update_docker() if self.docker_extension else {}
stats_podman = self.update_podman() if self.podman_extension else {}
stats = stats_docker.get('containers', []) + stats_podman.get('containers', [])
elif self.input_method == 'snmp':
# Update stats using SNMP
# Not available
pass
if self.input_method != 'local':
return self.get_init_value()
# Update stats
stats = []
for engine, watcher in iteritems(self.watchers):
version, containers = watcher.update(all_tag=self._all_tag())
for container in containers:
container["engine"] = 'docker'
stats.extend(containers)
# Sort and update the stats
# @TODO: Have a look because sort did not work for the moment (need memory stats ?)
self.sort_key, self.stats = sort_docker_stats(stats)
return self.stats
def update_docker(self):
"""Update Docker stats using the input method."""
version, containers = self.docker_extension.update(all_tag=self._all_tag())
for container in containers:
container["engine"] = 'docker'
return {"version": version, "containers": containers}
def update_podman(self):
"""Update Podman stats."""
version, containers = self.podman_extension.update(all_tag=self._all_tag())
for container in containers:
container["engine"] = 'podman'
return {"version": version, "containers": containers}
def get_user_ticks(self):
"""Return the user ticks by reading the environment variable."""
return os.sysconf(os.sysconf_names['SC_CLK_TCK'])
def memory_usage_no_cache(self, mem):
@staticmethod
def memory_usage_no_cache(mem: Dict[str, float]) -> float:
"""Return the 'real' memory usage by removing inactive_file to usage"""
# Ref: https://github.com/docker/docker-py/issues/3210
return mem['usage'] - (mem['inactive_file'] if 'inactive_file' in mem else 0)
def update_views(self):
def update_views(self) -> bool:
"""Update stats views."""
# Call the father's method
super().update_views()
@ -305,7 +290,7 @@ class PluginModel(GlancesPluginModel):
return True
def msg_curse(self, args=None, max_width=None):
def msg_curse(self, args=None, max_width: Optional[int] = None) -> List[str]:
"""Return the dict to display in the curse interface."""
# Init the return message
ret = []
@ -369,7 +354,9 @@ class PluginModel(GlancesPluginModel):
if self.views['show_pod_name']:
ret.append(self.curse_add_line(' {:{width}}'.format(container.get("pod_id", "-"), width=12)))
# Name
ret.append(self.curse_add_line(self._msg_name(container=container, max_width=name_max_width)))
ret.append(
self.curse_add_line(' {:{width}}'.format(container['name'][:name_max_width], width=name_max_width))
)
# Status
status = self.container_alert(container['status'])
msg = '{:>10}'.format(container['status'][0:10])
@ -441,12 +428,8 @@ class PluginModel(GlancesPluginModel):
return ret
def _msg_name(self, container, max_width):
"""Build the container name."""
name = container['name'][:max_width]
return ' {:{width}}'.format(name, width=max_width)
def container_alert(self, status):
@staticmethod
def container_alert(status: str) -> str:
"""Analyse the container status."""
if status == 'running':
return 'OK'
@ -457,7 +440,7 @@ class PluginModel(GlancesPluginModel):
return 'CAREFUL'
def sort_docker_stats(stats):
def sort_docker_stats(stats: List[Dict[str, Any]]) -> Tuple[str, List[Dict[str, Any]]]:
# Sort Docker stats using the same function than processes
sort_by = glances_processes.sort_key
sort_by_secondary = 'memory_usage'

View File

@ -0,0 +1,9 @@
from typing import Any, Dict, Protocol, Tuple
class ContainersExtension(Protocol):
def stop(self) -> None:
raise NotImplementedError
def update(self, all_tag) -> Tuple[Dict, list[Dict[str, Any]]]:
raise NotImplementedError

View File

@ -207,7 +207,7 @@ class DockerStatsFetcher:
return stats
class DockerContainersExtension:
class DockerExtension:
"""Glances' Containers Plugin's Docker Extension unit"""
CONTAINER_ACTIVE_STATUS = ['running', 'paused']

View File

@ -93,9 +93,9 @@ class PodmanContainerStatsFetcher:
stats["network"]['tx'] = api_stats["NetOutput"]
stats["network"]['time_since_update'] = 1
# Hardcode to 1 as podman already sends at the same fixed rate per second
else:
elif api_stats["Network"] is not None:
# api_stats["Network"] can be None if the infra container of the pod is killed
# For podman in rootless mode
# Note: No stats are being sent as of now in rootless mode. They are defaulting to 0 on podman end.
stats['network'] = {
"cumulative_rx": sum(interface["RxBytes"] for interface in api_stats["Network"].values()),
"cumulative_tx": sum(interface["TxBytes"] for interface in api_stats["Network"].values()),
@ -243,7 +243,7 @@ class PodmanPodStatsFetcher:
return {"ior": ior, "iow": iow, "time_since_update": 1}
class PodmanContainersExtension:
class PodmanExtension:
"""Glances' Containers Plugin's Docker Extension unit"""
CONTAINER_ACTIVE_STATUS = ['running', 'paused']

View File

@ -165,8 +165,6 @@ class PluginModel(GlancesPluginModel):
stats = self.update_local()
elif self.input_method == 'snmp':
stats = self.update_snmp()
else:
stats = self.get_init_value()
# Update the stats
self.stats = stats
@ -185,7 +183,7 @@ class PluginModel(GlancesPluginModel):
# Init new stats
stats = self.get_init_value()
stats['total'] = cpu_percent.get()
stats['total'] = cpu_percent.get_cpu()
# Standards stats
# - user: time spent by normal processes executing in user mode; on Linux this also includes guest time

View File

@ -114,127 +114,143 @@ class PluginModel(GlancesPluginModel):
@GlancesPluginModel._log_result_decorator
def update(self):
"""Update the FS stats using the input method."""
# Init new stats
stats = self.get_init_value()
# Update the stats
if self.input_method == 'local':
# Update stats using the standard system lib
# Grab the stats using the psutil disk_partitions
# If 'all'=False return physical devices only (e.g. hard disks, cd-rom drives, USB keys)
# and ignore all others (e.g. memory partitions such as /dev/shm)
try:
fs_stat = psutil.disk_partitions(all=False)
except (UnicodeDecodeError, PermissionError):
logger.debug("Plugin - fs: PsUtil fetch failed")
return self.stats
# Optional hack to allow logical mounts points (issue #448)
allowed_fs_types = self.get_conf_value('allow')
if allowed_fs_types:
# Avoid Psutil call unless mounts need to be allowed
try:
all_mounted_fs = psutil.disk_partitions(all=True)
except (UnicodeDecodeError, PermissionError):
logger.debug("Plugin - fs: PsUtil extended fetch failed")
else:
# Discard duplicates (#2299) and add entries matching allowed fs types
tracked_mnt_points = {f.mountpoint for f in fs_stat}
for f in all_mounted_fs:
if (
any(f.fstype.find(fs_type) >= 0 for fs_type in allowed_fs_types)
and f.mountpoint not in tracked_mnt_points
):
fs_stat.append(f)
# Loop over fs
for fs in fs_stat:
# Hide the stats if the mount point is in the exclude list
if not self.is_display(fs.mountpoint):
continue
# Grab the disk usage
try:
fs_usage = psutil.disk_usage(fs.mountpoint)
except OSError:
# Correct issue #346
# Disk is ejected during the command
continue
fs_current = {
'device_name': fs.device,
'fs_type': fs.fstype,
# Manage non breaking space (see issue #1065)
'mnt_point': u(fs.mountpoint).replace('\u00a0', ' '),
'size': fs_usage.total,
'used': fs_usage.used,
'free': fs_usage.free,
'percent': fs_usage.percent,
'key': self.get_key(),
}
# Hide the stats if the device name is in the exclude list
# Correct issue: glances.conf FS hide not applying #1666
if not self.is_display(fs_current['device_name']):
continue
# Add alias if exist (define in the configuration file)
if self.has_alias(fs_current['mnt_point']) is not None:
fs_current['alias'] = self.has_alias(fs_current['mnt_point'])
stats.append(fs_current)
elif self.input_method == 'snmp':
# Update stats using SNMP
# SNMP bulk command to get all file system in one shot
try:
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True)
except KeyError:
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True)
# Loop over fs
if self.short_system_name in ('windows', 'esxi'):
# Windows or ESXi tips
for fs in fs_stat:
# Memory stats are grabbed in the same OID table (ignore it)
if fs == 'Virtual Memory' or fs == 'Physical Memory' or fs == 'Real Memory':
continue
size = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
used = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
percent = float(used * 100 / size)
fs_current = {
'device_name': '',
'mnt_point': fs.partition(' ')[0],
'size': size,
'used': used,
'percent': percent,
'key': self.get_key(),
}
# Do not take hidden file system into account
if self.is_hide(fs_current['mnt_point']):
continue
stats.append(fs_current)
else:
# Default behavior
for fs in fs_stat:
fs_current = {
'device_name': fs_stat[fs]['device_name'],
'mnt_point': fs,
'size': int(fs_stat[fs]['size']) * 1024,
'used': int(fs_stat[fs]['used']) * 1024,
'percent': float(fs_stat[fs]['percent']),
'key': self.get_key(),
}
# Do not take hidden file system into account
if self.is_hide(fs_current['mnt_point']) or self.is_hide(fs_current['device_name']):
continue
stats.append(fs_current)
stats = self.update_local()
else:
stats = self.get_init_value()
# Update the stats
self.stats = stats
return self.stats
def update_local(self):
"""Update the FS stats using the input method."""
# Init new stats
stats = self.get_init_value()
# Update stats using the standard system lib
# Grab the stats using the psutil disk_partitions
# If 'all'=False return physical devices only (e.g. hard disks, cd-rom drives, USB keys)
# and ignore all others (e.g. memory partitions such as /dev/shm)
try:
fs_stat = psutil.disk_partitions(all=False)
except (UnicodeDecodeError, PermissionError):
logger.debug("Plugin - fs: PsUtil fetch failed")
return stats
# Optional hack to allow logical mounts points (issue #448)
allowed_fs_types = self.get_conf_value('allow')
if allowed_fs_types:
# Avoid Psutil call unless mounts need to be allowed
try:
all_mounted_fs = psutil.disk_partitions(all=True)
except (UnicodeDecodeError, PermissionError):
logger.debug("Plugin - fs: PsUtil extended fetch failed")
else:
# Discard duplicates (#2299) and add entries matching allowed fs types
tracked_mnt_points = {f.mountpoint for f in fs_stat}
for f in all_mounted_fs:
if (
any(f.fstype.find(fs_type) >= 0 for fs_type in allowed_fs_types)
and f.mountpoint not in tracked_mnt_points
):
fs_stat.append(f)
# Loop over fs
for fs in fs_stat:
# Hide the stats if the mount point is in the exclude list
# It avoids unnecessary call to PsUtil disk_usage
if not self.is_display(fs.mountpoint):
continue
# Grab the disk usage
try:
fs_usage = psutil.disk_usage(fs.mountpoint)
except OSError:
# Correct issue #346
# Disk is ejected during the command
continue
fs_current = {
'device_name': fs.device,
'fs_type': fs.fstype,
# Manage non breaking space (see issue #1065)
'mnt_point': u(fs.mountpoint).replace('\u00a0', ' '),
'size': fs_usage.total,
'used': fs_usage.used,
'free': fs_usage.free,
'percent': fs_usage.percent,
'key': self.get_key(),
}
# Hide the stats if the device name is in the exclude list
# Correct issue: glances.conf FS hide not applying #1666
if not self.is_display(fs_current['device_name']):
continue
# Add alias if exist (define in the configuration file)
if self.has_alias(fs_current['mnt_point']) is not None:
fs_current['alias'] = self.has_alias(fs_current['mnt_point'])
stats.append(fs_current)
return stats
def update_snmp(self):
"""Update the FS stats using the input method."""
# Init new stats
stats = self.get_init_value()
# Update stats using SNMP
# SNMP bulk command to get all file system in one shot
try:
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid[self.short_system_name], bulk=True)
except KeyError:
fs_stat = self.get_stats_snmp(snmp_oid=snmp_oid['default'], bulk=True)
# Loop over fs
if self.short_system_name in ('windows', 'esxi'):
# Windows or ESXi tips
for fs in fs_stat:
# Memory stats are grabbed in the same OID table (ignore it)
if fs == 'Virtual Memory' or fs == 'Physical Memory' or fs == 'Real Memory':
continue
size = int(fs_stat[fs]['size']) * int(fs_stat[fs]['alloc_unit'])
used = int(fs_stat[fs]['used']) * int(fs_stat[fs]['alloc_unit'])
percent = float(used * 100 / size)
fs_current = {
'device_name': '',
'mnt_point': fs.partition(' ')[0],
'size': size,
'used': used,
'percent': percent,
'key': self.get_key(),
}
# Do not take hidden file system into account
if self.is_hide(fs_current['mnt_point']):
continue
stats.append(fs_current)
else:
# Default behavior
for fs in fs_stat:
fs_current = {
'device_name': fs_stat[fs]['device_name'],
'mnt_point': fs,
'size': int(fs_stat[fs]['size']) * 1024,
'used': int(fs_stat[fs]['used']) * 1024,
'percent': float(fs_stat[fs]['percent']),
'key': self.get_key(),
}
# Do not take hidden file system into account
if self.is_hide(fs_current['mnt_point']) or self.is_hide(fs_current['device_name']):
continue
stats.append(fs_current)
return stats
def update_views(self):
"""Update stats views."""
# Call the father's method

View File

@ -9,6 +9,7 @@
"""Per-CPU plugin."""
from glances.cpu_percent import cpu_percent
from glances.globals import BSD, LINUX, MACOS, WINDOWS
from glances.plugins.plugin.model import GlancesPluginModel
# Fields description
@ -76,9 +77,16 @@ guest operating systems under the control of the Linux kernel.',
'description': '*(Linux)*: percent of time spent handling software interrupts.',
'unit': 'percent',
},
'dpc': {
'description': '*(Windows)*: percent of time spent handling deferred procedure calls.',
'unit': 'percent',
},
'interrupt': {
'description': '*(Windows)*: percent of time spent handling software interrupts.',
'unit': 'percent',
},
}
# Define the history items list
items_history_list = [
{'name': 'user', 'description': 'User CPU usage', 'y_unit': '%'},
@ -120,16 +128,12 @@ class PluginModel(GlancesPluginModel):
@GlancesPluginModel._log_result_decorator
def update(self):
"""Update per-CPU stats using the input method."""
# Init new stats
stats = self.get_init_value()
# Grab per-CPU stats using psutil's cpu_percent(percpu=True) and
# cpu_times_percent(percpu=True) methods
# Grab per-CPU stats using psutil's
if self.input_method == 'local':
stats = cpu_percent.get(percpu=True)
stats = cpu_percent.get_percpu()
else:
# Update stats using SNMP
pass
stats = self.get_init_value()
# Update the stats
self.stats = stats
@ -145,8 +149,17 @@ class PluginModel(GlancesPluginModel):
if not self.stats or not self.args.percpu or self.is_disabled():
return ret
# Define the default header
header = ['user', 'system', 'idle', 'iowait', 'steal']
# Define the headers based on OS
header = ['user', 'system']
if LINUX:
header.extend(['iowait', 'idle', 'irq', 'nice', 'steal', 'guest'])
elif MACOS:
header.extend(['idle', 'nice'])
elif BSD:
header.extend(['idle', 'irq', 'nice'])
elif WINDOWS:
header.extend(['dpc', 'interrupt'])
# Build the string message
if self.is_disabled('quicklook'):
@ -156,8 +169,6 @@ class PluginModel(GlancesPluginModel):
# Per CPU stats displayed per line
for stat in header:
if stat not in self.stats[0]:
continue
msg = f'{stat:>7}'
ret.append(self.curse_add_line(msg))
@ -183,8 +194,6 @@ class PluginModel(GlancesPluginModel):
msg = '{:4} '.format('?')
ret.append(self.curse_add_line(msg))
for stat in header:
if stat not in self.stats[0]:
continue
try:
msg = f'{cpu[stat]:6.1f}%'
except TypeError:
@ -196,12 +205,10 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_new_line())
if self.is_disabled('quicklook'):
ret.append(self.curse_add_line('CPU* '))
for stat in header:
if stat not in self.stats[0]:
continue
cpu_stat = sum([i[stat] for i in percpu_list[0 : self.max_cpu_display]]) / len(
[i[stat] for i in percpu_list[0 : self.max_cpu_display]]
)
percpu_stats = [i[stat] for i in percpu_list[0 : self.max_cpu_display]]
cpu_stat = sum(percpu_stats) / len(percpu_stats)
try:
msg = f'{cpu_stat:6.1f}%'
except TypeError:

View File

@ -98,7 +98,7 @@ class GlancesPluginModel:
logger.debug(f'Load section {self.plugin_name} in Glances configuration file')
self.load_limits(config=config)
# Init the alias (dictionnary)
# Init the alias (dictionary)
self.alias = self.read_alias()
# Init the actions

View File

@ -543,7 +543,7 @@ class PluginModel(GlancesPluginModel):
# Process list
# Loop over processes (sorted by the sort key previously compute)
# This is a Glances bottleneck (see flame graph),
# TODO: get_process_curses_data should be optimzed
# TODO: get_process_curses_data should be optimized
for position, process in enumerate(processes_list_sorted):
ret.extend(self.get_process_curses_data(process, position == args.cursor_position, args))

View File

@ -118,8 +118,8 @@ class PluginModel(GlancesPluginModel):
# Get the CPU percent value (global and per core)
# Stats is shared across all plugins
stats['cpu'] = cpu_percent.get()
stats['percpu'] = cpu_percent.get(percpu=True)
stats['cpu'] = cpu_percent.get_cpu()
stats['percpu'] = cpu_percent.get_percpu()
# Get the virtual and swap memory
stats['mem'] = psutil.virtual_memory().percent

View File

@ -48,7 +48,7 @@ fields_description = {
'description': 'Operating system version',
},
'hr_name': {
'description': 'Human readable operating sytem name',
'description': 'Human readable operating system name',
},
}

View File

@ -8,7 +8,7 @@
"""Wifi plugin.
Stats are retreived from the /proc/net/wireless file (Linux only):
Stats are retrieved from the /proc/net/wireless file (Linux only):
# cat /proc/net/wireless
Inter-| sta-| Quality | Discarded packets | Missed | WE

View File

@ -119,6 +119,14 @@ class GlancesProcesses:
"""Set args."""
self.args = args
def reset_internal_cache(self):
"""Reset the internal cache."""
self.cache_timer = Timer(0)
self.processlist_cache = {}
if hasattr(psutil.process_iter, 'cache_clear'):
# Cache clear only available in PsUtil 6 or higher
psutil.process_iter.cache_clear()
def reset_processcount(self):
"""Reset the global process count"""
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0, 'pid_max': None}
@ -285,7 +293,7 @@ class GlancesProcesses:
# - connections (TCP and UDP)
# - CPU min/max/mean
# Set the extended stats list (OS dependant)
# Set the extended stats list (OS dependent)
extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
if LINUX:
# num_fds only available on Unix system (see issue #1351)
@ -410,7 +418,7 @@ class GlancesProcesses:
#####################
sorted_attrs = ['cpu_percent', 'cpu_times', 'memory_percent', 'name', 'status', 'num_threads']
displayed_attr = ['memory_info', 'nice', 'pid']
# The following attributes are cached and only retreive every self.cache_timeout seconds
# The following attributes are cached and only retrieve every self.cache_timeout seconds
# Warning: 'name' can not be cached because it is used for filtering
cached_attrs = ['cmdline', 'username']
@ -445,7 +453,9 @@ class GlancesProcesses:
)
)
# Only get the info key
processlist = [p.info for p in processlist]
# PsUtil 6+ no longer check PID reused #2755 so use is_running in the loop
# Note: not sure it is realy needed but CPU consumption look the same with or without it
processlist = [p.info for p in processlist if p.is_running()]
# Sort the processes list by the current sort_key
processlist = sort_stats(processlist, sorted_by=self.sort_key, reverse=True)

View File

@ -136,7 +136,7 @@ class GlancesStats:
"""Load additional plugins if defined"""
def get_addl_plugins(self, plugin_path):
"""Get list of additonal plugins"""
"""Get list of additional plugins"""
_plugin_list = []
for plugin in os.listdir(plugin_path):
path = os.path.join(plugin_path, plugin)
@ -167,7 +167,7 @@ class GlancesStats:
sys.path.insert(0, path)
for plugin in get_addl_plugins(self, path):
if plugin in sys.modules:
logger.warn(f"Pugin {plugin} already in sys.modules, skipping (workaround: rename plugin)")
logger.warn(f"Plugin {plugin} already in sys.modules, skipping (workaround: rename plugin)")
else:
start_duration.reset()
try:
@ -260,19 +260,13 @@ class GlancesStats:
self._plugins[p].update_views()
def update(self):
"""Wrapper method to update the stats.
"""Wrapper method to update all stats.
Only called by standalone and server modes
"""
threads = []
# Start update of all enable plugins
for p in self.getPluginsList():
thread = threading.Thread(target=self.__update_plugin, args=(p,))
thread.start()
threads.append(thread)
# Wait the end of the update
for t in threads:
t.join()
for p in self.getPluginsList(enable=True):
self.__update_plugin(p)
def export(self, input_stats=None):
"""Export all the stats.

View File

@ -1,5 +1,5 @@
name: glances
version: '4.0.8+build2'
version: '4.1.1+build01' # Put the current stable version+buildXX
summary: Glances an Eye on your system. A top/htop alternative.
description: |

View File

@ -0,0 +1,25 @@
import sys
import time
sys.path.insert(0, '../glances')
###########
# from glances.cpu_percent import cpu_percent
# for _ in range(0, 5):
# print([i['total'] for i in cpu_percent.get_percpu()])
# time.sleep(2)
###########
from glances.main import GlancesMain
from glances.stats import GlancesStats
core = GlancesMain()
stats = GlancesStats(config=core.get_config(), args=core.get_args())
for _ in range(0, 5):
stats.update()
print([i['total'] for i in stats.get_plugin('percpu').get_raw()])
time.sleep(2)