Merge from develop
17
.flake8
@@ -1,20 +1,7 @@
|
||||
[flake8]
|
||||
|
||||
exclude = .git,__pycache__,docs/,build,dist
|
||||
ignore =
|
||||
W504 # line break after binary operator
|
||||
|
||||
# --- flake8-bugbear plugin
|
||||
B007 # Loop control variable 'keyword' not used within the loop body. If this is intended, start the name with an underscore.
|
||||
B014 # Redundant exception types in `except (IOError, OSError) as err:`. Write `except OSError as err:`, which catches exactly the same exceptions.
|
||||
B008 # Do not perform function calls in argument defaults.
|
||||
|
||||
# --- flake8-blind-except plugin
|
||||
B902 # blind except Exception: statement
|
||||
|
||||
# --- flake8-quotes plugin
|
||||
Q000 # Double quotes found but single quotes preferred
|
||||
|
||||
# --- flake8-quotes naming; disable all except N804 and N805
|
||||
W504, B007, B014, B008, B902, Q000,
|
||||
N801, N802, N803, N806, N807, N811, N812, N813, N814, N815, N816, N817, N818
|
||||
|
||||
# lines should not exceed 120 characters
|
||||
|
||||
6
.github/workflows/build.yml
vendored
@@ -96,7 +96,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ['alpine']
|
||||
os: ['alpine', 'ubuntu']
|
||||
tag: ${{ fromJson(needs.create_Docker_builds.outputs.tags) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Retrieve Repository Docker metadata
|
||||
id: docker_meta
|
||||
uses: crazy-max/ghaction-docker-meta@v4.1.1
|
||||
uses: crazy-max/ghaction-docker-meta@v4.3.0
|
||||
with:
|
||||
images: ${{ env.DEFAULT_DOCKER_IMAGE }}
|
||||
labels: |
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
push: ${{ env.PUSH_BRANCH == 'true' }}
|
||||
tags: "${{ env.DEFAULT_DOCKER_IMAGE }}:${{ matrix.os != 'alpine' && format('{0}-', matrix.os) || '' }}${{ matrix.tag.tag }}"
|
||||
|
||||
23
Makefile
@@ -64,6 +64,9 @@ flake8: venv-dev-upgrade ## Run flake8 linter.
|
||||
codespell: venv-dev-upgrade ## Run codespell to fix common misspellings in text files
|
||||
./venv/bin/codespell -S .git,./docs/_build,./Glances.egg-info,./venv,./glances/outputs,*.svg -L hart,bu,te,statics
|
||||
|
||||
semgrep: venv-dev-upgrade ## Run semgrep to find bugs and enforce code standards
|
||||
./venv/bin/semgrep --config=auto --lang python --use-git-ignore ./glances
|
||||
|
||||
profiling: ## How to start the profiling of the Glances software
|
||||
@echo "Please complete and run: sudo ./venv/bin/py-spy record -o ./docs/_static/glances-flame.svg -d 60 -s --pid <GLANCES PID>"
|
||||
|
||||
@@ -137,6 +140,11 @@ docker-alpine: ## Generate local docker images (Alpine)
|
||||
docker build --target minimal -f ./docker-files/alpine.Dockerfile -t glances:local-alpine-minimal .
|
||||
docker build --target dev -f ./docker-files/alpine.Dockerfile -t glances:local-alpine-dev .
|
||||
|
||||
docker-ubuntu: ## Generate local docker images (Ubuntu)
|
||||
docker build --target full -f ./docker-files/ubuntu.Dockerfile -t glances:local-ubuntu-full .
|
||||
docker build --target minimal -f ./docker-files/ubuntu.Dockerfile -t glances:local-ubuntu-minimal .
|
||||
docker build --target dev -f ./docker-files/ubuntu.Dockerfile -t glances:local-ubuntu-dev .
|
||||
|
||||
# ===================================================================
|
||||
# Run
|
||||
# ===================================================================
|
||||
@@ -151,13 +159,22 @@ run-local-conf: ## Start Glances in console mode with the system conf file
|
||||
./venv/bin/python -m glances
|
||||
|
||||
run-docker-alpine-minimal: ## Start Glances Alpine Docker minimal in console mode
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-minimal
|
||||
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-minimal
|
||||
|
||||
run-docker-alpine-full: ## Start Glances Alpine Docker full in console mode
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-full
|
||||
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-full
|
||||
|
||||
run-docker-alpine-dev: ## Start Glances Alpine Docker dev in console mode
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-dev
|
||||
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-alpine-dev
|
||||
|
||||
run-docker-ubuntu-minimal: ## Start Glances Ubuntu Docker minimal in console mode
|
||||
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-minimal
|
||||
|
||||
run-docker-ubuntu-full: ## Start Glances Ubuntu Docker full in console mode
|
||||
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-full
|
||||
|
||||
run-docker-ubuntu-dev: ## Start Glances Ubuntu Docker dev in console mode
|
||||
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it glances:local-ubuntu-dev
|
||||
|
||||
run-webserver: ## Start Glances in Web server mode
|
||||
./venv/bin/python -m glances -C ./conf/glances.conf -w
|
||||
|
||||
73
NEWS.rst
@@ -2,11 +2,82 @@
|
||||
Glances changelog
|
||||
==============================================================================
|
||||
|
||||
===============
|
||||
Version 3.4.0
|
||||
===============
|
||||
|
||||
See roadmap here: https://github.com/nicolargo/glances/issues?q=is%3Aopen+is%3Aissue+milestone%3A%22Glances+3.4.0%22
|
||||
|
||||
===============
|
||||
Version 3.3.1.1
|
||||
===============
|
||||
|
||||
Hard patch on the master branch.
|
||||
|
||||
Bug corrected:
|
||||
|
||||
* "ModuleNotFoundError: No module named 'ujson'" #2246
|
||||
* Remove surrounding quotes for quoted command arguments #2247 (related to #2239)
|
||||
|
||||
===============
|
||||
Version 3.3.1
|
||||
===============
|
||||
|
||||
Under development, see milestone https://github.com/nicolargo/glances/milestone/61
|
||||
Enhancements:
|
||||
|
||||
* Minor change on the help screen
|
||||
* Refactor some loop in the processes function
|
||||
* Replace json by ujson #2201
|
||||
|
||||
Bug corrected:
|
||||
|
||||
* Unable to see docker related information #2180
|
||||
* CSV export dependent on sort order for docker container cpu #2156
|
||||
* Error when process list is displayed in Programs mode #2209
|
||||
* Console formatting permanently messed up when other text printed #2211
|
||||
* API GET uptime returns formatted string, not seconds as the doc says #2158
|
||||
* Glances UI is breaking for multiline commands #2189
|
||||
|
||||
Documentation and CI:
|
||||
|
||||
* Add unitary test for memory profiling
|
||||
* Update memory profile chart
|
||||
* Add run-docker-ubuntu-* in Makefile
|
||||
* The open-web-browser option was missing dashes #2219
|
||||
* Correct regexp in glances.conf file example
|
||||
* What is CW from network #2222 (related to discussion #2221)
|
||||
* Change Glances repology URL
|
||||
* Add example for the date format
|
||||
* Correct Flake8 configuration file
|
||||
* Drop UT for Python 3.5 and 3.6 (no more available in Ubuntu 22.04)
|
||||
* Correct unitary test with Python 3.5
|
||||
* 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
|
||||
|
||||
Cyber security update:
|
||||
|
||||
* Security alert on ujson < 5.4
|
||||
* Merge pull request #2243 from nicolargo/renovate/nvidia-cuda-12.x
|
||||
* Merge pull request #2244 from nicolargo/renovate/crazy-max-ghaction-docker-meta-4.x
|
||||
* Merge pull request #2228 from nicolargo/renovate/zeroconf-0.x
|
||||
* Merge pull request #2242 from nicolargo/renovate/crazy-max-ghaction-docker-meta-4.x
|
||||
* Merge pull request #2239 from mfridge/action-command-split
|
||||
* Merge pull request #2165 from nicolargo/renovate/zeroconf-0.x
|
||||
* Merge pull request #2199 from nicolargo/renovate/alpine-3.x
|
||||
* Merge pull request #2202 from chncaption/oscs_fix_cdr0ts8au51t49so8c6g
|
||||
* Bump loader-utils from 2.0.0 to 2.0.3 in /glances/outputs/static #2187 - Update Web lib
|
||||
|
||||
Contributors for this version:
|
||||
|
||||
* Nicolargo
|
||||
* renovate[bot]
|
||||
* chncaption
|
||||
* fkwong
|
||||
* *mfridge
|
||||
|
||||
And also a big thanks to @RazCrimson (https://github.com/RazCrimson) for the support to the Glances community !
|
||||
|
||||
===============
|
||||
Version 3.3.0.4
|
||||
|
||||
29
README.rst
@@ -40,16 +40,22 @@ Glances - An eye on your system
|
||||
Summary
|
||||
=======
|
||||
|
||||
**Glances** is a cross-platform monitoring tool which aims to present a
|
||||
large amount of monitoring information through a curses or Web
|
||||
based interface. The information dynamically adapts depending on the
|
||||
size of the user interface.
|
||||
**Glances** is an open-source system cross-platform monitoring tool.
|
||||
It allows real-time monitoring of various aspects of your system such as
|
||||
CPU, memory, disk, network usage etc. It also allows monitoring of running processes,
|
||||
logged in users, temperatures, voltages, fan speeds etc.
|
||||
It also supports container monitoring, it supports different container management
|
||||
systems such as Docker, LXC. The information is presented in an easy to read dashboard
|
||||
and can also be used for remote monitoring of systems via a web interface or command
|
||||
line interface. It is easy to install and use and can be customized to show only
|
||||
the information that you are interested in.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/nicolargo/glances/develop/docs/_static/glances-summary.png
|
||||
|
||||
It can also work in client/server mode. Remote monitoring could be done
|
||||
via terminal, Web interface or API (XML-RPC and RESTful). Stats can also
|
||||
be exported to files or external time/value databases.
|
||||
In client/server mode, remote monitoring could be done via terminal,
|
||||
Web interface or API (XML-RPC and RESTful).
|
||||
Stats can also be exported to files or external time/value databases, CSV or direct
|
||||
output to STDOUT.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/nicolargo/glances/develop/docs/_static/glances-responsive-webdesign.png
|
||||
|
||||
@@ -105,6 +111,7 @@ Optional dependencies:
|
||||
- ``py-cpuinfo`` (for the Quicklook CPU info module)
|
||||
- ``pygal`` (for the graph export module)
|
||||
- ``pymdstat`` (for RAID support) [Linux-only]
|
||||
- ``pymongo`` (for the MongoDB export module) [Only for Python >= 3.7]
|
||||
- ``pysnmp`` (for SNMP support)
|
||||
- ``pySMART.smartx`` (for HDD Smart support) [Linux-only]
|
||||
- ``pyzmq`` (for the ZeroMQ export module)
|
||||
@@ -207,7 +214,7 @@ Run last version of Glances container in *console mode*:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it nicolargo/glances:latest-full
|
||||
docker run --rm -e TZ="${TZ}" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host --network host -it nicolargo/glances:latest-full
|
||||
|
||||
Additionally, if you want to use your own glances.conf file, you can
|
||||
create your own Dockerfile:
|
||||
@@ -224,7 +231,7 @@ variable setting parameters for the glances startup command):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker run -v `pwd`/glances.conf:/etc/glances.conf -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host -e GLANCES_OPT="-C /etc/glances.conf" -it nicolargo/glances:latest-full
|
||||
docker run -e TZ="${TZ}" -v `pwd`/glances.conf:/etc/glances.conf -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host -e GLANCES_OPT="-C /etc/glances.conf" -it nicolargo/glances:latest-full
|
||||
|
||||
Where \`pwd\`/glances.conf is a local directory containing your glances.conf file.
|
||||
|
||||
@@ -232,7 +239,7 @@ Run the container in *Web server mode*:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
docker run -d --restart="always" -p 61208-61209:61208-61209 -e GLANCES_OPT="-w" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host nicolargo/glances:latest-full
|
||||
docker run -d --restart="always" -p 61208-61209:61208-61209 -e TZ="${TZ}" -e GLANCES_OPT="-w" -v /var/run/docker.sock:/var/run/docker.sock:ro --pid host nicolargo/glances:latest-full
|
||||
|
||||
GNU/Linux
|
||||
---------
|
||||
@@ -464,7 +471,7 @@ Glances is distributed under the LGPL version 3 license. See ``COPYING`` for mor
|
||||
.. _readthedocs: https://glances.readthedocs.io/
|
||||
.. _forum: https://groups.google.com/forum/?hl=en#!forum/glances-users
|
||||
.. _wiki: https://github.com/nicolargo/glances/wiki/How-to-contribute-to-Glances-%3F
|
||||
.. _package: https://repology.org/metapackage/glances/packages
|
||||
.. _package: https://repology.org/project/glances/versions
|
||||
.. _sponsors: https://github.com/sponsors/nicolargo
|
||||
.. _wishlist: https://www.amazon.fr/hz/wishlist/ls/BWAAQKWFR3FI?ref_=wl_share
|
||||
.. _issue2021: https://github.com/nicolargo/glances/issues/2021#issuecomment-1197831157
|
||||
|
||||
@@ -12,6 +12,8 @@ check_update=true
|
||||
# History size (maximum number of values)
|
||||
# Default is 1200 values (~1h with the default refresh rate)
|
||||
history_size=1200
|
||||
# Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z)
|
||||
#strftime_format="%Y-%m-%d %H:%M:%S %Z"
|
||||
|
||||
##############################################################################
|
||||
# User interface
|
||||
@@ -212,7 +214,7 @@ critical=-85
|
||||
disable=False
|
||||
# Define the list of hidden disks (comma-separated regexp)
|
||||
#hide=sda2,sda5,loop.*
|
||||
hide=loop.*,/dev/loop*
|
||||
hide=loop.*,/dev/loop.*
|
||||
# Define the list of disks to be show (comma-separated)
|
||||
#show=sda.*
|
||||
# Alias for sda1
|
||||
@@ -235,12 +237,12 @@ critical=90
|
||||
#allow=shm
|
||||
|
||||
[irq]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/irq.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
[folders]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/folders.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/folders.html
|
||||
disable=False
|
||||
# Define a folder list to monitor
|
||||
# The list is composed of items (list_#nb <= 10)
|
||||
@@ -261,13 +263,18 @@ disable=False
|
||||
#folder_3_path=/nonexisting
|
||||
#folder_4_path=/root
|
||||
|
||||
[cloud]
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/cloud.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
[raid]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/raid.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
[smart]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/smart.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
@@ -305,6 +312,7 @@ battery_critical=95
|
||||
#core 0_fans_speed_alias=CPU Core 0 fan
|
||||
#or
|
||||
#core 0_alias=CPU Core 0
|
||||
#core 1_alias=CPU Core 1
|
||||
|
||||
[processcount]
|
||||
disable=False
|
||||
@@ -581,6 +589,15 @@ db=glances
|
||||
#user=root
|
||||
#password=root
|
||||
|
||||
[mongodb]
|
||||
# Configuration for the --export mongodb option
|
||||
# https://www.mongodb.com
|
||||
host=localhost
|
||||
port=27017
|
||||
db=glances
|
||||
user=root
|
||||
password=example
|
||||
|
||||
[kafka]
|
||||
# Configuration for the --export kafka option
|
||||
# http://kafka.apache.org/
|
||||
|
||||
@@ -8,4 +8,6 @@ autoflake
|
||||
codespell
|
||||
memory-profiler
|
||||
matplotlib
|
||||
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
semgrep
|
||||
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
@@ -21,9 +21,11 @@ services:
|
||||
privileged: true
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
- "./glances.conf:/glances/conf/glances.conf"
|
||||
environment:
|
||||
- "GLANCES_OPT=-w"
|
||||
- GLANCES_OPT: "-C /glances/conf/glances.conf -w"
|
||||
- TZ: "${TZ}"
|
||||
labels:
|
||||
- "traefik.port=61208"
|
||||
- "traefik.frontend.rule=Host:glances.docker.localhost"
|
||||
|
||||
@@ -5,13 +5,12 @@ services:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
restart: always
|
||||
ports:
|
||||
- "61208:61208"
|
||||
environment:
|
||||
GLANCES_OPT: "-C /glances/conf/glances.conf -w"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
- "./glances.conf:/glances/conf/glances.conf"
|
||||
pid: "host"
|
||||
privileged: true
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
- "./glances.conf:/glances/conf/glances.conf"
|
||||
environment:
|
||||
- GLANCES_OPT: "-C /glances/conf/glances.conf -w"
|
||||
- TZ: "${TZ}"
|
||||
|
||||
@@ -10,8 +10,10 @@ refresh=2
|
||||
# Does Glances should check if a newer version is available on PyPI ?
|
||||
check_update=false
|
||||
# History size (maximum number of values)
|
||||
# Default is 3600 seconds (1 hour)
|
||||
history_size=3600
|
||||
# Default is 1200 values (~1h with the default refresh rate)
|
||||
history_size=1200
|
||||
# Set the way Glances should display the date (default is %Y-%m-%d %H:%M:%S %Z)
|
||||
#strftime_format="%Y-%m-%d %H:%M:%S %Z"
|
||||
|
||||
##############################################################################
|
||||
# User interface
|
||||
@@ -212,7 +214,7 @@ critical=-85
|
||||
disable=False
|
||||
# Define the list of hidden disks (comma-separated regexp)
|
||||
#hide=sda2,sda5,loop.*
|
||||
hide=loop.*,/dev/loop*
|
||||
hide=loop.*,/dev/loop.*
|
||||
# Define the list of disks to be show (comma-separated)
|
||||
#show=sda.*
|
||||
# Alias for sda1
|
||||
@@ -235,12 +237,12 @@ critical=90
|
||||
#allow=shm
|
||||
|
||||
[irq]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/irq.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/irq.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
[folders]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/folders.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/folders.html
|
||||
disable=False
|
||||
# Define a folder list to monitor
|
||||
# The list is composed of items (list_#nb <= 10)
|
||||
@@ -261,13 +263,18 @@ disable=False
|
||||
#folder_3_path=/nonexisting
|
||||
#folder_4_path=/root
|
||||
|
||||
[cloud]
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/cloud.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
[raid]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/raid.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/raid.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
[smart]
|
||||
# Documentation: https://glances.readthedocs.io/en/stable/aoa/smart.html
|
||||
# Documentation: https://glances.readthedocs.io/en/latest/aoa/smart.html
|
||||
# This plugin is disabled by default
|
||||
disable=True
|
||||
|
||||
@@ -582,6 +589,15 @@ db=glances
|
||||
#user=root
|
||||
#password=root
|
||||
|
||||
[mongodb]
|
||||
# Configuration for the --export mongodb option
|
||||
# https://www.mongodb.com
|
||||
host=localhost
|
||||
port=27017
|
||||
db=glances
|
||||
user=root
|
||||
password=example
|
||||
|
||||
[kafka]
|
||||
# Configuration for the --export kafka option
|
||||
# http://kafka.apache.org/
|
||||
|
||||
@@ -26,7 +26,9 @@ RUN apk add --no-cache \
|
||||
curl \
|
||||
lm-sensors \
|
||||
wireless-tools \
|
||||
iputils
|
||||
smartmontools \
|
||||
iputils \
|
||||
tzdata
|
||||
|
||||
##############################################################################
|
||||
# Install the dependencies beforehand to make them cacheable
|
||||
@@ -89,10 +91,13 @@ RUN apk add --no-cache \
|
||||
python3 \
|
||||
py3-packaging \
|
||||
py3-dateutil \
|
||||
py3-requests \
|
||||
curl \
|
||||
lm-sensors \
|
||||
wireless-tools \
|
||||
iputils
|
||||
smartmontools \
|
||||
iputils \
|
||||
tzdata
|
||||
|
||||
COPY --from=buildRequirements /root/.local/bin /usr/local/bin/
|
||||
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /usr/lib/python${PYTHON_VERSION}/site-packages/
|
||||
|
||||
148
docker-files/ubuntu.Dockerfile
Normal file
@@ -0,0 +1,148 @@
|
||||
#
|
||||
# Glances Dockerfile (based on Ubuntu)
|
||||
#
|
||||
# https://github.com/nicolargo/glances
|
||||
#
|
||||
|
||||
# WARNING: the versions should be set.
|
||||
# Ex: Python 3.10 for Ubuntu 22.04
|
||||
# Note: ENV is for future running containers. ARG for building your Docker image.
|
||||
|
||||
ARG IMAGE_VERSION=12.1.0-base-ubuntu22.04
|
||||
ARG PYTHON_VERSION=3.10
|
||||
ARG PIP_MIRROR=https://mirrors.aliyun.com/pypi/simple/
|
||||
FROM nvidia/cuda:${IMAGE_VERSION} as build
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-wheel \
|
||||
musl-dev \
|
||||
build-essential \
|
||||
libzmq5 \
|
||||
curl \
|
||||
lm-sensors \
|
||||
wireless-tools \
|
||||
smartmontools \
|
||||
net-tools \
|
||||
tzdata \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
##############################################################################
|
||||
# Install the dependencies beforehand to make them cacheable
|
||||
|
||||
FROM build as buildRequirements
|
||||
ARG PYTHON_VERSION
|
||||
ARG PIP_MIRROR
|
||||
|
||||
ARG PIP_MIRROR=https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user -r requirements.txt -i ${PIP_MIRROR}
|
||||
|
||||
# Minimal means no webui, but it break what is done previously (see #2155)
|
||||
# So install the webui requirements...
|
||||
COPY webui-requirements.txt .
|
||||
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user -r webui-requirements.txt -i ${PIP_MIRROR}
|
||||
|
||||
# As minimal image we want to monitor others docker containers
|
||||
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user docker -i ${PIP_MIRROR}
|
||||
|
||||
# Force install otherwise it could be cached without rerun
|
||||
ARG CHANGING_ARG
|
||||
RUN python${PYTHON_VERSION} -m pip install --no-cache-dir --user glances -i ${PIP_MIRROR}
|
||||
|
||||
##############################################################################
|
||||
|
||||
FROM build as buildOptionalRequirements
|
||||
ARG PYTHON_VERSION
|
||||
ARG PIP_MIRROR
|
||||
|
||||
COPY requirements.txt .
|
||||
COPY optional-requirements.txt .
|
||||
RUN CASS_DRIVER_NO_CYTHON=1 pip3 install --no-cache-dir --user -r optional-requirements.txt -i ${PIP_MIRROR}
|
||||
|
||||
##############################################################################
|
||||
# full image
|
||||
##############################################################################
|
||||
|
||||
FROM build as full
|
||||
ARG PYTHON_VERSION
|
||||
|
||||
COPY --from=buildRequirements /root/.local/bin /root/.local/bin/
|
||||
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
|
||||
COPY --from=buildOptionalRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
|
||||
COPY ./docker-compose/glances.conf /etc/glances.conf
|
||||
|
||||
# EXPOSE PORT (XMLRPC / WebUI)
|
||||
EXPOSE 61209 61208
|
||||
|
||||
# Define default command.
|
||||
WORKDIR /glances
|
||||
CMD python3 -m glances -C /etc/glances.conf $GLANCES_OPT
|
||||
|
||||
##############################################################################
|
||||
# minimal image
|
||||
##############################################################################
|
||||
|
||||
# Create running images without any building dependency
|
||||
FROM nvidia/cuda:${IMAGE_VERSION} as minimal
|
||||
ARG PYTHON_VERSION
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
python3-packaging \
|
||||
python3-dateutil \
|
||||
python3-requests \
|
||||
curl \
|
||||
lm-sensors \
|
||||
wireless-tools \
|
||||
smartmontools \
|
||||
net-tools \
|
||||
tzdata \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=buildRequirements /root/.local/bin /root/.local/bin/
|
||||
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
|
||||
COPY ./docker-compose/glances.conf /etc/glances.conf
|
||||
|
||||
# EXPOSE PORT (XMLRPC / WebUI)
|
||||
EXPOSE 61209 61208
|
||||
|
||||
# Define default command.
|
||||
WORKDIR /glances
|
||||
CMD python3 -m glances -C /etc/glances.conf $GLANCES_OPT
|
||||
|
||||
##############################################################################
|
||||
# dev image
|
||||
##############################################################################
|
||||
|
||||
FROM full as dev
|
||||
ARG PYTHON_VERSION
|
||||
|
||||
COPY --from=buildRequirements /root/.local/bin /root/.local/bin/
|
||||
COPY --from=buildRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
|
||||
COPY --from=buildOptionalRequirements /root/.local/lib/python${PYTHON_VERSION}/site-packages /root/.local/lib/python${PYTHON_VERSION}/site-packages/
|
||||
COPY ./docker-compose/glances.conf /etc/glances.conf
|
||||
|
||||
# Copy the current Glances source code
|
||||
COPY . /glances
|
||||
|
||||
# EXPOSE PORT (XMLRPC / WebUI)
|
||||
EXPOSE 61209 61208
|
||||
|
||||
# Forward access and error logs to Docker's log collector
|
||||
RUN ln -sf /dev/stdout /tmp/glances-root.log \
|
||||
&& ln -sf /dev/stderr /var/log/error.log
|
||||
|
||||
# Define default command.
|
||||
WORKDIR /glances
|
||||
CMD python3 -m glances -C /etc/glances.conf $GLANCES_OPT
|
||||
BIN
docs/_static/cloud.png
vendored
Normal file
|
After Width: | Height: | Size: 21 KiB |
2424
docs/_static/glances-architexture.excalidraw
vendored
Normal file
4
docs/_static/glances-flame.svg
vendored
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
BIN
docs/_static/processlist-extended.png
vendored
Normal file
|
After Width: | Height: | Size: 62 KiB |
15
docs/aoa/cloud.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
.. _cloud:
|
||||
|
||||
CLOUD
|
||||
=====
|
||||
|
||||
This plugin diplays information about the cloud provider if your host is running on OpenStack.
|
||||
|
||||
The plugin use the standard OpenStack `metadata`_ service to retrieve the information.
|
||||
|
||||
This plugin is disable by default, please use the --enable-plugin cloud option
|
||||
to enable it.
|
||||
|
||||
.. image:: ../_static/cloud.png
|
||||
|
||||
.. _metadata: https://docs.openstack.org/nova/latest/user/metadata.html
|
||||
@@ -37,3 +37,9 @@ or another example:
|
||||
|
||||
[diskio]
|
||||
show=sda.*
|
||||
|
||||
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/
|
||||
@@ -47,4 +47,9 @@ under the ``[docker]`` section:
|
||||
|
||||
You can use all the variables ({{foo}}) available in the Docker plugin.
|
||||
|
||||
Filtering (for hide or show) 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/
|
||||
.. _docker-py: https://github.com/docker/docker-py
|
||||
|
||||
@@ -53,3 +53,9 @@ Example to only show /dev/sdb mount points:
|
||||
|
||||
[fs]
|
||||
show=/dev/sdb.*
|
||||
|
||||
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/
|
||||
@@ -22,7 +22,9 @@ file under the ``[ip]`` section:
|
||||
|
||||
|
||||
**NOTE:** Setting low values for `public_refresh_interval` will result in frequent
|
||||
HTTP requests to the IP detection servers. Recommended range: 120-600 seconds
|
||||
HTTP requests to the IP detection servers. Recommended range: 120-600 seconds.
|
||||
Glances uses online services in order to get the IP addresses. Your IP address could be
|
||||
blocked if too many requests are done.
|
||||
|
||||
If the Censys options are configured, the public IP address is also analysed (with the same interval)
|
||||
and additional information is displayed.
|
||||
|
||||
@@ -34,6 +34,7 @@ Legend:
|
||||
fs
|
||||
irq
|
||||
folders
|
||||
cloud
|
||||
raid
|
||||
smart
|
||||
sensors
|
||||
|
||||
@@ -53,3 +53,9 @@ virtual docker interface (docker0, docker1, ...):
|
||||
wlan0_tx_warning=900000
|
||||
wlan0_tx_critical=1000000
|
||||
wlan0_tx_log=True
|
||||
|
||||
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/
|
||||
@@ -15,10 +15,15 @@ Filtered view:
|
||||
|
||||
.. image:: ../_static/processlist-filter.png
|
||||
|
||||
Extended view:
|
||||
|
||||
.. image:: ../_static/processlist-extended.png
|
||||
|
||||
The process view consists of 3 parts:
|
||||
|
||||
- Processes summary
|
||||
- Monitored processes list (optional)
|
||||
- Monitored processes list (optional, only in standalone mode)
|
||||
- Extended stats for the selected process (optional)
|
||||
- Processes list
|
||||
|
||||
The processes summary line displays:
|
||||
@@ -56,12 +61,15 @@ You can also set the sort key in the UI:
|
||||
* - c
|
||||
- --sort-processes cpu_percent
|
||||
- Sort by CPU
|
||||
* - e
|
||||
- N/A
|
||||
- Pin the process and display extended stats
|
||||
* - i
|
||||
- --sort-processes io_counters
|
||||
- Sort by DISK I/O
|
||||
* - j
|
||||
- --programs
|
||||
- Accumulate processes by program
|
||||
- Accumulate processes by program (extended stats disable in this mode)
|
||||
* - m
|
||||
- --sort-processes memory_percent
|
||||
- Sort by MEM
|
||||
|
||||
@@ -31,6 +31,12 @@ There is no alert on this information.
|
||||
unitname_fan_speed_alias=Alias for fan speed
|
||||
|
||||
.. note 4::
|
||||
If a sensors has multiple identical features names (see #2280), then
|
||||
Glances will add a suffix to the feature name.
|
||||
For example, if you have one sensor with two Composite features, the
|
||||
second one will be named Composite_1.
|
||||
|
||||
.. note 5::
|
||||
The plugin could crash on some operating system (FreeBSD) with the
|
||||
TCP or UDP blackhole option > 0 (see issue #2106). In this case, you
|
||||
should disable the sensors (--disable-plugin sensors or from the
|
||||
|
||||
1195
docs/api.rst
@@ -174,7 +174,7 @@ Command-Line Options
|
||||
|
||||
set the server cache time [default: 1 sec]
|
||||
|
||||
.. option:: open-web-browser
|
||||
.. option:: --open-web-browser
|
||||
|
||||
try to open the Web UI in the default Web browser
|
||||
|
||||
|
||||
@@ -9,42 +9,35 @@ following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[couchdb]
|
||||
[mongodb]
|
||||
host=localhost
|
||||
port=5984
|
||||
user=root
|
||||
password=root
|
||||
port=27017
|
||||
db=glances
|
||||
user=root
|
||||
password=example
|
||||
|
||||
and run Glances with:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ glances --export couchdb
|
||||
$ glances --export mongodb
|
||||
|
||||
Documents are stored in native ``JSON`` format. Glances adds ``"type"``
|
||||
and ``"time"`` entries:
|
||||
Documents are stored in native the configured database (glances by default)
|
||||
with one collection per plugin.
|
||||
|
||||
- ``type``: plugin name
|
||||
- ``time``: timestamp (format: "2016-09-24T16:39:08.524828Z")
|
||||
|
||||
Example of Couch Document for the load stats:
|
||||
Example of MongoDB Document for the load stats:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"_id": "36cbbad81453c53ef08804cb2612d5b6",
|
||||
"_rev": "1-382400899bec5615cabb99aa34df49fb",
|
||||
"min15": 0.33,
|
||||
"time": "2016-09-24T16:39:08.524828Z",
|
||||
"min5": 0.4,
|
||||
"cpucore": 4,
|
||||
"load_warning": 1,
|
||||
"min1": 0.5,
|
||||
"history_size": 28800,
|
||||
"load_critical": 5,
|
||||
"type": "load",
|
||||
"load_careful": 0.7
|
||||
_id: ObjectId('63d78ffee5528e543ce5af3a'),
|
||||
min1: 1.46337890625,
|
||||
min5: 1.09619140625,
|
||||
min15: 1.07275390625,
|
||||
cpucore: 4,
|
||||
history_size: 1200,
|
||||
load_disable: 'False',
|
||||
load_careful: 0.7,
|
||||
load_warning: 1,
|
||||
load_critical: 5
|
||||
}
|
||||
|
||||
You can view the result using the CouchDB utils URL: http://127.0.0.1:5984/_utils/database.html?glances.
|
||||
|
||||
@@ -18,6 +18,7 @@ to providing stats to multiple services (see list below).
|
||||
json
|
||||
kafka
|
||||
mqtt
|
||||
mongodb
|
||||
opentsdb
|
||||
prometheus
|
||||
rabbitmq
|
||||
|
||||
50
docs/gw/mongodb.rst
Normal file
@@ -0,0 +1,50 @@
|
||||
.. _couchdb:
|
||||
|
||||
MongoDB
|
||||
=======
|
||||
|
||||
You can export statistics to a ``MongoDB`` server.
|
||||
The connection should be defined in the Glances configuration file as
|
||||
following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[couchdb]
|
||||
host=localhost
|
||||
port=
|
||||
user=root
|
||||
password=example
|
||||
db=glances
|
||||
|
||||
and run Glances with:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ glances --export couchdb
|
||||
|
||||
Documents are stored in native ``JSON`` format. Glances adds ``"type"``
|
||||
and ``"time"`` entries:
|
||||
|
||||
- ``type``: plugin name
|
||||
- ``time``: timestamp (format: "2016-09-24T16:39:08.524828Z")
|
||||
|
||||
Example of Couch Document for the load stats:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"_id": "36cbbad81453c53ef08804cb2612d5b6",
|
||||
"_rev": "1-382400899bec5615cabb99aa34df49fb",
|
||||
"min15": 0.33,
|
||||
"time": "2016-09-24T16:39:08.524828Z",
|
||||
"min5": 0.4,
|
||||
"cpucore": 4,
|
||||
"load_warning": 1,
|
||||
"min1": 0.5,
|
||||
"history_size": 28800,
|
||||
"load_critical": 5,
|
||||
"type": "load",
|
||||
"load_careful": 0.7
|
||||
}
|
||||
|
||||
You can view the result using the CouchDB utils URL: http://127.0.0.1:5984/_utils/database.html?glances.
|
||||
@@ -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" "Nov 03, 2022" "3.3.1_beta1" "Glances"
|
||||
.TH "GLANCES" "1" "Mar 11, 2023" "3.4.0_beta1" "Glances"
|
||||
.SH NAME
|
||||
glances \- An eye on your system
|
||||
.SH SYNOPSIS
|
||||
@@ -258,7 +258,7 @@ set the server cache time [default: 1 sec]
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B open\-web\-browser
|
||||
.B \-\-open\-web\-browser
|
||||
try to open the Web UI in the default Web browser
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
@@ -732,60 +732,60 @@ format):
|
||||
.nf
|
||||
.ft C
|
||||
{
|
||||
"version": 1,
|
||||
"disable_existing_loggers": "False",
|
||||
"root": {
|
||||
"level": "INFO",
|
||||
"handlers": ["file", "console"]
|
||||
\(dqversion\(dq: 1,
|
||||
\(dqdisable_existing_loggers\(dq: \(dqFalse\(dq,
|
||||
\(dqroot\(dq: {
|
||||
\(dqlevel\(dq: \(dqINFO\(dq,
|
||||
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq]
|
||||
},
|
||||
"formatters": {
|
||||
"standard": {
|
||||
"format": "%(asctime)s \-\- %(levelname)s \-\- %(message)s"
|
||||
\(dqformatters\(dq: {
|
||||
\(dqstandard\(dq: {
|
||||
\(dqformat\(dq: \(dq%(asctime)s \-\- %(levelname)s \-\- %(message)s\(dq
|
||||
},
|
||||
"short": {
|
||||
"format": "%(levelname)s: %(message)s"
|
||||
\(dqshort\(dq: {
|
||||
\(dqformat\(dq: \(dq%(levelname)s: %(message)s\(dq
|
||||
},
|
||||
"free": {
|
||||
"format": "%(message)s"
|
||||
\(dqfree\(dq: {
|
||||
\(dqformat\(dq: \(dq%(message)s\(dq
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
"file": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"formatter": "standard",
|
||||
"filename": "/var/tmp/glances.log"
|
||||
\(dqhandlers\(dq: {
|
||||
\(dqfile\(dq: {
|
||||
\(dqlevel\(dq: \(dqDEBUG\(dq,
|
||||
\(dqclass\(dq: \(dqlogging.handlers.RotatingFileHandler\(dq,
|
||||
\(dqformatter\(dq: \(dqstandard\(dq,
|
||||
\(dqfilename\(dq: \(dq/var/tmp/glances.log\(dq
|
||||
},
|
||||
"console": {
|
||||
"level": "CRITICAL",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "free"
|
||||
\(dqconsole\(dq: {
|
||||
\(dqlevel\(dq: \(dqCRITICAL\(dq,
|
||||
\(dqclass\(dq: \(dqlogging.StreamHandler\(dq,
|
||||
\(dqformatter\(dq: \(dqfree\(dq
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
"debug": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "DEBUG"
|
||||
\(dqloggers\(dq: {
|
||||
\(dqdebug\(dq: {
|
||||
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
|
||||
\(dqlevel\(dq: \(dqDEBUG\(dq
|
||||
},
|
||||
"verbose": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "INFO"
|
||||
\(dqverbose\(dq: {
|
||||
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
|
||||
\(dqlevel\(dq: \(dqINFO\(dq
|
||||
},
|
||||
"standard": {
|
||||
"handlers": ["file"],
|
||||
"level": "INFO"
|
||||
\(dqstandard\(dq: {
|
||||
\(dqhandlers\(dq: [\(dqfile\(dq],
|
||||
\(dqlevel\(dq: \(dqINFO\(dq
|
||||
},
|
||||
"requests": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "ERROR"
|
||||
\(dqrequests\(dq: {
|
||||
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
|
||||
\(dqlevel\(dq: \(dqERROR\(dq
|
||||
},
|
||||
"elasticsearch": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "ERROR"
|
||||
\(dqelasticsearch\(dq: {
|
||||
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
|
||||
\(dqlevel\(dq: \(dqERROR\(dq
|
||||
},
|
||||
"elasticsearch.trace": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "ERROR"
|
||||
\(dqelasticsearch.trace\(dq: {
|
||||
\(dqhandlers\(dq: [\(dqfile\(dq, \(dqconsole\(dq],
|
||||
\(dqlevel\(dq: \(dqERROR\(dq
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -885,6 +885,6 @@ $ glances –browser
|
||||
.sp
|
||||
Nicolas Hennion aka Nicolargo <\fI\%contact@nicolargo.com\fP>
|
||||
.SH COPYRIGHT
|
||||
2022, Nicolas Hennion
|
||||
2023, Nicolas Hennion
|
||||
.\" Generated by docutils manpage writer.
|
||||
.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Glances - An eye on your system
|
||||
|
||||
@@ -172,11 +172,7 @@ class Config(object):
|
||||
if not self.parser.has_section('global'):
|
||||
self.parser.add_section('global')
|
||||
self.set_default('global', 'strftime_format', '')
|
||||
|
||||
# check_update
|
||||
if not self.parser.has_section('global'):
|
||||
self.parser.add_section('global')
|
||||
self.set_default('global', 'check_update', 'false')
|
||||
self.set_default('global', 'check_update', 'true')
|
||||
|
||||
# Quicklook
|
||||
if not self.parser.has_section('quicklook'):
|
||||
|
||||
@@ -112,7 +112,6 @@ class Export(GlancesExport):
|
||||
|
||||
# Write input to the Cassandra table
|
||||
try:
|
||||
|
||||
stmt = "INSERT INTO {} (plugin, time, stat) VALUES (?, ?, ?)".format(self.table)
|
||||
query = self.session.prepare(stmt)
|
||||
self.session.execute(query, (name, uuid_from_time(datetime.now()), data))
|
||||
|
||||
@@ -81,10 +81,10 @@ class Export(GlancesExport):
|
||||
# Loop over plugins to export
|
||||
for plugin in self.plugins_to_export(stats):
|
||||
if isinstance(all_stats[plugin], list):
|
||||
for stat in all_stats[plugin]:
|
||||
for stat in sorted(all_stats[plugin], key=lambda x: x['key']):
|
||||
# First line: header
|
||||
if self.first_line:
|
||||
csv_header += ('{}_{}_{}'.format(plugin, self.get_item_key(stat), item) for item in stat)
|
||||
csv_header += ['{}_{}_{}'.format(plugin, self.get_item_key(stat), item) for item in stat]
|
||||
# Others lines: stats
|
||||
csv_data += itervalues(stat)
|
||||
elif isinstance(all_stats[plugin], dict):
|
||||
|
||||
78
glances/exports/glances_mongodb.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of Glances.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2023 Nicolas Hennion <nicolas@nicolargo.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-only
|
||||
#
|
||||
|
||||
"""MongoDB interface class."""
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from glances.logger import logger
|
||||
from glances.exports.glances_export import GlancesExport
|
||||
|
||||
import pymongo
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
|
||||
class Export(GlancesExport):
|
||||
|
||||
"""This class manages the MongoDB export module."""
|
||||
|
||||
def __init__(self, config=None, args=None):
|
||||
"""Init the MongoDB export IF."""
|
||||
super(Export, self).__init__(config=config, args=args)
|
||||
|
||||
# Mandatory configuration keys (additional to host and port)
|
||||
self.db = None
|
||||
|
||||
# Optional configuration keys
|
||||
self.user = None
|
||||
self.password = None
|
||||
|
||||
# Load the Cassandra configuration file section
|
||||
self.export_enable = self.load_conf('mongodb', mandatories=['host', 'port', 'db'], options=['user', 'password'])
|
||||
if not self.export_enable:
|
||||
sys.exit(2)
|
||||
|
||||
# Init the CouchDB client
|
||||
self.client = self.init()
|
||||
|
||||
def init(self):
|
||||
"""Init the connection to the CouchDB server."""
|
||||
if not self.export_enable:
|
||||
return None
|
||||
|
||||
server_uri = 'mongodb://%s:%s@%s:%s' % (quote_plus(self.user), quote_plus(self.password), self.host, self.port)
|
||||
|
||||
try:
|
||||
client = pymongo.MongoClient(server_uri)
|
||||
client.admin.command('ping')
|
||||
except Exception as e:
|
||||
logger.critical("Cannot connect to MongoDB server %s:%s (%s)" % (self.host, self.port, e))
|
||||
sys.exit(2)
|
||||
else:
|
||||
logger.info("Connected to the MongoDB server")
|
||||
|
||||
return client
|
||||
|
||||
def database(self):
|
||||
"""Return the CouchDB database object"""
|
||||
return self.client[self.db]
|
||||
|
||||
def export(self, name, columns, points):
|
||||
"""Write the points to the MongoDB server."""
|
||||
logger.debug("Export {} stats to MongoDB".format(name))
|
||||
|
||||
# Create DB input
|
||||
data = dict(zip(columns, points))
|
||||
|
||||
# Write data to the MongoDB database
|
||||
try:
|
||||
self.database()[name].insert_one(data)
|
||||
except Exception as e:
|
||||
logger.error("Cannot export {} stats to MongoDB ({})".format(name, e))
|
||||
@@ -235,6 +235,13 @@ Examples of use:
|
||||
dest='enable_separator',
|
||||
help='enable separator in the UI',
|
||||
),
|
||||
parser.add_argument(
|
||||
'--disable-cursor',
|
||||
action='store_true',
|
||||
default=False,
|
||||
dest='disable_cursor',
|
||||
help='disable cursor (process selection) in the UI',
|
||||
),
|
||||
# Sort processes list
|
||||
parser.add_argument(
|
||||
'--sort-processes',
|
||||
@@ -699,6 +706,11 @@ Examples of use:
|
||||
logger.critical("Process filter is only available in standalone mode")
|
||||
sys.exit(2)
|
||||
|
||||
# Cursor option is only available in standalone mode
|
||||
if not args.disable_cursor and not self.is_standalone():
|
||||
logger.critical("Cursor is only available in standalone mode")
|
||||
sys.exit(2)
|
||||
|
||||
# Disable HDDTemp if sensors are disabled
|
||||
if getattr(self.args, 'disable_sensors', False):
|
||||
disable(self.args, 'hddtemp')
|
||||
|
||||
@@ -48,10 +48,15 @@ class Outdated(object):
|
||||
|
||||
# Set default value...
|
||||
self.data = {u'installed_version': __version__, u'latest_version': '0.0', u'refresh_date': datetime.now()}
|
||||
# Read the configuration file
|
||||
self.load_config(config)
|
||||
|
||||
# Disable update check if `packaging` is not installed
|
||||
if not PACKAGING_IMPORT:
|
||||
self.args.disable_check_update = True
|
||||
|
||||
# Read the configuration file only if update check is not explicitly disabled
|
||||
if not self.args.disable_check_update:
|
||||
self.load_config(config)
|
||||
|
||||
logger.debug("Check Glances version up-to-date: {}".format(not self.args.disable_check_update))
|
||||
|
||||
# And update !
|
||||
|
||||
@@ -126,10 +126,8 @@ class GlancesBottle(object):
|
||||
if username == self.args.username:
|
||||
from glances.password import GlancesPassword
|
||||
|
||||
pwd = GlancesPassword(username=username,
|
||||
config=self.config)
|
||||
return pwd.check_password(self.args.password,
|
||||
pwd.sha256_hash(password))
|
||||
pwd = GlancesPassword(username=username, config=self.config)
|
||||
return pwd.check_password(self.args.password, pwd.get_hash(password))
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -161,6 +159,9 @@ class GlancesBottle(object):
|
||||
'/api/%s/<plugin>/<item>/history/<nb:int>' % self.API_VERSION, method="GET", callback=self._api_item_history
|
||||
)
|
||||
self._app.route('/api/%s/<plugin>/<item>/<value>' % self.API_VERSION, method="GET", callback=self._api_value)
|
||||
self._app.route(
|
||||
'/api/%s/<plugin>/<item>/<value:path>' % self.API_VERSION, method="GET", callback=self._api_value
|
||||
)
|
||||
bindmsg = 'Glances RESTful API Server started on {}api/{}/'.format(self.bind_url, self.API_VERSION)
|
||||
logger.info(bindmsg)
|
||||
|
||||
@@ -228,7 +229,7 @@ class GlancesBottle(object):
|
||||
"""
|
||||
response.status = 200
|
||||
|
||||
return None
|
||||
return "Active"
|
||||
|
||||
@compress
|
||||
def _api_help(self):
|
||||
|
||||
@@ -267,6 +267,7 @@ class _GlancesCurses(object):
|
||||
self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD
|
||||
self.ifWARNING_color2 = curses.color_pair(5) | A_BOLD
|
||||
self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
|
||||
self.ifINFO_color = curses.color_pair(8)
|
||||
self.filter_color = A_BOLD
|
||||
self.selected_color = A_BOLD
|
||||
|
||||
@@ -300,6 +301,7 @@ class _GlancesCurses(object):
|
||||
self.ifCAREFUL_color2 = curses.A_UNDERLINE
|
||||
self.ifWARNING_color2 = A_BOLD
|
||||
self.ifCRITICAL_color2 = curses.A_REVERSE
|
||||
self.ifINFO_color = A_BOLD
|
||||
self.filter_color = A_BOLD
|
||||
self.selected_color = A_BOLD
|
||||
|
||||
@@ -327,6 +329,7 @@ class _GlancesCurses(object):
|
||||
'CRITICAL_LOG': self.ifCRITICAL_color,
|
||||
'PASSWORD': curses.A_PROTECT,
|
||||
'SELECTED': self.selected_color,
|
||||
'INFO': self.ifINFO_color
|
||||
}
|
||||
|
||||
def set_cursor(self, value):
|
||||
@@ -406,13 +409,15 @@ class _GlancesCurses(object):
|
||||
elif self.pressedkey == ord('9'):
|
||||
# '9' > Theme from black to white and reverse
|
||||
self._init_colors()
|
||||
elif self.pressedkey == ord('e'):
|
||||
elif self.pressedkey == ord('e') and not self.args.programs:
|
||||
# 'e' > Enable/Disable process extended
|
||||
self.args.enable_process_extended = not self.args.enable_process_extended
|
||||
if not self.args.enable_process_extended:
|
||||
glances_processes.disable_extended()
|
||||
else:
|
||||
glances_processes.enable_extended()
|
||||
# When a process is selected (and only in standalone mode), disable the cursor
|
||||
self.args.disable_cursor = self.args.enable_process_extended and self.args.is_standalone
|
||||
elif self.pressedkey == ord('E'):
|
||||
# 'E' > Erase the process filter
|
||||
glances_processes.process_filter = None
|
||||
@@ -426,7 +431,7 @@ class _GlancesCurses(object):
|
||||
elif self.pressedkey == ord('-'):
|
||||
# '+' > Decrease process nice level
|
||||
self.decrease_nice_process = not self.decrease_nice_process
|
||||
elif self.pressedkey == ord('k'):
|
||||
elif self.pressedkey == ord('k') and not self.args.disable_cursor:
|
||||
# 'k' > Kill selected process (after confirmation)
|
||||
self.kill_process = not self.kill_process
|
||||
elif self.pressedkey == ord('w'):
|
||||
@@ -450,11 +455,11 @@ class _GlancesCurses(object):
|
||||
# ">" (right arrow) navigation through process sort
|
||||
next_sort = (self.loop_position() + 1) % len(self._sort_loop)
|
||||
glances_processes.set_sort_key(self._sort_loop[next_sort], False)
|
||||
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65:
|
||||
elif self.pressedkey == curses.KEY_UP or self.pressedkey == 65 and not self.args.disable_cursor:
|
||||
# 'UP' > Up in the server list
|
||||
if self.args.cursor_position > 0:
|
||||
self.args.cursor_position -= 1
|
||||
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66:
|
||||
elif self.pressedkey == curses.KEY_DOWN or self.pressedkey == 66 and not self.args.disable_cursor:
|
||||
# 'DOWN' > Down in the server list
|
||||
# if self.args.cursor_position < glances_processes.max_processes - 2:
|
||||
if self.args.cursor_position < glances_processes.processes_count:
|
||||
@@ -679,7 +684,6 @@ class _GlancesCurses(object):
|
||||
new_filter = self.display_popup(
|
||||
'Process filter pattern: \n\n'
|
||||
+ 'Examples:\n'
|
||||
+ '- python\n'
|
||||
+ '- .*python.*\n'
|
||||
+ '- /usr/lib.*\n'
|
||||
+ '- name:.*nautilus.*\n'
|
||||
@@ -1109,7 +1113,7 @@ class _GlancesCurses(object):
|
||||
|
||||
def erase(self):
|
||||
"""Erase the content of the screen."""
|
||||
self.term_window.erase()
|
||||
self.term_window.clear()
|
||||
|
||||
def flush(self, stats, cs_status=None):
|
||||
"""Clear and update the screen.
|
||||
|
||||
@@ -77,7 +77,7 @@ class Sparkline(object):
|
||||
|
||||
def get(self):
|
||||
"""Return the sparkline."""
|
||||
ret = sparklines(self.percents)[0]
|
||||
ret = sparklines(self.percents, minimum=0, maximum=100)[0]
|
||||
if self.__with_text:
|
||||
percents_without_none = [x for x in self.percents if x is not None]
|
||||
if len(percents_without_none) > 0:
|
||||
|
||||
@@ -70,7 +70,7 @@ def print_plugins_list(stat):
|
||||
print('')
|
||||
|
||||
|
||||
def print_plugin_export(plugin, stat_export):
|
||||
def print_plugin_stats(plugin, stat):
|
||||
sub_title = 'GET {}'.format(plugin)
|
||||
print(sub_title)
|
||||
print('-' * len(sub_title))
|
||||
@@ -79,7 +79,7 @@ def print_plugin_export(plugin, stat_export):
|
||||
print('Get plugin stats::')
|
||||
print('')
|
||||
print(' # curl {}/{}'.format(API_URL, plugin))
|
||||
print(indent_stat(stat_export))
|
||||
print(indent_stat(json.loads(stat.get_stats())))
|
||||
print('')
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ class GlancesStdoutApiDoc(object):
|
||||
stat_export = stat.get_export()
|
||||
if stat_export is None or stat_export == [] or stat_export == {}:
|
||||
continue
|
||||
print_plugin_export(plugin, stat_export)
|
||||
print_plugin_stats(plugin, stat)
|
||||
print_plugin_description(plugin, stat)
|
||||
print_plugin_item_value(plugin, stat, stat_export)
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
v-if="!args.disable_sensors"
|
||||
:data="data"
|
||||
></glances-plugin-sensors>
|
||||
<glances-plugin-now :data="data"></glances-plugin-now>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-18">
|
||||
@@ -138,13 +139,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-24">
|
||||
<glances-plugin-now :data="data"></glances-plugin-now>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ export default {
|
||||
return this.data.stats['cloud'];
|
||||
},
|
||||
provider() {
|
||||
return this.stats['ami-id'] !== undefined ? 'AWS EC2' : null;
|
||||
return this.stats['id'] !== undefined ? `${stats['platform']}` : null;
|
||||
},
|
||||
instance() {
|
||||
const { stats } = this;
|
||||
return this.stats['ami-id'] !== undefined
|
||||
? `${stats['instance-type']} instance ${stats['instance-id']} (${stats['reggion']})`
|
||||
return this.stats['id'] !== undefined
|
||||
? `${stats['type']} instance ${stats['name']} (${stats['region']})`
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
869
glances/outputs/static/package-lock.json
generated
@@ -3,27 +3,27 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.4.1",
|
||||
"favico.js": "^0.3.10",
|
||||
"hotkeys-js": "^3.10.0",
|
||||
"hotkeys-js": "^3.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"sanitize-html": "^2.7.2",
|
||||
"vue": "^3.2.41"
|
||||
"sanitize-html": "^2.8.1",
|
||||
"vue": "^3.2.45"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-loader": "^6.7.3",
|
||||
"del": "^7.0.0",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-plugin-vue": "^9.6.0",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"sass": "^1.55.0",
|
||||
"sass-loader": "^13.1.0",
|
||||
"sass": "^1.57.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-loader": "^17.0.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"vue-loader": "^17.0.1",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
BIN
glances/outputs/static/public/glances.js
vendored
@@ -44,17 +44,12 @@ class GlancesPassword(object):
|
||||
|
||||
def get_hash(self, plain_password, salt=''):
|
||||
"""Return the hashed password, salt + pbkdf2_hmac."""
|
||||
return hashlib.pbkdf2_hmac('sha256',
|
||||
plain_password.encode(),
|
||||
salt.encode(),
|
||||
100000,
|
||||
dklen=128).hex()
|
||||
return to_hex(hashlib.pbkdf2_hmac('sha256', plain_password.encode(), salt.encode(), 100000, dklen=128))
|
||||
|
||||
def hash_password(self, plain_password):
|
||||
"""Hash password with a salt based on UUID (universally unique identifier)."""
|
||||
salt = uuid.uuid4().hex
|
||||
encrypted_password = self.get_hash(plain_password,
|
||||
salt=salt)
|
||||
encrypted_password = self.get_hash(plain_password, salt=salt)
|
||||
return salt + '$' + encrypted_password
|
||||
|
||||
def check_password(self, hashed_password, plain_password):
|
||||
@@ -63,8 +58,7 @@ class GlancesPassword(object):
|
||||
Return the comparison with the encrypted_password.
|
||||
"""
|
||||
salt, encrypted_password = hashed_password.split('$')
|
||||
re_encrypted_password = self.get_hash(plain_password,
|
||||
salt = salt)
|
||||
re_encrypted_password = self.get_hash(plain_password, salt=salt)
|
||||
return encrypted_password == re_encrypted_password
|
||||
|
||||
def get_password(self, description='', confirm=False, clear=False):
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"""Cloud plugin.
|
||||
|
||||
Supported Cloud API:
|
||||
- OpenStack meta data (class ThreadOpenStack, see below): AWS, OVH...
|
||||
- OpenStack meta data (class ThreadOpenStack) - Vanilla OpenStack
|
||||
- OpenStackEC2 meta data (class ThreadOpenStackEC2) - Amazon EC2 compatible
|
||||
"""
|
||||
|
||||
import threading
|
||||
@@ -53,13 +54,16 @@ class PluginModel(GlancesPluginModel):
|
||||
|
||||
# Init thread to grab OpenStack stats asynchronously
|
||||
self.OPENSTACK = ThreadOpenStack()
|
||||
self.OPENSTACKEC2 = ThreadOpenStackEC2()
|
||||
|
||||
# Run the thread
|
||||
self.OPENSTACK.start()
|
||||
self.OPENSTACKEC2.start()
|
||||
|
||||
def exit(self):
|
||||
"""Overwrite the exit method to close threads."""
|
||||
self.OPENSTACK.stop()
|
||||
self.OPENSTACKEC2.stop()
|
||||
# Call the father class
|
||||
super(PluginModel, self).exit()
|
||||
|
||||
@@ -80,12 +84,15 @@ class PluginModel(GlancesPluginModel):
|
||||
# Update the stats
|
||||
if self.input_method == 'local':
|
||||
stats = self.OPENSTACK.stats
|
||||
if not stats:
|
||||
stats = self.OPENSTACKEC2.stats
|
||||
# Example:
|
||||
# Uncomment to test on physical computer
|
||||
# stats = {'ami-id': 'ami-id',
|
||||
# 'instance-id': 'instance-id',
|
||||
# 'instance-type': 'instance-type',
|
||||
# 'region': 'placement/availability-zone'}
|
||||
# Uncomment to test on physical computer (only for test purpose)
|
||||
# stats = {'id': 'ami-id',
|
||||
# 'name': 'My VM',
|
||||
# 'type': 'Gold',
|
||||
# 'region': 'France',
|
||||
# 'platform': 'OpenStack'}
|
||||
|
||||
# Update the stats
|
||||
self.stats = stats
|
||||
@@ -101,13 +108,14 @@ class PluginModel(GlancesPluginModel):
|
||||
return ret
|
||||
|
||||
# Generate the output
|
||||
if 'instance-type' in self.stats and 'instance-id' in self.stats and 'region' in self.stats:
|
||||
msg = 'Cloud '
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
msg = '{} instance {} ({})'.format(
|
||||
self.stats['instance-type'], self.stats['instance-id'], self.stats['region']
|
||||
)
|
||||
ret.append(self.curse_add_line(msg))
|
||||
msg = self.stats.get('platform', 'Unknown')
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
msg = ' {} instance {} ({})'.format(
|
||||
self.stats.get('type', 'Unknown'),
|
||||
self.stats.get('name', 'Unknown'),
|
||||
self.stats.get('region', 'Unknown')
|
||||
)
|
||||
ret.append(self.curse_add_line(msg))
|
||||
|
||||
# Return the message with decoration
|
||||
# logger.info(ret)
|
||||
@@ -121,13 +129,19 @@ class ThreadOpenStack(threading.Thread):
|
||||
stats is a dict
|
||||
"""
|
||||
|
||||
# The metadata service provides a way for instances to retrieve
|
||||
# instance-specific data via a REST API. Instances access this
|
||||
# service at 169.254.169.254 or at fe80::a9fe:a9fe.
|
||||
# All types of metadata, be it user-, nova- or vendor-provided,
|
||||
# can be accessed via this service.
|
||||
# https://docs.openstack.org/nova/latest/user/metadata-service.html
|
||||
OPENSTACK_API_URL = 'http://169.254.169.254/latest/meta-data'
|
||||
OPENSTACK_PLATFORM = "OpenStack"
|
||||
OPENSTACK_API_URL = 'http://169.254.169.254/openstack/latest/meta-data'
|
||||
OPENSTACK_API_METADATA = {
|
||||
'ami-id': 'ami-id',
|
||||
'instance-id': 'instance-id',
|
||||
'instance-type': 'instance-type',
|
||||
'region': 'placement/availability-zone',
|
||||
'id': 'project_id',
|
||||
'name': 'name',
|
||||
'type': 'meta/role',
|
||||
'region': 'availability_zone',
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
@@ -159,6 +173,9 @@ class ThreadOpenStack(threading.Thread):
|
||||
else:
|
||||
if r.ok:
|
||||
self._stats[k] = to_ascii(r.content)
|
||||
else:
|
||||
# No break during the loop, so we can set the platform
|
||||
self._stats['platform'] = self.OPENSTACK_PLATFORM
|
||||
|
||||
return True
|
||||
|
||||
@@ -180,3 +197,26 @@ class ThreadOpenStack(threading.Thread):
|
||||
def stopped(self):
|
||||
"""Return True is the thread is stopped."""
|
||||
return self._stopper.is_set()
|
||||
|
||||
|
||||
class ThreadOpenStackEC2(ThreadOpenStack):
|
||||
"""
|
||||
Specific thread to grab OpenStack EC2 (Amazon cloud) stats.
|
||||
|
||||
stats is a dict
|
||||
"""
|
||||
|
||||
# The metadata service provides a way for instances to retrieve
|
||||
# instance-specific data via a REST API. Instances access this
|
||||
# service at 169.254.169.254 or at fe80::a9fe:a9fe.
|
||||
# All types of metadata, be it user-, nova- or vendor-provided,
|
||||
# can be accessed via this service.
|
||||
# https://docs.openstack.org/nova/latest/user/metadata-service.html
|
||||
OPENSTACK_PLATFORM = "Amazon EC2"
|
||||
OPENSTACK_API_URL = 'http://169.254.169.254/latest/meta-data'
|
||||
OPENSTACK_API_METADATA = {
|
||||
'id': 'ami-id',
|
||||
'name': 'instance-id',
|
||||
'type': 'instance-type',
|
||||
'region': 'placement/availability-zone',
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ class PluginModel(GlancesPluginModel):
|
||||
('misc_erase_process_filter', msg_col.format('E', 'Erase process filter')),
|
||||
('misc_generate_history_graphs', msg_col.format('g', 'Generate history graphs')),
|
||||
('misc_help', msg_col.format('h', 'HELP')),
|
||||
('misc_accumulate_processes_by_program', msg_col.format('j', 'Accumulate processes by program')),
|
||||
('misc_accumulate_processes_by_program', msg_col.format('j', 'Display threads or programs')),
|
||||
('misc_increase_nice_process', msg_col.format('+', 'Increase nice process')),
|
||||
('misc_decrease_nice_process', msg_col.format('-', 'Decrease nice process (need admin rights)')),
|
||||
('misc_kill_process', msg_col.format('k', 'Kill process')),
|
||||
|
||||
@@ -32,11 +32,16 @@ fields_description = {
|
||||
'alias': {'description': 'Interface alias name (optional).', 'unit': 'string'},
|
||||
'rx': {'description': 'The received/input rate (in bit per second).', 'unit': 'bps'},
|
||||
'tx': {'description': 'The sent/output rate (in bit per second).', 'unit': 'bps'},
|
||||
'cx': {'description': 'The cumulative received+sent rate (in bit per second).', 'unit': 'bps'},
|
||||
'cumulative_rx': {
|
||||
'description': 'The number of bytes received through the interface (cumulative).',
|
||||
'unit': 'bytes',
|
||||
},
|
||||
'cumulative_tx': {'description': 'The number of bytes sent through the interface (cumulative).', 'unit': 'bytes'},
|
||||
'cumulative_cx': {
|
||||
'description': 'The cumulative number of bytes reveived and sent through the interface (cumulative).',
|
||||
'unit': 'bytes',
|
||||
},
|
||||
'speed': {
|
||||
'description': 'Maximum interface speed (in bit per second). Can return 0 on some operating-system.',
|
||||
'unit': 'bps',
|
||||
|
||||
@@ -145,9 +145,10 @@ class PluginModel(GlancesPluginModel):
|
||||
# Update stats using the standard system lib
|
||||
# Note: Update is done in the processcount plugin
|
||||
# Just return the processes list
|
||||
stats = glances_processes.getlist()
|
||||
if self.args.programs:
|
||||
stats = processes_to_programs(stats)
|
||||
stats = glances_processes.getlist(as_programs=True)
|
||||
else:
|
||||
stats = glances_processes.getlist()
|
||||
|
||||
elif self.input_method == 'snmp':
|
||||
# No SNMP grab for processes
|
||||
@@ -349,14 +350,14 @@ class PluginModel(GlancesPluginModel):
|
||||
"""Get curses data to display for a process.
|
||||
|
||||
- p is the process to display
|
||||
- selected is a tag=True if the selected process
|
||||
- selected is a tag=True if p is the selected process
|
||||
"""
|
||||
ret = [self.curse_new_line()]
|
||||
|
||||
# When a process is selected:
|
||||
# * display a special character at the beginning of the line
|
||||
# * underline the command name
|
||||
if args.is_standalone:
|
||||
ret.append(self.curse_add_line(unicode_message('PROCESS_SELECTOR') if selected else ' ', 'SELECTED'))
|
||||
ret.append(self.curse_add_line(unicode_message('PROCESS_SELECTOR') if (selected and not args.disable_cursor) else ' ', 'SELECTED'))
|
||||
|
||||
# CPU
|
||||
ret.append(self._get_process_curses_cpu(p, selected, args))
|
||||
@@ -403,7 +404,7 @@ class PluginModel(GlancesPluginModel):
|
||||
cmdline = p.get('cmdline', '?')
|
||||
|
||||
try:
|
||||
process_decoration = 'PROCESS_SELECTED' if (selected and args.is_standalone) else 'PROCESS'
|
||||
process_decoration = 'PROCESS_SELECTED' if (selected and not args.disable_cursor) else 'PROCESS'
|
||||
if cmdline:
|
||||
path, cmd, arguments = split_cmdline(bare_process_name, cmdline)
|
||||
# Manage end of line in arguments (see #1692)
|
||||
@@ -427,74 +428,14 @@ class PluginModel(GlancesPluginModel):
|
||||
logger.debug("Can not decode command line '{}' ({})".format(cmdline, e))
|
||||
ret.append(self.curse_add_line('', splittable=True))
|
||||
|
||||
# Add extended stats but only for the top processes
|
||||
if args.cursor_position == 0 and 'extended_stats' in p and args.enable_process_extended:
|
||||
# Left padding
|
||||
xpad = ' ' * 13
|
||||
# First line is CPU affinity
|
||||
if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
|
||||
ret.append(self.curse_new_line())
|
||||
msg = xpad + 'CPU affinity: ' + str(len(p['cpu_affinity'])) + ' cores'
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Second line is memory info
|
||||
if 'memory_info' in p and p['memory_info'] is not None:
|
||||
ret.append(self.curse_new_line())
|
||||
msg = '{}Memory info: {}'.format(xpad, p['memory_info'])
|
||||
if 'memory_swap' in p and p['memory_swap'] is not None:
|
||||
msg += ' swap ' + self.auto_unit(p['memory_swap'], low_precision=False)
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Third line is for open files/network sessions
|
||||
msg = ''
|
||||
if 'num_threads' in p and p['num_threads'] is not None:
|
||||
msg += str(p['num_threads']) + ' threads '
|
||||
if 'num_fds' in p and p['num_fds'] is not None:
|
||||
msg += str(p['num_fds']) + ' files '
|
||||
if 'num_handles' in p and p['num_handles'] is not None:
|
||||
msg += str(p['num_handles']) + ' handles '
|
||||
if 'tcp' in p and p['tcp'] is not None:
|
||||
msg += str(p['tcp']) + ' TCP '
|
||||
if 'udp' in p and p['udp'] is not None:
|
||||
msg += str(p['udp']) + ' UDP'
|
||||
if msg != '':
|
||||
ret.append(self.curse_new_line())
|
||||
msg = xpad + 'Open: ' + msg
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
# Fourth line is IO nice level (only Linux and Windows OS)
|
||||
if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
|
||||
ret.append(self.curse_new_line())
|
||||
msg = xpad + 'IO nice: '
|
||||
k = 'Class is '
|
||||
v = p['ionice'].ioclass
|
||||
# Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
|
||||
# Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
|
||||
if WINDOWS:
|
||||
if v == 0:
|
||||
msg += k + 'Very Low'
|
||||
elif v == 1:
|
||||
msg += k + 'Low'
|
||||
elif v == 2:
|
||||
msg += 'No specific I/O priority'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
else:
|
||||
if v == 0:
|
||||
msg += 'No specific I/O priority'
|
||||
elif v == 1:
|
||||
msg += k + 'Real Time'
|
||||
elif v == 2:
|
||||
msg += k + 'Best Effort'
|
||||
elif v == 3:
|
||||
msg += k + 'IDLE'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
# value is a number which goes from 0 to 7.
|
||||
# The higher the value, the lower the I/O priority of the process.
|
||||
if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
|
||||
msg += ' (value %s/7)' % str(p['ionice'].value)
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
|
||||
return ret
|
||||
|
||||
def is_selected_process(self, args):
|
||||
return args.is_standalone and \
|
||||
self.args.enable_process_extended and \
|
||||
args.cursor_position is not None and \
|
||||
glances_processes.extended_process is not None
|
||||
|
||||
def msg_curse(self, args=None, max_width=None):
|
||||
"""Return the dict to display in the curse interface."""
|
||||
# Init the return message
|
||||
@@ -506,6 +447,16 @@ class PluginModel(GlancesPluginModel):
|
||||
|
||||
# Compute the sort key
|
||||
process_sort_key = glances_processes.sort_key
|
||||
processes_list_sorted = self.__sort_stats(process_sort_key)
|
||||
|
||||
# Display extended stats for selected process
|
||||
#############################################
|
||||
|
||||
if self.is_selected_process(args):
|
||||
self.__msg_curse_extended_process(ret, glances_processes.extended_process)
|
||||
|
||||
# Display others processes list
|
||||
###############################
|
||||
|
||||
# Header
|
||||
self.__msg_curse_header(ret, process_sort_key, args)
|
||||
@@ -514,10 +465,10 @@ class PluginModel(GlancesPluginModel):
|
||||
# Loop over processes (sorted by the sort key previously compute)
|
||||
# This is a Glances bottleneck (see flame graph),
|
||||
# get_process_curses_data should be optimzed
|
||||
i = 0
|
||||
for p in self.__sort_stats(process_sort_key):
|
||||
ret.extend(self.get_process_curses_data(p, i == args.cursor_position, args))
|
||||
i += 1
|
||||
for position, process in enumerate(processes_list_sorted):
|
||||
ret.extend(self.get_process_curses_data(process,
|
||||
position == args.cursor_position,
|
||||
args))
|
||||
|
||||
# A filter is set Display the stats summaries
|
||||
if glances_processes.process_filter is not None:
|
||||
@@ -531,6 +482,116 @@ class PluginModel(GlancesPluginModel):
|
||||
# Return the message with decoration
|
||||
return ret
|
||||
|
||||
def __msg_curse_extended_process(self, ret, p):
|
||||
"""Get extended curses data for the selected process (see issue #2225)
|
||||
|
||||
The result depends of the process type (process or thread).
|
||||
|
||||
Input p is a dict with the following keys:
|
||||
{'status': 'S',
|
||||
'memory_info': pmem(rss=466890752, vms=3365347328, shared=68153344, text=659456, lib=0, data=774647808, dirty=0),
|
||||
'pid': 4980,
|
||||
'io_counters': [165385216, 0, 165385216, 0, 1],
|
||||
'num_threads': 20,
|
||||
'nice': 0,
|
||||
'memory_percent': 5.958135664449709,
|
||||
'cpu_percent': 0.0,
|
||||
'gids': pgids(real=1000, effective=1000, saved=1000),
|
||||
'cpu_times': pcputimes(user=696.38, system=119.98, children_user=0.0, children_system=0.0, iowait=0.0),
|
||||
'name': 'WebExtensions',
|
||||
'key': 'pid',
|
||||
'time_since_update': 2.1997854709625244,
|
||||
'cmdline': ['/snap/firefox/2154/usr/lib/firefox/firefox', '-contentproc', '-childID', '...'],
|
||||
'username': 'nicolargo',
|
||||
'cpu_min': 0.0,
|
||||
'cpu_max': 7.0,
|
||||
'cpu_mean': 3.2}
|
||||
"""
|
||||
if self.args.programs:
|
||||
self.__msg_curse_extended_process_program(ret, p)
|
||||
else:
|
||||
self.__msg_curse_extended_process_thread(ret, p)
|
||||
|
||||
def __msg_curse_extended_process_program(self, ret, p):
|
||||
# Title
|
||||
msg = "Pinned program {} ('e' to unpin)".format(p['name'])
|
||||
ret.append(self.curse_add_line(msg, "TITLE"))
|
||||
|
||||
ret.append(self.curse_new_line())
|
||||
ret.append(self.curse_new_line())
|
||||
|
||||
def __msg_curse_extended_process_thread(self, ret, p):
|
||||
# Title
|
||||
ret.append(self.curse_add_line("Pinned thread ", "TITLE"))
|
||||
ret.append(self.curse_add_line(p['name'], "UNDERLINE"))
|
||||
ret.append(self.curse_add_line(" ('e' to unpin)"))
|
||||
|
||||
# First line is CPU affinity
|
||||
ret.append(self.curse_new_line())
|
||||
ret.append(self.curse_add_line(' CPU Min/Max/Mean: '))
|
||||
msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(p['cpu_min'], p['cpu_max'], p['cpu_mean'])
|
||||
ret.append(self.curse_add_line(msg, decoration='INFO'))
|
||||
if 'cpu_affinity' in p and p['cpu_affinity'] is not None:
|
||||
ret.append(self.curse_add_line(' Affinity: '))
|
||||
ret.append(self.curse_add_line(str(len(p['cpu_affinity'])), decoration='INFO'))
|
||||
ret.append(self.curse_add_line(' cores', decoration='INFO'))
|
||||
if 'ionice' in p and p['ionice'] is not None and hasattr(p['ionice'], 'ioclass'):
|
||||
msg = ' IO nice: '
|
||||
k = 'Class is '
|
||||
v = p['ionice'].ioclass
|
||||
# Linux: The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
|
||||
# Windows: On Windows only ioclass is used and it can be set to 2 (normal), 1 (low) or 0 (very low).
|
||||
if WINDOWS:
|
||||
if v == 0:
|
||||
msg += k + 'Very Low'
|
||||
elif v == 1:
|
||||
msg += k + 'Low'
|
||||
elif v == 2:
|
||||
msg += 'No specific I/O priority'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
else:
|
||||
if v == 0:
|
||||
msg += 'No specific I/O priority'
|
||||
elif v == 1:
|
||||
msg += k + 'Real Time'
|
||||
elif v == 2:
|
||||
msg += k + 'Best Effort'
|
||||
elif v == 3:
|
||||
msg += k + 'IDLE'
|
||||
else:
|
||||
msg += k + str(v)
|
||||
# value is a number which goes from 0 to 7.
|
||||
# The higher the value, the lower the I/O priority of the process.
|
||||
if hasattr(p['ionice'], 'value') and p['ionice'].value != 0:
|
||||
msg += ' (value %s/7)' % str(p['ionice'].value)
|
||||
ret.append(self.curse_add_line(msg, splittable=True))
|
||||
|
||||
# Second line is memory info
|
||||
ret.append(self.curse_new_line())
|
||||
ret.append(self.curse_add_line(' MEM Min/Max/Mean: '))
|
||||
msg = '{: >7.1f}{: >7.1f}{: >7.1f}%'.format(p['memory_min'], p['memory_max'], p['memory_mean'])
|
||||
ret.append(self.curse_add_line(msg, decoration='INFO'))
|
||||
if 'memory_info' in p and p['memory_info'] is not None:
|
||||
ret.append(self.curse_add_line(' Memory info: '))
|
||||
for k in p['memory_info']._asdict():
|
||||
ret.append(self.curse_add_line(self.auto_unit(p['memory_info']._asdict()[k], low_precision=False), decoration='INFO', splittable=True))
|
||||
ret.append(self.curse_add_line(' ' + k + ' ', splittable=True))
|
||||
if 'memory_swap' in p and p['memory_swap'] is not None:
|
||||
ret.append(self.curse_add_line(self.auto_unit(p['memory_swap'], low_precision=False), decoration='INFO', splittable=True))
|
||||
ret.append(self.curse_add_line(' swap ', splittable=True))
|
||||
|
||||
# Third line is for open files/network sessions
|
||||
ret.append(self.curse_new_line())
|
||||
ret.append(self.curse_add_line(' Open: '))
|
||||
for stat_prefix in ['num_threads', 'num_fds', 'num_handles', 'tcp', 'udp']:
|
||||
if stat_prefix in p and p[stat_prefix] is not None:
|
||||
ret.append(self.curse_add_line(str(p[stat_prefix]), decoration='INFO'))
|
||||
ret.append(self.curse_add_line(' {} '.format(stat_prefix.replace('num_', ''))))
|
||||
|
||||
ret.append(self.curse_new_line())
|
||||
ret.append(self.curse_new_line())
|
||||
|
||||
def __msg_curse_header(self, ret, process_sort_key, args=None):
|
||||
"""Build the header and add it to the ret dict."""
|
||||
sort_style = 'SORT'
|
||||
@@ -577,10 +638,15 @@ class PluginModel(GlancesPluginModel):
|
||||
msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True
|
||||
)
|
||||
)
|
||||
if not self.args.programs:
|
||||
msg = self.layout_header['command'].format('Command', "('k' to kill)" if args.is_standalone else "")
|
||||
if args.is_standalone and not args.disable_cursor:
|
||||
if self.args.programs:
|
||||
shortkey = "('k' to kill)"
|
||||
else:
|
||||
shortkey = "('e' to pin | 'k' to kill)"
|
||||
else:
|
||||
msg = self.layout_header['command'].format('Programs', "('k' to kill)" if args.is_standalone else "")
|
||||
shortkey = ""
|
||||
msg = self.layout_header['command'].format("Programs" if self.args.programs else "Command",
|
||||
shortkey)
|
||||
ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'name' else 'DEFAULT'))
|
||||
|
||||
def __msg_curse_sum(self, ret, sep_char='_', mmm=None, args=None):
|
||||
|
||||
@@ -175,7 +175,7 @@ class PluginModel(GlancesPluginModel):
|
||||
# Alert processing
|
||||
if i['type'] == SENSOR_TEMP_TYPE:
|
||||
if self.is_limit('critical', stat_name='sensors_temperature_' + i['label']):
|
||||
# By default use the thresholds confiured in the glances.conf file (see #2058)
|
||||
# By default use the thresholds configured in the glances.conf file (see #2058)
|
||||
alert = self.get_alert(current=i['value'], header='temperature_' + i['label'])
|
||||
else:
|
||||
# Else use the system thresholds
|
||||
@@ -197,7 +197,7 @@ class PluginModel(GlancesPluginModel):
|
||||
self.views[i[self.get_key()]]['value']['decoration'] = alert
|
||||
|
||||
def battery_trend(self, stats):
|
||||
"""Return the trend characterr for the battery"""
|
||||
"""Return the trend character for the battery"""
|
||||
if 'status' not in stats:
|
||||
return ''
|
||||
if stats['status'].startswith('Charg'):
|
||||
@@ -331,12 +331,15 @@ class GlancesGrabSensors(object):
|
||||
else:
|
||||
return ret
|
||||
for chip_name, chip in iteritems(input_list):
|
||||
i = 1
|
||||
for feature in chip:
|
||||
label_index = 1
|
||||
for chip_name_index, feature in enumerate(chip):
|
||||
sensors_current = {}
|
||||
# Sensor name
|
||||
if feature.label == '':
|
||||
sensors_current['label'] = chip_name + ' ' + str(i)
|
||||
sensors_current['label'] = chip_name + ' ' + str(chip_name_index)
|
||||
elif feature.label in [i['label'] for i in ret]:
|
||||
sensors_current['label'] = feature.label + ' ' + str(label_index)
|
||||
label_index += 1
|
||||
else:
|
||||
sensors_current['label'] = feature.label
|
||||
# Sensors value, limit and unit
|
||||
@@ -348,7 +351,6 @@ class GlancesGrabSensors(object):
|
||||
sensors_current['critical'] = int(system_critical) if system_critical is not None else None
|
||||
# Add sensor to the list
|
||||
ret.append(sensors_current)
|
||||
i += 1
|
||||
return ret
|
||||
|
||||
def get(self, sensor_type=SENSOR_TEMP_TYPE):
|
||||
|
||||
@@ -12,6 +12,7 @@ import os
|
||||
from glances.globals import BSD, LINUX, MACOS, WINDOWS, iterkeys
|
||||
from glances.timer import Timer, getTimeSinceLastUpdate
|
||||
from glances.filter import GlancesFilter
|
||||
from glances.programs import processes_to_programs
|
||||
from glances.logger import logger
|
||||
|
||||
import psutil
|
||||
@@ -36,6 +37,10 @@ class GlancesProcesses(object):
|
||||
|
||||
def __init__(self, cache_timeout=60):
|
||||
"""Init the class to collect stats about processes."""
|
||||
# Init the args, coming from the GlancesStandalone class
|
||||
# Should be set by the set_args method
|
||||
self.args = None
|
||||
|
||||
# Add internals caches because psutil do not cache all the stats
|
||||
# See: https://github.com/giampaolo/psutil/issues/462
|
||||
self.username_cache = {}
|
||||
@@ -69,6 +74,7 @@ class GlancesProcesses(object):
|
||||
|
||||
# Extended stats for top process is enable by default
|
||||
self.disable_extended_tag = False
|
||||
self.extended_process = None
|
||||
|
||||
# Test if the system can grab io_counters
|
||||
try:
|
||||
@@ -108,6 +114,10 @@ class GlancesProcesses(object):
|
||||
self._max_values = {}
|
||||
self.reset_max_values()
|
||||
|
||||
def set_args(self, args):
|
||||
"""Set args."""
|
||||
self.args = args
|
||||
|
||||
def reset_processcount(self):
|
||||
"""Reset the global process count"""
|
||||
self.processcount = {'total': 0, 'running': 0, 'sleeping': 0, 'thread': 0, 'pid_max': None}
|
||||
@@ -246,6 +256,91 @@ class GlancesProcesses(object):
|
||||
for k in self._max_values_list:
|
||||
self._max_values[k] = 0.0
|
||||
|
||||
def get_extended_stats(self, proc):
|
||||
"""Get the extended stats for the given PID."""
|
||||
# - cpu_affinity (Linux, Windows, FreeBSD)
|
||||
# - ionice (Linux and Windows > Vista)
|
||||
# - num_ctx_switches (not available on Illumos/Solaris)
|
||||
# - num_fds (Unix-like)
|
||||
# - num_handles (Windows)
|
||||
# - memory_maps (only swap, Linux)
|
||||
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
|
||||
# - connections (TCP and UDP)
|
||||
# - CPU min/max/mean
|
||||
|
||||
# Set the extended stats list (OS dependant)
|
||||
extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
|
||||
if LINUX:
|
||||
# num_fds only available on Unix system (see issue #1351)
|
||||
extended_stats += ['num_fds']
|
||||
if WINDOWS:
|
||||
extended_stats += ['num_handles']
|
||||
|
||||
ret = {}
|
||||
try:
|
||||
# Get the extended stats
|
||||
selected_process = psutil.Process(proc['pid'])
|
||||
ret = selected_process.as_dict(attrs=extended_stats, ad_value=None)
|
||||
|
||||
if LINUX:
|
||||
try:
|
||||
ret['memory_swap'] = sum([v.swap for v in selected_process.memory_maps()])
|
||||
except (psutil.NoSuchProcess, KeyError):
|
||||
# (KeyError catch for issue #1551)
|
||||
pass
|
||||
except (psutil.AccessDenied, NotImplementedError):
|
||||
# NotImplementedError: /proc/${PID}/smaps file doesn't exist
|
||||
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option
|
||||
# is not enabled (see psutil #533/glances #413).
|
||||
ret['memory_swap'] = None
|
||||
try:
|
||||
ret['tcp'] = len(selected_process.connections(kind="tcp"))
|
||||
ret['udp'] = len(selected_process.connections(kind="udp"))
|
||||
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
||||
# Manage issue1283 (psutil.AccessDenied)
|
||||
ret['tcp'] = None
|
||||
ret['udp'] = None
|
||||
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
|
||||
logger.error('Can not grab extended stats ({})'.format(e))
|
||||
self.extended_process = None
|
||||
ret['extended_stats'] = False
|
||||
else:
|
||||
logger.debug('Grab extended stats for process {}'.format(proc['pid']))
|
||||
|
||||
# Compute CPU and MEM min/max/mean
|
||||
for stat_prefix in ['cpu', 'memory']:
|
||||
if stat_prefix + '_min' not in self.extended_process:
|
||||
ret[stat_prefix + '_min'] = proc[stat_prefix + '_percent']
|
||||
else:
|
||||
ret[stat_prefix + '_min'] = proc[stat_prefix + '_percent'] if proc[stat_prefix + '_min'] > proc[stat_prefix + '_percent'] else proc[stat_prefix + '_min']
|
||||
if stat_prefix + '_max' not in self.extended_process:
|
||||
ret[stat_prefix + '_max'] = proc[stat_prefix + '_percent']
|
||||
else:
|
||||
ret[stat_prefix + '_max'] = proc[stat_prefix + '_percent'] if proc[stat_prefix + '_max'] < proc[stat_prefix + '_percent'] else proc[stat_prefix + '_max']
|
||||
if stat_prefix + '_mean_sum' not in self.extended_process:
|
||||
ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_percent']
|
||||
else:
|
||||
ret[stat_prefix + '_mean_sum'] = proc[stat_prefix + '_mean_sum'] + proc[stat_prefix + '_percent']
|
||||
if stat_prefix + '_mean_counter' not in self.extended_process:
|
||||
ret[stat_prefix + '_mean_counter'] = 1
|
||||
else:
|
||||
ret[stat_prefix + '_mean_counter'] = proc[stat_prefix + '_mean_counter'] + 1
|
||||
ret[stat_prefix + '_mean'] = ret[stat_prefix + '_mean_sum'] / ret[stat_prefix + '_mean_counter']
|
||||
|
||||
ret['extended_stats'] = True
|
||||
return ret
|
||||
|
||||
def is_selected_extended_process(self, position):
|
||||
"""Return True if the process is the selected one for extended stats."""
|
||||
return hasattr(self.args, 'programs') and \
|
||||
not self.args.programs and \
|
||||
hasattr(self.args, 'enable_process_extended') and \
|
||||
self.args.enable_process_extended and \
|
||||
not self.disable_extended_tag and \
|
||||
hasattr(self.args, 'cursor_position') and \
|
||||
position == self.args.cursor_position and \
|
||||
not self.args.disable_cursor
|
||||
|
||||
def update(self):
|
||||
"""Update the processes stats."""
|
||||
# Reset the stats
|
||||
@@ -285,78 +380,44 @@ class GlancesProcesses(object):
|
||||
is_cached = True
|
||||
|
||||
# Build the processes stats list (it is why we need psutil>=5.3.0)
|
||||
# This is on of the main bottleneck of Glances (see flame graph)
|
||||
self.processlist = [
|
||||
p.info
|
||||
for p in psutil.process_iter(attrs=sorted_attrs, ad_value=None)
|
||||
# OS-related processes filter
|
||||
if not (BSD and p.info['name'] == 'idle')
|
||||
and not (WINDOWS and p.info['name'] == 'System Idle Process')
|
||||
and not (MACOS and p.info['name'] == 'kernel_task')
|
||||
and
|
||||
# Kernel threads filter
|
||||
not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0)
|
||||
]
|
||||
|
||||
# This is one of the main bottleneck of Glances (see flame graph)
|
||||
# Filter processes
|
||||
self.processlist = list(
|
||||
filter(
|
||||
lambda p: not (BSD and p.info['name'] == 'idle')
|
||||
and not (WINDOWS and p.info['name'] == 'System Idle Process')
|
||||
and not (MACOS and p.info['name'] == 'kernel_task')
|
||||
and not (self.no_kernel_threads and LINUX and p.info['gids'].real == 0),
|
||||
psutil.process_iter(attrs=sorted_attrs, ad_value=None),
|
||||
)
|
||||
)
|
||||
# Only get the info key
|
||||
self.processlist = [p.info for p in self.processlist]
|
||||
# Sort the processes list by the current sort_key
|
||||
self.processlist = sort_stats(self.processlist, sorted_by=self.sort_key, reverse=True)
|
||||
|
||||
# Update the processcount
|
||||
self.update_processcount(self.processlist)
|
||||
|
||||
# Loop over processes and add metadata
|
||||
first = True
|
||||
for proc in self.processlist:
|
||||
# Get extended stats, only for top processes (see issue #403).
|
||||
if first and not self.disable_extended_tag:
|
||||
# - cpu_affinity (Linux, Windows, FreeBSD)
|
||||
# - ionice (Linux and Windows > Vista)
|
||||
# - num_ctx_switches (not available on Illumos/Solaris)
|
||||
# - num_fds (Unix-like)
|
||||
# - num_handles (Windows)
|
||||
# - memory_maps (only swap, Linux)
|
||||
# https://www.cyberciti.biz/faq/linux-which-process-is-using-swap/
|
||||
# - connections (TCP and UDP)
|
||||
extended = {}
|
||||
try:
|
||||
top_process = psutil.Process(proc['pid'])
|
||||
extended_stats = ['cpu_affinity', 'ionice', 'num_ctx_switches']
|
||||
if LINUX:
|
||||
# num_fds only available on Unix system (see issue #1351)
|
||||
extended_stats += ['num_fds']
|
||||
if WINDOWS:
|
||||
extended_stats += ['num_handles']
|
||||
# Loop over processes and :
|
||||
# - add extended stats for selected process
|
||||
# - add metadata
|
||||
for position, proc in enumerate(self.processlist):
|
||||
# Extended stats
|
||||
################
|
||||
|
||||
# Get the extended stats
|
||||
extended = top_process.as_dict(attrs=extended_stats, ad_value=None)
|
||||
# Get the selected process when the 'e' key is pressed
|
||||
if self.is_selected_extended_process(position):
|
||||
self.extended_process = proc
|
||||
|
||||
if LINUX:
|
||||
try:
|
||||
extended['memory_swap'] = sum([v.swap for v in top_process.memory_maps()])
|
||||
except (psutil.NoSuchProcess, KeyError):
|
||||
# (KeyError catch for issue #1551)
|
||||
pass
|
||||
except (psutil.AccessDenied, NotImplementedError):
|
||||
# NotImplementedError: /proc/${PID}/smaps file doesn't exist
|
||||
# on kernel < 2.6.14 or CONFIG_MMU kernel configuration option
|
||||
# is not enabled (see psutil #533/glances #413).
|
||||
extended['memory_swap'] = None
|
||||
try:
|
||||
extended['tcp'] = len(top_process.connections(kind="tcp"))
|
||||
extended['udp'] = len(top_process.connections(kind="udp"))
|
||||
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
||||
# Manage issue1283 (psutil.AccessDenied)
|
||||
extended['tcp'] = None
|
||||
extended['udp'] = None
|
||||
except (psutil.NoSuchProcess, ValueError, AttributeError) as e:
|
||||
logger.error('Can not grab extended stats ({})'.format(e))
|
||||
extended['extended_stats'] = False
|
||||
else:
|
||||
logger.debug('Grab extended stats for process {}'.format(proc['pid']))
|
||||
extended['extended_stats'] = True
|
||||
proc.update(extended)
|
||||
first = False
|
||||
# /End of extended stats
|
||||
# Grab extended stats only for the selected process (see issue #2225)
|
||||
if self.extended_process is not None and \
|
||||
proc['pid'] == self.extended_process['pid']:
|
||||
proc.update(self.get_extended_stats(self.extended_process))
|
||||
self.extended_process = proc
|
||||
|
||||
# Meta data
|
||||
###########
|
||||
|
||||
# PID is the key
|
||||
proc['key'] = 'pid'
|
||||
@@ -409,8 +470,8 @@ class GlancesProcesses(object):
|
||||
# Save values to cache
|
||||
self.processlist_cache[proc['pid']] = {cached: proc[cached] for cached in cached_attrs}
|
||||
|
||||
# Apply filter
|
||||
self.processlist = [p for p in self.processlist if not (self._filter.is_filtered(p))]
|
||||
# Apply user filter
|
||||
self.processlist = list(filter(lambda p: not self._filter.is_filtered(p), self.processlist))
|
||||
|
||||
# Compute the maximum value for keys in self._max_values_list: CPU, MEM
|
||||
# Useful to highlight the processes with maximum values
|
||||
@@ -423,9 +484,14 @@ class GlancesProcesses(object):
|
||||
"""Get the number of processes."""
|
||||
return self.processcount
|
||||
|
||||
def getlist(self, sorted_by=None):
|
||||
"""Get the processlist."""
|
||||
return self.processlist
|
||||
def getlist(self, sorted_by=None, as_programs=False):
|
||||
"""Get the processlist.
|
||||
By default, return the list of threads.
|
||||
If as_programs is True, return the list of programs."""
|
||||
if as_programs:
|
||||
return processes_to_programs(self.processlist)
|
||||
else:
|
||||
return self.processlist
|
||||
|
||||
@property
|
||||
def sort_key(self):
|
||||
|
||||
@@ -36,7 +36,7 @@ def processes_to_programs(processes):
|
||||
'name': p['name'],
|
||||
'cmdline': [p['name']],
|
||||
'pid': '_',
|
||||
'username': p['username'],
|
||||
'username': p['username'] if 'username' in p else '_',
|
||||
'nice': p['nice'],
|
||||
'status': p['status'],
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
from glances.globals import nativestr
|
||||
from subprocess import Popen, PIPE
|
||||
import re
|
||||
|
||||
|
||||
def secure_popen(cmd):
|
||||
@@ -48,8 +49,9 @@ def __secure_popen(cmd):
|
||||
p_last = None
|
||||
# Split by pipe '|'
|
||||
for sub_cmd in cmd.split('|'):
|
||||
# Split by space ' '
|
||||
sub_cmd_split = [i for i in sub_cmd.split(' ') if i]
|
||||
# Split by space character, but do no split spaces within quotes (remove surrounding quotes, though)
|
||||
tmp_split = [_ for _ in list(filter(None, re.split(r'(\s+)|(".*?"+?)|(\'.*?\'+?)', sub_cmd))) if _ != ' ']
|
||||
sub_cmd_split = [_[1:-1] if (_[0] == _[-1] == '"') or (_[0] == _[-1] == '\'') else _ for _ in tmp_split]
|
||||
p = Popen(sub_cmd_split, shell=False, stdin=sub_cmd_stdin, stdout=PIPE, stderr=PIPE)
|
||||
if p_last is not None:
|
||||
# Allow p_last to receive a SIGPIPE if p exits.
|
||||
|
||||
@@ -94,7 +94,6 @@ class GlancesXMLRPCServer(SimpleXMLRPCServer, object):
|
||||
finished = False
|
||||
|
||||
def __init__(self, bind_address, bind_port=61209, requestHandler=GlancesXMLRPCHandler, config=None):
|
||||
|
||||
self.bind_address = bind_address
|
||||
self.bind_port = bind_port
|
||||
self.config = config
|
||||
|
||||
@@ -24,7 +24,6 @@ class GlancesSNMPClient(object):
|
||||
"""SNMP client class (based on pysnmp library)."""
|
||||
|
||||
def __init__(self, host='localhost', port=161, version='2c', community='public', user='private', auth=''):
|
||||
|
||||
super(GlancesSNMPClient, self).__init__()
|
||||
self.cmdGen = cmdgen.CommandGenerator()
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import time
|
||||
|
||||
from glances.globals import WINDOWS
|
||||
from glances.logger import logger
|
||||
from glances.outputs.glances_stdout_json import GlancesStdoutJson
|
||||
from glances.processes import glances_processes
|
||||
from glances.stats import GlancesStats
|
||||
from glances.outputs.glances_curses import GlancesCursesStandalone
|
||||
@@ -51,6 +50,9 @@ class GlancesStandalone(object):
|
||||
self.display_modules_list()
|
||||
sys.exit(0)
|
||||
|
||||
# The args is needed to get the selected process in the process list (Curses mode)
|
||||
glances_processes.set_args(args)
|
||||
|
||||
# If process extended stats is disabled by user
|
||||
if not args.enable_process_extended:
|
||||
logger.debug("Extended stats for top process are disabled")
|
||||
@@ -193,3 +195,8 @@ class GlancesStandalone(object):
|
||||
)
|
||||
)
|
||||
print("You should consider upgrading using: pip install --upgrade glances")
|
||||
print("Disable this warning temporarily using: glances --disable-check-update")
|
||||
print(
|
||||
"To disable it permanently, refer config reference at "
|
||||
"https://glances.readthedocs.io/en/latest/config.html#syntax"
|
||||
)
|
||||
|
||||
@@ -123,7 +123,7 @@ class GlancesStats(object):
|
||||
if args is not None:
|
||||
# If the all key is set in the disable_plugin option then look in the enable_plugin option
|
||||
if getattr(args, 'disable_all', False):
|
||||
logger.info('%s => %s', name, getattr(args, 'enable_' + name, False))
|
||||
logger.debug('%s => %s', name, getattr(args, 'enable_' + name, False))
|
||||
setattr(args, 'disable_' + name, not getattr(args, 'enable_' + name, False))
|
||||
else:
|
||||
setattr(args, 'disable_' + name, getattr(args, 'disable_' + name, False))
|
||||
|
||||
@@ -22,6 +22,7 @@ potsdb
|
||||
prometheus_client
|
||||
pygal
|
||||
pymdstat
|
||||
pymongo; python_version >= "3.7"
|
||||
pysnmp
|
||||
pySMART.smartx
|
||||
python-dateutil
|
||||
@@ -32,5 +33,5 @@ six
|
||||
sparklines
|
||||
statsd
|
||||
wifi
|
||||
zeroconf==0.19.1; python_version < "3.7"
|
||||
zeroconf==0.47.3; python_version < "3.7"
|
||||
zeroconf; python_version >= "3.7"
|
||||
|
||||
@@ -3,4 +3,4 @@ defusedxml
|
||||
packaging
|
||||
ujson<4; python_version >= "3.5" and python_version < "3.6"
|
||||
ujson<5; python_version >= "3.6" and python_version < "3.7"
|
||||
ujson; python_version >= "3.7"
|
||||
ujson>=5.4.0; python_version >= "3.7"
|
||||
|
||||
15
setup.py
@@ -37,7 +37,16 @@ def get_data_files():
|
||||
|
||||
|
||||
def get_install_requires():
|
||||
requires = ['psutil>=5.3.0', 'defusedxml', 'future', 'packaging']
|
||||
requires = [
|
||||
'psutil>=5.6.7',
|
||||
'defusedxml',
|
||||
'packaging',
|
||||
'future; python_version < "3.0"',
|
||||
'ujson<3; python_version < "3.0"',
|
||||
'ujson<4; python_version >= "3.5" and python_version < "3.6"',
|
||||
'ujson<5; python_version >= "3.6" and python_version < "3.7"',
|
||||
'ujson>=5.4.0; python_version >= "3.7"',
|
||||
]
|
||||
if sys.platform.startswith('win'):
|
||||
requires.append('bottle')
|
||||
requires.append('requests')
|
||||
@@ -127,13 +136,11 @@ setup(
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Topic :: System :: Monitoring'
|
||||
]
|
||||
)
|
||||
|
||||
@@ -222,6 +222,16 @@ class TestGlances(unittest.TestCase):
|
||||
self.assertIsInstance(req.json(), dict)
|
||||
self.assertIsInstance(req.json()['interface_name'], list)
|
||||
|
||||
def test_012_status(self):
|
||||
"""Check status endpoint."""
|
||||
method = "status"
|
||||
print('INFO: [TEST_012] Status')
|
||||
print("HTTP RESTful request: %s/%s" % (URL, method))
|
||||
req = self.http_get("%s/%s" % (URL, method))
|
||||
|
||||
self.assertTrue(req.ok)
|
||||
self.assertEqual(req.text, "Active")
|
||||
|
||||
def test_999_stop_server(self):
|
||||
"""Stop the Glances Web Server."""
|
||||
print('INFO: [TEST_999] Stop the Glances Web Server')
|
||||
|
||||